#pragma once

#include "../Pieces/Piece.h"
#include "Board.h"
#include "Bag.h"
#include "LineClear.h"

#include <vector>
#include <memory>
#include <optional>


/**
 * Links a board with the pieces moving in it
 */
class GameBoard {
    private:
        Board board;                        // the board in which pieces moves, (0, 0) is downleft
        Bag generator;                      // the piece generator
        std::shared_ptr<Piece> activePiece; // the piece currently in the board
        Position activePiecePosition;       // the position of the piece currently in the board
        std::shared_ptr<Piece> heldPiece;   // a piece being holded
        int nextQueueLength;                // the number of next pieces seeable at a time
        std::vector<Piece> nextQueue;       // the list of the next pieces to spawn in the board
        bool isLastMoveKick;                // wheter the last action the piece did was kicking
        bool movedLeftLast;                 // wheter the last sideway movement was a left one

    public:
        /**
         * Creates a new board, generator, and next queue
         */
        GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength);

        /**
         * Resets the board as if it was newly created
         */
        void reset();

    private:
        /**
         * Initializes the board
         */
        void initialize();

    public:
        /**
         * Tries moving the piece one position to the left
         * @return If it suceeded
         */
        bool moveLeft();

        /**
         * Tries moving the piece one position to the right
         * @return If it suceeded
         */
        bool moveRight();

        /**
         * Tries moving the piece one position down
         * @return If it suceeded
         */
        bool moveDown();

        /**
         * Tries rotating the piece and kicking it if necessary, if it's a 0° rotation, it will forcefully try kicking
         * @return If it suceeded
         */
        bool rotate(Rotation rotation);
    
    private:
        /**
         * Tries kicking the piece, testing position either above or below the piece's initial position
         * @return If it suceeded
         */
        bool tryKicking(bool testingBottom, const std::set<Position>& safePositions);

        /**
         * Tries fitting the kicked active piece at the specified position
         * @return If it suceeded
         */
        bool tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps);

        /**
         * Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
         * @return If the shifted active piece overlaps with one of the position
         */
        bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position(0, 0)) const;

    public:
        /**
         * Tries holding the active piece or swapping it if one was already stocked, while trying to apply an initial rotation to the newly spawned piece
         * @return If it suceeded
         */
        bool hold(std::optional<Rotation> initialRotation);

        /**
         * Spawns the next piece from the queue
         * @return If it spawned in a wall
         */
        bool spawnNextPiece();

        /**
         * Checks if one of the active piece's positions touches a wall in the board
         * @return If the active piece is in a wall
         */
        bool activePieceInWall(const Position& shift = Position(0, 0)) const;

        /**
         * Checks is the active piece as a wall directly below one of its position
         * @return If it touches a ground
         */
        bool touchesGround() const;

        /**
         * Computes what the piece position would be if it were to be dropped down as much as possible
         * @return The lowest position before hitting a wall
         */
        Position lowestPosition() const;

        /**
         * Locks the active piece into the board and clears lines if needed
         * @return The resulting line clear
         */
        LineClear lockPiece();

        /**
         * Adds a specified number of garbage rows to the bottom of the board, the hole position being random but the same for all of them
         */
        void addGarbageRows(int number);

        /**
         * @return The board
         */
        const Board& getBoard() const;

        /**
         * @return A pointer to the active piece, can be null
         */
        const std::shared_ptr<Piece>& getActivePiece() const;

        /**
         * @return The position of the active piece
         */
        const Position& getActivePiecePosition() const;

        /**
         * @return A pointer to the held piece, can be null
         */
        const std::shared_ptr<Piece>& getHeldPiece() const;

        /**
         * @return The next piece queue, can be empty
         */
        const std::vector<Piece>& getNextPieces() const;
    
    private:
        /**
         * Sets the active piece to its spawn position
         */
        void goToSpawnPosition();
    
    public:
        /**
         * Stream output operator, adds the board, the hold box and the next queue
         * @return A reference to the output stream
         */
        friend std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard);
};
