//
// Created by Dan Crosby on 6/4/20.
//

#include "StartPlayerTurnAction.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/util/HexMapUtils.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/action_type.pb.h"

namespace shardok {

static auto RequiredDailyFood(const Unit& unit, const SettingsGetter& settingsGetter) -> double {
    return settingsGetter.GetBattalionType(unit.battalion().type())->GetDailyFoodCostPerTroop() *
           unit.battalion().size();
}

auto StartPlayerTurnAction::InternalExecute(
        const GameStateW& currentState,
        const std::shared_ptr<RandomGenerator>& generator) const -> vector<ActionResult> {
    ActionResult result{};
    result.set_type(net::eagle0::shardok::common::ActionType::PLAYER_TURN_START);
    result.mutable_next_player()->set_value(newFactionId);

    // Reset any movedInZoc or charger info for the previous player
    if (startingState->eligible_charger_id() != -1) {
        AddChangedUnit(result, *startingState->units()->Get(startingState->eligible_charger_id()));
    }

    result.clear_eligible_charger();
    result.clear_possible_chargees();

    for (const Unit* movedUnit : *startingState->units()) {
        if (movedUnit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT)
            continue;
        if (movedUnit->has_moved_in_zoc()) {
            auto movedUnitAfter = *movedUnit;
            movedUnitAfter.mutate_has_moved_in_zoc(false);
            AddChangedUnit(result, movedUnitAfter);
        }
    };

    // Set min morale and all action points for the new player
    double minMoraleInCastle = settingsGetter.Backing().min_morale_in_castle();
    for (const Unit* changedUnit : *startingState->units()) {
        if (changedUnit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT)
            continue;
        if (changedUnit->player_id() != newFactionId) continue;

        auto updatedUnit = *changedUnit;

        bool inCastle = GetTerrain(startingState->hex_map(), changedUnit->location())
                                ->modifier()
                                .castle()
                                .present();
        float effectiveBase = updatedUnit.battalion().base_morale();
        if (startingState->hex_map()
                    ->monthly_weather()
                    ->Get(startingState->month() - 1)
                    ->is_winter() &&
            !inCastle) {
            effectiveBase += float(settingsGetter.Backing().winter_morale_adjustment());
        }
        float moraleDiff = effectiveBase - updatedUnit.battalion().morale();
        MutatingMaybeAdjustMorale(
                &updatedUnit.mutable_battalion(),
                moraleDiff * settingsGetter.Backing().morale_mean_reversion(),
                0.0,
                settingsGetter.GetBattalionType(updatedUnit.battalion().type()));

        if (updatedUnit.battalion().morale() < minMoraleInCastle && inCastle) {
            MutatingMaybeSetMorale(
                    &updatedUnit.mutable_battalion(),
                    minMoraleInCastle,
                    settingsGetter.GetBattalionType(updatedUnit.battalion().type()));
        }

        // Set remaining action points.
        ActionPoints remainingActionPoints = MaxActionPoints(updatedUnit);
        updatedUnit.mutate_remaining_action_points(remainingActionPoints);

        // Unflag having controlled this round
        if (updatedUnit.has_attached_hero() &&
            updatedUnit.attached_hero().control_info().controlled_unit_id() != -1) {
            updatedUnit.mutable_attached_hero().mutable_control_info().mutate_controlled_this_round(
                    false);
        }

        // Consume food and drain morale if needed
        double neededFood = RequiredDailyFood(updatedUnit, settingsGetter);
        if (neededFood <= updatedUnit.food_remaining()) {
            updatedUnit.mutate_food_remaining(updatedUnit.food_remaining() - neededFood);
        } else {
            updatedUnit.mutate_food_remaining(0);
            MutatingMaybeAdjustMorale(
                    &updatedUnit.mutable_battalion(),
                    0,
                    settingsGetter.Backing().morale_adjustment_for_not_enough_food(),
                    settingsGetter.GetBattalionType(updatedUnit.battalion().type()));
            updatedUnit.mutable_battalion().mutate_size(
                    updatedUnit.battalion().size() *
                    settingsGetter.Backing().size_multiplier_for_not_enough_food());
        }

        AddChangedUnit(result, updatedUnit);
    };

    return {result};
}
}  // namespace shardok
