#include "TextApp.h"

#include "../Core/Menu.h"

#include <map>
#include <set>
#include <string>
#include <sstream>
#include <algorithm>
#include <cstdlib>
#include <exception>

static const int FRAMES_PER_INPUT = FRAMES_PER_SECOND / 2; // the number of frames that will pass everytime the player gives an input
static const int MAXIMUM_PIECE_SIZE = 10;                  // the maximum size of pieces that will be loaded and utilizable
static const int DEFAULT_PIECE_SIZE = 4;                   // the default size of pieces that will be selected when first running the app
static const int MAXIMUM_BOARD_WIDTH = 30;                 // the maximum selectable width of the board
static const int MAXIMUM_BOARD_HEIGHT = 40;                // the maximum selectable height of the board
static const Gamemode DEFAULT_GAMEMODE = SPRINT;           // the gamemode that will be used when starting a new game


TextApp::TextApp() {
    this->defaultKeybinds();

    this->gameMenu.getPiecesList().loadPieces(MAXIMUM_PIECE_SIZE);
    this->gameMenu.getPiecesList().selectAllPieces(DEFAULT_PIECE_SIZE);

    this->gameMenu.getPlayerControls().setDAS(FRAMES_PER_INPUT);
    this->gameMenu.getPlayerControls().setARR(FRAMES_PER_INPUT);
    this->gameMenu.getPlayerControls().setSDR(0);
}

void TextApp::run() {
    bool quit = false;
    while (!quit) {
        std::cout << "\n\n\n";
        std::cout << "===| WELCOME TO JMINOS! |===" << std::endl;
        std::cout << "1- Change pieces" << std::endl;
        std::cout << "2- Change board" << std::endl;
        std::cout << "3- See controls" << std::endl;
        std::cout << "4- Start game" << std::endl;
        std::cout << "5- Quit" << std::endl;
        std::cout << "Choice: ";

        std::string answer;
        std::getline(std::cin, answer);
        int selectedAnswer = 0;
        try {
            selectedAnswer = std::stoi(answer);
        }
        catch (std::exception ignored) {}

        switch (selectedAnswer) {
            case 1 : {this->choosePieces(); break;}
            case 2 : {this->chooseBoardSize(); break;}
            case 3 : {this->seeKeybinds(); break;}
            case 4 : {this->startGame(); break;}
            case 5 : {quit = true; break;}
            default : std::cout << "Invalid answer!" << std::endl;
        }
    }
    std::cout << "===| SEE YA NEXT TIME! |===" << std::endl;
}

void TextApp::choosePieces() {
    std::cout << "\n\n\n";
    std::cout << "Choose which piece sizes to play with (from 1 to " << MAXIMUM_PIECE_SIZE << "), separate mutltiple sizes with blank spaces." << std::endl;
    std::cout << "Choice: ";
    std::string answer;
    std::getline(std::cin, answer);

    this->gameMenu.getPiecesList().unselectAll();
    std::cout << "Selected pieces of sizes:"; 
    std::stringstream answerStream(answer);
    for (std::string size; std::getline(answerStream, size, ' ');) {
        try {
            int selectedSize = std::stoi(size);
            if (selectedSize >= 1 && selectedSize <= MAXIMUM_PIECE_SIZE) {
                if (this->gameMenu.getPiecesList().selectAllPieces(selectedSize)) {
                    std::cout << " " << selectedSize;
                }
            }
        }
        catch (std::exception ignored) {}
    }

    std::string waiting;
    std::getline(std::cin, waiting);
}

void TextApp::chooseBoardSize() {
    std::string answer;

    std::cout << "\n\n\n";
    std::cout << "Current board width and height: " << this->gameMenu.getBoardWidth() << "x" << this->gameMenu.getBoardHeight() << std::endl;

    std::cout << "Choose the width of the board (from 1 to " << MAXIMUM_BOARD_WIDTH << ")." << std::endl;
    std::cout << "Choice: ";
    std::getline(std::cin, answer);
    try {
        int selectedSize = std::stoi(answer);
        if (selectedSize >= 1 && selectedSize <= MAXIMUM_BOARD_WIDTH) {
            this->gameMenu.setBoardWidth(selectedSize);
        }
    }
    catch (std::exception ignored) {}

    std::cout << "Choose the height of the board (from 1 to " << MAXIMUM_BOARD_HEIGHT << ")." << std::endl;
    std::cout << "Choice: ";
    std::getline(std::cin, answer);
    try {
        int selectedSize = std::stoi(answer);
        if (selectedSize >= 1 && selectedSize <= MAXIMUM_BOARD_HEIGHT) {
            this->gameMenu.setBoardHeight(selectedSize);
        }
    }
    catch (std::exception ignored) {}

    std::cout << "New board width and height: " << this->gameMenu.getBoardWidth() << "x" << this->gameMenu.getBoardHeight();

    std::string waiting;
    std::getline(std::cin, waiting);
}

