#include "botinterface.hpp"
#include "common.hpp"
#include <array>
#include <cstdlib>
#include <queue>
#include <stdexcept>
#include <SFML/System/Vector2.hpp>

namespace snakeplusplus
{
    PlayerDirection lastKnownDirection = kNone;

    AISnake::AISnake() {
        ;
    }

    PlayerDirection AISnake::GetInput(const sf::Vector2f* source)
    {
        sf::Vector2f directionDelta;
        if (*source == path.top()) { path.pop(); }
        if (path.empty()) { return kUp; } // Snake is trapped
        directionDelta = *source - path.top();
        path.pop();
        if ((directionDelta.y == 1)
                && (lastKnownDirection != kDown))
            { lastKnownDirection = kUp; }
        else if ((directionDelta.y == -1)
                && (lastKnownDirection != kUp)) 
            { lastKnownDirection = kDown; }
        else if ((directionDelta.x == 1)
                && (lastKnownDirection != kRight)) 
            { lastKnownDirection = kLeft; }
        else if ((directionDelta.x == -1)
                && (lastKnownDirection != kLeft)) 
            { lastKnownDirection = kRight; }
        return lastKnownDirection;
    }

    // Gets a new path for the bot to follow
    // Uses DFS algorithm
    void AISnake::GetNewPath(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries, const int snakeSize)
    {
        // Search for food
        if (snakeSize < 160) {
            BFS(gameBoard, source, boundaries);
        } else {
            DFS(gameBoard, source, boundaries);
        }
        // Create path for food
        path.push(botPathUnsanitized.top());
        botPathUnsanitized.pop();
        while (!botPathUnsanitized.empty()) {
            sf::Vector2f deltaVector = botPathUnsanitized.top() - path.top();
            int delta = abs(deltaVector.x) + abs(deltaVector.y);
            if (delta == 1) {
                path.push(botPathUnsanitized.top());
            }
            botPathUnsanitized.pop();
        }
    }

    void AISnake::BFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) {
        std::queue<sf::Vector2f> search;
        std::vector<std::vector<bool>> visited(boundaries.y, std::vector<bool> (boundaries.x, false));
        bool foodFound = false;
        search.push(source);
        while (!search.empty()) {
            sf::Vector2f currentLocation = search.front();
            search.pop();
            if (foodFound) { break; }
            if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; }
            if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') {
                foodFound = true;
            }
            botPathUnsanitized.push(currentLocation);
            std::array<sf::Vector2f, 4> localLocations;
            localLocations.fill(currentLocation);
            localLocations[0].y += 1;
            localLocations[1].x += 1;
            localLocations[2].y -= 1;
            localLocations[3].x -= 1;
            for (auto i : localLocations) { 
                try {
                    if (gameBoard.at(i.y).at(i.x) == 'X') {
                        botPathUnsanitized.push(i);
                        foodFound = true;
                    }
                } catch (const std::out_of_range& error) {
                    continue; // Out of bounds
                }
            }
            for (sf::Vector2f newLocation : localLocations) {
                try {
                    if ((!visited.at(newLocation.y).at(newLocation.x))
                            && (gameBoard.at(newLocation.y).at(newLocation.x) == ' ')) {
                        search.push(newLocation);
                    }
                } catch (const std::out_of_range& error) {
                    continue; // Out of bounds
                }
            }
            visited.at(currentLocation.y).at(currentLocation.x) = true;
        }
    }

    void AISnake::DFS(const std::vector< std::vector<char> >& gameBoard, const sf::Vector2f& source, const sf::Vector2f& boundaries) {
        std::stack<sf::Vector2f> search;
        std::vector<std::vector<bool>> visited(boundaries.y, std::vector<bool> (boundaries.x, false));
        bool foodFound = false;
        search.push(source);
        while (!search.empty()) {
            sf::Vector2f currentLocation = search.top();
            search.pop();
            if (foodFound) { break; }
            if (visited.at(currentLocation.y).at(currentLocation.x)) { continue; }
            if (gameBoard.at(currentLocation.y).at(currentLocation.x) == 'X') {
                foodFound = true;
            }
            botPathUnsanitized.push(currentLocation);
            std::array<sf::Vector2f, 4> localLocations;
            localLocations.fill(currentLocation);
            localLocations[0].y += 1;
            localLocations[1].x += 1;
            localLocations[2].y -= 1;
            localLocations[3].x -= 1;
            for (auto i : localLocations) { 
                try {
                    if (gameBoard.at(i.y).at(i.x) == 'X') {
                        botPathUnsanitized.push(i);
                        foodFound = true;
                    }
                } catch (const std::out_of_range& error) {
                    continue; // Out of bounds
                }
            }
            for (sf::Vector2f newLocation : localLocations) {
                try {
                    if ((!visited.at(newLocation.y).at(newLocation.x))
                            && (gameBoard.at(newLocation.y).at(newLocation.x) == ' ')) {
                        search.push(newLocation);
                    }
                } catch (const std::out_of_range& error) {
                    continue; // Out of bounds
                }
            }
            visited.at(currentLocation.y).at(currentLocation.x) = true;
        }
    }
}