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

#include "ReduceCommand.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/actions/ActionRequiresHeroException.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/map/TileModifierWithCoords.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/util/PlayerUtils.hpp"

namespace shardok {

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

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

[[nodiscard]] auto ReduceCommand::ExecuteWithRoll(const GameStateW& currentState, double roll) const
        -> vector<ActionResult> {
    ActionResult result{};
    result.set_type(ActionType::REDUCE);
    result.mutable_player()->set_value(GetPlayerId());
    result.mutable_actor()->set_value(actorId);
    *result.mutable_target_coords() = ToCoordsProto(target);

    const auto* actor = currentState->units()->Get(actorId);
    if (!actor->has_attached_hero()) { throw ActionRequiresHeroException("reduce"); }

    auto actorAfter = *actor;
    MutatingSpendActionPoints(&actorAfter, cost);
    MutatingBumpAgilityXp(&actorAfter, agilityXp);
    MutatingBumpStrengthXp(&actorAfter, strengthXp);
    MutatingBumpAllKnowledgeToMinimum(&actorAfter, minimumKnowledge);
    AddChangedUnit(result, actorAfter);

    const auto& actingHero = actor->attached_hero();

    bool hasForestAccess = HasForestAccess(
            actor->location(),
            currentState->units(),
            currentState->hex_map(),
            AlliedPids(currentState, GetPlayerId()),
            GetPlayerId());

    double modifiedRoll = (roll + 2.0 * BonusFromStat(actingHero.agility()) +
                           2.0 * BonusFromStat(actingHero.strength()) +
                           2.0 * BonusFromStat(actingHero.wisdom())) +
                          (hasForestAccess ? forestBonus : 0.0);

    double damageGiven = std::max(0.0, (modifiedRoll / 50.0) * averageReduceDebuf);

    const Terrain* targetTerrain = GetTerrain(currentState->hex_map(), target);

    auto modifierAfter = WithReducedCastleIntegrity(
            WithReducedBridgeIntegrity(targetTerrain->modifier(), damageGiven),
            damageGiven);
    *result.add_changed_tile_modifiers() = MakeTmc(target, modifierAfter);

    const Unit* targetOccupant = currentState.GetOccupant(target);
    if (targetOccupant) {
        CombatDamage unitDamageGiven =
                CombatDamage::Builder()
                        .SetImpact(damageGiven * unitDamagePerReduceDebuf / 2.0)
                        .SetCrushing(damageGiven * unitDamagePerReduceDebuf / 2.0)
                        .Build();

        auto occupantAfter = WithTakenDamageFb(
                targetOccupant,
                unitDamageGiven,
                *targetTerrain,
                front,
                false,
                true,
                settings);
        occupantAfter.mutate_fortified(false);

        AddChangedUnit(result, occupantAfter);
    }

    return {result};
}

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

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

    return proto;
}

}  // namespace shardok
