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

#include "PlayerSetupCommandFactory.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/actions/EndPlayerSetupCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/PlaceHiddenUnitCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/actions/PlaceUnitCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/PlayerUtils.hpp"

namespace shardok {

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

auto PlayerSetupCommandFactory::AddAvailablePlaceAndHideUnitCommandsForOneUnit(
        CommandList &existingCommands,
        const bool isDefender,
        const Unit *unit,
        const GameStateW &gameState) const -> void {
    const auto &thisUnitStartingPositions =
            (isDefender ? gameState->hex_map()->defender_starting_positions()
                        : gameState->hex_map()->attacker_starting_positions()->Get(
                                  unit->starting_position_index()))
                    ->positions();

    CoordsSet unusedStartingPositions(gameState->hex_map());
    for (const Coords *possiblePosition : *thisUnitStartingPositions) {
        if (!gameState.GetOccupant(*possiblePosition)) {
            unusedStartingPositions.Add(*possiblePosition);
        }
    }

    const vector<PlayerId> allyPids = AlliedPids(gameState, unit->player_id());
    CoordsSet unusedHidingPositions(gameState->hex_map());

    for (const Coords &possibleHidingPosition : GetAllCoords(gameState->hex_map())) {
        if (!gameState.GetOccupant(possibleHidingPosition)) {
            const Terrain *terrain = GetTerrain(gameState->hex_map(), possibleHidingPosition);
            if (AllowsHiding(terrain)) { unusedHidingPositions.Add(possibleHidingPosition); }
        }
    }

    for (const Coords &position : unusedStartingPositions) {
        // Defending rangers cannot place in a hide-able position (can only hide)
        if (!isDefender || !(CanPlaceHidden(settings, unit) &&
                             AllowsHiding(GetTerrain(gameState->hex_map(), position)))) {
            existingCommands.push_back(std::make_shared<PlaceUnitCommand>(unit, position));
        }
    }

    // Defending rangers can hide
    if (isDefender && CanPlaceHidden(settings, unit)) {
        for (const Coords &position : unusedHidingPositions) {
            existingCommands.push_back(std::make_shared<PlaceHiddenUnitCommand>(
                    unit->player_id(),
                    unit->unit_id(),
                    position,
                    settings.Backing().minimum_knowledge_for_profession()));
        }
    }
}

auto PlayerSetupCommandFactory::AddAvailablePlayerSetupCommands(
        CommandList &existingCommands,
        PlayerId playerId,
        const GameStateW &gameState) const -> void {
    if (playerId < 0) return;
    if (gameState->status()->state() !=
        net::eagle0::shardok::storage::fb::GameStatus_::State_SET_UP)
        return;

    const bool isDefender = PlayerIsDefender(gameState, playerId);

    const auto unplacedUnits = ReserveUnitsForPlayer(gameState->units(), playerId);
    const auto placedUnits = PlacedUnitsForPlayer(gameState->units(), playerId);

    if (!placedUnits.empty()) {
        existingCommands.push_back(std::make_shared<EndPlayerSetupCommand>(
                playerId,
                NextPlayerId(gameState, playerId, /*allowUncontrolled=*/false),
                gameState,
                settings));
    }
    if (placedUnits.size() >= 10) return;
    if (unplacedUnits.empty()) return;

    for (const auto &kv : unplacedUnits) {
        AddAvailablePlaceAndHideUnitCommandsForOneUnit(
                existingCommands,
                isDefender,
                kv.second,
                gameState);
    }
}

auto CanPlaceHidden(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
