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

#include "HideCommand.hpp"

#include <utility>

#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.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 {

HideCommand::HideCommand(
        const ActionCost &cost,
        const PlayerId playerId,
        const UnitId actorId,
        const Coords target,
        const int agilityXp,
        const int professionKnowledgeBump)
    : ShardokCommand(playerId),
      cost(cost),
      actorId(actorId),
      target(target),
      agilityXp(agilityXp),
      minimumKnowledgeAfter(professionKnowledgeBump) {}

auto HideCommand::InternalExecute(
        const GameStateW &currentState,
        const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
    auto actorAfter = *currentState->units()->Get(actorId);
    MutatingSpendActionPoints(&actorAfter, cost);

    MutatingBumpAgilityXp(&actorAfter, agilityXp);
    MutatingBumpAllKnowledgeToMinimum(&actorAfter, minimumKnowledgeAfter);

    ActionResult hideResult{};
    hideResult.mutable_player()->set_value(GetPlayerId());
    hideResult.mutable_actor()->set_value(actorId);

    const auto *occupant = currentState.GetOccupant(target);
    if (occupant) {
        auto occupantAfter = *occupant;
        occupantAfter.mutate_hidden(false);

        hideResult.set_type(net::eagle0::shardok::common::HIDE_FAILED);
        AddChangedUnit(hideResult, actorAfter);
        AddChangedUnit(hideResult, occupantAfter);
    } else {
        actorAfter.mutable_location() = target;
        actorAfter.mutate_hidden(true);
        auto alliedPids = AlliedPids(currentState, GetPlayerId());

        vector<Unit> adjacentEnemyRangers{};
        for (const auto &adjCoords :
             HexMapUtils::GetAdjacentCoords(currentState->hex_map(), target)) {
            const auto *oneOverOccupant = currentState.GetOccupant(adjCoords);
            if (!oneOverOccupant) continue;
            PlayerId occupantPid = oneOverOccupant->player_id();
            if (occupantPid == GetPlayerId()) continue;
            if (common::Contains(alliedPids, occupantPid)) continue;

            if (!oneOverOccupant->has_attached_hero()) continue;
            if (oneOverOccupant->attached_hero().profession_info().profession() ==
                net::eagle0::shardok::storage::fb::Profession_RANGER)
                adjacentEnemyRangers.push_back(*oneOverOccupant);
        }

        if (adjacentEnemyRangers.empty()) {
            hideResult.set_type(net::eagle0::shardok::common::HIDE);
        } else {
            hideResult.set_type(net::eagle0::shardok::common::HIDE_FAILED);
            actorAfter.mutate_hidden(false);

            for (auto enemyRanger : adjacentEnemyRangers) {
                enemyRanger.mutate_hidden(false);
                AddChangedUnit(hideResult, enemyRanger);
            }
        }

        AddChangedUnit(hideResult, actorAfter);
    }

    return {hideResult};
}

auto HideCommand::GetCommandProto() const -> CommandProto {
    CommandProto proto{};

    proto.set_player(GetPlayerId());
    proto.set_type(net::eagle0::shardok::common::HIDE_COMMAND);
    proto.mutable_actor()->set_value(actorId);
    *proto.mutable_target() = ToCoordsProto(target);

    return proto;
}

}  // namespace shardok
