//
// Created by Dan Crosby on 2/16/21.
//

#include "IceAndSnowAdjustmentActionFactory.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/actions/FallIntoWaterAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/IceAdjustmentAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/SnowAdjustmentAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/UndeadFrozenAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/WeatherHelpers.hpp"

namespace shardok {

[[nodiscard]] auto IceIntegrityAdjustment(
        int averageTemp,
        const WeatherFb* weather,
        const Terrain* terrain,
        const SettingsGetter& settings) -> double;
[[nodiscard]] auto SnowIntegrityAdjustment(
        int averageTemp,
        const WeatherFb* weather,
        const Terrain* terrain,
        const SettingsGetter& settings,
        const std::shared_ptr<RandomGenerator>& randomGenerator) -> double;

IceAndSnowAdjustmentActionFactory::IceAndSnowAdjustmentActionFactory(
        const shardok::SettingsGetter& getter)
    : settings(getter) {}

auto IceAndSnowAdjustmentActionFactory::MakeIceAdjustmentAction(
        const HexMap* hexMap,
        const Coords& coords,
        const Units& units,
        const Terrain* terrain,
        const int month,
        const WeatherFb* weather) const -> shardok::ActionSPtr {
    std::shared_ptr<ShardokAction> fallAction = nullptr;
    std::shared_ptr<ShardokAction> frozenAction = nullptr;

    const auto* const occupant = Occupant(units, coords);
    if (occupant) {
        const auto& battType = settings.GetBattalionType(occupant->battalion().type());

        auto modifierWithoutIce = terrain->modifier();
        modifierWithoutIce.mutable_ice().mutate_present(false);
        // If the unit can move across water, check for falling in
        if (battType->GetCostToEnterTerrain(terrain->type(), modifierWithoutIce).type ==
            ActionCost::impossible) {
            fallAction = std::make_shared<FallIntoWaterAction>(
                    occupant->unit_id(),
                    settings,
                    settings.Backing().fall_in_water_damage_per_troop(),
                    settings.Backing().fall_in_water_base_escape_odds());
        } else {
            // Otherwise, check for being frozen
            frozenAction = std::make_shared<UndeadFrozenAction>(occupant);
        }
    }

    return std::make_shared<IceAdjustmentAction>(
            coords,
            terrain,
            IceIntegrityAdjustment(
                    hexMap->monthly_weather()->Get(month - 1)->average_temperature(),
                    weather,
                    terrain,
                    settings),
            fallAction,
            frozenAction);
}

auto IceAndSnowAdjustmentActionFactory::MakeSnowAdjustmentAction(
        const HexMap* hexMap,
        const Coords& coords,
        const Terrain* terrain,
        const int month,
        const WeatherFb* weather) const -> shardok::ActionSPtr {
    return std::make_shared<SnowAdjustmentAction>(
            coords,
            terrain,
            hexMap->monthly_weather()->Get(month - 1)->average_temperature(),
            *weather,
            settings);
}

auto IsSnowing(const WeatherFb* weather) -> bool {
    return weather->conditions() == net::eagle0::shardok::storage::fb::WeatherConditions_SNOW ||
           weather->conditions() == net::eagle0::shardok::storage::fb::WeatherConditions_BLIZZARD;
}

auto IceAndSnowAdjustmentActionFactory::MakeIceAndSnowAdjustmentActions(
        const HexMap* hexMap,
        const Units& units,
        const int month,
        const WeatherFb* weather) const -> shardok::ActionList {
    ActionList actions;

    for (const Coords& coords : GetAllCoords(hexMap)) {
        const auto* terrain = GetTerrain(hexMap, coords);

        if (IsWater(terrain->type())) {
            actions.emplace_back(
                    MakeIceAdjustmentAction(hexMap, coords, units, terrain, month, weather));
        } else if (IsSnowing(weather) || (terrain->modifier().snow().present())) {
            actions.emplace_back(MakeSnowAdjustmentAction(hexMap, coords, terrain, month, weather));
        }
    }

    return actions;
}

auto IceIntegrityAdjustment(
        const int averageTemp,
        const WeatherFb* weather,
        const Terrain* terrain,
        const SettingsGetter& settings) -> double {
    double adjustment = 32 - averageTemp;
    switch (weather->conditions()) {
        case net::eagle0::shardok::storage::fb::WeatherConditions_SUN:
            adjustment += settings.Backing().ice_integrity_adjustment_sun();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_CLOUDS:
            adjustment += settings.Backing().ice_integrity_adjustment_clouds();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_RAIN:
            adjustment += settings.Backing().ice_integrity_adjustment_rain();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_THUNDERSTORM:
            adjustment += settings.Backing().ice_integrity_adjustment_thunderstorm();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_SNOW:
            adjustment += settings.Backing().ice_integrity_adjustment_snow();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_BLIZZARD:
            adjustment += settings.Backing().ice_integrity_adjustment_blizzard();
            break;

        default: throw ShardokInternalErrorException("Impossible weather condition");
    }

    if (terrain->type() == net::eagle0::shardok::storage::fb::Terrain_::Type_RIVER) {
        adjustment += settings.Backing().ice_integrity_adjustment_river();
    }

    if (terrain->modifier().fire().present()) {
        adjustment += settings.Backing().ice_integrity_adjustment_fire();
    }

    return adjustment;
}

auto SnowIntegrityAdjustment(
        const int averageTemp,
        const WeatherFb* weather,
        const Terrain* terrain,
        const SettingsGetter& settings,
        const std::shared_ptr<RandomGenerator>& randomGenerator) -> double {
    double adjustment = 32 - averageTemp;
    if (adjustment > 0) adjustment = 0;
    switch (weather->conditions()) {
        case net::eagle0::shardok::storage::fb::WeatherConditions_SUN:
            adjustment += settings.Backing().snow_integrity_adjustment_sun();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_CLOUDS:
            adjustment += settings.Backing().snow_integrity_adjustment_clouds();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_RAIN:
            adjustment += settings.Backing().snow_integrity_adjustment_rain();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_THUNDERSTORM:
            adjustment += settings.Backing().snow_integrity_adjustment_thunderstorm();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_SNOW:
            adjustment += (randomGenerator->OpenEndedHighPercentile() / 100.0) *
                          settings.Backing().snow_integrity_adjustment_snow();
            break;

        case net::eagle0::shardok::storage::fb::WeatherConditions_BLIZZARD:
            adjustment += (randomGenerator->OpenEndedHighPercentile() / 100.0) *
                          settings.Backing().snow_integrity_adjustment_blizzard();
            break;

        default: throw ShardokInternalErrorException("Impossible weather condition");
    }

    if (terrain->modifier().fire().present()) {
        adjustment += settings.Backing().snow_integrity_adjustment_fire();
    }

    return adjustment;
}

}  // namespace shardok
