#include "PiecesFiles.h"

#include "Generator.h"
#include "Piece.h"

#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <algorithm>

#include "../Common/Compression.h"


PiecesFiles::PiecesFiles() {
}

bool PiecesFiles::savePieces(int polyominoSize) const {
    std::string filePath;
    if (!this->getFilePath(polyominoSize, filePath)) {
        return false;
    }

    Generator generator;
    std::vector<Polyomino> polyominoes = generator.generatePolyominoes(polyominoSize);

    return this->savePieces(polyominoSize, polyominoes);
}

bool PiecesFiles::savePieces(int polyominoSize, std::vector<Polyomino>& polyominoes) const {
    std::string filePath;
    if (!this->getFilePath(polyominoSize, filePath)) {
        return false;
    }

    constexpr std::size_t INITIAL_CAPACITY = 2048;
    DataBuffer buffer(INITIAL_CAPACITY);

    // sorting the polyominoes is done after setting spawn position to ensure the order is always the same
    for (Polyomino& nMino : polyominoes) {
        nMino.goToSpawnPosition();
    }
    std::sort(polyominoes.begin(), polyominoes.end());

    for (const Polyomino& polyomino : polyominoes) {
        // write the characteristics of the piece
        bool isConvex = polyomino.isConvex();
        bool hasHole = (isConvex) ? false : polyomino.hasHole();
        std::uint8_t infoByte = (isConvex << 7) + (hasHole << 6) + polyomino.getLength();
        buffer << infoByte;

        const int bitsNeeded = polyomino.getLength() * polyomino.getLength();
        const int bytesNeeded = bitsNeeded / 8 + 1;

        static const Polyomino::PolyominoData byteMask = 0xFF;
        const Polyomino::PolyominoData& polyominoData = polyomino.getPositionsData();

        // write the positions of the piece
        for (int i = 0; i < bytesNeeded; i++) {
            Polyomino::PolyominoData pData = polyominoData >> (i * 8);
            unsigned long data = 
                (pData & byteMask).to_ulong();
            buffer << static_cast<std::uint8_t>(data);
        }
    }

    DataBuffer compressed = Compress(buffer);

    return compressed.WriteFile(filePath);
}

bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const {
    std::string filePath;
    if (!this->getFilePath(polyominoSize, filePath)) {
        return false;
    }

    DataBuffer compressed;
    if(!compressed.ReadFile(filePath))
        return false;

    DataBuffer buffer = Decompress(compressed, compressed.GetSize());
        
    std::ifstream piecesFile(filePath, std::ios::binary);
    if (!piecesFile.good()) {
        return false;
    }

    pieces.clear();
    convexPieces.clear();
    holelessPieces.clear();
    otherPieces.clear();

    // we shift the first color of each size so that the small polyominoes (size 1-2-3) don't all have the same color
    Block pieceBlock = firstPieceBlockType();
    for (int i = 0; i < polyominoSize; i++) {
        nextPieceBlockType(pieceBlock);
    }

    constexpr char convexMask = 0b1000'0000;
    constexpr char holeMask = 0b0100'0000;
    constexpr char lengthMask = 0b0011'1111;

    std::uint8_t infoByte;
    std::uint8_t positionByte;
    int i = 0;
    while (!buffer.IsFinished()) {
        // if (piecesFile.eof()) break;
        buffer >> infoByte;

        // read piece infos
        bool isConvex = (infoByte & convexMask) >> 7;
        bool hasHole = (infoByte & holeMask) >> 6;
        int length = (infoByte & lengthMask);

        const int bitsNeeded = length * length;
        const int bytesNeeded = bitsNeeded / 8 + 1;

        // read positions
        Polyomino::PolyominoData polyominoData{};

        for (int j = 0; j < bytesNeeded; j++) {
            buffer >> positionByte;
            Polyomino::PolyominoData tempByte(positionByte);
            polyominoData |= (tempByte << (j * 8));
        }

        pieces.emplace_back(Polyomino(std::move(polyominoData), length), pieceBlock);

        nextPieceBlockType(pieceBlock);

        if (isConvex) {
            convexPieces.push_back(i);
        }
        else if (hasHole) {
            otherPieces.push_back(i);
        }
        else {
            holelessPieces.push_back(i);
        }

        i++;
    }

    return true;
}

bool PiecesFiles::getFilePath(int polyominoSize, std::string& filePath) const {
    std::string dataFolderPath = "data/pieces/";

    if (!std::filesystem::exists(dataFolderPath)) {
        std::filesystem::create_directories(dataFolderPath);
    }

    if (!std::filesystem::is_directory(dataFolderPath)) {
        return false;
    }

    filePath = dataFolderPath + std::to_string(polyominoSize) + "minos.bin";
    return true;
}
