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

#include "FreezeWaterCommandFactory.hpp"

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

namespace shardok {

auto WaterIsFullyFrozen(const Terrain *terrain) {
    if (!terrain->modifier().ice().present()) return false;
    return (terrain->modifier().ice().integrity() == 100.0);
}

[[nodiscard]] auto GetFreezeWaterOdds(
        const SettingsGetter &settings,
        const WeatherFb *currentWeather,
        double intelligence) -> PercentileRollOdds;

void FreezeWaterCommandFactory::AddAvailableFreezeWaterCommands(
        CommandList &commands,
        const Unit *unit,
        const ActionPoints remainingActionPoints,
        const Coords &position,
        const WeatherFb *weather,
        const HexMap *hexMap,
        const Units *units) const {
    if (!unit->has_attached_hero()) return;
    const auto &hero = unit->attached_hero();

    if (hero.profession_info().profession() == net::eagle0::shardok::storage::fb::Profession_MAGE &&
        settings.GetBattalionType(unit->battalion().type())->allowsCasting) {
        const ActionCost cost =
                settings.ActionCostFor(settings.Backing().freeze_water_action_point_cost());

        if (cost.IsPossible(remainingActionPoints)) {
            const CoordsSet adjacentCoords = HexMapUtils::GetAdjacentCoords(hexMap, position);
            for (const Coords &coords : adjacentCoords) {
                const auto &targetTerrain = GetTerrain(hexMap, coords);
                if (IsWater(targetTerrain->type()) && !WaterIsFullyFrozen(targetTerrain)) {
                    PercentileRollOdds odds = GetFreezeWaterOdds(settings, weather, hero.wisdom());
                    const auto *underwaterOccupant = Occupant(units, coords);

                    if (underwaterOccupant == nullptr ||
                        settings.GetBattalionType(underwaterOccupant->battalion().type())
                                        ->costToEnterWater.type ==
                                ActionCost::ActionCostType::impossible) {
                        commands.push_back(std::make_shared<FreezeWaterCommand>(
                                settings.ActionCostFor(
                                        settings.Backing().freeze_water_action_point_cost()),
                                unit,
                                coords,
                                targetTerrain,
                                odds,
                                settings.Backing().freeze_water_integrity(),
                                settings.Backing().freeze_water_wisdom_xp(),
                                settings.Backing().minimum_knowledge_for_profession()));
                    } else {
                        commands.push_back(std::make_shared<FreezeWaterCommand>(
                                settings.ActionCostFor(
                                        settings.Backing().freeze_water_action_point_cost()),
                                unit,
                                coords,
                                targetTerrain,
                                underwaterOccupant,
                                odds,
                                settings.Backing().freeze_water_integrity(),
                                settings.Backing().freeze_water_wisdom_xp(),
                                settings.Backing().minimum_knowledge_for_profession()));
                    }
                }
            }
        }
    }
}
void FreezeWaterCommandFactory::AddAvailableCommands(
        CommandList &commands,
        const CommandFactory::CommandParams &params) const {
    AddAvailableFreezeWaterCommands(
            commands,
            params.unit,
            params.remainingActionPoints,
            params.position,
            params.weatherFb,
            params.hexMap,
            params.units);
}

auto GetFreezeWaterOdds(
        const SettingsGetter &settings,
        const WeatherFb *currentWeather,
        const double intelligence) -> PercentileRollOdds {
    constexpr int16_t terrainFactor = 0;

    int16_t weatherFactor = 0;
    switch (currentWeather->conditions()) {
        case net::eagle0::shardok::storage::fb::WeatherConditions_SUN:
            weatherFactor = settings.Backing().freeze_water_bonus_sun();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_CLOUDS:
            weatherFactor = settings.Backing().freeze_water_bonus_clouds();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_RAIN:
            weatherFactor = settings.Backing().freeze_water_bonus_rain();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_THUNDERSTORM:
            weatherFactor = settings.Backing().freeze_water_bonus_thunderstorm();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_SNOW:
            weatherFactor = settings.Backing().freeze_water_bonus_snow();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_BLIZZARD:
            weatherFactor = settings.Backing().freeze_water_bonus_blizzard();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_UNKNOWN:
            throw ShardokInternalErrorException(
                    "Impossible weather conditions " +
                    std::to_string(currentWeather->conditions()));
            break;
    }

    constexpr int16_t windFactor = 0;

    const std::vector<double> stats = {intelligence};

    return MakeOdds(
            settings.Backing().freeze_water_base_odds(),
            terrainFactor,
            weatherFactor,
            windFactor,
            stats,
            /* otherFactors=*/{});
}

}  // namespace shardok