void TextApp::seeKeybinds() const {
    std::cout << "\n\n\n";
    std::cout << "Quit/Pause/Retry: quit/pause/retry" << std::endl;
    std::cout << "Hold : h" << std::endl;
    std::cout << "Soft/Hard drop : sd/hd" << std::endl;
    std::cout << "Move left/right : l/r" << std::endl;
    std::cout << "Rotate 0/CW/180/CCW: c/cw/cc/ccw" << std::endl;
    std::cout << "\n";
    std::cout << "To do several actions at the same time, separe them with blank spaces." << std::endl;
    std::string waiting;
    std::getline(std::cin, waiting);
}

void TextApp::defaultKeybinds() {
    this->keybinds.clear();
    this->keybinds.insert({"pause", PAUSE});
    this->keybinds.insert({"retry", RETRY});
    this->keybinds.insert({"h", HOLD});
    this->keybinds.insert({"sd", SOFT_DROP});
    this->keybinds.insert({"hd", HARD_DROP});
    this->keybinds.insert({"l", MOVE_LEFT});
    this->keybinds.insert({"r", MOVE_RIGHT});
    this->keybinds.insert({"c", ROTATE_0});
    this->keybinds.insert({"cw", ROTATE_CW});
    this->keybinds.insert({"cc", ROTATE_180});
    this->keybinds.insert({"ccw", ROTATE_CCW});
}

void TextApp::startGame() const {
    Game game = this->gameMenu.startGame(DEFAULT_GAMEMODE);
    game.start();

    std::cout << "\n\n\n";
    this->printGame(game);

    bool quit = false;
    bool paused = false;
    std::string answer;
    while (!quit) {
        std::cout << "Actions: ";
        std::getline(std::cin, answer);
        std::stringstream answerStream(answer);
        std::vector<std::string> actions;
        for (std::string action; std::getline(answerStream, action, ' ');) {
            actions.push_back(action);
        }

        std::set<Action> playerActions;
        std::set<Action> lastFrameActions;
        bool retrying = false;
        for (std::string action : actions) {
            if (action == "quit") {
                quit = true;
            }
            else {
                try {
                    Action playerAction = this->keybinds.at(action);
                    if (playerAction == RETRY) {
                        retrying = true;
                    }
                    else if (playerAction == PAUSE) {
                        paused = (!paused);
                    }
                    else if (playerAction == SOFT_DROP || playerAction == MOVE_LEFT || playerAction == MOVE_RIGHT) {
                        playerActions.insert(playerAction);
                        lastFrameActions.insert(playerAction);
                    }
                    else if (playerAction == HOLD || playerAction == HARD_DROP) {
                        lastFrameActions.insert(playerAction);
                    }
                    else {
                        playerActions.insert(playerAction);
                    }
                }
                catch (std::exception ignored) {}
            }
        }

        if (!paused && !quit) {
            if (retrying) {
                game.reset();
                game.start();
            }
            else {
                for (int i = 0; i < (FRAMES_PER_INPUT - 1); i++) {
                    game.nextFrame(playerActions);
                }
                game.nextFrame(lastFrameActions);
            }
        }

        if (!quit) {
            std::cout << "\n\n\n";
            if (paused) {
                std::cout << "--<[PAUSED]>--" << std::endl;
            }
            this->printGame(game);
        }

        if (game.hasLost()) {
            quit = true;
            std::cout << "You lost!" << std::endl;
        }
        else if (game.hasWon()) {
            quit = true;
            std::cout << "You won!" << std::endl;
        }
    }
}

