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

#include "PerformUndeadCommandsAction.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/ShardokCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/action_result_applier/ActionResultApplier.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/HexMapHelpers.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"
#include "src/main/protobuf/net/eagle0/shardok/api/command_descriptor.pb.h"

namespace shardok {

using CommandProto = net::eagle0::shardok::api::CommandDescriptor;

auto ChooseUndeadCommand(
        const CommandListSPtr &commands,
        const Units *units,
        const HexMap *hexMap,
        const std::shared_ptr<RandomGenerator> &randomGenerator) -> CommandSPtr;

auto PerformUndeadCommandsAction::InternalExecute(
        const GameStateW &currentState,
        const std::shared_ptr<RandomGenerator> &generator) const -> vector<ActionResultProto> {
    GameStateW runningGameState = startingGameState;
    vector<ActionResultProto> allResults{};

    while (runningGameState->current_player() == UNCONTROLLED_PLAYER_ID) {
        auto commands = availableCommandsFactory->GetAvailableCommands(
                runningGameState,
                UNCONTROLLED_PLAYER_ID,
                /* includeFollowUps=*/false);

        if (commands) {
            const auto chosenCommand = ChooseUndeadCommand(
                    commands,
                    runningGameState->units(),
                    runningGameState->hex_map(),
                    generator);

            auto results = chosenCommand->Execute(runningGameState, generator);
            for (const auto &oneResult : results) {
                allResults.push_back(oneResult);
                runningGameState = ApplyResult(runningGameState, oneResult, settings);
            }
        }
    }

    return allResults;
}

auto ChooseUndeadCommand(
        const CommandListSPtr &commands,
        const Units *units,
        const HexMap *hexMap,
        const std::shared_ptr<RandomGenerator> &randomGenerator) -> CommandSPtr {
    CommandList attackCommands;

    for (const auto &command : *commands) {
        const CommandProto &possibleAttackCommandProto = command->GetCommandProto();
        if (!possibleAttackCommandProto.has_actor()) continue;
        const auto *const actor = units->Get(possibleAttackCommandProto.actor().value());
        if (actor->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            (possibleAttackCommandProto.type() == net::eagle0::shardok::common::MELEE_COMMAND ||
             possibleAttackCommandProto.type() == net::eagle0::shardok::common::CHARGE_COMMAND)) {
            // If this command is to attack the formerly commanding necromancer, take it.
            const auto *const occupant =
                    Occupant(units, FromCoordsProto(possibleAttackCommandProto.target()));
            if (actor->targeted_unit() != -1 && occupant &&
                occupant->unit_id() == actor->targeted_unit()) {
                return command;
            }
            attackCommands.push_back(command);
        }
    }

    CommandList moveCommands;
    CommandSPtr bestMoveCommand = nullptr;

    for (const auto &command : *commands) {
        const CommandProto &possibleMoveOrEndTurnCommand = command->GetCommandProto();
        if (possibleMoveOrEndTurnCommand.type() == net::eagle0::shardok::common::END_TURN_COMMAND) {
            moveCommands.push_back(command);
            continue;
        }
        if (!possibleMoveOrEndTurnCommand.has_actor()) continue;
        if (possibleMoveOrEndTurnCommand.type() != net::eagle0::shardok::common::MOVE_COMMAND)
            continue;

        // See if this move would allow us to move adjacent the enemy
        const auto *const actor = units->Get(possibleMoveOrEndTurnCommand.actor().value());
        if (actor->status() == net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT &&
            actor->targeted_unit() != -1) {
            const UnitId targetedUnitId = actor->targeted_unit();
            for (const Coords &adjCoords : HexMapUtils::GetAdjacentCoords(
                         hexMap,
                         FromCoordsProto(possibleMoveOrEndTurnCommand.target()))) {
                const auto *const occupant = Occupant(units, adjCoords);
                if (occupant && occupant->unit_id() == targetedUnitId) {
                    if (bestMoveCommand == nullptr ||
                        bestMoveCommand->GetCommandProto().action_points().value() >
                                possibleMoveOrEndTurnCommand.action_points().value()) {
                        bestMoveCommand = command;
                    }
                }
            }
        }
        moveCommands.push_back(command);
    }

    if (bestMoveCommand) { return bestMoveCommand; }

    // If we got here, we can't attack the ex commander and we can't move adjacent to the ex
    // commander. Try a random melee first, else a random move or end turn.

    if (!attackCommands.empty()) { return randomGenerator->RandomElement(attackCommands); }

    return randomGenerator->RandomElement(moveCommands);
}

}  // namespace shardok
