//
// Created by Dan Crosby on 4/10/20.
//

#include "src/main/cpp/net/eagle0/shardok/library/commands/challenge_duel_command/ChallengeDuelCommand.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/commands/challenge_duel_command/DuelUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/CombatUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/command_type.pb.h"

namespace shardok {
using net::eagle0::shardok::common::CHALLENGE_DUEL_COMMAND;

auto ChallengeDuelCommand::InternalExecute(
        const GameStateW& currentState,
        const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
    const auto acceptRoll = generator->Percentile();
    return ExecuteWithRolls(currentState, acceptRoll, generator);
}

[[nodiscard]] auto ChallengeDuelCommand::ExecuteWithRolls(
        const GameStateW& currentState,
        double acceptRoll,
        const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
    const auto* challengerBefore = currentState->units()->Get(challengerId);

    auto challengerAfter = *challengerBefore;
    MutatingSpendActionPoints(
            &challengerAfter,
            ActionCost::UsesAllActionCost(settings.Backing().challenge_duel_action_point_cost()));

    const auto* defenderBefore = currentState->units()->Get(defenderId);
    auto defenderAfter = *defenderBefore;

    MutatingBumpAllKnowledgeToMinimum(&challengerAfter, minimumKnowledgeAfter);

    int strengthXp = settings.Backing().duel_strength_xp();
    int agilityXp = settings.Backing().duel_agility_xp();

    MutatingBumpStrengthXp(&challengerAfter, strengthXp);
    MutatingBumpAgilityXp(&challengerAfter, agilityXp);
    MutatingBumpStrengthXp(&defenderAfter, strengthXp),
            MutatingBumpAgilityXp(&defenderAfter, agilityXp);

    // If the defender doesn't have enough vigor to accept the duel, exit early; there's no penalty
    // for this
    if (defenderAfter.attached_hero().vigor() < settings.Backing().minimum_vigor_to_accept_duel()) {
        ActionResult declineResult{};
        declineResult.set_type(net::eagle0::shardok::common::DUEL_DECLINED);
        declineResult.mutable_player()->set_value(challengerAfter.player_id());
        declineResult.mutable_actor()->set_value(challengerAfter.unit_id());
        declineResult.mutable_target_unit()->set_value(defenderAfter.unit_id());

        AddChangedUnit(declineResult, challengerAfter);

        return {declineResult};
    }

    PercentileRollOdds acceptOdds = GetAcceptOdds(
            settings,
            challengerBefore,
            defenderBefore,
            *GetTerrain(currentState->hex_map(), challengerBefore->location()),
            *GetTerrain(currentState->hex_map(), defenderBefore->location()));

    bool accepted = PercentileRollSucceeds(acceptOdds, acceptRoll);

    if (!accepted) {
        MutatingMaybeAdjustMorale(
                &defenderAfter.mutable_battalion(),
                settings.Backing().duel_declined_morale_adjustment(),
                0.0,
                settings.GetBattalionType(defenderAfter.battalion().type()));

        ActionResult declineResult{};
        declineResult.set_type(net::eagle0::shardok::common::DUEL_DECLINED);
        declineResult.mutable_player()->set_value(challengerAfter.player_id());
        declineResult.mutable_actor()->set_value(challengerAfter.unit_id());
        declineResult.mutable_target_unit()->set_value(defenderAfter.unit_id());

        AddChangedUnit(declineResult, challengerAfter);
        AddChangedUnit(declineResult, defenderAfter);

        return {declineResult};
    }

    auto& challengingHero = challengerAfter.mutable_attached_hero();
    auto& defendingHero = defenderAfter.mutable_attached_hero();

    int roundsRemaining = 30;
    while (!IsDestroyed(challengingHero) && !IsDestroyed(defendingHero) && roundsRemaining > 0) {
        CombatDamage challengerInflicted = CombatUtils::CalculateHeroDamageGiven(
                                                   challengingHero,
                                                   generator->OpenEndedPercentile(),
                                                   settings) *
                                           0.2;
        CombatDamage defenderInflicted = CombatUtils::CalculateHeroDamageGiven(
                                                 defendingHero,
                                                 generator->ReverseOpenEndedPercentile(),
                                                 settings) *
                                         0.2;

        // gets too tired to fight
        MutatingTakeDamage(&challengingHero, defenderInflicted, 0, false);
        MutatingTakeDamage(&defendingHero, challengerInflicted, 0, false);
        roundsRemaining--;
    }

    ActionResult acceptedResult{};
    acceptedResult.set_type(net::eagle0::shardok::common::DUEL_ACCEPTED);
    acceptedResult.mutable_player()->set_value(challengerAfter.player_id());
    acceptedResult.mutable_actor()->set_value(challengerAfter.unit_id());
    acceptedResult.mutable_target_unit()->set_value(defenderAfter.unit_id());

    int winnerCharismaXp = settings.Backing().duel_winner_charisma_xp();
    double winnerMoraleBoost = settings.Backing().duel_won_morale_boost();
    if (IsDestroyed(challengingHero)) {
        MutatingMaybeAdjustMorale(
                &defenderAfter.mutable_battalion(),
                winnerMoraleBoost,
                0.0,
                settings.GetBattalionType(defenderAfter.battalion().type()));
        MutatingBumpCharismaXp(&defenderAfter, winnerCharismaXp);
    }
    if (IsDestroyed(defendingHero)) {
        MutatingMaybeAdjustMorale(
                &challengerAfter.mutable_battalion(),
                winnerMoraleBoost,
                0.0,
                settings.GetBattalionType(challengerAfter.battalion().type()));
        MutatingBumpCharismaXp(&challengerAfter, winnerCharismaXp);
    }

    AddChangedUnit(acceptedResult, challengerAfter);
    AddChangedUnit(acceptedResult, defenderAfter);

    return {acceptedResult};
}

auto ChallengeDuelCommand::GetCommandProto() const -> CommandProto {
    CommandProto cmd{};
    cmd.set_type(CHALLENGE_DUEL_COMMAND);
    cmd.set_player(GetPlayerId());
    cmd.mutable_actor()->set_value(challengerId);
    cmd.mutable_target_unit()->set_value(defenderId);
    *cmd.mutable_target() = ToCoordsProto(target);
    cmd.set_will_unhide(true);

    return cmd;
}

}  // namespace shardok