#include "Settings.h"

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

#include <vector>
#include <optional>
#include <fstream>
#include <algorithm>
#include <SFML/Graphics.hpp>

static const sf::Vector2u BASE_WINDOW_SIZE = {80, 50};
static const int WINDOW_SIZE_MULTIPLIERS[] = {4, 6, 10, 14, 20, 30, 40};
static const int WINDOW_SIZE_LAST_MODE = (sizeof(WINDOW_SIZE_MULTIPLIERS) / sizeof(int)) - 1;
static const int START_TIMER_MAX = 4;
static const int DISTRIBUTION_MAX = 20;


Settings::Settings(bool loadPieces) {
    this->keybinds.clear();
    this->keybinds.reserve(NUMBER_OF_KEYBINDS);
    for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
        this->keybinds.emplace_back(i);
    }

    this->loadSettingsFromFile(loadPieces, {});
}

void Settings::loadPieces(int loadablePiecesSizeRequest) {
    if (loadablePiecesSizeRequest < MINIMUM_PIECES_SIZE) { 
        loadablePiecesSizeRequest = MINIMUM_PIECES_SIZE;
    }
    else if (loadablePiecesSizeRequest > MAXIMUM_PIECES_SIZE) {
        loadablePiecesSizeRequest = MAXIMUM_PIECES_SIZE;
    }

    bool succeeded = true;
    int i = 1;
    while (succeeded && (i <= loadablePiecesSizeRequest)) {
        succeeded = this->menu.getPiecesList().loadPieces(i);
        i++;
    }

    if (succeeded) {
        this->loadablePiecesSize = loadablePiecesSizeRequest;
    }
    this->loadedPieces = succeeded;
}

void Settings::loadSettingsFromFile(bool loadPieces, std::optional<int> loadablePiecesSizeRequest) {
    std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
    char byte;

    // file format version
    settingsFile.get(byte);

    // loadable pieces size
    settingsFile.get(byte);
    this->loadablePiecesSize = byte;

    if (loadPieces) {
        if (loadablePiecesSizeRequest.has_value()) {
            this->loadPieces(loadablePiecesSizeRequest.value());
        }
        else {
            this->loadPieces(byte);
        }
    }
    else {
        this->loadedPieces = false;
    }

    // keybind layout
    settingsFile.get(byte);
    this->chosenKeybinds = byte;

    // DAS tuning
    settingsFile.get(byte);
    this->menu.getPlayerControls().setDAS(byte);

    // ARR tuning
    settingsFile.get(byte);
    this->menu.getPlayerControls().setARR(byte);

    // SDR tuning
    settingsFile.get(byte);
    this->menu.getPlayerControls().setSDR(byte);

    // window size mode
    settingsFile.get(byte);
    this->windowSizeMode = byte;

    // start timer length
    settingsFile.get(byte);
    this->startTimerLength = byte;

    // gamemode
    settingsFile.get(byte);
    this->gamemode = Gamemode(byte);

    // board width
    settingsFile.get(byte);
    this->menu.setBoardWidth(byte);

    // board height
    settingsFile.get(byte);
    this->menu.setBoardHeight(byte);

    if (this->loadedPieces) {
        // piece distribution
        settingsFile.get(byte);
        this->menu.getPiecesList().setDistributionMode(DistributionMode(byte));

        this->distributions.clear();
        this->distributions.push_back(0);
        for (int i = 1; i <= 15; i++) {
            settingsFile.get(byte);
            this->distributions.push_back(byte);
        }
        this->confirmDistribution();

        // selected pieces
        char pieceType;
        char pieceSize;
        char lowByte;
        char midByte;
        char highByte;

        this->selectedPieces.clear();
        while (settingsFile.get(pieceType)) {
            if (settingsFile.eof()) break;
            
            if (getSizeOfPieces(PiecesType(pieceType)) == 0) {
                settingsFile.get(pieceSize);

                this->selectedPieces.emplace_back(PiecesType(pieceType), pieceSize);
            }
            else {
                settingsFile.get(lowByte);
                settingsFile.get(midByte);
                settingsFile.get(highByte);

                int pieceNumber = ((unsigned char) lowByte) + ((unsigned char) midByte << 8) + ((unsigned char) highByte << 16);
                this->selectedPieces.emplace_back(PiecesType(pieceType), pieceNumber);
            }
        }
        this->confirmSelectedPieces();
    }
    else {
        this->distributions.clear();
        this->selectedPieces.clear();
    }
}

