#include "GraphApp.h"
#include "../Pieces/PiecesFiles.h"

#include <filesystem>
#include <fstream>

[[nodiscard]] bool resetSettingsFile();
[[nodiscard]] bool resetKeybindFile(int layout);


int main() {
    std::srand(std::time(NULL));

    bool everythingIsOK = true;

    // CHECK PIECES FILES

    PiecesFiles pf;
    bool warned = false;
    for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
        if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
            #ifndef DEBUG
                if (!warned && i > DEBUG_PIECES_SIZE) {
                    std::cout << "IMPORTANT: You are currently in release mode, if you do not wish to generate big pieces (can take several minutes), type 'xmake f -m debug'." << std::endl;
                    warned = true;
                }
            #endif

            std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
            everythingIsOK &= pf.savePieces(i);
        }
    }

    #ifdef DEBUG
        std::cout << "IMPORTANT: You are currently in debug mode, if you wish to use bigger pieces, type 'xmake f -m release'." << std::endl;
    
        bool releasePiecesGenerated = true;
        for (int i = DEBUG_PIECES_SIZE + 1; i <= RELEASE_PIECES_SIZE; i++) {
            if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
                releasePiecesGenerated = false;
            }
        }
        if (!releasePiecesGenerated) {
            std::cout << "NOTE: You do not have all pieces generated, generating can take several minutes." << std::endl;
        }
    #endif

    bool everythingGenerated = true;
    for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
        std::string filePath = "data/pieces/" + std::to_string(i) + "minos.bin";

        if (!std::filesystem::exists(filePath)) {
            std::cout << "ERROR: Could not open file " + filePath << std::endl;
            everythingIsOK &= false;
        }
    }

    // CHECK CONFIG FILES

    if (!std::filesystem::exists("data/config/settings.bin")) {
        std::cout << "INFO: Settings file not found, generating..." << std::endl;
        everythingIsOK &= resetSettingsFile();

        for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
            if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) {
                std::cout << "INFO: Keybind file number " << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
                everythingIsOK &= resetKeybindFile(i);
            }
        }
    }
    else {
        std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
        char byte;

        settingsFile.get(byte);
        if ((unsigned char) byte < CURRENT_FILE_FORMAT_VERSION) {
            std::cout << "INFO: Files format changed, regenerating..." << std::endl;
            everythingIsOK &= resetSettingsFile();
            for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
                everythingIsOK &= resetKeybindFile(i);
            }
        }
    }

    // LAUNCH APP

    if (everythingIsOK) {
        GraphApp UI;
        UI.run();
    }
    else {
        std::cout << "ERROR: The game could not launch properly." << std::endl;
        std::cout << "Press enter to quit. ";
        std::cin.get();
    }

    return 0;
}


bool resetSettingsFile() {
    if (!std::filesystem::exists("data/config")) {
        std::filesystem::create_directories("data/config");
    }
    
    std::string filePath ="data/config/settings.bin";
    std::ofstream settingsFile(filePath, std::ios::trunc | std::ios::binary);
    if (!settingsFile.good()) {
        std::cerr << "ERROR: Could not open file " + filePath << std::endl;
        return false;
    }

    char byte;
    Menu menu;

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

    // loadable pieces size
    byte = MINIMUM_PIECES_SIZE;
    settingsFile.write(&byte, 1);

    // keybind layout
    byte = 0;
    settingsFile.write(&byte, 1);

    // DAS tuning
    byte = menu.getPlayerControls().getDAS();
    settingsFile.write(&byte, 1);

    // ARR tuning
    byte = menu.getPlayerControls().getARR();
    settingsFile.write(&byte, 1);

    // SDR tuning
    byte = menu.getPlayerControls().getSDR();
    settingsFile.write(&byte, 1);

    // window size mode
    byte = 2;
    settingsFile.write(&byte, 1);

    // start timer length
    byte = 2;
    settingsFile.write(&byte, 1);

    // gamemode
    byte = Gamemode(0);
    settingsFile.write(&byte, 1);

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

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

    // piece distribution
    byte = DEFAULT;
    settingsFile.write(&byte, 1);
    for (int i = 1; i <= 15; i++) {
        byte = 1;
        settingsFile.write(&byte, 1);
    }

    // selected pieces
    byte = DEFAULT_SELECTION.first;
    settingsFile.write(&byte, 1);
    byte = DEFAULT_SELECTION.second;
    settingsFile.write(&byte, 1);

    return true;
}

