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

#include "HideCommandFactory.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/commands/HideCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ZoneOfControlCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/UnitFilter.hpp"

namespace shardok {

auto CanHide(const SettingsGetter &settings, const Unit *unit) -> bool;

auto HideCommandFactory::MakeHideCommand(const Unit *actor, const Coords &target) const
        -> shardok::CommandSPtr {
    return std::make_shared<HideCommand>(
            ActionCost::UsesAllActionCost(settings.Backing().hide_action_point_cost()),
            actor->player_id(),
            actor->unit_id(),
            target,
            settings.Backing().hide_agility_xp(),
            settings.Backing().minimum_knowledge_for_profession());
}

auto HideCommandFactory::PositionIsHideable(
        const Unit *actor,
        CommandList &existingCommands,
        const HexMap *hexMap,
        const Coords &target,
        const Units *units,
        const std::vector<shardok::PlayerId> &allyPids) const -> bool {
    if (!AllowsHiding(GetTerrain(hexMap, target))) return false;

    const auto occupant = Occupant(units, target);
    if (occupant) {
        if (occupant->player_id() == actor->player_id()) return false;
        if (common::Contains(allyPids, occupant->player_id())) return false;
        if (!occupant->hidden()) return false;
    }

    for (const Coords &adjCoords : HexMapUtils::GetAdjacentCoords(hexMap, target)) {
        const auto oneOverOccupant =
                KnownEnemyOccupant(actor->player_id(), units, allyPids, adjCoords);
        if (!oneOverOccupant) continue;
        if (!oneOverOccupant->has_attached_hero()) continue;

        auto unitView =
                UnitFilteredForPlayer(settings, oneOverOccupant, actor->player_id(), allyPids);

        if (unitView.attached_hero().profession_info().profession() ==
            net::eagle0::shardok::api::HeroView_ProfessionSpecificInfo_Profession_RANGER)
            return false;
    }

    return true;
}

auto HideCommandFactory::AddAvailableHideCommands(
        CommandList &existingCommands,
        const Unit *actor,
        const shardok::ActionPoints remainingActionPoints,
        const Coords &position,
        const HexMap *hexMap,
        const Units *units,
        const std::vector<shardok::PlayerId> &allyPids) const -> void {
    if (!CanHide(settings, actor)) return;
    if (actor->hidden()) return;
    if (remainingActionPoints < settings.Backing().hide_action_point_cost()) return;
    if (IsInEnemyZoc(settings, actor->player_id(), units, hexMap, allyPids, position)) return;

    CoordsSet hideablePositions(hexMap);
    if (PositionIsHideable(actor, existingCommands, hexMap, position, units, allyPids)) {
        hideablePositions.Add(position);
    }

    for (const Coords &adj : HexMapUtils::GetAdjacentCoords(hexMap, position)) {
        if (PositionIsHideable(actor, existingCommands, hexMap, adj, units, allyPids)) {
            hideablePositions.Add(adj);
        }
    }

    // Only allow hiding if there were at least 2 options
    if (hideablePositions.size() > 1) {
        for (const Coords &pos : hideablePositions) {
            existingCommands.push_back(MakeHideCommand(actor, pos));
        }
    }
}
void HideCommandFactory::AddAvailableCommands(
        CommandList &commands,
        const CommandFactory::CommandParams &params) const {
    AddAvailableHideCommands(
            commands,
            params.unit,
            params.remainingActionPoints,
            params.position,
            params.hexMap,
            params.units,
            params.allyPids);
}

auto CanHide(const SettingsGetter &settings, const Unit *unit) -> bool {
    return (unit->has_attached_hero() &&
            unit->attached_hero().profession_info().profession() ==
                    net::eagle0::shardok::storage::fb::Profession_RANGER &&
            settings.GetBattalionType(unit->battalion().type())->allowsStealth);
}

}  // namespace shardok