//
//  MeleeCommand.cpp
//  eagle0_engine
//
//  Created by Dan Crosby on 12/29/18.
//  Copyright © 2018 none. All rights reserved.
//

#include "MeleeCommand.hpp"

#include <utility>

#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/TerrainHelpers.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/util/HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ZoneOfControlCalculator.hpp"

namespace shardok {
MeleeCommand::MeleeCommand(
        const ActionCost& cost,
        const SettingsGetter& settingsGetter,
        const PlayerId playerId,
        const UnitId attackerId,
        const UnitId defenderId,
        const Coords& defenderLocation,
        PercentileRollOdds ambushOdds)
    : ShardokCommand(playerId),
      cost(cost),
      settings(settingsGetter),
      attackerId(attackerId),
      defenderId(defenderId),
      defenderLocation(defenderLocation),
      ambushOdds(std::move(ambushOdds)) {}

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

    return ExecuteWithRolls(currentState, ambushRoll, attackerRoll, defenderRoll);
}

[[nodiscard]] auto MeleeCommand::ExecuteWithRolls(
        const GameStateW& currentState,
        double ambushRoll,
        double attackerRoll,
        double defenderRoll) const -> vector<ActionResult> {
    const Unit* attacker = currentState->units()->Get(attackerId);
    Unit attackerAfter = *attacker;

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

    auto defenderTerrain = GetTerrain(currentState->hex_map(), defender->location());
    bool isAmbush = attacker->hidden() && PercentileRollSucceeds(ambushOdds, ambushRoll) &&
                    CombatUtils::DefenderTerrainAllowsAmbush(*defenderTerrain);

    AttackOrientation attackOrientation = AttackOrientationForAttack(
            settings,
            attacker->player_id(),
            currentState->units(),
            currentState->hex_map(),
            attacker->location(),
            defender->location());

    auto results = CombatUtils::InternalPerformMelee(
            cost,
            &attackerAfter,
            &defenderAfter,
            *defenderTerrain,
            attackOrientation,
            attackerRoll,
            defenderRoll,
            /* isCharge=*/false,
            isAmbush,
            settings);

    return vector<ActionResult>{
            (isAmbush ? CombatUtils::MeleeAmbushResult(results)
                      : CombatUtils::MeleeActionResult(results))};
}

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

    proto.set_player(GetPlayerId());
    proto.set_type(net::eagle0::shardok::common::MELEE_COMMAND);
    proto.mutable_actor()->set_value(attackerId);
    *proto.mutable_target() = ToCoordsProto(defenderLocation);
    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::MELEE_COMMAND);
    proto.mutable_roll_request()->set_acting_unit_id(attackerId);

    return proto;
}
}  // namespace shardok
