//
// Created by Dan Crosby on 4/17/21.
//

#include "AIAttackLocations.hpp"

#include "src/main/cpp/net/eagle0/shardok/ai/AIScoreUtilities.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"

namespace shardok {

auto LocationsWithRangedAttack(const Unit *unit, const AttackLocations &locations) -> CoordsSet {
    CoordsSet rangedAttackLocationsForUnit(locations.ColumnCount(), locations.RowCount());

    if (HasAttachedHeroWithProfession(unit, net::eagle0::shardok::storage::fb::Profession_MAGE)) {
        rangedAttackLocationsForUnit += locations.MageLocations();
    } else if (HasAttachedHeroWithProfession(
                       unit,
                       net::eagle0::shardok::storage::fb::Profession_NECROMANCER)) {
        rangedAttackLocationsForUnit += locations.NecromancerLocations();
    } else if (HasAttachedHeroWithProfession(
                       unit,
                       net::eagle0::shardok::storage::fb::Profession_ENGINEER)) {
        rangedAttackLocationsForUnit += locations.EngineerLocations();
    }

    if (unit->volleys_remaining() > 0) {
        rangedAttackLocationsForUnit += locations.ArcheryLocations();
    }

    return rangedAttackLocationsForUnit;
}

auto AttackLocations::LocationsWithEnemyInRange(const Unit *unit) const -> CoordsSet {
    CoordsSet locationsForUnit = LocationsWithRangedAttack(unit, *this);
    locationsForUnit += this->AdjacentLocations();
    return locationsForUnit;
}

AttackLocationsCache::AttackLocationsCache(const HexMap *hexMap, const SettingsGetter &settings)
    : rowCount(hexMap->row_count()),
      columnCount(hexMap->column_count()) {
    const int mageRange =
            std::max(settings.Backing().meteor_range(), settings.Backing().lightning_range());
    const auto fearRange = settings.Backing().fear_range();
    const auto reduceRange = settings.Backing().reduce_range();

    const int indexCount = hexMap->row_count() * hexMap->column_count();

    notLateGameLocations.reserve(indexCount);
    lateGameLocations.reserve(indexCount);

    auto emptySet = CoordsSet(columnCount, rowCount);

    for (MapIndex r = 0; r < hexMap->row_count(); r++) {
        for (MapIndex c = 0; c < hexMap->column_count(); c++) {
            auto location = Coords(r, c);
            const auto terrain = GetTerrain(hexMap, location);

            auto mageL = TilesWithinDistance(hexMap, location, mageRange);
            auto necroL = TilesWithinDistance(hexMap, location, fearRange);
            auto oneAway = HexMapUtils::GetAdjacentCoords(hexMap, location);
            auto twoAway = TwoAwayTilesUnblockedByMountains(hexMap, location);

            // Only include reduce locations if there is a castle or a bridge present
            auto engL = CoordsSet(hexMap);
            if ((terrain->modifier().castle().present() &&
                 terrain->modifier().castle().integrity() > 0) ||
                (terrain->modifier().bridge().present())) {
                engL = HexMapUtils::TilesWithinDistanceUnblockedByMountains(
                        hexMap,
                        location,
                        reduceRange);
                engL.Remove(location);
                common::FilterInPlace(engL, [&hexMap](const Coords &c) {
                    return AllowsFortification(GetTerrain(hexMap, c));
                });
            }

            auto archeryLocations = twoAway;

            notLateGameLocations.emplace_back(
                    rowCount,
                    columnCount,
                    mageL,
                    necroL,
                    engL,
                    archeryLocations,
                    oneAway);

            lateGameLocations.emplace_back(
                    rowCount,
                    columnCount,
                    emptySet,
                    emptySet,
                    emptySet,
                    twoAway,
                    oneAway);
        }
    }
}

auto AttackLocationsCache::CachedLocations(const Coords &singleTarget, const bool lateGame)
        -> const AttackLocations & {
    const int index = singleTarget.row() * columnCount + singleTarget.column();

    if (lateGame) return lateGameLocations[index];
    else
        return notLateGameLocations[index];
}

auto AttackLocationsCache::CachedLocations(const CoordsSet &targets, const bool lateGame)
        -> AttackLocations {
    AttackLocations al(rowCount, columnCount);

    for (const auto &target : targets) {
        const int index = target.row() * columnCount + target.column();
        if (lateGame) {
            al += lateGameLocations[index];
        } else {
            al += notLateGameLocations[index];
        }
    }

    return al;
}

auto AttackLocationsCache::CachedLocations(const vector<const Unit *> &enemies, const bool lateGame)
        -> AttackLocations {
    CoordsSet targets(columnCount, rowCount);
    for (const Unit *enemy : enemies) {
        if (enemy->location().row() >= 0 && enemy->location().column() >= 0) {
            targets.Add(enemy->location());
        }
    }
    return CachedLocations(targets, lateGame);
}

}  // namespace shardok