void Settings::saveSettingsToFile() const {
    if (!this->loadedPieces) return;

    this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile();

    std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
    char byte;

    // file format version
    byte = CURRENT_FILE_FORMAT_VERSION;
    settingsFile.write(&byte, 1);

    // loadable pieces size
    byte = this->loadablePiecesSize;
    settingsFile.write(&byte, 1);

    // keybind layout
    byte = this->chosenKeybinds;
    settingsFile.write(&byte, 1);

    // DAS tuning
    byte = this->menu.readPlayerControls().getDAS();
    settingsFile.write(&byte, 1);

    // ARR tuning
    byte = this->menu.readPlayerControls().getARR();
    settingsFile.write(&byte, 1);

    // SDR tuning
    byte = this->menu.readPlayerControls().getSDR();
    settingsFile.write(&byte, 1);

    // window size mode
    byte = this->windowSizeMode;
    settingsFile.write(&byte, 1);

    // start timer length
    byte = this->startTimerLength;
    settingsFile.write(&byte, 1);

    // gamemode
    byte = this->gamemode;
    settingsFile.write(&byte, 1);

    // board width
    byte = this->menu.getBoardWidth();
    settingsFile.write(&byte, 1);

    // board height
    byte = this->menu.getBoardHeight();
    settingsFile.write(&byte, 1);

    // piece distribution
    byte = this->menu.readPiecesList().getDistributionMode();
    settingsFile.write(&byte, 1);

    for (int i = 1; i <= 15; i++) {
        byte = this->distributions.at(i);
        settingsFile.write(&byte, 1);
    }

    // selected pieces
    for (const auto& [type, value] : this->selectedPieces) {
        byte = type;
        settingsFile.write(&byte, 1);
        if (getSizeOfPieces(type) == 0) {
            byte = value;
            settingsFile.write(&byte, 1);
        }
        else {
            int number = value;
            for (int i = 0; i < 3; i++) {
                byte = (number % 256);
                settingsFile.write(&byte, 1);
                number = (number >> 8);
            }
        }
    }
}

bool Settings::selectNextKeybinds() {
    if (this->chosenKeybinds < (NUMBER_OF_KEYBINDS - 1)) {
        this->chosenKeybinds++;
        return true;
    }
    return false;
}

bool Settings::selectPreviousKeybinds() {
    if (this->chosenKeybinds > 0) {
        this->chosenKeybinds--;
        return true;
    }
    return false;
}

bool Settings::canModifyCurrentKeybinds() const {
    return (this->chosenKeybinds == CUSTOMIZABLE_KEYBINDS);
}

bool Settings::widenWindow() {
    if (this->windowSizeMode < WINDOW_SIZE_LAST_MODE) {
        this->windowSizeMode++;
        return true;
    }
    return false;
}

bool Settings::shortenWindow() {
    if (this->windowSizeMode > 0) {
        this->windowSizeMode--;
        return true;
    }
    return false;
}

void Settings::changeVideoMode(sf::RenderWindow& window) const {
    sf::VideoMode videoMode(BASE_WINDOW_SIZE * (unsigned int) WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode]);
    window.create(videoMode, "jminos", sf::Style::Close | sf::Style::Titlebar);
    
    sf::Vector2u desktopSize = sf::VideoMode::getDesktopMode().size;
    sf::Vector2u windowSize = window.getSize();
    window.setPosition(sf::Vector2i((desktopSize.x / 2) - (windowSize.x / 2), (desktopSize.y / 2) - (windowSize.y / 2)));
}

