#include "GamePiecesAppMenu.h"

#include "AppMenu.h"
#include "GameDistributionAppMenu.h"
#include "../PlayerCursor.h"

#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>


GamePiecesAppMenu::GamePiecesAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
    AppMenu(menuStack, settings, renderWindow),
    playerCursor({1, (unsigned int) this->settings->getSelectedPieces().size() + 1u}) {
    
    for (int i = 1; i <= this->settings->getLoadablePiecesSize(); i++) {
        this->playerCursor.addRow(i + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(i) + 4);
    }

    if (this->settings->getLoadablePiecesSize() < MAXIMUM_PIECES_SIZE) {
        this->playerCursor.addRow(this->settings->getLoadablePiecesSize() + 2, 1);
    }
}

void GamePiecesAppMenu::computeFrame() {
    this->updateMetaBinds();
    this->playerCursor.updatePosition();

    if (this->playerCursor.movedDown() && this->playerCursor.getPosition().y == 2) {
        this->playerCursor.goToPosition({0, 2});
    }

    if (this->enterReleased) {
        if (this->playerCursor.getPosition().y == 0) {
            this->menuStack->push(std::make_shared<GameDistributionAppMenu>(this->menuStack, this->settings, this->renderWindow));
        }

        if (this->playerCursor.getPosition().y == (this->settings->getLoadablePiecesSize() + 2)) {
            int newMaxSize = this->settings->getLoadablePiecesSize() + 1;
            this->settings->loadPieces(newMaxSize);

            this->playerCursor.removeRow(newMaxSize + 1);
            this->playerCursor.addRow(newMaxSize + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(newMaxSize) + 4);
            this->playerCursor.goToPosition({0u, newMaxSize + 1u});

            if (newMaxSize < MAXIMUM_PIECES_SIZE) {
                this->playerCursor.addRow(newMaxSize + 2, 1);
            }
        }
        else if (this->playerCursor.getPosition().y > 1) {
            if (this->playerCursor.getPosition().x >= 4) {
                this->settings->selectPieces(createSinglePieceType(this->playerCursor.getPosition().y - 1), this->playerCursor.getPosition().x - 4);
            }
            else {
                switch (this->playerCursor.getPosition().x) {
                    case 0 : {this->settings->selectPieces(ALL_PIECES, this->playerCursor.getPosition().y - 1); break;}
                    case 1 : {this->settings->selectPieces(CONVEX_PIECES, this->playerCursor.getPosition().y - 1); break;}
                    case 2 : {this->settings->selectPieces(HOLELESS_PIECES, this->playerCursor.getPosition().y - 1); break;}
                    case 3 : {this->settings->selectPieces(OTHER_PIECES, this->playerCursor.getPosition().y - 1); break;}
                }
            }
            this->playerCursor.addPosition(0, 1);
        }
    }
    if (this->escReleased) {
        if (this->playerCursor.getPosition().y == 1) {
            if (this->playerCursor.getPosition().x > 0) {
                this->settings->unselectPieces(this->playerCursor.getPosition().x - 1);
                this->playerCursor.removePosition(this->playerCursor.getPosition().x - 1, 1);
            }
        }
        else {
            this->settings->confirmSelectedPieces();
            this->menuStack->pop();
        }
    }
}

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

    sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
    text.setFillColor(sf::Color(0, 0, 0));
    text.setOutlineColor(sf::Color(255, 255, 255));

    this->placeTitle(text, {}, "PIECES SELECT", 5.f, {});

    if (this->playerCursor.getPosition().y == 0) {
        this->placeText(text, this->playerCursor, "PIECES DISTRIBUTION", 5.f, 15.f, sf::Vector2u{0, 0});
        this->drawSelectedPiecesRow(25.f);
        this->drawRow(1, 35.f, true);
        this->drawRow(2, 45.f, true);
    }
    else {
        this->drawSelectedPiecesRow(15.f);

        bool drawFromFirstElem = (this->playerCursor.getPosition().y == 1);
        bool addExtraLine = (this->settings->getLoadablePiecesSize() < MAXIMUM_PIECES_SIZE);
        int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 1, this->settings->getLoadablePiecesSize() - 2 + (addExtraLine ? 1 : 0));
        this->drawRow(firstElem, 25.f, drawFromFirstElem);
        this->drawRow(firstElem + 1, 35.f, drawFromFirstElem);
        this->drawRow(firstElem + 2, 45.f, drawFromFirstElem);
    }

    this->renderWindow->display();
}

