#include "GamePlayingAppMenu.h"

#include "AppMenu.h"

#include <stack>
#include <memory>
#include <algorithm>
#include <cmath>
#include <string>
#include <SFML/Graphics.hpp>


GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
    AppMenu(menuStack, settings, renderWindow),
    game(this->settings->getMenu().startGame(this->settings->getGamemode())) {
    
    this->startTimer = this->settings->getStartTimerLength() * FRAMES_PER_SECOND;
    if (this->startTimer == 0) {
        this->game.start();
    }
    this->paused = false;
    this->pausePressed = false;
    this->retryPressed = false;

    int maxWidthMultiplier = (this->settings->getWindowSizeMultiplier() * 40) / (this->game.getBoard().getWidth());
    int maxHeightMultiplier = (this->settings->getWindowSizeMultiplier() * 50) / (this->game.getBoard().getBaseHeight() + 10);
    this->cellSizeZoom = std::min(maxWidthMultiplier, maxHeightMultiplier);

    float boardWidth = this->game.getBoard().getWidth() * this->cellSizeZoom;
    float boardHeight = (this->game.getBoard().getBaseHeight() + 10) * this->cellSizeZoom;
    this->boardPosition = sf::FloatRect(sf::Vector2f((this->settings->getWindowSizeMultiplier() * 40) - (boardWidth / 2),
                                                     (this->settings->getWindowSizeMultiplier() * 25) - (boardHeight / 2)),
                                        sf::Vector2f(boardWidth, boardHeight));
    
    
    for (int i = 0; i < 5; i++) {
        this->nextQueuePosition[i] = sf::FloatRect(sf::Vector2f(this->boardPosition.position.x + boardWidth + (5.f * this->settings->getWindowSizeMultiplier()), (10.f + (10.f * i)) * this->settings->getWindowSizeMultiplier()),
                                                   sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier()));
    }

    this->nextCellSizeZoom = this->nextQueuePosition[0].size.y;
    for (const auto& piece : this->settings->getMenu().getPiecesList().getSelectedPieces()) {
        float nextPieceCellSizeZoom = ((int) this->nextQueuePosition[0].size.y) / this->settings->getMenu().getPiecesList().lookAtPiece(piece).getLength();
        this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom);
    }

    this->holdBoxPosition = sf::FloatRect(sf::Vector2f(this->boardPosition.position.x - ((8.f + 5.f) * this->settings->getWindowSizeMultiplier()), (10.f) * this->settings->getWindowSizeMultiplier()),
                                          sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier()));
    this->holdCellSizeZoom = this->nextCellSizeZoom;
}

void GamePlayingAppMenu::computeFrame() {
    this->updateMetaBinds();

    if (this->startTimer > 0) {
        this->startTimer--;
        if (this->startTimer == 0) {
            this->game.start();
        }
    }

    if (this->escReleased) {
        this->menuStack->pop();
    }
    else {
        std::set<Action> actions;
        for (Action action : ACTION_LIST_IN_ORDER) {
            for (sfKey key : this->settings->getKeybinds().getKeybinds(action)) {
                if (sf::Keyboard::isKeyPressed(key)) {
                    actions.insert(action);
                }
            }
        }
    
        if (actions.contains(RETRY)) {
            this->retryPressed = true;
        }
        else {
            if (this->retryPressed) {
                this->game.reset();
                this->startTimer = this->settings->getStartTimerLength() * FRAMES_PER_SECOND;
                if (this->startTimer == 0) {
                    this->game.start();
                }
            }
            this->retryPressed = false;
        }

        if (actions.contains(PAUSE)) {
            this->pausePressed = true;
        }
        else {
            if (this->pausePressed) {
                this->paused = (!this->paused);
            }
            this->pausePressed = false;
        }
    
        if (!paused) {
            this->game.nextFrame(actions);
        }
    }
}

