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

#include "AIAttackerStrategySelector.hpp"

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

namespace shardok {

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

constexpr double MAXIMUM_RATIO_FOR_ATTACKER_TO_FLEE = 0.50;

auto AIAttackerStrategySelector::BestAttackerStrategy(
        const PlayerId attackerPid,
        const net::eagle0::shardok::storage::fb::GameState* gameState,
        const CoordsSet& criticalTileCoords,
        const APDCache& apdCache,
        const ALCache& alCache,
        const SettingsGetter& settings,
        const AIWaterCrossingCommandChooser& waterCrossingCommandChooser,
        const vector<CommandProto>& availableCommands) -> AIStrategy {
    uint32_t attackerUnitCount = 0;
    int defenderOccupiedCriticalTileCount = 0;
    int attackerTroops = 0;
    int defenderTroops = 0;
    bool canFlee = false;

    vector<const Unit*> attackerUnits{};
    CoordsSet defenderPositions(gameState->hex_map());
    for (const Unit* unit : *gameState->units()) {
        if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT)
            continue;

        const auto* pi = PlayerInfoForPid(gameState, unit->player_id());

        if (pi != nullptr) {
            if (pi->is_defender()) {
                if (unit->location().row() >= 0) {
                    defenderTroops += unit->battalion().size();

                    if (criticalTileCoords.Contains(unit->location())) {
                        ++defenderOccupiedCriticalTileCount;
                    }

                    defenderPositions.Add(unit->location());
                }
            } else if (unit->player_id() == attackerPid) {
                ++attackerUnitCount;
                attackerTroops += unit->battalion().size();
                if (unit->can_flee()) canFlee = true;
                attackerUnits.push_back(unit);
            } else {
                // other attacking or free units; ignore
            }
        }
    }

    AIStrategy chosenStrategy;
    if (canFlee && attackerTroops < MAXIMUM_RATIO_FOR_ATTACKER_TO_FLEE * defenderTroops) {
        chosenStrategy = FleeStrategy;
    } else if (const CoordsSet startCrossingLocations =
                       waterCrossingCommandChooser
                               .StartCrossingFrom(settings, gameState, criticalTileCoords);
               !startCrossingLocations.empty()) {
        chosenStrategy = CrossRiversStrategy(startCrossingLocations);
    } else if (attackerUnitCount < criticalTileCoords.size()) {
        chosenStrategy = AttackUnitsStrategy(GenerateTargetPriorities(
                Occupants(
                        *gameState->units(),
                        gameState->hex_map()->row_count(),
                        gameState->hex_map()->column_count()),
                gameState->hex_map(),
                defenderPositions,
                attackerPid,
                attackerUnits,
                apdCache,
                alCache,
                ActionPointDistancesCache::GetMapId(gameState->hex_map()),
                settings));
    }
    // If any critical tile is occupied by the defender, attack the castles.
    // Otherwise, try to hold the castles.
    else if (defenderOccupiedCriticalTileCount > 0) {
        chosenStrategy = AttackCastlesStrategy(GenerateTargetPriorities(
                Occupants(
                        *gameState->units(),
                        gameState->hex_map()->row_count(),
                        gameState->hex_map()->column_count()),
                gameState->hex_map(),
                criticalTileCoords,
                attackerPid,
                attackerUnits,
                apdCache,
                alCache,
                ActionPointDistancesCache::GetMapId(gameState->hex_map()),
                settings));
    } else {
        chosenStrategy = HoldCastlesStrategy;
    }

    return chosenStrategy;
}

}  // namespace shardok