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

#include "ArcheryCommand.hpp"

#include <utility>

#include "src/main/cpp/net/eagle0/shardok/library/map/Coordinates.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/CombatUtils.hpp"

namespace shardok {
using std::vector;

ArcheryCommand::ArcheryCommand(
        const SettingsGetter& settingsGetter,
        PlayerId attackerPlayerId,
        UnitId attackerId,
        UnitId defenderId,
        const Coords& defenderLocation,
        PercentileRollOdds ambushOdds,
        const Terrain& defenderTerrain)
    : ShardokCommand(attackerPlayerId),
      settings(settingsGetter),
      attackerId(attackerId),
      defenderId(defenderId),
      defenderLocation(defenderLocation),
      ambushOdds(std::move(ambushOdds)),
      defenderTerrain(defenderTerrain) {}

auto ArcheryCommand::InternalExecuteWithRoll(
        const GameStateW& currentState,
        const std::shared_ptr<RandomGenerator>& generator,
        const std::optional<int32_t> roll) const -> vector<ActionResult> {
    const auto ambushRoll = generator->Percentile();
    const auto attackerRoll = roll.value_or(generator->OpenEndedPercentile());
    return ExecuteWithRolls(currentState, ambushRoll, attackerRoll);
}

[[nodiscard]] auto ArcheryCommand::ExecuteWithRolls(
        const GameStateW& currentState,
        double ambushRoll,
        double attackerRoll) const -> vector<ActionResult> {
    Unit attackerAfter = *currentState->units()->Get(attackerId);
    MutatingSpendActionPoints(
            &attackerAfter,
            ActionCost::UsesAllActionCost(settings.Backing().archery_action_point_cost()));

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

    if (!attackerAfter.has_attached_hero()) {
        throw ShardokNotImplementedException("archery without hero");
    }

    bool isAmbush = false;
    if (attackerAfter.hidden() && PercentileRollSucceeds(ambushOdds, ambushRoll) &&
        CombatUtils::DefenderTerrainAllowsAmbush(defenderTerrain)) {
        isAmbush = true;
    }

    attackerAfter.mutate_volleys_remaining(attackerAfter.volleys_remaining() - 1);
    attackerAfter.mutate_hidden(false);
    int agilityXp = isAmbush ? settings.Backing().archery_ambush_agility_xp()
                             : settings.Backing().archery_agility_xp();
    MutatingBumpAgilityXp(&attackerAfter, agilityXp);

    CombatDamage unitDamage =
            CombatUtils::CalculateUnitArcheryDamageGiven(attackerAfter, attackerRoll, settings);

    CombatDamage totalDamage = (defenderAfter.stun_rounds_remaining() > 0
                                        ? settings.Backing().damage_received_multiplier_from_stun()
                                        : 1.0) *
                               unitDamage;

    MutatingApplyDamageFrom(
            &defenderAfter,
            &attackerAfter,
            totalDamage,
            defenderTerrain,
            front,
            isAmbush,
            settings.Backing().morale_shift_per_casualty(),
            settings.Backing().base_deadliness(),
            settings.GetBattalionType(defenderAfter.battalion().type()),
            settings.GetBattalionType(attackerAfter.battalion().type()));

    ActionResult result{};
    result.set_type(isAmbush ? ActionType::ARCHERY_AMBUSH : ActionType::ARCHERY_ATTACK);
    result.mutable_player()->set_value(attackerAfter.player_id());
    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 ArcheryCommand::GetCommandProto() const -> CommandProto {
    CommandProto proto{};

    proto.set_player(GetPlayerId());
    proto.set_type(net::eagle0::shardok::common::ARCHERY_COMMAND);
    proto.mutable_actor()->set_value(attackerId);
    *proto.mutable_target() = ToCoordsProto(defenderLocation);
    proto.mutable_target_unit()->set_value(defenderId);
    proto.set_will_unhide(true);
    proto.mutable_roll_request()->set_roll_type(
            net::eagle0::shardok::api::ROLL_TYPE_D100_OPEN_ENDED);
    proto.mutable_roll_request()->set_command_type(net::eagle0::shardok::common::ARCHERY_COMMAND);
    proto.mutable_roll_request()->set_acting_unit_id(attackerId);

    return proto;
}

}  // namespace shardok