bool resetKeybindFile(int layout) {
    if (layout < 0 || layout > NUMBER_OF_KEYBINDS) {
        std::cerr << "ERROR: Trying to create keybind layout number " << layout << " which is outside of range (" << NUMBER_OF_KEYBINDS << ")." << std::endl;
        return false;
    }

    if (!std::filesystem::exists("data/config/keybinds")) {
        std::filesystem::create_directories("data/config/keybinds");
    }

    std::string filePath = "data/config/keybinds/layout" + std::to_string(layout) + ".bin";
    std::ofstream layoutFile(filePath, std::ios::trunc | std::ios::binary);
    if (!layoutFile.good()) {
        std::cerr << "ERROR: Could not open file " + filePath << std::endl;
        return false;
    }

    if (layout == NUMBER_OF_KEYBINDS) {
        return true;
    }

    std::map<Action, sfKey> keybinds;

    if (layout != 4) {
        keybinds.insert({PAUSE, sfKey::P});
        keybinds.insert({RETRY, sfKey::R});
    }

    if (layout == 0) {
        keybinds.insert({MOVE_LEFT, sfKey::Left});
        keybinds.insert({MOVE_RIGHT, sfKey::Right});
        keybinds.insert({SOFT_DROP, sfKey::Down});
        keybinds.insert({HARD_DROP, sfKey::Space});
        keybinds.insert({ROTATE_CW, sfKey::Up});
        keybinds.insert({ROTATE_CCW, sfKey::Z});
        keybinds.insert({ROTATE_180, sfKey::X});
        keybinds.insert({ROTATE_0, sfKey::LShift});
        keybinds.insert({HOLD, sfKey::C});
    }
    if (layout == 1) {
        keybinds.insert({MOVE_LEFT, sfKey::Z});
        keybinds.insert({MOVE_RIGHT, sfKey::C});
        keybinds.insert({SOFT_DROP, sfKey::X});
        keybinds.insert({HARD_DROP, sfKey::S});
        keybinds.insert({ROTATE_CW, sfKey::M});
        keybinds.insert({ROTATE_CCW, sfKey::Comma});
        keybinds.insert({ROTATE_180, sfKey::J});
        keybinds.insert({ROTATE_0, sfKey::K});
        keybinds.insert({HOLD, sfKey::LShift});
    }
    if (layout == 2) {
        keybinds.insert({MOVE_LEFT, sfKey::A});
        keybinds.insert({MOVE_RIGHT, sfKey::D});
        keybinds.insert({SOFT_DROP, sfKey::W});
        keybinds.insert({HARD_DROP, sfKey::S});
        keybinds.insert({ROTATE_CW, sfKey::Left});
        keybinds.insert({ROTATE_CCW, sfKey::Right});
        keybinds.insert({ROTATE_180, sfKey::Up});
        keybinds.insert({ROTATE_0, sfKey::Down});
        keybinds.insert({HOLD, sfKey::RShift});
    }
    if (layout == 3) {
        keybinds.insert({MOVE_LEFT, sfKey::Left});
        keybinds.insert({MOVE_RIGHT, sfKey::Right});
        keybinds.insert({SOFT_DROP, sfKey::Down});
        keybinds.insert({HARD_DROP, sfKey::Up});
        keybinds.insert({ROTATE_CW, sfKey::E});
        keybinds.insert({ROTATE_CCW, sfKey::A});
        keybinds.insert({ROTATE_180, sfKey::Num2});
        keybinds.insert({ROTATE_0, sfKey::Tab});
        keybinds.insert({HOLD, sfKey::Z});
    }

    char byte;
    for (Action action : ACTION_LIST_IN_ORDER) {
        byte = action;
        layoutFile.write(&byte, 1);

        if (keybinds.contains(action)) {
            byte = (int) keybinds.at(action);
            layoutFile.write(&byte, 1);
        }
        
        byte = 0xFF;
        layoutFile.write(&byte, 1);
    }

    return true;
}