void GamePlayingAppMenu::drawFrame() const {
    this->renderWindow->clear(sf::Color(200, 200, 200));

    sf::Color bonesBlockColor(0, 0, 0);
    sf::Color bonesBlockGhostColor(100, 100, 100);
    bool areBlockBones = this->game.areBlocksBones();
    bool isBoardInvisible = this->game.isBoardInvisible() && !(this->game.hasWon() || this->game.hasLost());

    sf::Vector2f cellSize(this->cellSizeZoom, this->cellSizeZoom);
    float cellOutlineThickness = this->cellSizeZoom / 4;
    bool drawActivePiece = (this->game.getActivePiece() != nullptr) && (!this->game.hasLost());

    // board
    for (int y = this->game.getBoard().getBaseHeight() + 9; y >= 0; y--) {
        for (int x = 0; x < this->game.getBoard().getWidth(); x++) {
            Block block = this->game.getBoard().getBlock(Position(x, y));
            if (isBoardInvisible) block = NOTHING;

            sf::RectangleShape cell(cellSize);
            cell.setFillColor((areBlockBones && block != NOTHING)
                ? bonesBlockColor
                : this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
            cell.setPosition(this->getBoardBlockPosition(x, y));
            this->renderWindow->draw(cell);
        }
    }

    if (drawActivePiece) {
        // ghost piece
        sf::Color ghostColor = areBlockBones
            ? bonesBlockGhostColor
            : this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);

        for (const Position& position : this->game.getActivePiece()->getPositions()) {
            Position cellPosition = (this->game.getGhostPiecePosition() + position);

            sf::RectangleShape cell(cellSize);
            cell.setFillColor(ghostColor);
            cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
            this->renderWindow->draw(cell);
        }

        // active piece outline
        sf::Color pieceOultlineColor = sf::Color(255, 255 - (255 * this->game.getForcedLockDelayProgression()), 255 - (255 * this->game.getForcedLockDelayProgression()));

        for (const Position& position : this->game.getActivePiece()->getPositions()) {
            Position cellPosition = (this->game.getActivePiecePosition() + position);

            sf::RectangleShape cell(cellSize);
            cell.setOutlineThickness(cellOutlineThickness);
            cell.setOutlineColor(pieceOultlineColor);
            cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
            this->renderWindow->draw(cell);
        }
    }

    // top out line
    sf::RectangleShape topOutLine(sf::Vector2f(this->cellSizeZoom * this->game.getBoard().getWidth(), std::roundf(this->cellSizeZoom / 4)));
    topOutLine.setPosition(this->getBoardBlockPosition(0, this->game.getBoard().getBaseHeight() - 1));
    topOutLine.setFillColor(sf::Color(255, 0, 0));
    this->renderWindow->draw(topOutLine);

    if (drawActivePiece) {
        // active piece
        sf::Color pieceColor = areBlockBones
            ? bonesBlockColor
            : this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), -200 * (this->game.getLockDelayProgression()));

        for (const Position& position : this->game.getActivePiece()->getPositions()) {
            Position cellPosition = (this->game.getActivePiecePosition() + position);

            sf::RectangleShape cell(cellSize);
            cell.setFillColor(pieceColor);
            cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
            this->renderWindow->draw(cell);
        }
    }

    // next queue
    int upShift = 0;
    for (int i = 0; i < std::min((int) this->game.getNextPieces().size(), 5); i++) {
        sf::FloatRect nextBox = this->nextQueuePosition[i];
        nextBox.position.y -= upShift;

        sf::Vector2f nextCellSize(this->nextCellSizeZoom, this->nextCellSizeZoom);
        sf::Color pieceColor = areBlockBones
            ? bonesBlockColor
            : this->getColorOfBlock(this->game.getNextPieces().at(i).getBlockType(), 0);
        sf::Color boxColor = sf::Color(180, 180, 180);

        int lowestRank = 0;
        for (int y = 0; y < this->game.getNextPieces().at(i).getLength(); y++) {
            for (int x = 0; x < this->game.getNextPieces().at(i).getLength(); x++) {
                sf::RectangleShape cell(nextCellSize);
                if (this->game.getNextPieces().at(i).containsSquare(Position(x, y))) {
                    cell.setFillColor(pieceColor);
                    lowestRank = y;
                }
                else {
                    cell.setFillColor(boxColor);
                }
                cell.setPosition(sf::Vector2f(nextBox.position.x + (x * this->nextCellSizeZoom),
                                              nextBox.position.y + ((this->game.getNextPieces().at(i).getLength() - y - 1) * this->nextCellSizeZoom)));
                this->renderWindow->draw(cell);
            }
        }

        upShift += nextBox.size.y - (this->game.getNextPieces().at(i).getLength() * this->nextCellSizeZoom);
    }

    // hold box
    if (this->game.getHeldPiece() != nullptr) {
        sf::Vector2f holdCellSize(this->holdCellSizeZoom, this->holdCellSizeZoom);
        sf::Color color = areBlockBones
            ? bonesBlockColor
            : this->getColorOfBlock(this->game.getHeldPiece()->getBlockType(), 0);
        sf::Color boxColor = sf::Color(180, 180, 180);
        
        for (int y = 0; y < this->game.getHeldPiece()->getLength(); y++) {
            for (int x = 0; x < this->game.getHeldPiece()->getLength(); x++) {
                sf::RectangleShape cell(holdCellSize);
                if (this->game.getHeldPiece()->containsSquare(Position(x, y))) {
                    cell.setFillColor(color);
                }
                else {
                    cell.setFillColor(boxColor);
                }
                cell.setPosition(sf::Vector2f(this->holdBoxPosition.position.x + (x * this->nextCellSizeZoom),
                                              this->holdBoxPosition.position.y + ((this->game.getHeldPiece()->getLength() - y - 1) * this->holdCellSizeZoom)));
                this->renderWindow->draw(cell);
            }
        }
    }

    // stats
    int windowSizeMultiplier = this->settings->getWindowSizeMultiplier();
    int fontSize = (this->boardPosition.size.x > (windowSizeMultiplier * 30.f)) ? (windowSizeMultiplier) : (windowSizeMultiplier * 2);
    sf::Text text(this->pressStartFont, "", fontSize);
    text.setFillColor(sf::Color(0, 0, 0));

    int millisecondes = this->game.getFramesPassed() * (1000.f / FRAMES_PER_SECOND);
    std::string showedMillisecondes = std::to_string(millisecondes % 1000);
    while (showedMillisecondes.size() < 3) {
        showedMillisecondes = "0" + showedMillisecondes;
    }
    std::string showedSecondes = std::to_string((millisecondes / 1000) % 60);
    while (showedSecondes.size() < 2) {
        showedSecondes = "0" + showedSecondes;
    }
    std::string showedMinutes = std::to_string((millisecondes / (60 * 1000)));
    std::string showedTime = showedMinutes + ":" + showedSecondes + "." + showedMillisecondes;

    this->placeText(text, {}, getGamemodeName(this->settings->getGamemode()), 1.f, 3.f, {});
    this->placeText(text, {}, getGamemodeGoal(this->settings->getGamemode()), 1.f, 6.f, {});

    if (this->game.isOnB2BChain()) {
        this->placeText(text, {}, "B2B", 1.f, 22.f, {});
    }
    this->placeText(text, {}, "LINES:" + std::to_string(this->game.getClearedLines()), 1.f, 27.f, {});
    this->placeText(text, {}, "LEVEL:" + std::to_string(this->game.getLevel()), 1.f, 32.f, {});
    this->placeText(text, {}, "SCORE:" + std::to_string(this->game.getScore()), 1.f, 37.f, {});
    this->placeText(text, {}, "GRADE:" + std::to_string(this->game.getGrade()), 1.f, 42.f, {});
    this->placeText(text, {}, showedTime, 1.f, 47.f, {});

    // game state
    text.setOutlineColor(sf::Color(255, 255, 255));
    text.setOutlineThickness(windowSizeMultiplier / 2.f);
    text.setCharacterSize(windowSizeMultiplier * 4);

    if (this->game.hasWon()) {
        this->placeTitle(text, {}, "WIN", 25.f, {});
    }
    else if (this->game.hasLost()) {
        this->placeTitle(text, {}, "LOSE", 25.f, {});
    }
    else if (this->paused) {
        this->placeTitle(text, {}, "PAUSE", 25.f, {});
    }
    else if (this->startTimer > 0) {
        this->placeTitle(text, {}, std::to_string(((this->startTimer - 1) / ((this->settings->getStartTimerLength() * FRAMES_PER_SECOND) / 4))), 25.f, {});
    }

    this->renderWindow->display();
}

sf::Vector2f GamePlayingAppMenu::getBoardBlockPosition(int x, int y) const {
    return sf::Vector2f(this->boardPosition.position.x + (x * this->cellSizeZoom),
                        this->boardPosition.position.y + ((this->game.getBoard().getBaseHeight() + 9 - y) * this->cellSizeZoom));
}