bool Settings::lengthenStartTimer() {
    if (this->startTimerLength < START_TIMER_MAX) {
        this->startTimerLength++;
        return true;
    }
    return false;
}

bool Settings::shortenStartTimer() {
    if (this->startTimerLength > 0) {
        this->startTimerLength--;
        return true;
    }
    return false;
}

void Settings::setGamemode(Gamemode gamemode) {
    this->gamemode = gamemode;
}

void Settings::selectPieces(PiecesType type, int value) {
    if (!this->loadedPieces) return;

    this->selectedPieces.emplace_back(type, value);
}

void Settings::unselectPieces(int index) {
    if (!this->loadedPieces) return;
    if (index >= this->selectedPieces.size()) return;

    this->selectedPieces.erase(this->selectedPieces.begin() + index);
}

void Settings::confirmSelectedPieces() {
    if (!this->loadedPieces) return;

    this->menu.getPiecesList().unselectAll();

    bool selectedNone = true;
    for (const auto& [type, value] : this->selectedPieces) {
        int size = getSizeOfPieces(type);

        if (size == 0) {
            if (!(value > this->loadablePiecesSize)) {
                switch (type) {
                    case CONVEX_PIECES : {this->menu.getPiecesList().selectConvexPieces(value); break;}
                    case HOLELESS_PIECES : {this->menu.getPiecesList().selectHolelessPieces(value); break;}
                    case OTHER_PIECES : {this->menu.getPiecesList().selectOtherPieces(value); break;}
                    case ALL_PIECES : {this->menu.getPiecesList().selectAllPieces(value); break;}
                }
                selectedNone = false;
            }
        }
        else {
            if (!(getSizeOfPieces(type) > this->loadablePiecesSize)) {
                this->menu.getPiecesList().selectPiece(size, value);
                selectedNone = false;
            }
        }
    }

    if (selectedNone) {
        this->selectedPieces.push_back(DEFAULT_SELECTION);
        this->confirmSelectedPieces();
    }
}

bool Settings::increaseDistribution(int size) {
    if (!this->loadedPieces) return false;
    if (size < 1 || size > this->loadablePiecesSize) return false;

    if (this->distributions.at(size) < DISTRIBUTION_MAX) {
        this->distributions.at(size)++;
        return true;
    }
    return false;
}

bool Settings::decreaseDistribution(int size) {
    if (!this->loadedPieces) return false;
    if (size < 1 || size > this->loadablePiecesSize) return false;

    if (this->distributions.at(size) > 0) {
        this->distributions.at(size)--;
        return true;
    }
    return false;
}

void Settings::confirmDistribution() {
    if (!this->loadedPieces) return;

    for (int i = 1; i <= 15; i++) {
        this->menu.getPiecesList().changeCustomDistribution(i, (double) 1 / (this->distributions.at(i) + 0.001));
    }
}

Menu& Settings::getMenu() {
    return this->menu;
}

Keybinds& Settings::getKeybinds() {
    return this->keybinds.at(this->chosenKeybinds);
}

int Settings::getLoadablePiecesSize() const {
    return this->loadablePiecesSize;
}

bool Settings::hasLoadedPieces() const {
    return this->loadedPieces;
}

int Settings::getKeybindsLayout() const {
    return this->chosenKeybinds;
}

Gamemode Settings::getGamemode() const {
    return this->gamemode;
}

int Settings::getWindowSizeMultiplier() const {
    return WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode];
}

int Settings::getStartTimerLength() const {
    return this->startTimerLength;
}

const std::vector<std::pair<PiecesType, int>>& Settings::getSelectedPieces() const {
    return this->selectedPieces;
}

const std::vector<int>& Settings::getDistributions() const {
    return this->distributions;
}
