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

#include "ScoutCommand.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/actions/ActionRequiresHeroException.hpp"
#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/view_filters/OddsFilter.hpp"

namespace shardok {

using net::eagle0::shardok::common::ActionType;

auto InternalScoutOneTile(
        const Unit* scouter,
        const Unit* occupant,
        const double knowledgeIncrement) -> std::optional<Unit> {
    if (occupant && occupant->player_id() != scouter->player_id()) {
        Unit occupantAfter = *occupant;
        if (occupantAfter.hidden()) {
            occupantAfter.mutate_hidden(false);
        } else {
            MutatingBumpOpponentKnowledge(&occupantAfter, scouter->player_id(), knowledgeIncrement);
        }

        return occupantAfter;
    }

    return {};
}

auto ScoutCommand::InternalExecute(
        const GameStateW& currentState,
        const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
    const auto roll = generator->OpenEndedPercentile();
    return ExecuteWithRoll(currentState, roll);
}

[[nodiscard]] auto ScoutCommand::ExecuteWithRoll(const GameStateW& currentState, double roll) const
        -> vector<ActionResult> {
    const auto* actor = currentState->units()->Get(actorId);

    if (!actor->has_attached_hero()) { throw ActionRequiresHeroException("scout"); }

    Unit actorAfter = *actor;
    MutatingSpendActionPoints(&actorAfter, cost);
    MutatingBumpAgilityXp(&actorAfter, agilityXp);
    MutatingBumpWisdomXp(&actorAfter, wisdomXp);

    ActionResult result{};
    result.mutable_player()->set_value(actorAfter.player_id());
    result.mutable_actor()->set_value(actorAfter.unit_id());
    *result.mutable_target_coords() = ToCoordsProto(target);

    if (PercentileRollSucceeds(odds, roll)) {
        result.set_type(ActionType::SCOUTED);

        AddChangedUnit(result, actorAfter);

        const auto* directUnit = currentState.GetOccupant(target);

        if (directUnit) {
            auto possibleTarget =
                    InternalScoutOneTile(actor, directUnit, scoutDirectKnowledgeIncrement);
            if (possibleTarget.has_value()) { AddChangedUnit(result, possibleTarget.value()); }
        }

        for (const auto& adjacentCoords :
             HexMapUtils::GetAdjacentCoords(currentState->hex_map(), target)) {
            const auto* adjacentUnit = currentState.GetOccupant(adjacentCoords);
            auto adjacentTarget =
                    InternalScoutOneTile(actor, adjacentUnit, scoutAdjacentKnowledgeIncrement);
            if (adjacentTarget.has_value()) { AddChangedUnit(result, adjacentTarget.value()); }
        }
    } else {
        result.set_type(ActionType::SCOUT_FAILED);

        // take damage & stuff
        actorAfter.mutate_hidden(false);
        AddChangedUnit(result, actorAfter);
    }

    return {result};
}

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

    proto.set_player(GetPlayerId());
    proto.set_type(net::eagle0::shardok::common::SCOUT_COMMAND);
    proto.mutable_actor()->set_value(actorId);
    *proto.mutable_target() = ToCoordsProto(target);
    *proto.mutable_odds() = OddsFilteredForPlayer(odds, GetPlayerId());

    return proto;
}

int32_t ScoutCommand::GetOddsPercentile() const {
    return OddsFilteredForPlayer(odds, GetPlayerId()).success_chance();
}

}  // namespace shardok
