//
//  DuelUtils.cpp
//  eagle0
//
//  Created by Dan Crosby on 9/18/18.
//  Copyright © 2018 none. All rights reserved.
//

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

#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/CombatUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/UnitFilter.hpp"

namespace shardok {
using std::vector;

constexpr int NUM_RUNS = 10;
constexpr int COMBAT_ROUNDS = 50;
constexpr double BATTALION_STRENGTH_WEIGHT = 0.5;
constexpr double HERO_STRENGTH_WEIGHT = 1.0;

auto GetAcceptOdds(
        const SettingsGetter &settings,
        const Unit *challenger,
        const Unit *defender,
        const Terrain &challengerTerrain,
        const Terrain &defenderTerrain) -> PercentileRollOdds {
    net::eagle0::shardok::api::UnitView filteredView =
            UnitFilteredForPlayer(settings, challenger, defender->player_id(), {});
    vector<double> stats = {(double)defender->attached_hero().bravery()};
    vector<OtherFactor> otherFactors{};

    Unit believedChallenger = *challenger;
    MutatingSpendActionPoints(
            &believedChallenger,
            ActionCost::UsesAllActionCost(settings.Backing().challenge_duel_action_point_cost()));

    Unit believedDefender = *defender;

    bool statsKnown = filteredView.battalion().has_training();

    int defenderWins = 0;

    const auto &chType = settings.GetBattalionType(challenger->battalion().type());
    const auto &dfType = settings.GetBattalionType(defender->battalion().type());
    auto moraleShiftPerCasualty = settings.Backing().morale_shift_per_casualty();

    double baseDeadliness = settings.Backing().base_deadliness();

    for (int i = 0; i < NUM_RUNS; i++) {
        double defenderRoll;
        if (statsKnown) {
            defenderRoll = i * 100.0 / (NUM_RUNS - 1);
        } else {
            defenderRoll = 50.0;

            believedChallenger.mutable_battalion().mutate_training(i * 100.0 / NUM_RUNS);
            believedChallenger.mutable_battalion().mutate_armament(i * 100.0 / NUM_RUNS);
        }
        auto chUnit = believedChallenger;
        auto dfUnit = believedDefender;

        int combatRoundsRemaining = COMBAT_ROUNDS;
        while (!IsDestroyed(chUnit) && !IsDestroyed(dfUnit) && combatRoundsRemaining) {
            CombatDamage challengerDamage =
                    CombatUtils::CalculateUnitDamageGiven(&chUnit, 50.0, 50.0, settings);
            CombatDamage defenderDamage = CombatUtils::CalculateUnitDamageGiven(
                    &dfUnit,
                    defenderRoll,
                    defenderRoll,
                    settings);

            MutatingApplyDamageFrom(
                    &chUnit,
                    &dfUnit,
                    defenderDamage,
                    challengerTerrain,
                    front,
                    false,
                    moraleShiftPerCasualty,
                    baseDeadliness,
                    chType,
                    dfType);
            MutatingApplyDamageFrom(
                    &dfUnit,
                    &chUnit,
                    challengerDamage,
                    defenderTerrain,
                    front,
                    false,
                    moraleShiftPerCasualty,
                    baseDeadliness,
                    dfType,
                    chType);

            combatRoundsRemaining--;
        }
        if (IsDestroyed(chUnit) && !IsDestroyed(dfUnit)) defenderWins++;
    }

    double battalionStrengthSwing = (NUM_RUNS - 2.0 * defenderWins) / NUM_RUNS;
    double battalionStrengthFactor =
            BATTALION_STRENGTH_WEIGHT * battalionStrengthSwing * defender->attached_hero().wisdom();
    otherFactors.push_back(MakeOtherFactor((int)battalionStrengthFactor, "battalion strength"));

    bool heroStatsKnown = filteredView.attached_hero().has_stats();

    auto believedChallenger2 = *challenger;
    MutatingSpendActionPoints(
            &believedChallenger2,
            ActionCost::UsesAllActionCost(settings.Backing().challenge_duel_action_point_cost()));
    Hero believedChallengingHero = believedChallenger2.attached_hero();

    double believedVigor = believedChallengingHero.vigor();

    Hero defendingHero = defender->attached_hero();
    defenderWins = 0;
    for (int i = 0; i < NUM_RUNS; i++) {
        double defenderRoll;
        if (heroStatsKnown) {
            defenderRoll = i * 100.0 / (NUM_RUNS - 1);
        } else {
            defenderRoll = 50.0;
            believedChallengingHero.mutate_strength(i * 100.0 / NUM_RUNS);
            believedChallengingHero.mutate_vigor(believedVigor);
        }
        auto chHero = believedChallengingHero;
        auto dfHero = defendingHero;

        int combatRoundsRemaining = COMBAT_ROUNDS;
        while (!IsDestroyed(chHero) && !IsDestroyed(dfHero) && combatRoundsRemaining) {
            CombatDamage challengerDamage =
                    CombatUtils::CalculateHeroDamageGiven(chHero, 50.0, settings, true);
            CombatDamage defenderDamage =
                    CombatUtils::CalculateHeroDamageGiven(dfHero, defenderRoll, settings, true);

            MutatingTakeDamage(&chHero, defenderDamage, 0, false);
            MutatingTakeDamage(&dfHero, challengerDamage, 0, false);

            combatRoundsRemaining--;
        }
        if (IsDestroyed(chHero)) defenderWins++;
    }
    double duelOddsSwing = (2.0 * defenderWins - NUM_RUNS) / NUM_RUNS;
    double expectedDuelOutcomeFactor =
            HERO_STRENGTH_WEIGHT * duelOddsSwing * defender->attached_hero().wisdom();
    otherFactors.push_back(
            MakeOtherFactor((int)expectedDuelOutcomeFactor, "expected duel outcome"));

    return MakeOdds(settings.Backing().duel_base_accept_odds(), 0, 0, 0, stats, otherFactors);
}

}  // namespace shardok
