//
// Created by sebastian on 02.08.26.
//

#include "LudoGameMode.h"

#include <algorithm>
#include <cassert>

#include "gameMode/LudoRules.h"
#include "spdlog/spdlog.h"

void ludo::LudoGameMode::sendCommand(const std::variant<MovePieceCommand, RollDiceCommand> &command) {
    std::visit([this](const auto& cmd) { handle(cmd);}, command);
}

std::vector<ludo::LudoEvent> ludo::LudoGameMode::pollEvents() {
    auto events = std::move(m_pendingEvents);
    m_pendingEvents.clear();
    return events;
}

ludo::LudoRules::MoveResult ludo::LudoGameMode::movePieceInternal(int pieceIndex, int diceValue) {
    auto result = LudoRules::computeMoveResult(m_gameState, pieceIndex, diceValue);

    Piece& piece = m_gameState.m_pieces[pieceIndex];
    piece.state = result.targetState;
    piece.position = result.toPosition;

    if (result.captured) {
        Piece& capturedPiece = m_gameState.m_pieces[result.capturedPieceIndex];
        capturedPiece.state = PieceState::AtHome;
        capturedPiece.position = -1;
    }

    return result;
}

void ludo::LudoGameMode::autoReleasePiece(int pieceIndex) {
    auto result = movePieceInternal(pieceIndex, m_gameState.getDiceValue());
    publishEvent(PieceMovedEvent::create(pieceIndex, result.fromPosition, result.toPosition, result.captured, result.startState, result.targetState));
    if (result.captured) {
        //Todo: Determine real toPosition Index dependent on home belegung
        std::vector<int> freeHomePositions = m_gameState.getFreeHomePositions(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex));
        const int smallestFreeHomePosition = *std::ranges::min_element(freeHomePositions);
        publishEvent(PieceMovedEvent::create(result.capturedPieceIndex, result.fromPosition, smallestFreeHomePosition, result.captured, result.startState, result.targetState));
    }
}

void ludo::LudoGameMode::handle(const RollDiceCommand &command) {
    if (!isCurrentPlayer(command.player) || m_gameState.m_phase != TurnPhase::AwaitingRoll) return;

    m_gameState.m_diceValue = rollDice();
    publishEvent(DiceRolledEvent(command.player, m_gameState.m_diceValue));

    if (m_gameState.m_diceValue == 6) {
        if (auto homeIndex = LudoRules::findLowestHomeSlotPiece(m_gameState, getColorOf(command.player))) {
            if (!LudoRules::isBlockedByOwnPiece(m_gameState, getColorOf(command.player), PieceState::OnTrack, LudoRules::getEntryOffset(getColorOf(command.player)))) {
                autoReleasePiece(*homeIndex);
                finishTurn();
                return;
            }
        }
    }

    if (LudoRules::getMoveablePieceIndices(m_gameState, getColorOf(command.player), m_gameState.m_diceValue).empty()) {
        finishTurn(); // kein gültiger zug, sofort weitergeben
    } else {
         m_gameState.m_phase = TurnPhase::AwaitingPieceSelection;
    }
}

ludo::PlayerColor ludo::LudoGameMode::getColorOf(engine::PlayerID player) const {
    const auto& order = getPlayerOrder();
    auto it = std::find(order.begin(), order.end(), player);
    assert(it != order.end() && "getColorOf mit unregistrierter PlayerID aufgerufen");
    return static_cast<PlayerColor>(std::distance(order.begin(), it));
}

void ludo::LudoGameMode::handle(const MovePieceCommand &command) {
    if (!isCurrentPlayer(command.player)) {
        spdlog::warn("MovePieceCommand abgewiesen: {} ist nicht am Zug (aktuell: {})", command.player, getCurrentPlayer());
        return;
    }
    if (m_gameState.m_phase != TurnPhase::AwaitingPieceSelection) {
        spdlog::warn("MovePieceCommand abgewiesen: falsche Phase ({})", static_cast<int>(m_gameState.m_phase));
        return;
    }

    auto movable = LudoRules::getMoveablePieceIndices(m_gameState, getColorOf(command.player), m_gameState.m_diceValue);
    if (std::find(movable.begin(), movable.end(), command.pieceIndex) == movable.end()) {
        spdlog::warn("MovePieceCommand abgewiesen: pieceIndex={} nicht in movable-Liste", command.pieceIndex);
        return;
    }
        // ungültiger Zug, abweisen
    spdlog::info("MovePieceCommand akzeptiert: pieceIndex={}", command.pieceIndex);

    auto result = movePieceInternal(command.pieceIndex, m_gameState.m_diceValue);
    publishEvent(PieceMovedEvent::create(command.pieceIndex, result.fromPosition, result.toPosition, result.captured, result.startState, result.targetState));
    if (result.captured) {
        spdlog::info("MovePieceCommand: gegnerische Figur auf {} geworfen", result.toPosition);
        auto freeHomePositions = m_gameState.getFreeHomePositions(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex));
        if (!freeHomePositions.empty()) {
            Piece capturedPiece = m_gameState.m_pieces[result.capturedPieceIndex];
            PieceMovedEvent event = PieceMovedEvent::create(result.capturedPieceIndex, capturedPiece.position, freeHomePositions.front(), true, capturedPiece.state, PieceState::AtHome);
            publishEvent(event);
        } else {
            throw std::runtime_error(
            std::format("No free home positions for captured piece {}",std::string(ludo::toString(m_gameState.getColorOfPieceIndex(result.capturedPieceIndex))))
            );
        }
    }
    finishTurn();
}

void ludo::LudoGameMode::finishTurn() {
    bool extraTurn = (m_gameState.m_diceValue == 6 && (++m_gameState.m_consecutiveSixes < 2));

    if (m_gameState.m_diceValue != 6) {
        m_gameState.m_consecutiveSixes = 0;
    }

    endTurn(extraTurn);
    m_gameState.m_phase = TurnPhase::AwaitingRoll;
    publishEvent(TurnChangedEvent{getCurrentPlayer()});
}


int ludo::LudoGameMode::rollDice() {
    return m_diceSource->roll();
}

void ludo::LudoGameMode::publishEvent(LudoEvent event) {
    m_pendingEvents.push_back(event);
    m_eventBus.publish(event);
}