//
// Created by Dan Crosby on 3/11/20.
//

#include "BraveWaterCommand.hpp"

#include <utility>

#include "src/main/cpp/net/eagle0/shardok/library/actions/DefensiveAmbushAction.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/ActionResultFlatbufferHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/OddsFilter.hpp"

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

vector<ActionResult> BraveWaterCommand::InternalExecuteWithRoll(
        const GameStateW &currentState,
        const std::shared_ptr<RandomGenerator> &generator,
        const optional<int32_t> roll) const {
    // Succeeds on high roll
    const auto successRoll = 101 - roll.value_or(generator->Percentile());
    const auto lossesRoll = generator->Percentile();
    const auto ambusherRoll = generator->Percentile();

    return ExecuteWithRolls(successRoll, lossesRoll, ambusherRoll, currentState, generator);
}

[[nodiscard]] auto BraveWaterCommand::ExecuteWithRolls(
        double successRoll,
        double lossesRoll,
        double ambusherRoll,
        const GameStateW &currentState,
        const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResult> {
    auto actorAfterSpend = *currentState->units()->Get(actorId);
    MutatingSpendActionPoints(&actorAfterSpend, cost);

    actorAfterSpend.mutate_stun_rounds_remaining(1);
    double lossPercentage = (lossesRoll / 100.0) * maxLossPercentage *
                            static_cast<double>(100 - GetSuccessChance(successOdds)) / 100.0;
    auto sizeBefore = actorAfterSpend.battalion().size();
    auto armamentBefore = actorAfterSpend.battalion().armament();
    auto sizeAfter = static_cast<int32_t>(sizeBefore - sizeBefore * lossPercentage);
    auto armamentAfter = static_cast<float>(armamentBefore - armamentBefore * lossPercentage);
    auto vigorBefore = actorAfterSpend.attached_hero().vigor();
    auto vigorAfter = static_cast<float>(vigorBefore - lossPercentage * vigorBefore);

    actorAfterSpend.mutable_battalion().mutate_size(sizeAfter);
    actorAfterSpend.mutable_battalion().mutate_armament(armamentAfter);
    actorAfterSpend.mutable_attached_hero().mutate_vigor(vigorAfter);

    ActionResult crossingResult;
    crossingResult.mutable_player()->set_value(actorAfterSpend.player_id());
    crossingResult.mutable_actor()->set_value(actorAfterSpend.unit_id());

    vector<ActionResult> allResults{};

    if (PercentileRollSucceeds(successOdds, successRoll)) {
        if (ambusherId.has_value()) {
            // Tile is occupied -- ambush!
            crossingResult.set_type(ActionType::CROSSED_WATER);
            AddChangedUnit(crossingResult, actorAfterSpend);
            allResults.push_back(crossingResult);

            auto ambushResults =
                    DefensiveAmbushAction(ambusherId.value(), actorId, ambusherRoll, settings)
                            .Execute(currentState, generator);
            allResults.insert(
                    std::end(allResults),
                    std::begin(ambushResults),
                    std::end(ambushResults));
        } else {
            crossingResult.set_type(ActionType::CROSSED_WATER);
            actorAfterSpend.mutable_location() = target;
            AddChangedUnit(crossingResult, actorAfterSpend);
            allResults.push_back(crossingResult);
        }
    } else {
        crossingResult.set_type(ActionType::CROSS_WATER_FAILED);
        AddChangedUnit(crossingResult, actorAfterSpend);
        // location doesn't change
        allResults.push_back(crossingResult);
    }

    return allResults;
}

BraveWaterCommand::BraveWaterCommand(
        const ActionCost &cost,
        const PlayerId playerId,
        const UnitId actorId,
        const std::optional<UnitId> &ambusherId,
        const Coords &target,
        PercentileRollOdds successOdds,
        const double maxLossPercentage,
        const SettingsGetter &settingsGetter)
    : ShardokCommand(playerId),
      cost(cost),
      actorId(actorId),
      ambusherId(ambusherId),
      target(target),
      successOdds(std::move(successOdds)),
      maxLossPercentage(maxLossPercentage),
      settings(settingsGetter) {}

auto BraveWaterCommand::GetCommandProto() const -> CommandProto {
    CommandProto commandProto;

    const auto oddsView = OddsFilteredForPlayer(successOdds, GetPlayerId());

    commandProto.set_type(net::eagle0::shardok::common::BRAVE_WATER_COMMAND);
    *commandProto.mutable_target() = ToCoordsProto(target);
    commandProto.set_player(GetPlayerId());
    commandProto.mutable_actor()->set_value(actorId);
    *commandProto.mutable_odds() = oddsView;
    commandProto.mutable_action_points()->set_value(cost.points);
    commandProto.mutable_roll_request()->set_roll_type(net::eagle0::shardok::api::ROLL_TYPE_D100);
    commandProto.mutable_roll_request()->set_command_type(
            net::eagle0::shardok::common::BRAVE_WATER_COMMAND);
    commandProto.mutable_roll_request()->set_acting_unit_id(actorId);
    *commandProto.mutable_roll_request()->mutable_odds() = oddsView;

    return commandProto;
}

int32_t BraveWaterCommand::GetOddsPercentile() const {
    return OddsFilteredForPlayer(successOdds, GetPlayerId()).success_chance();
}
}  // namespace shardok