void TextApp::printGame(const Game& game) const {
    int maxHeight = game.getBoard().getGridHeight();
    if (game.getActivePiece() != nullptr) {
        for (const Position& position : game.getActivePiece()->getPositions()) {
            maxHeight = std::max(maxHeight, position.y + game.getActivePiecePosition().y);
        }
    }
    
    bool lineCountPrinted = false;
    bool holdBoxStartedPrinting = false;
    bool holdBoxFinishedPrinting = false;
    bool nextQueueStartedPrinting = false;
    bool nextQueueFinishedPrinting = false;
    int nextQueuePrintedPiece;
    int printedPieceLineHeight;

    for (int y = maxHeight; y >= 0; y--) {
        for (int x = 0; x < game.getBoard().getWidth(); x++) {
            /* BOARD PRINTING */
            bool isActivePieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position(x, y) - game.getActivePiecePosition()));
            bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position(x, y) - game.getGhostPiecePosition()));
            Block block = (isActivePieceHere || isGhostPieceHere) ? game.getActivePiece()->getBlockType() : game.getBoard().getBlock(Position(x, y));

            if (isActivePieceHere || isGhostPieceHere) {
                std::cout << getConsoleColorCode(block);
            }
            else {
                std::cout << getResetConsoleColorCode();
            }

            if (block != NOTHING && (!(isGhostPieceHere && !isActivePieceHere))) {
                std::cout << "*";
            }
            else {
                if (y < game.getBoard().getBaseHeight()) {
                    if (isGhostPieceHere) {
                        std::cout << "=";
                    }
                    else {
                        std::cout << "-";
                    }
                }
                else {
                    std::cout << " ";
                }
            }
        }

        if (y < game.getBoard().getGridHeight()) {
            /* SIDEBAR PRINTING */
            std::cout << "     ";
            if (!lineCountPrinted) {
                std::cout << getResetConsoleColorCode() << "Lines: " << game.getClearedLines();
                lineCountPrinted = true;
            }
            else if (!holdBoxFinishedPrinting) {
                if (!holdBoxStartedPrinting) {
                    std::cout << getResetConsoleColorCode() << "Hold:";
                    printedPieceLineHeight = (game.getHeldPiece() == nullptr) ? -1 : (game.getHeldPiece()->getLength() - 1);
                    holdBoxStartedPrinting = true;
                }
                else {
                    for (int i = 0; i < game.getHeldPiece()->getLength(); i++) {
                        if (game.getHeldPiece()->getPositions().contains(Position(i, printedPieceLineHeight))) {
                            std::cout << getConsoleColorCode(game.getHeldPiece()->getBlockType()) << "*";
                        }
                        else {
                            std::cout << getResetConsoleColorCode() << "-";
                        }
                    }
                    printedPieceLineHeight--;
                }
                if (printedPieceLineHeight < 0) {
                    holdBoxFinishedPrinting = true;
                }
            }
            else if (!nextQueueFinishedPrinting) {
                if (!nextQueueStartedPrinting) {
                    std::cout << getResetConsoleColorCode() << "Next:";
                    printedPieceLineHeight = (game.getNextPieces().size() == 0) ? -1 : (game.getNextPieces().at(0).getLength() - 1);
                    nextQueuePrintedPiece = 0;
                    nextQueueStartedPrinting = true;
                }
                else {
                    for (int i = 0; i < game.getNextPieces().at(nextQueuePrintedPiece).getLength(); i++) {
                        if (game.getNextPieces().at(nextQueuePrintedPiece).getPositions().contains(Position(i, printedPieceLineHeight))) {
                            std::cout << getConsoleColorCode(game.getNextPieces().at(nextQueuePrintedPiece).getBlockType()) << "*";
                        }
                        else {
                            std::cout << getResetConsoleColorCode() << "-";
                        }
                    }
                    printedPieceLineHeight--;
                }
                if (printedPieceLineHeight < 0) {
                    nextQueuePrintedPiece++;
                    if (nextQueuePrintedPiece >= game.getNextPieces().size()) {
                        nextQueueFinishedPrinting = true;
                    }
                    else {
                        printedPieceLineHeight = game.getNextPieces().at(nextQueuePrintedPiece).getLength() - 1;
                    }
                }
            }
        }
        
        std::cout << "\n";
    }

    std::cout << getResetConsoleColorCode();
}
