//
// Created by Dan Crosby on 1/5/22.
//

#include "AIWaterCrossingCommandChooser.hpp"

#include "AIMinimumDistanceAndTarget.hpp"
#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCalculator.hpp"

namespace shardok {

using Unit = net::eagle0::shardok::storage::fb::Unit;

constexpr ScoreValue kNoRequiredCrossingScore = std::numeric_limits<ScoreValue>::max();
constexpr ScoreValue kNoCrossingCreatorsScore = std::numeric_limits<ScoreValue>::min();

[[nodiscard]] auto AIWaterCrossingCommandChooser::WaterCrossingScore(
        const SettingsGetter &settingsGetter,
        const GameState *gameState,
        const CoordsSet &castleCoords,
        const CoordsSet &startCrossingFrom) const -> ScoreValue {
    int castleClaimCount = 0;
    for (const auto *unit : *gameState->units()) {
        if (unit->player_id() != playerId) continue;
        const auto status = unit->status();
        if (status != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            status != net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT)
            continue;
        if (!unit->has_attached_hero()) continue;

        ++castleClaimCount;
    }

    CoordsSet destinations = castleCoords;
    if (castleClaimCount < castleCoords.size()) {
        destinations = CoordsSet(gameState->hex_map());
        for (const auto *enemyUnit : *gameState->units()) {
            if (enemyUnit->player_id() == playerId) continue;
            const auto status = enemyUnit->status();
            if (status != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
            AssertValid(enemyUnit->location(), gameState->hex_map());
            destinations.Add(enemyUnit->location());
        }
    }

    const auto unitIdsRequiringCrossing = UnitIdsRequiringWaterCrossing(
            gameState,
            playerId,
            castleCoords,
            apdCache,
            settingsGetter);
    if (unitIdsRequiringCrossing.empty()) return kNoRequiredCrossingScore;

    const auto unitIdsCreatingCrossing =
            UnitIdsToCreateWaterCrossing(gameState, playerId, apdCache, settingsGetter);
    if (unitIdsCreatingCrossing.empty()) return kNoCrossingCreatorsScore;

    fprintf(stderr, "%lu units require a water crossing\n", unitIdsRequiringCrossing.size());

    ScoreValue totalScore = 0;

    const auto mapId = ActionPointDistancesCache::GetMapId(gameState->hex_map());

    // First put a big penalty on the distance for units that can create a crossing
    for (const UnitId uid : unitIdsCreatingCrossing) {
        const Unit *unit = gameState->units()->Get(uid);
        const auto &battalionType = settingsGetter.GetBattalionType(unit->battalion().type());
        Coords location = unit->location();

        int thisDistance;
        if (location.row() < 0) thisDistance = 1000;
        else {
            const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);

            thisDistance = MinimumDistance(apd, location, startCrossingFrom);
        }

        totalScore -= thisDistance * 100.0;
    }

    // Now a smaller penalty for distance for units that need to cross, except if they block -- then
    // a large penalty
    for (const UnitId uid : unitIdsRequiringCrossing) {
        // If this unit ID can also create a crossing, we already handled it
        if (common::Contains(unitIdsCreatingCrossing, uid)) continue;

        const Unit *unit = gameState->units()->Get(uid);
        const auto &battalionType = settingsGetter.GetBattalionType(unit->battalion().type());
        Coords location = unit->location();
        const auto *apd = apdCache->GetRaw(gameState->hex_map(), mapId, battalionType, false);

        int thisDistance;
        if (location.row() < 0) thisDistance = 1000;
        else { thisDistance = MinimumDistance(apd, location, startCrossingFrom); }

        bool targetBlocks = false;
        // If we're not capable of creating a crossing, don't get in the way of somebody that is.
        for (const UnitId crossingUid : unitIdsCreatingCrossing) {
            const auto *crossingCapableUnit = gameState->units()->Get(crossingUid);

            // Don't check for units that aren't yet placed
            if (crossingCapableUnit->location().row() < 0) continue;
            AssertValid(crossingCapableUnit->location(), gameState->hex_map());

            if (thisDistance <
                MinimumDistance(apd, crossingCapableUnit->location(), startCrossingFrom)) {
                targetBlocks = true;
                break;
            }
        }
        if (targetBlocks) continue;

        totalScore -= thisDistance;
    }

    return totalScore;
}

auto AIWaterCrossingCommandChooser::StartCrossingFrom(
        const SettingsGetter &settingsGetter,
        const GameState *gameState,
        const CoordsSet &castleCoords) const -> CoordsSet {
    CoordsSet startCrossingFrom(gameState->hex_map());

    int castleClaimCount = 0;
    for (const auto *unit : *gameState->units()) {
        if (unit->player_id() != playerId) continue;
        const auto status = unit->status();
        if (status != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            status != net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT)
            continue;
        if (!unit->has_attached_hero()) continue;

        ++castleClaimCount;
    }

    CoordsSet destinations = castleCoords;
    if (castleClaimCount < castleCoords.size()) {
        destinations = CoordsSet(gameState->hex_map());
        for (const auto *enemyUnit : *gameState->units()) {
            if (enemyUnit->player_id() == playerId) continue;
            const auto status = enemyUnit->status();
            if (status != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
            AssertValid(enemyUnit->location(), gameState->hex_map());
            destinations.Add(enemyUnit->location());
        }
    }

    const auto unitIdsRequiringCrossing = UnitIdsRequiringWaterCrossing(
            gameState,
            playerId,
            castleCoords,
            apdCache,
            settingsGetter);
    if (unitIdsRequiringCrossing.empty()) return startCrossingFrom;

    const auto unitIdsCreatingCrossing =
            UnitIdsToCreateWaterCrossing(gameState, playerId, apdCache, settingsGetter);
    if (unitIdsCreatingCrossing.empty()) return startCrossingFrom;

    for (const UnitId uid : unitIdsRequiringCrossing) {
        const Unit *unit = gameState->units()->Get(uid);
        const auto &battalionType = settingsGetter.GetBattalionType(unit->battalion().type());
        Coords origin = unit->location();

        // FIXME: this is just grabbing the first starting position, ideally we'd try them all
        if (origin.row() < 0) {
            origin = *gameState->hex_map()
                              ->attacker_starting_positions()
                              ->Get(unit->starting_position_index())
                              ->positions()
                              ->Get(0);
        }

        if (!destinations.empty()) {
            Coords destination = *destinations.begin();
            AssertValid(destination, gameState->hex_map());

            auto waterCrossingTiles = WaterCrossingTiles(
                    origin,
                    destination,
                    gameState->hex_map(),
                    apdCache,
                    battalionType);
            // FIXME: this isn't really the right calculation, this will get anything that works
            // for any one unit
            startCrossingFrom += CrossingStartLocations(gameState->hex_map(), waterCrossingTiles);
        }
    }

    return startCrossingFrom;
}

}  // namespace shardok