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

#include "FearCommand.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/view_filters/OddsFilter.hpp"

namespace shardok {

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

static const string opponentCharismaString = "opponentCharisma";

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

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

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

    auto actorAfter = *actor;
    MutatingSpendActionPoints(&actorAfter, cost);
    MutatingBumpCharismaXp(&actorAfter, charismaXp);
    MutatingBumpAllKnowledgeToMinimum(&actorAfter, minimumKnowledge);

    const auto odds = FearCommand::GetFearOdds(
            fearBaseOdds,
            actorAfter.attached_hero().charisma(),
            actorAfter.attached_hero().wisdom(),
            victim->attached_hero().charisma());
    bool succeeded = PercentileRollSucceeds(odds, roll);

    ActionResult result{};
    result.mutable_player()->set_value(GetPlayerId());
    result.mutable_actor()->set_value(actorId);
    result.mutable_target_unit()->set_value(victimId);
    AddChangedUnit(result, actorAfter);

    auto victimAfter = *victim;
    if (succeeded) {
        result.set_type(ActionType::FEAR_ATTACK);

        MutatingAdjustMorale(victimAfter.mutable_battalion(), fearDebuf, 0.0);
        victimAfter.mutate_stun_rounds_remaining(victimAfter.stun_rounds_remaining() + 1);
    } else {
        result.set_type(ActionType::FEAR_ATTACK_FAILED);

        MutatingAdjustMorale(victimAfter.mutable_battalion(), fearFailedBuf, 0.0);
    }

    AddChangedUnit(result, victimAfter);
    return {result};
}

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

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

    return proto;
}

auto FearCommand::GetFearOdds(
        const int16_t fearBaseOdds,
        double attackerCharisma,
        double attackerWisdom,
        const double defenderCharisma) -> PercentileRollOdds {
    return MakeOdds(
            fearBaseOdds,
            0,
            0,
            0,
            {attackerCharisma, attackerCharisma, attackerWisdom},
            {MakeOtherFactor(int(50.0 - defenderCharisma), opponentCharismaString)});
}

int32_t FearCommand::GetOddsPercentile() const {
    return OddsFilteredForPlayer(believedOdds, GetPlayerId()).success_chance();
}

}  // namespace shardok