void GamePiecesAppMenu::drawSelectedPiecesRow(float yPos) const {
    sf::RectangleShape rect({(float) this->renderWindow->getSize().x, 8.f * this->settings->getWindowSizeMultiplier()});
    rect.setPosition({0.f, (yPos - 4.f) * this->settings->getWindowSizeMultiplier()});
    rect.setFillColor({240, 240, 240});
    this->renderWindow->draw(rect);

    sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier());
    text.setFillColor({0, 0, 0});

    int elem = (this->playerCursor.getPosition().y == 1) ? std::max(((int) this->playerCursor.getPosition().x) - 4, 0) : 0;
    float xProgress = 1.f;

    bool first = true;
    while (true) {
        if ((this->playerCursor.getPosition().y == 1) && (elem == this->playerCursor.getPosition().x)) {
            this->placeText(text, {}, "|", xProgress, yPos, {});
            xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
        }

        if (elem >= this->settings->getSelectedPieces().size()) return;

        const auto& [pieceType, value] = this->settings->getSelectedPieces().at(elem);

        int pieceSize = getSizeOfPieces(pieceType);
        if (pieceSize > 0) {
            if (!(pieceSize > this->settings->getLoadablePiecesSize())) {
                const Piece& piece = this->settings->getMenu().readPiecesList().lookAtPiece({pieceSize, value});
                int cellSize = (8 * this->settings->getWindowSizeMultiplier()) / (piece.getLength());
                sf::FloatRect piecePosition(sf::Vector2f(xProgress, yPos - 4.f) * (float) this->settings->getWindowSizeMultiplier(), sf::Vector2f(8 , 8) * (float) this->settings->getWindowSizeMultiplier());
                this->drawPiece(piece, cellSize, piecePosition, false);
                xProgress += (1.f + 8.f);
            }
            else {
                this->placeText(text, {}, "UNLOADED_" + std::to_string(pieceSize), xProgress, yPos, {});
                xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
            }
        }
        else {
            if (!(value > this->settings->getLoadablePiecesSize())) {
                this->placeText(text, {}, ((first) ? "" : " ") + getPiecesTypeName(pieceType) + "_" + std::to_string(value), xProgress, yPos, {});
                xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
            }
            else {
                this->placeText(text, {}, "UNLOADED_" + std::to_string(pieceSize), xProgress, yPos, {});
                xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
            }
        }

        elem++;
    }
}

void GamePiecesAppMenu::drawRow(int piecesSize, float yPos, bool drawFromFirstElem) const {
    if (piecesSize > this->settings->getLoadablePiecesSize()) {
        sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
        text.setOutlineThickness(this->settings->getWindowSizeMultiplier() / 2);
        if (this->playerCursor.getPosition().y == (piecesSize + 1)) {
            text.setOutlineColor({255, 255, 255});
        }
        else {
            text.setOutlineColor({0, 0, 0});
        }

        std::string sizeString = "LOAD SIZE " + std::to_string(piecesSize) + "? ";
        if (piecesSize <= 10) {
            text.setFillColor({0, 255, 0});
            this->placeText(text, {}, sizeString + "(LOW LOAD TIME)", 1.f, yPos, {});
        }
        else if (piecesSize <= 13) {
            text.setFillColor({255, 255, 0});
            this->placeText(text, {}, sizeString + "(MEDIUM LOAD TIME)", 1.f, yPos, {}); 
        }
        else {
            text.setFillColor({255, 0, 0});
            this->placeText(text, {}, sizeString + "(LONG LOAD TIME)", 1.f, yPos, {}); 
        }
    }
    else {
        int numberOfPieces = this->settings->getMenu().readPiecesList().getNumberOfPieces(piecesSize);
        int firstElem = (drawFromFirstElem) ? -4 : std::max(((int) this->playerCursor.getPosition().x) - 7, -4);

        sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier());
        text.setFillColor({0, 0, 0});
        text.setOutlineColor({255, 255, 255});

        this->placeText(text, {}, "SIZE " + std::to_string(piecesSize), 1.f, yPos, {});

        for (int i = 0; i < 7; i++) {
            if (i + firstElem >= numberOfPieces) return;

            if ((i + firstElem) < 0) {
                switch (i + firstElem) {
                    case -4 : {this->placeText(text, this->playerCursor, "ALL", 10.f + (i * 10.f), yPos, sf::Vector2u{0, piecesSize + 1u}); break;}
                    case -3 : {this->placeText(text, this->playerCursor, "CONVEX", 10.f + (i * 10.f), yPos, sf::Vector2u{1, piecesSize + 1u}); break;}
                    case -2 : {this->placeText(text, this->playerCursor, "HOLELESS", 10.f + (i * 10.f), yPos, sf::Vector2u{2, piecesSize + 1u}); break;}
                    case -1 : {this->placeText(text, this->playerCursor, "OTHER", 10.f + (i * 10.f), yPos, sf::Vector2u{3, piecesSize + 1u}); break;}
                }
            }
            else {
                const Piece& piece = this->settings->getMenu().readPiecesList().lookAtPiece({piecesSize, firstElem + i});
                int cellSize = (8 * this->settings->getWindowSizeMultiplier()) / (piece.getLength());
                sf::FloatRect piecePosition(sf::Vector2f(10.f + (i * 10.f), yPos - 4.f) * (float) this->settings->getWindowSizeMultiplier(), sf::Vector2f(8 , 8) * (float) this->settings->getWindowSizeMultiplier());
                this->drawPiece(piece, cellSize, piecePosition, this->playerCursor.getPosition() == sf::Vector2u{i + firstElem + 4u, piecesSize + 1u});
            }
        }
    }
}

void GamePiecesAppMenu::drawPiece(const Piece& piece, int cellSize, const sf::FloatRect& piecePosition, bool selected) const {
    sf::RectangleShape rect(piecePosition.size);
    rect.setPosition(piecePosition.position);
    rect.setFillColor({180, 180, 180});
    if (selected) {
        rect.setOutlineColor({0, 0, 0});
        rect.setOutlineThickness(this->settings->getWindowSizeMultiplier() / 2);
    }
    this->renderWindow->draw(rect);

    sf::RectangleShape cell(sf::Vector2f(cellSize, cellSize));
    cell.setFillColor(this->getColorOfBlock(piece.getBlockType(), 0));

    for (const Position& cellPosition : piece.getPositions()) {
        cell.setPosition(sf::Vector2f(piecePosition.position.x + (cellPosition.x * cellSize),
                                      piecePosition.position.y + ((piece.getLength() - cellPosition.y - 1) * cellSize) ));
        this->renderWindow->draw(cell);
    }
}
