//
// Created by Dan Crosby on 3/11/20.
//

#include "BraveWaterCommandFactory.hpp"

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

namespace shardok {

PercentileRollOdds GetBraveWaterOdds(
        const SettingsGetter &settings,
        const double agility,
        const double wisdom,
        const double armament,
        const bool isRanger,
        const WeatherFb *weather) {
    const auto baseOdds = settings.Backing().brave_water_base_odds();
    int weatherFactor = 0;
    switch (weather->conditions()) {
        case net::eagle0::shardok::storage::fb::WeatherConditions_RAIN:
            weatherFactor = settings.Backing().brave_water_odds_rain();
            break;
        case net::eagle0::shardok::storage::fb::WeatherConditions_THUNDERSTORM:
            weatherFactor = settings.Backing().brave_water_odds_thunderstorm();
            break;
        case net::eagle0::shardok::storage::fb::WeatherConditions_SNOW:
            weatherFactor = settings.Backing().brave_water_odds_snow();
            break;
        case net::eagle0::shardok::storage::fb::WeatherConditions_BLIZZARD:
            weatherFactor = settings.Backing().brave_water_odds_blizzard();
            break;

        default: break;
    }
    const int statFactor =
            (int)(agility * settings.Backing().brave_water_agility_factor() +
                  wisdom * settings.Backing().brave_water_wisdom_factor());
    const double armamentFactor = armament * settings.Backing().brave_water_armament_factor();

    auto otherFactors = vector<OtherFactor>{MakeOtherFactor(armamentFactor, "Armament")};

    if (isRanger) {
        otherFactors.emplace_back(
                MakeOtherFactor(settings.Backing().brave_water_ranger_bonus(), "Ranger"));
    }

    return MakeOdds(
            /*base*/ baseOdds,
            /*terrain*/ 0,
            weatherFactor,
            /*wind*/ 0,
            statFactor,
            otherFactors);
}

auto BraveWaterCommandFactory::MakeBraveWaterCommand(
        const Unit *actor,
        const std::optional<UnitId> &ambusher,
        const Coords &target,
        const WeatherFb *weather) const -> CommandSPtr {
    return std::make_shared<BraveWaterCommand>(
            settings.ActionCostFor(settings.Backing().brave_water_action_point_cost()),
            actor->player_id(),
            actor->unit_id(),
            ambusher,
            target,
            GetBraveWaterOdds(
                    settings,
                    actor->attached_hero().agility(),
                    actor->attached_hero().wisdom(),
                    actor->battalion().armament(),
                    actor->attached_hero().profession_info().profession() ==
                            net::eagle0::shardok::storage::fb::Profession_RANGER,
                    weather),
            settings.Backing().brave_water_max_loss(),
            settings);
}

BraveWaterCommandFactory::BraveWaterCommandFactory(const SettingsGetter &getter)
    : settings(getter) {}

void BraveWaterCommandFactory::AddAvailableBraveWaterCommands(
        shardok::CommandList &commands,
        const Unit *unit,
        const shardok::ActionPoints remainingActionPoints,
        const Coords &position,
        const WeatherFb *weather,
        const HexMap *hexMap,
        const Units *units) const {
    if (!unit->has_attached_hero()) return;
    if (unit->attached_hero().vigor() < settings.Backing().minimum_vigor_to_act()) return;

    const auto &battalionType = settings.GetBattalionType(unit->battalion().type());
    if (!battalionType->allowsBraveWater) return;

    const auto *currentTerrain = GetTerrain(hexMap, position);

    if (!IsWater(currentTerrain->type())) {
        const ActionCost cost =
                settings.ActionCostFor(settings.Backing().brave_water_action_point_cost());

        if (cost.IsPossible(remainingActionPoints)) {
            const CoordsSet tilesAcrossWater = GetTilesAcrossWater(hexMap, position);
            CoordsSet braveWaterTargets(hexMap);

            for (const Coords &possibleTarget : tilesAcrossWater) {
                const auto terrain = GetTerrain(hexMap, possibleTarget);

                if (IsBelievedEmpty(unit->player_id(), possibleTarget, units) &&
                    !IsWater(terrain->type()) &&
                    battalionType->GetCostToEnterTerrain(terrain).type !=
                            ActionCost::ActionCostType::impossible) {
                    braveWaterTargets.Add(possibleTarget);
                }
            }

            for (const auto &target : braveWaterTargets) {
                std::optional<UnitId> ambusher = std::optional<UnitId>();
                const auto &targetOccupant = Occupant(units, target);
                if (targetOccupant) { ambusher = targetOccupant->unit_id(); }
                commands.push_back(BraveWaterCommandFactory(settings)
                                           .MakeBraveWaterCommand(unit, ambusher, target, weather));
            }
        }
    }
}
void BraveWaterCommandFactory::AddAvailableCommands(
        CommandList &commands,
        const CommandParams &params) const {
    return AddAvailableBraveWaterCommands(
            commands,
            params.unit,
            params.remainingActionPoints,
            params.position,
            params.weatherFb,
            params.hexMap,
            params.units);
}

}  // namespace shardok