//
// Created by Dan Crosby on 3/9/21.
//

#include "RaiseDeadCommandFactory.hpp"

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

namespace shardok {

[[nodiscard]] auto CanRaiseDead(const SettingsGetter &settings, const Unit *unit) -> bool;
[[nodiscard]] auto GetRaiseDeadOdds(
        const SettingsGetter &settings,
        double charisma,
        double intelligence) -> PercentileRollOdds;

void RaiseDeadCommandFactory::AddAvailableRaiseDeadCommands(
        CommandList &commands,
        const Unit *unit,
        const ActionPoints remainingActionPoints,
        const Coords &position,
        const HexMap *hexMap,
        const Units *units) const {
    // requires a hero
    if (!unit->has_attached_hero()) { return; }

    const auto &hero = unit->attached_hero();

    // Can't raise if you already have one
    if (hero.control_info().controlled_unit_id() != -1) { return; }

    if (CanRaiseDead(settings, unit)) {
        const auto costForRaiseDead =
                settings.ActionCostFor(settings.Backing().raise_dead_action_point_cost());
        const int range = settings.Backing().raise_dead_range();
        const double charisma = hero.charisma();
        const double intelligence = hero.wisdom();

        if (costForRaiseDead.IsPossible(remainingActionPoints)) {
            const BattalionTypeSPtr &undeadType = settings.GetBattalionType(
                    net::eagle0::shardok::storage::fb::BattalionTypeId_UNDEAD);

            for (int distance = 1; distance <= range; distance++) {
                const CoordsSet coordsSet = TilesWithExactDistance(hexMap, position, distance);

                for (const Coords &raiseDeadCoords : coordsSet) {
                    if (!IsBelievedEmpty(unit->player_id(), raiseDeadCoords, units)) continue;
                    if (undeadType->GetCostToEnterTerrain(GetTerrain(hexMap, raiseDeadCoords))
                                .type == ActionCost::impossible)
                        continue;

                    PercentileRollOdds difficulty =
                            GetRaiseDeadOdds(settings, charisma, intelligence);

                    commands.push_back(std::make_shared<RaiseDeadCommand>(
                            settings.ActionCostFor(
                                    settings.Backing().raise_dead_action_point_cost()),
                            unit->player_id(),
                            unit->unit_id(),
                            raiseDeadCoords,
                            difficulty,
                            settings.Backing().raise_dead_wisdom_xp(),
                            settings.Backing().raise_dead_charisma_xp(),
                            settings.Backing().minimum_knowledge_for_profession(),
                            settings));
                }
            }
        }
    }
}
void RaiseDeadCommandFactory::AddAvailableCommands(
        CommandList &commands,
        const CommandFactory::CommandParams &params) const {
    AddAvailableRaiseDeadCommands(
            commands,
            params.unit,
            params.remainingActionPoints,
            params.position,
            params.hexMap,
            params.units);
}

auto GetRaiseDeadOdds(const SettingsGetter &settings, double charisma, double intelligence)
        -> PercentileRollOdds {
    constexpr int16_t terrainModifier = 0;
    constexpr int16_t weatherModifier = 0;
    constexpr int16_t windModifier = 0;

    return MakeOdds(
            settings.Backing().raise_dead_base_odds(),
            terrainModifier,
            weatherModifier,
            windModifier,
            {charisma, charisma, intelligence});
}

auto CanRaiseDead(const SettingsGetter &settings, const Unit *unit) -> bool {
    if (!unit->has_attached_hero()) return false;
    return unit->attached_hero().profession_info().profession() ==
                   net::eagle0::shardok::storage::fb::Profession_NECROMANCER &&
           settings.GetBattalionType(unit->battalion().type())->allowsCasting;
}

}  // namespace shardok
