//
// Created by Dan Crosby on 5/10/21.
//

#include "AIDistanceDebuf.hpp"

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

namespace shardok {

constexpr double kPerUnitDebufDecay = 0.5;
constexpr double kDecaySum = kPerUnitDebufDecay / (1 - kPerUnitDebufDecay);

auto CostsWithoutAndWithBraving(
        const ActionPointDistances *actionPointDistancesWithoutBraving,
        const ActionPointDistances *actionPointDistancesWithBraving,
        const Coords &startLocation,
        const CoordsSet &targets,
        int &outPointCostWithoutBraving,
        int &outPointCostWithBraving) {
    outPointCostWithoutBraving = ActionPointDistances::IMPOSSIBLE;
    outPointCostWithBraving = ActionPointDistances::IMPOSSIBLE;
    for (const auto &target : targets) {
        const DIST_T costWithoutBraving =
                actionPointDistancesWithoutBraving->Distance(startLocation, target);
        if (costWithoutBraving < outPointCostWithoutBraving) {
            outPointCostWithoutBraving = costWithoutBraving;
        }
    }

    if (outPointCostWithoutBraving == ActionPointDistances::IMPOSSIBLE) {
        for (const auto &target : targets) {
            if (const DIST_T costWithBraving =
                        actionPointDistancesWithBraving->Distance(startLocation, target);
                costWithBraving < outPointCostWithBraving) {
                outPointCostWithBraving = costWithBraving;
            }
        }
    }
}

auto CountAttacker(const Unit *attacker, bool includeUndead) -> bool;

auto DefenderDistanceBuf(
        const Coords &defenderLocation,
        const HexMap *hexMap,
        const MapId &mapId,
        const vector<const Unit *> &attackerUnits,
        const APDCache &apdCache,
        const ALCache &alCache,
        const SettingsGetter &settings,
        const int braveWaterActionPointCost,
        const bool lateGame,
        const bool includeUndead) -> double {
    const auto &locationsToAttackMe = alCache->CachedLocations(defenderLocation, lateGame);

    struct WithoutAndWith {
        int without;
        int with;

        WithoutAndWith(const int a, const int b) : without(a), with(b) {}
    } __attribute__((aligned(8)));

    vector<WithoutAndWith> pointCosts{};
    pointCosts.reserve(attackerUnits.size());

    vector<const ActionPointDistances *> notBravingDistances(6, nullptr);
    vector<const ActionPointDistances *> bravingDistances(6, nullptr);
    for (const Unit *attacker : attackerUnits) {
        const int typeInt = attacker->battalion().type();
        if (notBravingDistances[typeInt] == nullptr) {
            notBravingDistances[typeInt] = apdCache->GetRaw(
                    hexMap,
                    mapId,
                    settings.GetBattalionType(attacker->battalion().type()),
                    false);
            bravingDistances[typeInt] = apdCache->GetRaw(
                    hexMap,
                    mapId,
                    settings.GetBattalionType(attacker->battalion().type()),
                    true,
                    braveWaterActionPointCost);
        }
    }

    for (const Unit *attacker : attackerUnits) {
        if (!CountAttacker(attacker, includeUndead)) continue;
        int pointCostToDesiredTargetWithBraving = 0;
        int pointCostToDesiredTargetWithoutBraving = 0;

        auto locationsSet = locationsToAttackMe.LocationsWithEnemyInRange(attacker);

        CostsWithoutAndWithBraving(
                notBravingDistances[attacker->battalion().type()],
                bravingDistances[attacker->battalion().type()],
                attacker->location(),
                locationsSet,
                pointCostToDesiredTargetWithoutBraving,
                pointCostToDesiredTargetWithBraving);

        pointCosts.emplace_back(
                pointCostToDesiredTargetWithoutBraving,
                pointCostToDesiredTargetWithBraving);
    }

    std::sort(
            begin(pointCosts),
            end(pointCosts),
            [](const WithoutAndWith &left, const WithoutAndWith &right) {
                return left.without == right.without ? left.with < right.with
                                                     : left.without < right.without;
            });

    // experiment: add dummy values to the end with very high cost, so there's never an advantage
    // to leaving a defender unit alive in a castle.
    for (size_t i = attackerUnits.size(); i < 20; i++) { pointCosts.emplace_back(999, 999); }

    double sum = 0.0;
    double decr = kPerUnitDebufDecay;
    for (const WithoutAndWith &pointCost : pointCosts) {
        const int effectiveCost =
                (pointCost.without == 999) ? int(1.2 * pointCost.with) : pointCost.without;
        sum += decr / (effectiveCost + 1);
        decr *= kPerUnitDebufDecay;
    }

    return 1.0 - sum / kDecaySum;
}

auto CountAttacker(const Unit *attacker, const bool includeUndead) -> bool {
    const auto status = attacker->status();

    return (status == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) &&
           (includeUndead || attacker->battalion().type() !=
                                     net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD);
}
}  // namespace shardok
