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

#include "SnowAdjustmentAction.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/TileModifierHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifier.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifierWithCoords.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/terrain.pb.h"

namespace shardok {

using TerrainProto = net::eagle0::shardok::common::Terrain;

auto TempAdjustment(const int8_t averageTemp) -> double { return std::min(32 - averageTemp, 0); }

auto ConditionsAdjustmentForWeather(const WeatherFb& weather, const SettingsGetter& settings)
        -> double {
    switch (weather.conditions()) {
        case net::eagle0::shardok::storage::fb::WeatherConditions_SUN:
            return settings.Backing().snow_integrity_adjustment_sun();

        case net::eagle0::shardok::storage::fb::WeatherConditions_CLOUDS:
            return settings.Backing().snow_integrity_adjustment_clouds();

        case net::eagle0::shardok::storage::fb::WeatherConditions_RAIN:
            return settings.Backing().snow_integrity_adjustment_rain();

        case net::eagle0::shardok::storage::fb::WeatherConditions_THUNDERSTORM:
            return settings.Backing().snow_integrity_adjustment_thunderstorm();

        case net::eagle0::shardok::storage::fb::WeatherConditions_SNOW:
            return settings.Backing().snow_integrity_adjustment_snow();

        case net::eagle0::shardok::storage::fb::WeatherConditions_BLIZZARD:
            return settings.Backing().snow_integrity_adjustment_blizzard();

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

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

auto FireAdjustmentForTerrain(const Terrain* terrain, const SettingsGetter& settings) {
    if (terrain->modifier().fire().present()) {
        return settings.Backing().snow_integrity_adjustment_fire();
    }
    return 0.0;
}

SnowAdjustmentAction::SnowAdjustmentAction(
        const Coords coords,
        const Terrain* terrain,
        const int8_t averageTemp,
        const WeatherFb& weather,
        const SettingsGetter& settings)
    : coords(coords),
      terrain(*terrain),
      tempAdjustment(TempAdjustment(averageTemp)),
      conditionsAdjustment(ConditionsAdjustmentForWeather(weather, settings)),
      varyConditionsAdjustment(AdjustmentVariesForWeather(weather)),
      fireAdjustment(FireAdjustmentForTerrain(terrain, settings)) {}

auto effectiveSnow(const Terrain& terr) -> double {
    if (terr.modifier().snow().present()) return terr.modifier().snow().integrity();
    return 0.0;
}

auto effectiveSnow(const TerrainProto& terr) -> double {
    if (!terr.has_modifier()) return 0.0;
    if (!terr.modifier().has_snow()) return 0.0;
    if (!terr.modifier().snow().has_integrity()) return 0.0;
    return terr.modifier().snow().integrity().value();
}

auto SnowAdjustmentAction::InternalExecute(
        const GameStateW& currentState,
        const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
    vector<ActionResult> results{};

    TerrainProto newTerrain{};
    newTerrain.set_type(static_cast<net::eagle0::shardok::common::Terrain_Type>(terrain.type()));
    *newTerrain.mutable_modifier() = fb::ToTileModifierProto(terrain.modifier());

    auto randomizedConditionsAdjustment = conditionsAdjustment;
    if (varyConditionsAdjustment) {
        const auto conditionsAdjustmentRoll = generator->OpenEndedHighPercentile();
        randomizedConditionsAdjustment *= (conditionsAdjustmentRoll / 100.0);
    }

    MutatingAdjustSnowIntegrity(
            newTerrain.mutable_modifier(),
            tempAdjustment + randomizedConditionsAdjustment + fireAdjustment);
    if (effectiveSnow(terrain) == effectiveSnow(newTerrain)) return results;

    ActionResult resultProto;
    resultProto.set_type(net::eagle0::shardok::common::SNOW_ADJUSTED);
    *resultProto.mutable_target_coords() = ToCoordsProto(coords);
    *resultProto.add_changed_tile_modifiers() = MakeTmc(coords, newTerrain.modifier());

    results.push_back(resultProto);

    return results;
}
}  // namespace shardok