//
// Created by Dan Crosby on 5/16/20.
//

#include "src/main/cpp/net/eagle0/shardok/library/command_factories/archery_command_factory/ArcheryCommandFactory.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/command_factories/ambush_odds/AmbushOdds.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/commands/ArcheryCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/HexMapDirection.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"

namespace shardok {

auto ConditionsAllowArchery(const net::eagle0::shardok::storage::fb::WeatherConditions conditions)
        -> bool {
    switch (conditions) {
        case net::eagle0::shardok::storage::fb::WeatherConditions_SUN:
        case net::eagle0::shardok::storage::fb::WeatherConditions_CLOUDS:
        case net::eagle0::shardok::storage::fb::WeatherConditions_RAIN:
        case net::eagle0::shardok::storage::fb::WeatherConditions_SNOW: return true;

        case net::eagle0::shardok::storage::fb::WeatherConditions_THUNDERSTORM:
        case net::eagle0::shardok::storage::fb::WeatherConditions_BLIZZARD: return false;

        default:
            throw ShardokInternalErrorException(
                    "Unknown weather conditions" +
                    std::string(net::eagle0::shardok::storage::fb::EnumNameWeatherConditions(
                            conditions)));
    }
}

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

auto ArcheryCommandFactory::MakeArcheryCommand(
        const Unit *attacker,
        const Unit *defender,
        const Terrain &defenderTerrain) const -> CommandSPtr {
    PercentileRollOdds ambushOdds{};
    if (attacker->hidden()) {
        if (attacker->has_attached_hero()) {
            const auto &hero = attacker->attached_hero();
            ambushOdds = AmbushOdds::GetAmbushOdds(
                    settings,
                    defenderTerrain,
                    hero.agility(),
                    hero.wisdom());
        }
    }

    return std::make_shared<ArcheryCommand>(
            settings,
            attacker->player_id(),
            attacker->unit_id(),
            defender->unit_id(),
            defender->location(),
            ambushOdds,
            defenderTerrain);
}

void ArcheryCommandFactory::AddAvailableArcheryCommands(
        CommandList &commands,
        const Unit *unit,
        const ActionPoints remainingActionPoints,
        const Coords &position,
        const WeatherFb *weather,
        const HexMap *hexMap,
        const Units *units,
        const vector<PlayerId> &allyPids) const {
    if (unit->volleys_remaining() < 1) return;
    if (!unit->can_archery()) return;
    if (unit->attached_hero().vigor() < settings.Backing().minimum_vigor_to_act()) { return; }
    if (!ConditionsAllowArchery(weather->conditions())) return;

    const ActionCost costForArchery =
            ActionCost::UsesAllActionCost(settings.Backing().archery_action_point_cost());
    if (costForArchery.IsPossible(remainingActionPoints)) {
        CoordsSet targetCoords = TwoAwayTilesUnblockedByMountains(hexMap, position);

        const auto &battalionType = settings.GetBattalionType(unit->battalion().type());
        if (battalionType->alwaysArcheryCapable &&
            GetTerrain(hexMap, position)->modifier().castle().present()) {
            targetCoords += HexMapUtils::GetAdjacentCoords(hexMap, position);
        }

        for (const Coords &archeryCoords : targetCoords) {
            const auto *const enemyOccupant =
                    KnownEnemyOccupant(unit->player_id(), units, allyPids, archeryCoords);
            if (enemyOccupant) {
                commands.push_back(MakeArcheryCommand(
                        unit,
                        enemyOccupant,
                        *GetTerrain(hexMap, archeryCoords)));
            }
        }
    }
}
void ArcheryCommandFactory::AddAvailableCommands(CommandList &commands, const CommandParams &params)
        const {
    AddAvailableArcheryCommands(
            commands,
            params.unit,
            params.remainingActionPoints,
            params.position,
            params.weatherFb,
            params.hexMap,
            params.units,
            params.allyPids);
}

}  // namespace shardok
