// GameState.cpp
#include <stdexcept>
#include <SFML/Graphics.hpp>
#include "botinterface.hpp"
#include "common.hpp"
#include "playerinterface.hpp"
#include "gamestate.hpp"

GameEngine::GameEngine(void) {
    InitializeGenerator();
}

void GameEngine::Start(void) {
    PrepareGameBoard();
    if (!state.m_bNoDisplay)
        graphics.StartGameWindow();
    if (state.m_bIsBotControlled)
        graphics.SetShowGame(true);
    Loop();
}

void GameEngine::Reset(void) {
    if (!state.m_bIsBotControlled)
        graphics.CheckContinue();
    else
        bot.AddIteration(player.body.size());
    player.Reset();
    PrepareGameBoard();
    state.m_bIsGameOver = false;
    if (!state.m_bIsBotControlled)
        return;
    if (!state.m_bSmart) {
        while (!bot.path.empty())
            bot.path.pop();
    }
    if (state.m_bNoDisplay)
        graphics.SetShowGame(false);
    if (state.m_bSkipIterations)
        graphics.SetShowGame((bot.amountPlayed + 1) % 50 == 0);
    // TODO: Replace with value to force this effect
    graphics.SetShowGame(true);
}

void GameEngine::Loop(void) {
    int currentScore = 0;
    while (graphics.IsOpen() || state.m_bNoDisplay) {
        if (state.m_bIsGameOver)
            Reset();
        UpdatePlayerSpeed();
        PlaceNewSnakePart(MovePlayer());
        RegenerateFood();
        currentScore = player.body.size() * 100;
        if (!state.m_bNoDisplay)
            graphics.DisplayGameState(gameBoard, currentScore);
    }
}

sf::Vector2f GameEngine::MovePlayer(void) {
    return sf::Vector2f(player.headLocation.x + player.speed.x, player.headLocation.y + player.speed.y);
}


sf::Vector2f GameEngine::GetGameBoundaries(void) {
    return graphics.gameBoundaries;
}

PlayerDirection GameEngine::GetCurrentDirection(void) {
    if (player.speed.x) {
        if (player.speed.x > 0)
            return kRight;
        else
            return kLeft;
    } else {
        if (player.speed.y > 0)
            return kDown;
        else
            return kUp;
    }
}

int GameEngine::GetPlayerSize(void) {
    return player.body.size();
}

sf::Vector2f GameEngine::GetHeadLocation(void) {
    return player.headLocation;
}

sf::Vector2f GameEngine::GetFoodLocation(void) {
    return playerFood.location;
}

void GameEngine::PlaceNewSnakePart(sf::Vector2f location) {
    if (!player.speed.x && !player.speed.y)
        return;
    try {
        GameSpace* locationState = &gameBoard.at(location.y).at(location.x);
        if (locationState->m_bSnake && (player.body.size() > 1))
            state.m_bIsGameOver = true; // Game should end (Snake touching snake)
        locationState->m_bSnake = true;
        player.body.push(locationState);
        player.headLocation = location;
        if (playerFood.location != location)
            player.Pop();
        else {
            locationState->m_bFood = false;
            if (state.m_bIsBotControlled)
                bot.ResetPath();
        }
    } catch (const std::out_of_range& error) {
        state.m_bIsGameOver = true; // Snake ran into edge
    }
}


// Generates new food until not colliding with player
void GameEngine::RegenerateFood(void) {
    // Generate a new food location if the current one is occupied
    while (gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bSnake)
        playerFood.GenerateNewFood(GetGameBoundaries());

    // Update the game board with the new food location
    gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = 1;
}


void GameEngine::PrepareGameBoard(void) {
    // Create empty game board
    gameBoard.clear();
    sf::Vector2f boardDimensions = GetGameBoundaries();
    gameBoard.resize(boardDimensions.y, std::vector<GameSpace>(boardDimensions.x));

    // Snake setup
    player.headLocation.x = GenerateRandomNumber(boardDimensions.x);
    player.headLocation.y = GenerateRandomNumber(boardDimensions.y);
    GameSpace* locationState = &gameBoard.at(player.headLocation.y).at(player.headLocation.x);
    player.body.push(locationState);
    locationState->m_bSnake = true;

    // Food setup
    playerFood.GenerateNewFood(boardDimensions);
    gameBoard.at(playerFood.location.y).at(playerFood.location.x).m_bFood = true;
    return;
}

void GameEngine::UpdatePlayerSpeed(void) {
    PlayerDirection controller;
    if (state.m_bIsBotControlled) { 
        if (bot.path.empty())
            bot.GetNewPath(); 
        controller = bot.GetInput();
    } else
        controller = GetPlayerInput();

    switch (controller) { 
        case kUp:
            if (player.speed.y == kUnitSpeed)
                break;
            player.speed.x = 0;
            player.speed.y = -kUnitSpeed;
            break;
        case kLeft:
            if (player.speed.x == kUnitSpeed)
                break;
            player.speed.x = -kUnitSpeed;
            player.speed.y = 0;
            break;
        case kRight:
            if (player.speed.x == -kUnitSpeed)
                break;
            player.speed.x = kUnitSpeed;
            player.speed.y = 0;
            break;
        case kDown:
            if (player.speed.y == -kUnitSpeed)
                break;
            player.speed.x = 0;
            player.speed.y = kUnitSpeed;
            break;
        default:
            break;
    }
    return;
}