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

#include "LightningBoltCommand.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/actions/ActionRequiresHeroException.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/TerrainHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"

namespace shardok {

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

LightningBoltCommand::LightningBoltCommand(
        const SettingsGetter& settingsGetter,
        const PlayerId playerId,
        const UnitId attackerId,
        const UnitId defenderId,
        const Coords& defenderLocation,
        const double damageCap)
    : ShardokCommand(playerId),
      settings(settingsGetter),
      attackerId(attackerId),
      defenderId(defenderId),
      defenderLocation(defenderLocation),
      damageCap(damageCap) {}

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

auto LightningBoltCommand::ExecuteWithRoll(const GameStateW& currentState, double attackerRoll)
        const -> vector<ActionResult> {
    const Unit* attackerBefore = currentState->units()->Get(attackerId);
    if (!attackerBefore->has_attached_hero()) {
        throw ActionRequiresHeroException("lightning bolt without hero");
    }
    Unit attackerAfter = *attackerBefore;
    MutatingSpendActionPoints(
            &attackerAfter,
            ActionCost::UsesAllActionCost(settings.Backing().lightning_action_point_cost()));
    const auto& attHero = attackerAfter.attached_hero();

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

    MutatingBumpWisdomXp(&attackerAfter, settings.Backing().lightning_bolt_wisdom_xp());
    MutatingBumpAllKnowledgeToMinimum(
            &attackerAfter,
            settings.Backing().minimum_knowledge_for_profession());

    double statFactor = settings.Backing().lightning_wisdom_factor() * attHero.wisdom() +
                        settings.Backing().lightning_agility_factor() * attHero.agility();
    CombatDamage attackerDamage =
            CombatDamage::Builder()
                    .SetPenetratingLightning(
                            statFactor * settings.Backing().lightning_damage_factor())
                    .Build();

    MutatingApplyDamageFrom(
            &defenderAfter,
            &attackerAfter,
            attackerDamage,
            *GetTerrain(currentState->hex_map(), defenderBefore->location()),
            front,
            /* isAmbush=*/false,
            settings.Backing().morale_shift_per_casualty(),
            settings.Backing().base_deadliness(),
            settings.GetBattalionType(defenderAfter.battalion().type()),
            settings.GetBattalionType(attackerAfter.battalion().type()));

    double minLevel = 1.0 - damageCap;
    if (defenderAfter.has_attached_hero() &&
        defenderAfter.attached_hero().vigor() <
                minLevel * defenderBefore->attached_hero().vigor()) {
        defenderAfter.mutable_attached_hero().mutate_vigor(
                minLevel * defenderBefore->attached_hero().vigor());
    }
    if (defenderAfter.battalion().size() < minLevel * defenderBefore->battalion().size()) {
        defenderAfter.mutable_battalion().mutate_size(
                minLevel * defenderBefore->battalion().size());
    }

    ActionResult result{};
    result.set_type(ActionType::LIGHTNING_BOLT);
    result.mutable_player()->set_value(GetPlayerId());
    result.mutable_actor()->set_value(attackerAfter.unit_id());
    result.mutable_target_unit()->set_value(defenderAfter.unit_id());
    *result.mutable_target_coords() = ToCoordsProto(defenderAfter.location());

    AddChangedUnit(result, attackerAfter);
    AddChangedUnit(result, defenderAfter);

    return {result};
}

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

    proto.set_player(GetPlayerId());
    proto.set_type(net::eagle0::shardok::common::LIGHTNING_BOLT_COMMAND);
    proto.mutable_actor()->set_value(attackerId);
    *proto.mutable_target() = ToCoordsProto(defenderLocation);
    proto.mutable_target_unit()->set_value(defenderId);
    proto.set_will_unhide(true);

    return proto;
}

}  // namespace shardok
