Database Manager: A C++ Console Application

Joined
Jun 12, 2020
Messages
28
Reaction score
1
C++:
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <mutex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <memory>
#include <limits>
#include <conio.h> // Pour _getch() sur Windows

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#include <Windows.h> // Pour afficher les couleurs
#endif // _WIN32

namespace fs = std::filesystem;
using namespace std;

/************************************
 * Gestion des couleurs de la console
 ************************************/
class ConsoleColor {
public:
    enum TextColor {
        BLACK = 0,
        DARK_BLUE = 1,
        DARK_GREEN = 2,
        LIGHT_BLUE = 3,
        DARK_RED = 4,
        MAGENTA = 5,
        ORANGE = 6,
        LIGHT_GRAY = 7,
        GRAY = 8,
        BLUE = 9,
        GREEN = 10,
        CYAN = 11,
        RED = 12,
        PINK = 13,
        YELLOW = 14,
        WHITE = 15
    };

    enum BackgroundColor {
        BG_BLACK = 0,
        BG_DARK_BLUE = 1,
        BG_DARK_GREEN = 2,
        BG_LIGHT_BLUE = 3,
        BG_DARK_RED = 4,
        BG_MAGENTA = 5,
        BG_ORANGE = 6,
        BG_LIGHT_GRAY = 7,
        BG_GRAY = 8,
        BG_BLUE = 9,
        BG_GREEN = 10,
        BG_CYAN = 11,
        BG_RED = 12,
        BG_PINK = 13,
        BG_YELLOW = 14,
        BG_WHITE = 15
    };

    static void print(const string &message, TextColor textColor = WHITE, BackgroundColor bgColor = BG_BLACK) {
#if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, bgColor << 4 | textColor);
        cout << message;
        SetConsoleTextAttribute(handle, WHITE);
#else
        cout << message;
#endif
    }

    static void println(const string &message, TextColor textColor = WHITE, BackgroundColor bgColor = BG_BLACK) {
        print(message + "\n", textColor, bgColor);
    }
};

/************************************
 * Classes pour le menu interactif
 ************************************/
class MenuOption {
public:
    MenuOption(const string &title, function<void()> action)
        : title(title), action(action) {}

    void select() {
        action();
    }

    string getTitle() const {
        return title;
    }

private:
    string title;
    function<void()> action;
};

class IUserInterface {
public:
    virtual void display(const string &message) = 0;
    virtual char getInput() = 0;
    virtual ~IUserInterface() = default;
};

class ConsoleUserInterface : public IUserInterface {
public:
    void display(const string &message) override {
        cout << message << endl;
    }

    char getInput() override {
        return _getch();
    }
};

/************************************
 * Classe Menu (menu interactif dans la console)
 ************************************/
class Menu {
public:
    Menu(const string &title, int width, int height)
        : title(title), width(width), height(height) {}

    void addOption(const MenuOption &option) {
        options.push_back(option);
    }

    void display(IUserInterface *ui) {
        ConsoleColor::println("=== " + title + " ===", ConsoleColor::YELLOW, ConsoleColor::BG_BLACK);
        for (size_t i = 0; i < options.size(); ++i) {
            if (currentSelection == i) {
                ConsoleColor::print("> " + options[i].getTitle(), ConsoleColor::WHITE, ConsoleColor::BG_BLUE);
            } else {
                ConsoleColor::print("  " + options[i].getTitle(), ConsoleColor::WHITE, ConsoleColor::BG_BLACK);
            }
            cout << endl;
        }
        ConsoleColor::println("====================", ConsoleColor::WHITE, ConsoleColor::BG_BLACK);
        ConsoleColor::println("Utilisez ↑/↓ pour naviguer, ← pour revenir, → ou Entrée pour sélectionner, Échap pour quitter.", ConsoleColor::WHITE, ConsoleColor::BG_BLACK);
    }

    void navigate(char input) {
        if (input == -32) { // touche spéciale
            char direction = _getch();
            if (direction == 72) { // Flèche haut
                currentSelection = (currentSelection + options.size() - 1) % options.size();
            } else if (direction == 80) { // Flèche bas
                currentSelection = (currentSelection + 1) % options.size();
            } else if (direction == 75) { // Flèche gauche
                throw runtime_error("Retour");
            } else if (direction == 77) { // Flèche droite
                select();
            }
        } else if (input == 13) { // Entrée
            select();
        }
    }

    void select() {
        options[currentSelection].select();
    }

private:
    string title;
    vector<MenuOption> options;
    size_t currentSelection = 0;
    int width;
    int height;
};

/************************************
 * Utilitaires CSV
 ************************************/
/// Trim d'une chaîne (supprime espaces en début et fin)
inline std::string trim(const std::string& s) noexcept {
    const auto begin = s.find_first_not_of(" \t\n\r");
    if (begin == std::string::npos) return "";
    const auto end = s.find_last_not_of(" \t\n\r");
    return s.substr(begin, end - begin + 1);
}

/// Parse une ligne CSV en utilisant un séparateur donné
inline std::vector<std::string> parseCSVLine(const std::string& line, char delimiter = ';') noexcept {
    std::vector<std::string> tokens;
    std::istringstream ss(line);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        tokens.push_back(trim(token));
    }
    return tokens;
}

/************************************
 * Classe de hachage utilitaire
 ************************************/
/// Permet de réaliser un hachage 2-universel simple.
class Hasher {
public:
    template <typename T>
    static std::size_t hash(std::size_t seed, const T& val) noexcept {
        std::hash<T> hasher;
        return hasher(val) ^ (seed * 0x9e3779b97f4a7c15ULL);
    }
};

/************************************
 * Classe Sketch
 ************************************/
/// Implémente le Count-Min Sketch avec une logique de médiane.
class Sketch {
public:
    /// Constructeur initialisant une structure 3D de compteurs.
    Sketch(std::size_t d, std::size_t w, std::size_t r) noexcept
        : d(d), w(w), r(r),
          counters(d, std::vector<std::vector<int>>(w, std::vector<int>(r, 0))) {}

    /// Mise à jour du sketch avec un élément x.
    template <typename T>
    void update(const T& x) noexcept {
        for (std::size_t o = 0; o < r; ++o) {
            for (std::size_t i = 0; i < d; ++i) {
                std::size_t j = Hasher::hash(i, x) % w;
                ++counters[i][j][o];
            }
        }
    }

    /// Calcul de la médiane d'un vecteur d'entiers.
    int medianOfMeans(const std::vector<int>& values) const noexcept {
        auto temp = values;
        std::sort(temp.begin(), temp.end());
        return temp[temp.size() / 2];
    }

    /// Interroge le sketch en retournant les indices des topV valeurs les plus élevées.
    template <typename T>
    std::vector<std::size_t> query(const T& q, std::size_t topV) const noexcept {
        std::vector<int> similarities;
        similarities.reserve(w);
        for (std::size_t j = 0; j < w; ++j) {
            std::vector<int> temps;
            for (std::size_t i = 0; i < d; ++i) {
                temps.insert(temps.end(), counters[i][j].begin(), counters[i][j].end());
            }
            similarities.push_back(medianOfMeans(temps));
        }
        std::vector<std::size_t> indices(w);
        for (std::size_t i = 0; i < w; ++i)
            indices[i] = i;
        std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) {
            return similarities[a] > similarities[b];
        });
        if (topV > indices.size()) topV = indices.size();
        indices.resize(topV);
        return indices;
    }

private:
    const std::size_t d;
    const std::size_t w;
    const std::size_t r;
    std::vector<std::vector<std::vector<int>>> counters;
};

/************************************
 * Classe TUID : gestion d'ID et du Sketch
 ************************************/
class TUID {
public:
    TUID() noexcept
        : lastID(0), sketch(4, 100, 5) {}

    /// Génère et renvoie le prochain identifiant unique.
    int getNextID() noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        int next = ++lastID;
        sketch.update(next);
        return next;
    }

    /// Remet à zéro le générateur d'ID.
    void reset(int value = 0) noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        lastID = value;
    }

    /// Interroge le sketch et retourne les indices correspondants.
    std::vector<std::size_t> querySketch(const int& q, std::size_t topV = 5) const noexcept {
        return sketch.query(q, topV);
    }

private:
    int lastID;
    Sketch sketch;
    mutable std::mutex mutex_;
};

/************************************
 * Structures représentant un enregistrement et une table
 ************************************/
struct Record {
    int id;                         // ID auto-généré unique
    std::vector<std::string> data;  // Données pour les colonnes (hors CleID et ID)
};

struct Table {
    std::string name;                         // Nom de la table
    std::vector<std::string> columns;         // Liste des colonnes (les deux premières étant réservées)
    std::map<std::string, Record> records;    // Clé au format "0001" -> enregistrement
};

/************************************
 * Classe Database
 ************************************/
class Database {
public:
    Database() : tableDir("db_tables") {
        initializeFolder();
        loadExistingTables();
    }

    /// Crée une nouvelle table et l'enregistre.
    Table* createTable() {
        Table newTable;
        cout << "Entrez le nom de la nouvelle table : ";
        std::getline(std::cin, newTable.name);
        if (newTable.name.empty()) {
            cerr << "Le nom de la table ne peut pas être vide." << endl;
            return nullptr;
        }
        if (tables.find(newTable.name) != tables.end()) {
            cerr << "Une table du même nom existe déjà." << endl;
            return nullptr;
        }

        cout << "Entrez les noms des colonnes (séparés par des virgules) : ";
        std::string columnsInput;
        std::getline(std::cin, columnsInput);
        // Les deux premières colonnes sont réservées.
        newTable.columns.push_back("CleID");
        newTable.columns.push_back("ID");

        auto tokens = parseCSVLine(columnsInput, ',');
        for (const auto& col : tokens) {
            if (!col.empty())
                newTable.columns.push_back(col);
        }
        try {
            saveTable(newTable);
        } catch (const std::exception& e) {
            cerr << "Erreur lors de l'enregistrement de la table : " << e.what() << endl;
            return nullptr;
        }

        tables[newTable.name] = newTable;
        cout << "Table '" << newTable.name << "' créée avec succès." << endl;
        return &tables[newTable.name];
    }

    /// Sélectionne une table existante via son nom.
    Table* selectTable() {
        if (tables.empty()) {
            cout << "Aucune table n'existe. Veuillez en créer une d'abord." << endl;
            return nullptr;
        }
        cout << "Liste des tables existantes :" << endl;
        for (const auto& [name, table] : tables)
            cout << "- " << name << "\n";
        cout << "Entrez le nom de la table à utiliser : ";
        std::string name;
        std::getline(std::cin, name);
        auto it = tables.find(name);
        if (it == tables.end()) {
            cout << "Table non trouvée." << endl;
            return nullptr;
        }
        try {
            loadTable(it->second);
        } catch (const std::exception& e) {
            cerr << "Erreur lors du chargement de la table : " << e.what() << endl;
            return nullptr;
        }
        return &it->second;
    }

    /// Menu interactif pour gérer les enregistrements (ajout, affichage, mise à jour et suppression).
    void recordMenu(Table& table) {
        while (true) {
            cout << "\n===== MENU DES ENREGISTREMENTS =====" << endl;
            cout << "1. Ajouter un enregistrement" << endl;
            cout << "2. Afficher tous les enregistrements" << endl;
            cout << "3. Rechercher un enregistrement (par CleID)" << endl;
            cout << "4. Modifier un enregistrement" << endl;
            cout << "5. Supprimer un enregistrement" << endl;
            cout << "6. Retour au menu principal" << endl;
            cout << "Votre choix : ";
            std::string choix;
            std::getline(std::cin, choix);

            if (choix == "1") {
                addRecord(table);
            } else if (choix == "2") {
                displayRecords(table);
            } else if (choix == "3") {
                searchRecord(table);
            } else if (choix == "4") {
                updateRecord(table);
            } else if (choix == "5") {
                deleteRecord(table);
            } else if (choix == "6") {
                break;
            } else {
                cout << "Choix invalide, veuillez réessayer." << endl;
            }
        }
    }

    /// Ajoute un enregistrement à la table active.
    void addRecord(Table& table) {
        Record newRecord;
        newRecord.id = uidGenerator.getNextID();
        const std::string key = generateKeyID(newRecord.id);
        const auto numColumns = table.columns.size();
        std::string input;

        for (std::size_t i = 2; i < numColumns; ++i) { // Colonnes à partir de la troisième
            cout << "Entrez la valeur pour '" << table.columns[i] << "' : ";
            std::getline(std::cin, input);
            if (input.find_first_not_of(" \t\n\r") == std::string::npos) {
                cerr << "La valeur pour '" << table.columns[i] << "' ne peut être vide." << endl;
                return;
            }
            newRecord.data.push_back(input);
        }
        if (table.records.find(key) != table.records.end()) {
            cerr << "Erreur : un enregistrement avec cet ID existe déjà." << endl;
            return;
        }
        table.records[key] = std::move(newRecord);
        try {
            saveTable(table);
        } catch (const std::exception& e) {
            cerr << "Erreur lors de l'enregistrement de la table : " << e.what() << endl;
            return;
        }
        cout << "Enregistrement ajouté avec succès. Clé générée : " << key << endl;
    }

    /// Affiche tous les enregistrements d'une table.
    void displayRecords(const Table& table) const noexcept {
        if (table.records.empty()) {
            cout << "Aucun enregistrement n'existe dans cette table." << endl;
            return;
        }
        cout << "\nEnregistrements de la table '" << table.name << "' :" << endl;
        for (const auto& [key, rec] : table.records) {
            cout << key << " => ID: " << rec.id;
            for (std::size_t i = 0; i < rec.data.size(); ++i) {
                cout << ", " << table.columns[i + 2] << ": " << rec.data[i];
            }
            cout << "\n";
        }
    }

    /// Recherche un enregistrement en fonction de la clé (CleID).
    void searchRecord(const Table& table) const {
        cout << "Entrez la clé de l'enregistrement à rechercher : ";
        std::string key;
        std::getline(std::cin, key);
        auto it = table.records.find(key);
        if (it == table.records.end()) {
            cout << "Enregistrement non trouvé." << endl;
            return;
        }
        const Record& rec = it->second;
        cout << "Enregistrement trouvé : " << key << " => ID: " << rec.id;
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            cout << ", " << table.columns[i + 2] << ": " << rec.data[i];
        }
        cout << "\n";
    }

    /// Permet de modifier un enregistrement existant.
    void updateRecord(Table& table) {
        cout << "Entrez la clé de l'enregistrement à modifier : ";
        std::string key;
        std::getline(std::cin, key);
        auto it = table.records.find(key);
        if (it == table.records.end()) {
            cout << "Enregistrement non trouvé." << endl;
            return;
        }
        Record& rec = it->second;
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            cout << "Valeur actuelle pour '" << table.columns[i + 2] << "' : " << rec.data[i] << endl;
            cout << "Entrez la nouvelle valeur (laisser vide pour conserver) : ";
            std::string nouvelleValeur;
            std::getline(std::cin, nouvelleValeur);
            if (!nouvelleValeur.empty()) {
                rec.data[i] = nouvelleValeur;
            }
        }
        try {
            saveTable(table);
        } catch (const std::exception& e) {
            cerr << "Erreur lors de la sauvegarde de la table : " << e.what() << endl;
            return;
        }
        cout << "Enregistrement mis à jour avec succès." << endl;
    }

    /// Permet la suppression d'un enregistrement existant.
    void deleteRecord(Table& table) {
        cout << "Entrez la clé de l'enregistrement à supprimer : ";
        std::string key;
        std::getline(std::cin, key);
        auto it = table.records.find(key);
        if (it == table.records.end()) {
            cout << "Enregistrement non trouvé." << endl;
            return;
        }
        table.records.erase(it);
        try {
            saveTable(table);
        } catch (const std::exception& e) {
            cerr << "Erreur lors de la sauvegarde de la table : " << e.what() << endl;
            return;
        }
        cout << "Enregistrement supprimé avec succès." << endl;
    }

private:
    const std::string tableDir;
    std::map<std::string, Table> tables;
    TUID uidGenerator;

    /// Initialise le dossier de sauvegarde des tables.
    void initializeFolder() {
        try {
            if (!fs::exists(tableDir))
                fs::create_directory(tableDir);
        } catch (const fs::filesystem_error& e) {
            throw std::runtime_error("Erreur lors de la création du dossier : " + std::string(e.what()));
        }
    }

    /// Génère une clé (format "0001") à partir d'un ID.
    std::string generateKeyID(int id) const {
        std::ostringstream oss;
        oss << std::setw(4) << std::setfill('0') << id;
        return oss.str();
    }

    /// Sauvegarde une table dans un fichier CSV.
    void saveTable(const Table &table) const {
        const std::string path = tableDir + "/" + table.name + ".csv";
        std::ofstream file(path);
        if (!file.is_open()) {
            throw std::runtime_error("Erreur lors de l'ouverture du fichier " + path + " pour écriture.");
        }
        // Écriture de l'en-tête
        for (std::size_t i = 0; i < table.columns.size(); ++i) {
            file << table.columns[i];
            if (i + 1 < table.columns.size())
                file << ";";
        }
        file << "\n";
        // Écriture des enregistrements
        for (const auto& [key, rec] : table.records) {
            file << key << ";" << rec.id;
            for (const auto& d : rec.data)
                file << ";" << d;
            file << "\n";
        }
        file.close();
        if (file.fail()) {
            throw std::runtime_error("Erreur lors de l'écriture dans le fichier " + path);
        }
    }

    /// Charge une table depuis un fichier CSV.
    void loadTable(Table &table) {
        const std::string path = tableDir + "/" + table.name + ".csv";
        std::ifstream file(path);
        if (!file.is_open()) {
            throw std::runtime_error("Fichier " + path + " introuvable.");
        }
        table.records.clear();
        std::string line;
        bool firstLine = true;
        while (std::getline(file, line)) {
            if (firstLine) { // En-tête
                table.columns = parseCSVLine(line, ';');
                firstLine = false;
                continue;
            }
            if (line.empty()) continue;
            auto tokens = parseCSVLine(line, ';');
            if (tokens.size() < 2) continue;
            Record rec;
            const std::string key = tokens[0];
            try {
                rec.id = std::stoi(tokens[1]);
            } catch (const std::exception&) {
                cerr << "Erreur de conversion de l'ID pour la clé " << key << endl;
                continue;
            }
            for (std::size_t i = 2; i < tokens.size(); ++i)
                rec.data.push_back(tokens[i]);
            table.records[key] = std::move(rec);
        }
        file.close();
        // Met à jour le TUID avec l'ID maximum observé.
        int maxID = 0;
        for (const auto& [key, rec] : table.records) {
            maxID = std::max(maxID, rec.id);
        }
        uidGenerator.reset(maxID);
    }

    /// Charge toutes les tables existantes dans le dossier.
    void loadExistingTables() {
        try {
            if (!fs::exists(tableDir))
                return;
            for (const auto& entry : fs::directory_iterator(tableDir)) {
                if (entry.is_regular_file() && entry.path().extension() == ".csv") {
                    Table table;
                    table.name = entry.path().stem().string();
                    loadTable(table);
                    tables[table.name] = std::move(table);
                }
            }
        } catch (const fs::filesystem_error& e) {
            cerr << "Erreur lors de la lecture du répertoire : " << e.what() << endl;
        }
    }
};

/************************************
 * Classe MenuManager
 ************************************/
class MenuManager {
public:
    MenuManager(std::unique_ptr<IUserInterface> ui) : userInterface(std::move(ui)) {}

    void run() {
        bool exitFlag = false;
        while (!exitFlag) {
            try {
                Menu mainMenu("Menu Principal", 80, 24);
                mainMenu.addOption(MenuOption("Gérer la base de données", [this]() { runDatabaseMenu(); }));
                mainMenu.addOption(MenuOption("Quitter", []() {
                    ConsoleColor::println("Au revoir!", ConsoleColor::RED);
                    exit(0);
                }));
                runMenu(mainMenu);
                exitFlag = true;
            } catch (const runtime_error &e) {
                if (string(e.what()) != "Retour")
                    throw;
            }
        }
    }

private:
    std::unique_ptr<IUserInterface> userInterface;

    void clearScreen() {
#if defined(_WIN32)
        system("cls");
#else
        system("clear");
#endif
    }

    void runMenu(Menu &menu) {
        while (true) {
            clearScreen();
            menu.display(userInterface.get());
            char input = userInterface->getInput();
            if (input == 27) { // Échap
                break;
            }
            try {
                menu.navigate(input);
            } catch (const runtime_error &e) {
                if (string(e.what()) == "Retour")
                    return;
            }
        }
    }

    void runDatabaseMenu() {
        Database db;
        while (true) {
            clearScreen();
            Menu dbMenu("Menu Base de Données", 80, 24);
            dbMenu.addOption(MenuOption("Créer une nouvelle table", [&db]() {
                Table* table = db.createTable();
                if (table)
                    db.recordMenu(*table);
            }));
            dbMenu.addOption(MenuOption("Utiliser une table existante", [&db]() {
                Table* table = db.selectTable();
                if (table)
                    db.recordMenu(*table);
            }));
            dbMenu.addOption(MenuOption("Retour", []() {}));
            try {
                runMenu(dbMenu);
                break;
            } catch (const std::runtime_error &e) {
                if (string(e.what()) != "Retour")
                    throw;
                else
                    break;
            }
        }
    }
};

/************************************
 * Fonction main
 ************************************/
int main() {
    try {
        unique_ptr<IUserInterface> ui = make_unique<ConsoleUserInterface>();
        MenuManager manager(std::move(ui));
        manager.run();

        // Exemple d'utilisation direct de la base de données pour tester le TUID et le Sketch :
        Database db;
        Table* table = db.createTable();
        if (table) {
            db.addRecord(*table);
            db.displayRecords(*table);

            TUID generator;
            int nextID = generator.getNextID();
            cout << "\nProchain ID généré : " << nextID << "\n";
            auto indices = generator.querySketch(nextID, 3);
            cout << "Indices retournés par querySketch : ";
            for (const auto idx : indices) {
                cout << idx << " ";
            }
            cout << "\nAppuyez sur une touche pour fermer...";
            (void)getchar();
        }
    } catch (const std::exception &ex) {
        cerr << "Exception capturée dans main : " << ex.what() << "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
Project Description
This project is a console application in C++ that allows for the management of a simple database. It includes features to create tables, add, display, modify, and delete records. The application uses an interactive menu to navigate through the different options.

Features

  • Console Color Management: Allows displaying colored messages for better readability.
  • Interactive Menu: A menu system that enables the user to easily navigate between different options.
  • Record Management: Adding, displaying, searching, modifying, and deleting records in the tables.
  • Table Management: Creating new tables and selecting existing tables.
  • Use of Advanced Data Structures: Utilizes Count-Min Sketch for hashing operations and TUID for managing unique identifiers.
Prerequisites

  • A C++ compiler compatible with C++17 or higher.
  • The C++ standard library (including for file management).
Compilation
To compile the project, use the following command in your terminal:



bash


g++ -o database_manager main.cpp -std=c++17

Make sure that the file main.cpp contains the provided source code.

Execution
To run the application, use the following command:



bash


./database_manager

Usage

  1. Create a New Table: Select the option to create a new table and follow the instructions to enter the table name and column names.
  2. Use an Existing Table: Select an existing table to manage its records.
  3. Add a Record: Enter the values for each column of the record.
  4. Display All Records: Display all records from the selected table.
  5. Modify a Record: Search for a record by its key and modify the values.
  6. Delete a Record: Delete a record by providing its key.
Notes

  • The first two columns of each table are reserved for CleID and ID.
  • Records are saved in CSV files in the db_tables folder.
  • In case of an error, appropriate error messages will be displayed in the console.
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
Introducing the Enhanced C++ Database Management System
I am excited to share an improved version of my C++ Database Management System, which I previously published. This updated version incorporates several enhancements aimed at improving usability, performance, and code organization.

Key Features of the Enhanced Version:​


  1. User -Friendly Console Interface: The application now features a more intuitive console interface, allowing users to navigate through menus using arrow keys. This makes it easier to manage database tables and records.
  2. Improved Error Handling: The code has been refactored to provide clearer error messages and handle exceptions more gracefully, ensuring a smoother user experience.
  3. Dynamic Record Display: Users can now view records in a more structured format, with clear labels and organized output, making it easier to read and understand the data.
  4. Enhanced Data Management: The system supports adding, updating, deleting, and searching for records with improved input validation to prevent errors.
  5. Count-Min Sketch Implementation: The new version includes a Count-Min Sketch algorithm for efficient frequency estimation, which can be useful for analyzing large datasets.
  6. Modular Design: The code is organized into distinct classes and functions, promoting better maintainability and readability.
  7. Cross-Platform Compatibility: The application is designed to work seamlessly on both Windows and Unix-like systems, ensuring a wider range of usability.

Code Overview​


The code is structured into several key components, including:

  • ConsoleColor Class: Manages console text colors for better visual feedback.
  • IUser Interface and ConsoleUser Interface: Abstracts user input and output handling.
  • Menu and MenuOption Classes: Facilitates the creation of interactive menus.
  • Database Class: Handles table creation, record management, and file operations.
  • TUID Class: Generates unique IDs for records using a thread-safe approach.

Getting Started​


To run the application, simply compile the code and execute the resulting binary. You will be prompted to create or select a database table, after which you can manage records as needed.
I hope you find this enhanced version useful for your projects or studies. I welcome any feedback or suggestions for further improvements!
Feel free to check out the code below:
C++:
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <array>
#include <chrono>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <mutex>
#include <optional>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <memory>
#include <limits>
#include <conio.h> // Pour _getch() sur Windows

#ifdef _WIN32
    #define WIN32_LEAN_AND_MEAN
    #define VC_EXTRALEAN
    #include <Windows.h> // Pour afficher les couleurs
#endif // _WIN32

namespace fs = std::filesystem;

// -----------------------------------------------------------------------------
// Gestion des couleurs de la console
// -----------------------------------------------------------------------------
class ConsoleColor {
public:
    enum class TextColor : int {
        BLACK = 0,
        DARK_BLUE = 1,
        DARK_GREEN = 2,
        LIGHT_BLUE = 3,
        DARK_RED = 4,
        MAGENTA = 5,
        ORANGE = 6,
        LIGHT_GRAY = 7,
        GRAY = 8,
        BLUE = 9,
        GREEN = 10,
        CYAN = 11,
        RED = 12,
        PINK = 13,
        YELLOW = 14,
        WHITE = 15
    };

    enum class BackgroundColor : int {
        BG_BLACK = 0,
        BG_DARK_BLUE = 1,
        BG_DARK_GREEN = 2,
        BG_LIGHT_BLUE = 3,
        BG_DARK_RED = 4,
        BG_MAGENTA = 5,
        BG_ORANGE = 6,
        BG_LIGHT_GRAY = 7,
        BG_GRAY = 8,
        BG_BLUE = 9,
        BG_GREEN = 10,
        BG_CYAN = 11,
        BG_RED = 12,
        BG_PINK = 13,
        BG_YELLOW = 14,
        BG_WHITE = 15
    };

    static void print(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
        std::cout << message;
        SetConsoleTextAttribute(handle, static_cast<int>(TextColor::WHITE));
    #else
        std::cout << message;
    #endif
    }

    static void println(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
        print(std::string(message) + "\n", textColor, bgColor);
    }
    
    static void setColor(TextColor textColor, BackgroundColor bgColor) {
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
    }

    static void reset() {
        setColor(TextColor::WHITE, BackgroundColor::BG_BLACK);
    }
    
};

// -----------------------------------------------------------------------------
// Interface et classes pour le menu interactif
// -----------------------------------------------------------------------------
class IUserInterface {
public:
    virtual void display(std::string_view message) = 0;
    virtual char getInput() = 0;
    virtual ~IUserInterface() = default;
};

class ConsoleUserInterface : public IUserInterface {
public:
    void display(std::string_view message) override {
        std::cout << message << std::endl;
    }

    char getInput() override {
        return _getch();
    }
};

class MenuOption {
public:
    MenuOption(std::string title, std::function<void()> action)
        : title_(std::move(title)), action_(std::move(action)) {}

    void select() {
        action_();
    }

    std::string getTitle() const {
        return title_;
    }

private:
    std::string title_;
    std::function<void()> action_;
};

class Menu {
public:
    Menu(std::string title, int width, int height)
        : title_(std::move(title)), width_(width), height_(height) {}
    void addOption(const MenuOption& option) {
        options_.push_back(option);
    }
    void displayMenu(IUserInterface* ui) { // Correction ici
    ConsoleColor::println("=== " + title_ + " ===", ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
    for (std::size_t i = 0; i < options_.size(); ++i) {
        if (currentSelection_ == i) {
            ConsoleColor::print("> " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        } else {
            ConsoleColor::print("  " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        }
        std::cout << std::endl;
    }
    ConsoleColor::println("====================", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
    ConsoleColor::println("Up/Down arrows to navigate, Left arrow to go back, Right arrow or Enter to select, Escape to exit.", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
}

    // Navigation par touches.
    // Si la touche « gauche » est pressée, une exception est levée pour signaler le retour.
    void navigate(char input) {
        if (input == -32) { // Touche spéciale
            char direction = _getch();
            if (direction == 72) { // Flèche haut
                currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
            } else if (direction == 80) { // Flèche bas
                currentSelection_ = (currentSelection_ + 1) % options_.size();
            } else if (direction == 75) { // Flèche gauche
                throw std::runtime_error("Return");
            } else if (direction == 77) { // Flèche droite
                select();
            }
        } else if (input == 13) { // Entrée
            select();
        }
    }

    void select() {
        options_[currentSelection_].select();
    }
    
    void navigateUp() {
    currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
}

void navigateDown() {
    currentSelection_ = (currentSelection_ + 1) % options_.size();
}

private:
    std::string title_;
    std::vector<MenuOption> options_;
    std::size_t currentSelection_ = 0;
    int width_;
    int height_;
};

// -----------------------------------------------------------------------------
// Utilitaires CSV et hachage
// -----------------------------------------------------------------------------
inline std::string trim(std::string_view s) noexcept {
    const auto begin = s.find_first_not_of(" \t\n\r");
    if (begin == std::string_view::npos)
        return "";
    const auto end = s.find_last_not_of(" \t\n\r");
    return std::string(s.substr(begin, end - begin + 1));
}

inline std::vector<std::string> parseCSVLine(const std::string& line, char delimiter = ';') noexcept {
    std::vector<std::string> tokens;
    std::istringstream ss(line);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        tokens.push_back(trim(token));
    }
    return tokens;
}
/// Permet de réaliser un hachage 2-universel simple.
class Hasher {
public:
    template <typename T>
    static std::size_t hash(std::size_t seed, const T& val) noexcept {
        std::hash<T> hasher;
        return hasher(val) ^ (seed * 0x9e3779b97f4a7c15ULL);
    }
};

// -----------------------------------------------------------------------------
// Classe Sketch (Count-Min Sketch avec logique de médiane)
// -----------------------------------------------------------------------------
class Sketch {
public:
    Sketch(std::size_t d, std::size_t w, std::size_t r) noexcept
        : d_(d), w_(w), r_(r),
          counters_(d, std::vector<std::vector<int>>(w, std::vector<int>(r, 0))) {}

    template <typename T>
    void update(const T& x) noexcept {
        for (std::size_t o = 0; o < r_; ++o) {
            for (std::size_t i = 0; i < d_; ++i) {
                std::size_t j = Hasher::hash(i, x) % w_;
                ++counters_[i][j][o];
            }
        }
    }

    int medianOfMeans(const std::vector<int>& values) const noexcept {
        std::vector<int> temp = values;
        std::sort(temp.begin(), temp.end());
        return temp[temp.size() / 2];
    }

    template <typename T>
    std::vector<std::size_t> query(const T& q, std::size_t topV) const noexcept {
        std::vector<int> similarities;
        similarities.reserve(w_);
        for (std::size_t j = 0; j < w_; ++j) {
            std::vector<int> temps;
            for (std::size_t i = 0; i < d_; ++i) {
                temps.insert(temps.end(), counters_[i][j].begin(), counters_[i][j].end());
            }
            similarities.push_back(medianOfMeans(temps));
        }
        std::vector<std::size_t> indices(w_);
        for (std::size_t i = 0; i < w_; ++i) {
            indices[i] = i;
        }
        std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) {
            return similarities[a] > similarities[b];
        });
        if (topV > indices.size())
            topV = indices.size();
        indices.resize(topV);
        return indices;
    }

private:
    std::size_t d_;
    std::size_t w_;
    std::size_t r_;
    std::vector<std::vector<std::vector<int>>> counters_;
};

// -----------------------------------------------------------------------------
// Classe TUID : gestion d'ID et du Sketch
// -----------------------------------------------------------------------------
class TUID {
public:
    TUID() noexcept
        : lastID_(0), sketch_(4, 100, 5) {}

    int getNextID() noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        int next = ++lastID_;
        sketch_.update(next);
        return next;
    }

    void reset(int value = 0) noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        lastID_ = value;
    }

    std::vector<std::size_t> querySketch(const int& q, std::size_t topV = 5) const noexcept {
        return sketch_.query(q, topV);
    }

private:
    int lastID_;
    Sketch sketch_;
    mutable std::mutex mutex_;
};

// -----------------------------------------------------------------------------
// Structures pour les enregistrements et la table
// -----------------------------------------------------------------------------
struct Record {
    int id;  // ID auto-généré unique
    std::vector<std::string> data;  // Données pour les colonnes hors CleID et ID
};

struct Table {
    std::string name;                         // Nom de la table
    std::vector<std::string> columns;         // Liste des colonnes (les deux premières étant réservées)
    std::map<std::string, Record> records;    // Clé format "0001" -> enregistrement
};

// -----------------------------------------------------------------------------
// Classe Database
// -----------------------------------------------------------------------------
class Database {
public:
    Database() : tableDir_("db_tables") {
        initializeFolder();
        loadExistingTables();
    }

    // Crée une nouvelle table et l'enregistre
    // Retourne std::optional<Table> pour optimiser le retour en cas d'erreur.
    std::optional<Table> createTable() {
        Table newTable;
        std::cout << "Enter the name of the new table: ";
        std::getline(std::cin, newTable.name);
        if (newTable.name.empty()) {
            throw std::runtime_error("The name of the table cannot be empty.");
        }
        if (tables_.find(newTable.name) != tables_.end()) {
            throw std::runtime_error("A table with the same name already exists.");
        }

        std::cout << "Enter the names of the columns (separated by commas): ";
        std::string columnsInput;
        std::getline(std::cin, columnsInput);
        // Colonnes réservées
        newTable.columns.push_back("KeyID");
        newTable.columns.push_back("ID");

        auto tokens = parseCSVLine(columnsInput, ',');
        for (const auto &col : tokens) {
            if (!col.empty())
                newTable.columns.push_back(col);
        }

        try {
            saveTable(newTable);
        } catch (const std::exception &e) {
            throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
        }
        tables_[newTable.name] = newTable;
       std::cout << "Table '" << newTable.name << "' created successfully." << std::endl;
        return newTable;
    }

    // Sélectionne une table existante via son nom
    Table* selectTable() {
    if (tables_.empty()) {
        std::cout << "No table exists. Please create one first." << std::endl;
        return nullptr;
    }

    std::vector<std::string> tableNames;  // Remove 'const' here to allow push_back
    for (const auto &entry : tables_) {
        tableNames.push_back(entry.first);
    }

    size_t currentSelection = 0;

    while (true) {
        system("cls"); // Ou system("clear") sous Linux/macOS
        ConsoleColor::println("\n===== TABLE SELECTION =====", ConsoleColor::TextColor::YELLOW);
        
        for (size_t i = 0; i < tableNames.size(); ++i) {
            if (i == currentSelection) {
                ConsoleColor::println("> " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::println("  " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
        }

        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);

        int ch = _getch();
        if (ch == 27) { // Échap
            return nullptr;
        } else if (ch == 224) { // Touche spéciale
            int arrow = _getch();
            if (arrow == 72) { // Flèche haut
                currentSelection = (currentSelection == 0) ? tableNames.size() - 1 : currentSelection - 1;
            } else if (arrow == 80) { // Flèche bas
                currentSelection = (currentSelection + 1) % tableNames.size();
            }
        } else if (ch == 13) { // Entrée
            const std::string& name = tableNames[currentSelection];
            auto it = tables_.find(name);
            if (it == tables_.end()) {
                std::cout << "Table not found." << std::endl;
                return nullptr;
            }
            try {
                loadTable(it->second);
            } catch (const std::exception &e) {
                throw std::runtime_error("Error while loading the table: " + std::string(e.what()));
            }
            return &it->second;
        }
    }
}


    
    void displayOptions(const std::vector<std::string>& options, size_t currentSelection) {
    for (size_t i = 0; i < options.size(); ++i) {
        if (i == currentSelection) {
            ConsoleColor::println("> " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        } else {
            ConsoleColor::println("  " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        }
    }
}

bool handleInput(int ch, size_t& currentSelection, size_t optionsCount, Table& table) {
    if (ch == 27) { // Touche Échap
        return true; // Indique qu'on veut sortir du menu
    } else if (ch == 224) { // Touche spéciale flèches sous Windows
        int arrow = _getch();
        if (arrow == 72) { // Flèche haut
            currentSelection = (currentSelection == 0) ? optionsCount - 1 : currentSelection - 1;
        } else if (arrow == 80) { // Flèche bas
            currentSelection = (currentSelection + 1) % optionsCount;
        }
    } else if (ch == 13) { // Entrée
        switch (currentSelection) {
            case 0: addRecord(table); break;
            case 1: displayRecords(table); break;
            case 2: searchRecord(table); break;
            case 3: updateRecord(table); break;
            case 4: deleteRecord(table); break;
            case 5: return true; // Retour au menu principal
        }
        ConsoleColor::println("\nPress any key to continue...");
        _getch();
    }
    return false; // Indique qu'on reste dans le menu
}


    // Menu interactif pour gérer les enregistrements (ajout, affichage, modification et suppression)
    void recordMenu(Table& table) {
    const std::vector<std::string> options = {
        "Add a record",
        "Display all records",
        "Search for a record (by KeyID)",
        "Edit a record",
        "Delete a record",
        "Return to the main menu"
    };

    size_t currentSelection = 0;

    while (true) {
        system("cls"); // Ou system("clear") sous Linux/macOS
        ConsoleColor::println("\n===== RECORDS MENU =====", ConsoleColor::TextColor::YELLOW);
        displayOptions(options, currentSelection);

        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);


        int ch = _getch();
        if (handleInput(ch, currentSelection, options.size(), table)) {
            break; // Sortie du menu
        }
    }
}
    std::string getInputForColumn(const std::string& columnName) {
    std::string input;
    std::cout << "Enter the value for '" << columnName << "' : ";
    std::getline(std::cin, input);
    if (input.find_first_not_of(" \t\n\r") == std::string::npos) {
        throw std::runtime_error("The value for '" + columnName + "' cannot be empty.");
    }
    return input;
}
    // Ajoute un enregistrement à la table active.
    void addRecord(Table& table) {
    Record newRecord;
    newRecord.id = uidGenerator_.getNextID();
    const std::string key = generateKeyID(newRecord.id);
    
    // Saisie pour chaque colonne (à partir de la 3ème colonne)
    for (std::size_t i = 2; i < table.columns.size(); ++i) {
        newRecord.data.push_back(getInputForColumn(table.columns[i]));
    }

    if (table.records.find(key) != table.records.end()) {
        throw std::runtime_error("Error: a record with this ID already exists.");
    }

    table.records[key] = std::move(newRecord);
    saveTable(table);
    std::cout << "Record added successfully. Generated key: " << key << std::endl;
}

    // Affiche tous les enregistrements d'une table.
    /*
    void displayRecords(const Table& table) const noexcept {
    if (table.records.empty()) {
        std::cout << "No records exist in this table." << std::endl;
        return;
    }

    // Largeurs fixes pour l'affichage
    const int keyWidth     = 10;
    const int idWidth      = 10;
    const int dataColWidth = 15;
    const std::string colSeparator = " "; // séparateur entre les colonnes

    // Nombre total de colonnes affichées (clé, id et données éventuelles)
    int nbDisplayedCols = 2 + (table.columns.size() > 2 ? table.columns.size() - 2 : 0);

    // Calcul de la largeur totale (on a nbDisplayedCols - 1 séparateurs)
    int totalWidth = keyWidth + idWidth + (nbDisplayedCols - 2) * dataColWidth + dataColWidth
                     + (nbDisplayedCols - 1) * static_cast<int>(colSeparator.size());

    // Affichage des en-têtes avec couleur de fond (alignement à gauche pour l'entête)
    ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
    std::cout << std::left << std::setw(keyWidth) << "Key" << colSeparator
              << std::left << std::setw(idWidth)  << "ID" << colSeparator;
    // Affichage des en-têtes pour les colonnes de données (sans séparateur final)
    for (std::size_t i = 2; i < table.columns.size(); ++i) {
        std::cout << std::left << std::setw(dataColWidth) << table.columns[i];
        if (i < table.columns.size() - 1) {  // ajouter le séparateur si ce n'est PAS le dernier
            std::cout << colSeparator;
        }
    }
    ConsoleColor::reset();
    std::cout << std::endl;

    // Affichage de la ligne de séparation
    std::cout << std::string(totalWidth, '-') << std::endl;

    // Affichage des enregistrements
    for (const auto& [key, rec] : table.records) {
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        // Affichage des deux premières colonnes avec séparateur
        std::cout << std::left << std::setw(keyWidth) << key << colSeparator
                  << std::left << std::setw(idWidth)  << rec.id << colSeparator;
        // Affichage des champs de données
        std::size_t nbData = rec.data.size();
        for (std::size_t i = 0; i < nbData; ++i) {
            // Pour le dernier champ, utiliser un alignement à droite
            if (i == nbData - 1) {
                std::cout << std::right << std::setw(dataColWidth) << rec.data[i];
                // Pas de séparateur final
            } else {
                std::cout << std::left << std::setw(dataColWidth) << rec.data[i] << colSeparator;
            }
        }
        ConsoleColor::reset();
        std::cout << std::endl;

        // Ligne de séparation après chaque enregistrement
        std::cout << std::string(totalWidth, '-') << std::endl;
    }
}
*/
void displayRecords(const Table& table) const noexcept {
    if (table.records.empty()) {
        std::cout << "No records exist in this table." << std::endl;
        return;
    }
    
    // On regroupe les enregistrements dans un vecteur pour disposer d'un ordre fixe
    std::vector<std::pair<std::string, Record>> recVector(table.records.begin(), table.records.end());
    
    // S'assurer que currentIndex est initialisé à 0
    std::size_t currentIndex = 0;
    
    // Fonction lambda pour afficher un enregistrement avec un design amélioré
    auto displayRecordAt = [&]() {
        // Effacer l'écran (dépendant du système)
        #ifdef _WIN32
            system("cls");
        #else
            system("clear");
        #endif
        
        const auto& key = recVector[currentIndex].first;
        const Record& rec = recVector[currentIndex].second;
        
        const int labelWidth = 12;
        const int fieldWidth = 30;
        // Calculer la largeur totale (marges et bordures comprises)
        const int totalWidth = 2 + labelWidth + fieldWidth + 2;
        
        // Affichage de l'entête avec bandeau de couleur
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        std::cout << "\n" << std::string(totalWidth, '=') << std::endl;
        // Afficher le numéro d'enregistrement en ajoutant 1 à currentIndex
        std::cout << std::left << std::setw(totalWidth)
                  << "  Record (" + std::to_string(currentIndex + 1) + " sur " + std::to_string(recVector.size()) + ")  "
                  << std::endl;
        std::cout << std::string(totalWidth, '=') << std::endl;
        ConsoleColor::reset();
        
        // Générer la bordure
        std::string borderLine = "+" + std::string(totalWidth - 2, '-') + "+";
        
        // Lambda pour afficher un champ donné
        auto printField = [&](const std::string &label, const auto &value) {
            std::ostringstream oss;
            oss << value;
            std::cout << "| " << std::left << std::setw(labelWidth) << label
                      << " : " << std::left << std::setw(fieldWidth) << oss.str() << " |" << std::endl;
        };
        
        std::cout << borderLine << std::endl;
        // Affichage des champs avec coloration pour les libellés
        ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        printField("KEY", key);
        printField("ID", rec.id);
        ConsoleColor::reset();
        
        // Afficher les autres champs, à partir du 3ème élément de table.columns
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            std::string columnTitle = (i + 2 < table.columns.size())
                                      ? table.columns[i + 2]
                                      : ("Field" + std::to_string(i));
            printField(columnTitle, rec.data[i]);
        }
        
        std::cout << borderLine << std::endl;
        
        // Afficher le message d'aide en bas
        ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
        std::cout << "\nUtilisez ← et → pour naviguer, ou tapez 'q' pour quitter." << std::endl;
        ConsoleColor::reset();
    };
    
    // Afficher le premier enregistrement en s'assurant que currentIndex vaut 0
    displayRecordAt();
    
    // Boucle pour la navigation entre enregistrements
    bool quit = false;
    while (!quit) {
        #ifdef _WIN32
            int ch = _getch();
            if (ch == 'q' || ch == 'Q') {
                quit = true;
            }
            else if (ch == 224) { // touche spéciale (flèches, ...)
                int arrow = _getch();
                if (arrow == 75) { // flèche gauche
                    if (currentIndex > 0) {
                        --currentIndex;
                        displayRecordAt();
                    }
                }
                else if (arrow == 77) { // flèche droite
                    if (currentIndex + 1 < recVector.size()) {
                        ++currentIndex;
                        displayRecordAt();
                    }
                }
            }
        #else
            int ch = _getch();
            if (ch == 'q' || ch == 'Q') {
                quit = true;
            }
            else if (ch == 27) { // caractère d'échappement
                int ch1 = _getch();
                int ch2 = _getch();
                if(ch1 == 91) {
                    if(ch2 == 68) { // flèche gauche
                        if (currentIndex > 0) {
                            --currentIndex;
                            displayRecordAt();
                        }
                    }
                    else if(ch2 == 67) { // flèche droite
                        if (currentIndex + 1 < recVector.size()) {
                            ++currentIndex;
                            displayRecordAt();
                        }
                    }
                }
            }
        #endif
    }
}



    // Recherche un enregistrement par CleID.
   void searchRecord(const Table& table) const {
    std::cout << "Enter the key of the record to search for: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }

    const Record& rec = it->second;
    // Affichage formaté du résultat
    ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
    std::cout << "\n=== Record found ===" << std::endl;
    ConsoleColor::reset();

    // Largeur pour l'intitulé des champs
    const int labelWidth = 10;
    std::cout << std::left << std::setw(labelWidth) << "KEY:" << key    << std::endl;
    std::cout << std::left << std::setw(labelWidth) << "ID:"  << rec.id << std::endl;

    // Affichage des autres champs en utilisant les colonnes à partir du 3ème élément de table.columns
    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        // Sécurité : si table.columns ne contient pas d'élément à l'indice i+2, on fournit un nom générique
        std::string columnTitle = (i + 2 < table.columns.size()) ? table.columns[i + 2] : "Field" + std::to_string(i);
        std::cout << std::left << std::setw(labelWidth) << (columnTitle + ":")
                  << rec.data[i] << std::endl;
    }
    std::cout << "\n";
}

    // Permet de modifier un enregistrement existant.
    void updateRecord(Table& table) {
        std::cout << "Enter the key of the record to modify: : ";
        std::string key;
        std::getline(std::cin, key);
        auto it = table.records.find(key);
        if (it == table.records.end()) {
            std::cout << "Record not found." << std::endl;
            return;
        }
        Record& rec = it->second;
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            std::cout << "Current value for '" << table.columns[i + 2] << "' : " << rec.data[i] << std::endl;
            std::cout << "Enter the new value (leave empty to keep) : ";
            std::string nouvelleValeur;
            std::getline(std::cin, nouvelleValeur);
            if (!nouvelleValeur.empty()) {
                rec.data[i] = nouvelleValeur;
            }
        }
        try {
            saveTable(table);
        } catch (const std::exception &e) {
            throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
        }
        std::cout << "Record updated successfully." << std::endl;
    }

    // Permet de supprimer un enregistrement existant.
    void deleteRecord(Table& table) {
        std::cout << "Enter the key of the record to delete : ";
        std::string key;
        std::getline(std::cin, key);
        auto it = table.records.find(key);
        if (it == table.records.end()) {
            std::cout << "Record not found." << std::endl;
            return;
        }
        table.records.erase(it);
        try {
            saveTable(table);
        } catch (const std::exception &e) {
            throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
        }
        std::cout << "Record deleted successfully." << std::endl;
    }

private:
    const std::string tableDir_;
    std::map<std::string, Table> tables_;
    TUID uidGenerator_;

    // Initialise le dossier de sauvegarde des tables
    void initializeFolder() {
        try {
            if (!fs::exists(tableDir_)) {
                fs::create_directory(tableDir_);
            }
        } catch (const fs::filesystem_error &e) {
            throw std::runtime_error("Error while creating the folder: " + std::string(e.what()));
        }
    }

    // Génère une clé (format "0001") à partir d'un ID.
    std::string generateKeyID(int id) const {
        std::ostringstream oss;
        oss << std::setw(4) << std::setfill('0') << id;
        return oss.str();
    }

    // Sauvegarde une table dans un fichier CSV.
    void saveTable(const Table &table) const {
        const std::string path = tableDir_ + "/" + table.name + ".csv";
        std::ofstream file(path);
        if (!file.is_open()) {
            throw std::runtime_error("Error while opening the file " + path + " for writing.");
        }
        // Écriture de l'en-tête
        for (std::size_t i = 0; i < table.columns.size(); ++i) {
            file << table.columns[i];
            if (i + 1 < table.columns.size())
                file << ";";
        }
        file << "\n";
        // Écriture des enregistrements
        for (const auto& [key, rec] : table.records) {
            file << key << ";" << rec.id;
            for (const auto& d : rec.data)
                file << ";" << d;
            file << "\n";
        }
        file.close();
        if (file.fail()) {
            throw std::runtime_error("Error while writing to the file " + path);
        }
    }

    // Charge une table depuis un fichier CSV.
    void loadTable(Table &table) {
        const std::string path = tableDir_ + "/" + table.name + ".csv";
        std::ifstream file(path);
        if (!file.is_open()) {
           throw std::runtime_error("File " + path + " not found.");
        }
        table.records.clear();
        std::string line;
        bool firstLine = true;
        while (std::getline(file, line)) {
            if (firstLine) {
                table.columns = parseCSVLine(line, ';');
                firstLine = false;
                continue;
            }
            if (line.empty())
                continue;
            auto tokens = parseCSVLine(line, ';');
            if (tokens.size() < 2)
                continue;
            Record rec;
            const std::string key = tokens[0];
            try {
                rec.id = std::stoi(tokens[1]);
            } catch (const std::exception&) {
                std::cerr << "Error converting the ID for the key " << key << std::endl;
                continue;
            }
            for (std::size_t i = 2; i < tokens.size(); ++i)
                rec.data.push_back(tokens[i]);
            table.records[key] = std::move(rec);
        }
        file.close();

        // Mise à jour du TUID avec l'ID maximum observé.
        int maxID = 0;
        for (const auto &entry : table.records) {
            maxID = std::max(maxID, entry.second.id);
        }
        uidGenerator_.reset(maxID);
    }

    // Charge toutes les tables existantes dans le dossier.
    void loadExistingTables() {
        try {
            if (!fs::exists(tableDir_))
                return;
            for (const auto &entry : fs::directory_iterator(tableDir_)) {
                if (entry.is_regular_file() && entry.path().extension() == ".csv") {
                    Table table;
                    table.name = entry.path().stem().string();
                    loadTable(table);
                    tables_[table.name] = std::move(table);
                }
            }
        } catch (const fs::filesystem_error &e) {
            std::cerr << "Error while reading the directory: " << e.what() << std::endl;
        }
    }
};

// -----------------------------------------------------------------------------
// Classe MenuManager
// -----------------------------------------------------------------------------
class MenuManager {
public:
    MenuManager(std::unique_ptr<IUserInterface> ui)
        : userInterface_(std::move(ui)) {}

    void run() {
        bool exitFlag = false;
        while (!exitFlag) {
            try {
                Menu mainMenu("Main Menu", 80, 24);
                mainMenu.addOption(MenuOption("Manage the database", [this]() { runDatabaseMenu(); }));
                mainMenu.addOption(MenuOption("Quit", []() {
                    ConsoleColor::println("Goodbye!", ConsoleColor::TextColor::RED);
                    std::exit(EXIT_SUCCESS);
                }));
                runMenu(mainMenu);
                exitFlag = true;
            } catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Back")
                    throw;
            }
        }
    }

private:
    std::unique_ptr<IUserInterface> userInterface_;

    void clearScreen() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
    }

    void runMenu(Menu &menu) {
    while (true) {
        clearScreen();
        menu.displayMenu(userInterface_.get());
        char input = userInterface_->getInput();

        // Gestion des touches spéciales pour la navigation
        if (input == -32) { // Touche spéciale
            char direction = userInterface_->getInput();
            if (direction == 72) { // Flèche haut
                // Logique pour naviguer vers le haut
                // Vous pouvez ajouter une méthode dans Menu pour gérer cela
                menu.navigateUp();
                continue;
            } else if (direction == 80) { // Flèche bas
                // Logique pour naviguer vers le bas
                // Vous pouvez ajouter une méthode dans Menu pour gérer cela
                menu.navigateDown();
                continue;
            }
        }

        if (input == 27) { // Échap
            break;
        }

        try {
            menu.navigate(input);
        } catch (const std::runtime_error &e) {
            if (std::string(e.what()) == "Retour") {
                return;
            } else {
                ConsoleColor::println("Error: " + std::string(e.what()), ConsoleColor::TextColor::RED);
                std::cout << "Press any key to try again...";
                userInterface_->getInput(); // Attendre que l'utilisateur appuie sur une touche
            }
        }
    }
}


    void runDatabaseMenu() {
        Database db;
        while (true) {
            clearScreen();
            Menu dbMenu("Database Menu", 80, 24);
            dbMenu.addOption(MenuOption("Create a new table", [&db]() {
                try {
                    auto newTableOpt = db.createTable();
                    if (newTableOpt)
                        db.recordMenu(db.selectTable() ? *db.selectTable() : newTableOpt.value());
                } catch (const std::exception &e) {
                    std::cerr << e.what() << std::endl;
                    system("pause");
                }
            }));
            dbMenu.addOption(MenuOption("Use an existing table", [&db]() {
                Table* table = db.selectTable();
                if (table)
                    db.recordMenu(*table);
            }));
            dbMenu.addOption(MenuOption("Back", []() {}));
            try {
                runMenu(dbMenu);
                break;
            } catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Back")
                    throw;
                else
                    break;
            }
        }
    }
};

// -----------------------------------------------------------------------------
// Fonction main
// -----------------------------------------------------------------------------
int main() {
    try {
        auto ui = std::make_unique<ConsoleUserInterface>();
        MenuManager manager(std::move(ui));
        manager.run();

        // Exemple d'utilisation directe de la base de données pour tester TUID et Sketch
        Database db;
        auto tableOpt = db.createTable();
        if (tableOpt) {
            Table* table = db.selectTable();
            if (table) {
                db.addRecord(*table);
                db.displayRecords(*table);

                TUID generator;
                int nextID = generator.getNextID();
                std::cout << "\nNext generated ID : " << nextID << "\n";
                auto indices = generator.querySketch(nextID, 3);
                std::cout << "Indices returned by querySketch : ";
                for (const auto idx : indices) {
                    std::cout << idx << " ";
                }
                std::cout << "\nPress any key to close...";
                (void)getchar();
            }
        }
    } catch (const std::exception &ex) {
        std::cerr << "Exception caught in main : " << ex.what() << "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
 
Joined
Sep 20, 2022
Messages
237
Reaction score
39
Feedback:

When office workers need a simple database, they use a spread sheet, ignore all the calculation features, and just use it like a word processor. Horrifying.

That's just my opinion, I base that on a very small sample of office workers.

I've never found simple database programs like this useful, on the other hand, I made a csv text editor that let me move columns around, and that was very useful for editing table files.

I used those table routines for everything; editing data files, viewing program output, prompting for screen input, directory browser, and a program editor.
 
Last edited:
Joined
Jun 12, 2020
Messages
28
Reaction score
1
I understand the difficulty of proposing something useful and effective for those who work in offices. Indeed, I also coded the database with a mini web server, but lacking confidence, I preferred to share the console application. As for spreadsheets, many, I think, are still stuck in the technological shock that spreadsheets represented in the 90s... and believe they can do everything with them. However, the algorithms of databases are not very effective in Excel formulas; perhaps it would be better to write the table join (for example) and file management codes in macros (VBA). I'm not entirely convinced of the effectiveness in the end.
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
I would like to thank you for the positive feedback and clarify that using the mini web server comes with security risks that not all users will be able to manage in addition to everything else. Thank you again.
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
I have improved my C++ database management system by adding Base64 encoding for all data entries. This allows me to avoid problems caused by special characters and punctuation, which could corrupt data or cause errors.
Encoding the data ensures it is safely stored and handled, letting me input a wider variety of information without issues. This update makes the application more reliable and user-friendly.
C++:
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#include <memory>
#include <conio.h> // pour _getch() sur Windows

#ifdef _WIN32
    #include <windows.h>
#else
    #include <unistd.h>
#endif

namespace fs = std::filesystem;

// -----------------------------------------------------------------------------
// Gestion des couleurs de la console
// -----------------------------------------------------------------------------
class ConsoleColor {
public:
    enum class TextColor : int {
        BLACK = 0,
        DARK_BLUE = 1,
        DARK_GREEN = 2,
        LIGHT_BLUE = 3,
        DARK_RED = 4,
        MAGENTA = 5,
        ORANGE = 6,
        LIGHT_GRAY = 7,
        GRAY = 8,
        BLUE = 9,
        GREEN = 10,
        CYAN = 11,
        RED = 12,
        PINK = 13,
        YELLOW = 14,
        WHITE = 15
    };

    enum class BackgroundColor : int {
        BG_BLACK = 0,
        BG_DARK_BLUE = 1,
        BG_DARK_GREEN = 2,
        BG_LIGHT_BLUE = 3,
        BG_DARK_RED = 4,
        BG_MAGENTA = 5,
        BG_ORANGE = 6,
        BG_LIGHT_GRAY = 7,
        BG_GRAY = 8,
        BG_BLUE = 9,
        BG_GREEN = 10,
        BG_CYAN = 11,
        BG_RED = 12,
        BG_PINK = 13,
        BG_YELLOW = 14,
        BG_WHITE = 15
    };

    static void print(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
        std::cout << message;
        SetConsoleTextAttribute(handle, static_cast<int>(TextColor::WHITE));
    #else
        std::cout << message;
    #endif
    }

    static void println(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
        print(std::string(message) + "\n", textColor, bgColor);
    }
    
    static void setColor(TextColor textColor, BackgroundColor bgColor) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
    #endif
    }

    static void reset() {
        setColor(TextColor::WHITE, BackgroundColor::BG_BLACK);
    }
};

// -----------------------------------------------------------------------------
// Interface et classes pour le menu interactif
// -----------------------------------------------------------------------------
class IUserInterface {
public:
    virtual void display(std::string_view message) = 0;
    virtual char getInput() = 0;
    virtual ~IUserInterface() = default;
};

class ConsoleUserInterface : public IUserInterface {
public:
    void display(std::string_view message) override {
        std::cout << message << std::endl;
    }

    char getInput() override {
        return _getch();
    }
};

class MenuOption {
public:
    MenuOption(std::string title, std::function<void()> action)
        : title_(std::move(title)), action_(std::move(action)) {}

    void select() {
        action_();
    }

    std::string getTitle() const {
        return title_;
    }

private:
    std::string title_;
    std::function<void()> action_;
};

class Menu {
public:
    Menu(std::string title, int width, int height)
        : title_(std::move(title)), width_(width), height_(height) {}
    void addOption(const MenuOption& option) {
        options_.push_back(option);
    }
    void displayMenu(IUserInterface* ui) {
        ConsoleColor::println("=== " + title_ + " ===", ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        for (std::size_t i = 0; i < options_.size(); ++i) {
            if (currentSelection_ == i) {
                ConsoleColor::print("> " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::print("  " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
            std::cout << std::endl;
        }
        ConsoleColor::println("====================", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        ConsoleColor::println("Up/Down arrows to navigate, Left arrow to go back, Right arrow or Enter to select, Escape to exit.", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
    }

    void navigate(char input) {
        if (input == -32) { // touche spéciale
            char direction = _getch();
            if (direction == 72) { // flèche haut
                currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
            } else if (direction == 80) { // flèche bas
                currentSelection_ = (currentSelection_ + 1) % options_.size();
            } else if (direction == 75) { // flèche gauche
                throw std::runtime_error("Return");
            } else if (direction == 77) { // flèche droite
                select();
            }
        } else if (input == 13) { // touche Entrée
            select();
        }
    }

    void select() {
        options_[currentSelection_].select();
    }
    
    void navigateUp() {
        currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
    }

    void navigateDown() {
        currentSelection_ = (currentSelection_ + 1) % options_.size();
    }

    const std::vector<MenuOption>& getOptions() const {
        return options_;
    }

private:
    std::string title_;
    std::vector<MenuOption> options_;
    std::size_t currentSelection_ = 0;
    int width_;
    int height_;
};

// -----------------------------------------------------------------------------
// Utilitaires CSV et hachage
// -----------------------------------------------------------------------------
inline std::string trim(std::string_view s) noexcept {
    const auto begin = s.find_first_not_of(" \t\n\r");
    if (begin == std::string_view::npos)
        return "";
    const auto end = s.find_last_not_of(" \t\n\r");
    return std::string(s.substr(begin, end - begin + 1));
}

inline std::vector<std::string> parseCSVLine(const std::string& line, char delimiter = ';') noexcept {
    std::vector<std::string> tokens;
    std::istringstream ss(line);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        tokens.push_back(trim(token));
    }
    return tokens;
}

/// Permet de réaliser un hachage 2-universel simple.
class Hasher {
public:
    template <typename T>
    static std::size_t hash(std::size_t seed, const T& val) noexcept {
        std::hash<T> hasher;
        return hasher(val) ^ (seed * 0x9e3779b97f4a7c15ULL);
    }
};

// -----------------------------------------------------------------------------
// Classe Sketch (Count-Min Sketch avec logique de médiane)
// -----------------------------------------------------------------------------
class Sketch {
public:
    Sketch(std::size_t d, std::size_t w, std::size_t r) noexcept
        : d_(d), w_(w), r_(r),
          counters_(d, std::vector<std::vector<int>>(w, std::vector<int>(r, 0))) {}

    template <typename T>
    void update(const T& x) noexcept {
        for (std::size_t o = 0; o < r_; ++o) {
            for (std::size_t i = 0; i < d_; ++i) {
                std::size_t j = Hasher::hash(i, x) % w_;
                ++counters_[i][j][o];
            }
        }
    }

    int medianOfMeans(const std::vector<int>& values) const noexcept {
        std::vector<int> temp = values;
        std::sort(temp.begin(), temp.end());
        return temp[temp.size() / 2];
    }

    template <typename T>
    std::vector<std::size_t> query(const T& q, std::size_t topV) const noexcept {
        std::vector<int> similarities;
        similarities.reserve(w_);
        for (std::size_t j = 0; j < w_; ++j) {
            std::vector<int> temps;
            for (std::size_t i = 0; i < d_; ++i) {
                temps.insert(temps.end(), counters_[i][j].begin(), counters_[i][j].end());
            }
            similarities.push_back(medianOfMeans(temps));
        }
        std::vector<std::size_t> indices(w_);
        for (std::size_t i = 0; i < w_; ++i) {
            indices[i] = i;
        }
        std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) {
            return similarities[a] > similarities[b];
        });
        if (topV > indices.size())
            topV = indices.size();
        indices.resize(topV);
        return indices;
    }

private:
    std::size_t d_;
    std::size_t w_;
    std::size_t r_;
    std::vector<std::vector<std::vector<int>>> counters_;
};

// -----------------------------------------------------------------------------
// Classe TUID : gestion d'ID et du Sketch
// -----------------------------------------------------------------------------
class TUID {
public:
    TUID() noexcept
        : lastID_(0), sketch_(4, 100, 5) {}

    int getNextID() noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        int next = ++lastID_;
        sketch_.update(next);
        return next;
    }

    void reset(int value = 0) noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        lastID_ = value;
    }

    std::vector<std::size_t> querySketch(const int& q, std::size_t topV = 5) const noexcept {
        return sketch_.query(q, topV);
    }

private:
    int lastID_;
    Sketch sketch_;
    mutable std::mutex mutex_;
};

// -----------------------------------------------------------------------------
// Structures pour les enregistrements et la table
// -----------------------------------------------------------------------------
struct Record {
    int id;  // ID auto-généré unique
    std::vector<std::string> data;  // Données pour les colonnes hors CleID et ID
};

struct Table {
    std::string name;                         // Nom de la table
    std::vector<std::string> columns;         // Liste des colonnes (les deux premières étant réservées)
    std::map<std::string, Record> records;    // Clé format "0001" -> enregistrement
};

// -----------------------------------------------------------------------------
// Fonctions Base64 pour encoder/décoder les chaînes
// -----------------------------------------------------------------------------
static const unsigned char base64_table[65] =

    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";


std::string base64_encode(const unsigned char *src, size_t len) {

    unsigned char *out, *pos;

    const unsigned char *end, *in;


    size_t olen;

    olen = 4 * ((len + 2) / 3); /* 3-byte blocks to 4-byte */


    if (olen < len)

        return std::string(); /* integer overflow */


    std::string outStr;

    outStr.resize(olen);

    out = (unsigned char*)&outStr[0];


    end = src + len;

    in = src;

    pos = out;

    while (end - in >= 3) {

        *pos++ = base64_table[in[0] >> 2];

        *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];

        *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];

        *pos++ = base64_table[in[2] & 0x3f];

        in += 3;

    }


    if (end - in) {

        *pos++ = base64_table[in[0] >> 2];

        if (end - in == 1) {

            *pos++ = base64_table[(in[0] & 0x03) << 4];

            *pos++ = '=';

        } else {

            *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];

            *pos++ = base64_table[(in[1] & 0x0f) << 2];

        }

        *pos++ = '=';

    }


    return outStr;

}


static const int B64index[256] = { 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,

0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,

0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62, 63, 62, 62, 63, 52, 53, 54, 55,

56, 57, 58, 59, 60, 61,  0,  0,  0,  0,  0,  0,  0,  0,  1,  2,  3,  4,  5,  6,

7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,

0,  0,  0, 63,  0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,

41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 };


std::string b64decode(const void* data, const size_t len)
{
    unsigned char* p = (unsigned char*)data;
    int pad = len > 0 && (len % 4 || p[len - 1] == '=');
    const size_t L = ((len + 3) / 4 - pad) * 4;
    std::string str(L / 4 * 3 + pad, '\0');

    for (size_t i = 0, j = 0; i < L; i += 4)
    {
        int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
        str[j++] = n >> 16;
        str[j++] = n >> 8 & 0xFF;
        str[j++] = n & 0xFF;
    }
    if (pad)
    {
        int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12;
        str[str.size() - 1] = n >> 16;

        if (len > L + 2 && p[L + 2] != '=')
        {
            n |= B64index[p[L + 2]] << 6;
            str.push_back(n >> 8 & 0xFF);
        }
    }
    return str;
}


// -----------------------------------------------------------------------------
// Classe Database avec sauvegarde et chargement en Base64
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Classe Database avec sauvegarde et chargement en Base64 partiel
// -----------------------------------------------------------------------------
class Database {
public:
Database() : tableDir_("db_tables") {
initializeFolder();
loadExistingTables();
}



// Crée une nouvelle table et l'enregistre
std::optional<Table> createTable() {
    Table newTable;
    std::cout << "Enter the name of the new table: ";
    std::getline(std::cin, newTable.name);
    if (newTable.name.empty()) {
        throw std::runtime_error("The name of the table cannot be empty.");
    }
    if (tables_.find(newTable.name) != tables_.end()) {
        throw std::runtime_error("A table with the same name already exists.");
    }

    std::cout << "Enter the names of the columns (separated by commas): ";
    std::string columnsInput;
    std::getline(std::cin, columnsInput);
    // Colonnes réservées
    newTable.columns.push_back("KeyID");
    newTable.columns.push_back("ID");

    auto tokens = parseCSVLine(columnsInput, ',');
    for (const auto &col : tokens) {
        if (!col.empty())
            newTable.columns.push_back(col);
    }

    try {
        saveTable(newTable);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    tables_[newTable.name] = newTable;
    std::cout << "Table '" << newTable.name << "' created successfully." << std::endl;
    return newTable;
}

// Sélectionne une table existante via son nom
Table* selectTable() {
    if (tables_.empty()) {
        std::cout << "No table exists. Please create one first." << std::endl;
        return nullptr;
    }
    std::vector<std::string> tableNames;
    for (const auto &entry : tables_) {
        tableNames.push_back(entry.first);
    }
    size_t currentSelection = 0;
    while (true) {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        ConsoleColor::println("\n===== TABLE SELECTION =====", ConsoleColor::TextColor::YELLOW);
        for (size_t i = 0; i < tableNames.size(); ++i) {
            if (i == currentSelection) {
                ConsoleColor::println("> " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::println("  " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
        }
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (ch == 27) { // Escape
            return nullptr;
        } else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 72) {
                currentSelection = (currentSelection == 0) ? tableNames.size() - 1 : currentSelection - 1;
            } else if (arrow == 80) {
                currentSelection = (currentSelection + 1) % tableNames.size();
            }
        } else if (ch == 13) { // Enter
            const std::string& name = tableNames[currentSelection];
            auto it = tables_.find(name);
            if (it == tables_.end()) {
                std::cout << "Table not found." << std::endl;
                return nullptr;
            }
            try {
                loadTable(it->second);
            } catch (const std::exception &e) {
                throw std::runtime_error("Error while loading the table: " + std::string(e.what()));
            }
            return &it->second;
        }
    }
}

void displayOptions(const std::vector<std::string>& options, size_t currentSelection) {
    for (size_t i = 0; i < options.size(); ++i) {
        if (i == currentSelection) {
            ConsoleColor::println("> " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        } else {
            ConsoleColor::println("  " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        }
    }
}

bool handleInput(int ch, size_t& currentSelection, size_t optionsCount, Table& table) {
    if (ch == 27) { // Escape
        return true;
    } else if (ch == 224) {
        int arrow = _getch();
        if (arrow == 72) {
            currentSelection = (currentSelection == 0) ? optionsCount - 1 : currentSelection - 1;
        } else if (arrow == 80) {
            currentSelection = (currentSelection + 1) % optionsCount;
        }
    } else if (ch == 13) {
        switch (currentSelection) {
            case 0: addRecord(table); break;
            case 1: displayRecords(table); break;
            case 2: searchRecord(table); break;
            case 3: updateRecord(table); break;
            case 4: deleteRecord(table); break;
            case 5: return true;
        }
        ConsoleColor::println("\nPress any key to continue...");
        _getch();
    }
    return false;
}

// Menu interactif pour gérer les enregistrements
void recordMenu(Table& table) {
    const std::vector<std::string> options = {
        "Add a record",
        "Display all records",
        "Search for a record (by KeyID)",
        "Edit a record",
        "Delete a record",
        "Return to the main menu"
    };

    size_t currentSelection = 0;
    while (true) {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        ConsoleColor::println("\n===== RECORDS MENU =====", ConsoleColor::TextColor::YELLOW);
        displayOptions(options, currentSelection);
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (handleInput(ch, currentSelection, options.size(), table)) {
            break;
        }
    }
}

// -------------------------------------------------------------------------
// Fonctions de gestion des enregistrements
// -------------------------------------------------------------------------
std::string getInputForColumn(const std::string& columnName) {
    std::string input;
    std::cout << "Enter the value for '" << columnName << "' : ";
    std::getline(std::cin, input);
    if (input.find_first_not_of(" \t\n\r") == std::string::npos) {
        throw std::runtime_error("The value for '" + columnName + "' cannot be empty.");
    }
    return input;
}

void addRecord(Table& table) {
    Record newRecord;
    newRecord.id = uidGenerator_.getNextID();
    const std::string key = generateKeyID(newRecord.id);
    // Saisie pour chaque colonne (à partir de la 3ème colonne)
    for (std::size_t i = 2; i < table.columns.size(); ++i) {
        newRecord.data.push_back(getInputForColumn(table.columns[i]));
    }
    if (table.records.find(key) != table.records.end()) {
        throw std::runtime_error("Error: a record with this ID already exists.");
    }
    table.records[key] = std::move(newRecord);
    saveTable(table);
    std::cout << "Record added successfully. Generated key: " << key << std::endl;
}

void displayRecords(const Table& table) const noexcept {
    if (table.records.empty()) {
        std::cout << "No records exist in this table." << std::endl;
        return;
    }
    
    std::vector<std::pair<std::string, Record>> recVector(table.records.begin(), table.records.end());
    std::size_t currentIndex = 0;
    auto displayRecordAt = [&]() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        const auto& key = recVector[currentIndex].first;
        const Record& rec = recVector[currentIndex].second;
        const int labelWidth = 12;
        const int fieldWidth = 30;
        const int totalWidth = 2 + labelWidth + fieldWidth + 2;
        
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        std::cout << "\n" << std::string(totalWidth, '=') << std::endl;
        std::cout << std::left << std::setw(totalWidth)
                  << "  Record (" + std::to_string(currentIndex + 1) + " of " + std::to_string(recVector.size()) + ")  "
                  << std::endl;
        std::cout << std::string(totalWidth, '=') << std::endl;
        ConsoleColor::reset();
        
        std::string borderLine = "+" + std::string(totalWidth - 2, '-') + "+";
        auto printField = [&](const std::string &label, const auto &value) {
            std::ostringstream oss;
            oss << value;
            std::cout << "| " << std::left << std::setw(labelWidth) << label
                      << " : " << std::left << std::setw(fieldWidth) << oss.str() << " |" << std::endl;
        };
        
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        printField("KEY", key);
        printField("ID", rec.id);
        ConsoleColor::reset();
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            std::string columnTitle = (i + 2 < table.columns.size())
                                      ? table.columns[i + 2]
                                      : ("Field" + std::to_string(i));
            printField(columnTitle, rec.data[i]);
        }
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
        std::cout << "\nUse ←/→ to navigate, or type 'q' to quit." << std::endl;
        ConsoleColor::reset();
    };

    // Afficher le premier record
    displayRecordAt();
    bool quit = false;
    while (!quit) {
    #if defined(_WIN32)
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 75) {
                if (currentIndex > 0) {
                    --currentIndex;
                    displayRecordAt();
                }
            }
            else if (arrow == 77) {
                if (currentIndex + 1 < recVector.size()) {
                    ++currentIndex;
                    displayRecordAt();
                }
            }
        }
    #else
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 27) {
            int ch1 = _getch();
            int ch2 = _getch();
            if(ch1 == 91) {
                if(ch2 == 68) {
                    if (currentIndex > 0) {
                        --currentIndex;
                        displayRecordAt();
                    }
                }
                else if(ch2 == 67) {
                    if (currentIndex + 1 < recVector.size()) {
                        ++currentIndex;
                        displayRecordAt();
                    }
                }
            }
        }
    #endif
    }
}

void searchRecord(const Table& table) const {
    std::cout << "Enter the key of the record to search for: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    const Record& rec = it->second;
    ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
    std::cout << "\n=== Record found ===" << std::endl;
    ConsoleColor::reset();

    const int labelWidth = 10;
    std::cout << std::left << std::setw(labelWidth) << "KEY:" << key << std::endl;
    std::cout << std::left << std::setw(labelWidth) << "ID:"  << rec.id << std::endl;
    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        std::string columnTitle = (i + 2 < table.columns.size()) ? table.columns[i + 2] : "Field" + std::to_string(i);
        std::cout << std::left << std::setw(labelWidth) << (columnTitle + ":") << rec.data[i] << std::endl;
    }
    std::cout << "\n";
}

void updateRecord(Table& table) {
    std::cout << "Enter the key of the record to modify: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    Record& rec = it->second;
    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        std::cout << "Current value for '" << table.columns[i + 2] << "' : " << rec.data[i] << std::endl;
        std::cout << "Enter the new value (leave empty to keep) : ";
        std::string newValue;
        std::getline(std::cin, newValue);
        if (!newValue.empty()) {
            rec.data[i] = newValue;
        }
    }
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record updated successfully." << std::endl;
}

void deleteRecord(Table& table) {
    std::cout << "Enter the key of the record to delete: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    table.records.erase(it);
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record deleted successfully." << std::endl;
}

private:
const std::string tableDir_;
std::map<std::string, Table> tables_;
TUID uidGenerator_;



// Initialise le dossier de sauvegarde
void initializeFolder() {
    try {
        if (!fs::exists(tableDir_)) {
            fs::create_directory(tableDir_);
        }
    } catch (const fs::filesystem_error &e) {
        throw std::runtime_error("Error while creating the folder: " + std::string(e.what()));
    }
}

// Génère une clé au format "0001"
std::string generateKeyID(int id) const {
    std::ostringstream oss;
    oss << std::setw(4) << std::setfill('0') << id;
    return oss.str();
}

// Sauvegarde d'une table dans un fichier CSV.
// Ici, l'en-tête est en clair;
// pour chaque enregistrement, la clé et l'ID sont écrits en clair,
// puis chaque donnée (à partir de la 3ème colonne) est encodée en Base64.
void saveTable(const Table &table) const {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ofstream file(path, std::ios::out);
    if (!file.is_open()) {
        throw std::runtime_error("Error while opening the file " + path + " for writing.");
    }

    // Écriture de l'en-tête (les colonnes) en clair
    {
        std::ostringstream oss;
        for (std::size_t i = 0; i < table.columns.size(); ++i) {
            oss << table.columns[i];
            if (i + 1 < table.columns.size()) {
                oss << ";";
            }
        }
        file << oss.str() << "\n";
    }

    // Écriture des enregistrements
    for (const auto& [key, rec] : table.records) {
        std::ostringstream line;
        // La clé et l'ID en clair
        line << key << ";" << rec.id;
        // Pour chaque donnée, on encode en Base64
        for (const auto& d : rec.data) {
            std::string encodedData = base64_encode(reinterpret_cast<const unsigned char*>(d.c_str()), d.size());
            line << ";" << encodedData;
        }
        file << line.str() << "\n";
    }
    file.close();
    if (file.fail()) {
        throw std::runtime_error("Error while writing to the file " + path);
    }
}

// Charge une table depuis un fichier CSV.
// Les colonnes (en-tête) sont lues en clair.
// Pour chaque enregistrement, la clé et l'ID sont lus en clair,
// et chaque donnée (à partir de la 3ème colonne) est décodée de Base64.
void loadTable(Table &table) {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ifstream file(path);
    if (!file.is_open()) {
       throw std::runtime_error("File " + path + " not found.");
    }
    table.records.clear();
    std::string line;
    bool firstLine = true;
    while (std::getline(file, line)) {
        if (line.empty())
            continue;
        if (firstLine) {
            table.columns = parseCSVLine(line, ';'); // Lire l'en-tête en clair
            firstLine = false;
            continue;
        }
        auto tokens = parseCSVLine(line, ';');
        if (tokens.size() < 2)
            continue;
        Record rec;
        const std::string key = tokens[0]; // Clé en clair
        try {
            rec.id = std::stoi(tokens[1]); // ID en clair
        } catch (const std::exception&) {
            std::cerr << "Error converting the ID for the key " << key << std::endl;
            continue;
        }
        // Pour chaque donnée, décodage depuis Base64
        for (std::size_t i = 2; i < tokens.size(); ++i) {
            rec.data.push_back(b64decode(tokens[i].c_str(), tokens[i].size()));
        }
        table.records[key] = std::move(rec);
    }
    file.close();

    // Mise à jour du TUID avec le maximum des IDs
    int maxID = 0;
    for (const auto &entry : table.records) {
        maxID = std::max(maxID, entry.second.id);
    }
    uidGenerator_.reset(maxID);
}

// Charge toutes les tables existantes dans le dossier
void loadExistingTables() {
    try {
        if (!fs::exists(tableDir_))
            return;
        for (const auto &entry : fs::directory_iterator(tableDir_)) {
            if (entry.is_regular_file() && entry.path().extension() == ".csv") {
                Table table;
                table.name = entry.path().stem().string();
                loadTable(table);
                tables_[table.name] = std::move(table);
            }
        }
    } catch (const fs::filesystem_error &e) {
        std::cerr << "Error while reading the directory: " << e.what() << std::endl;
    }
}

};

// -----------------------------------------------------------------------------
// Classe MenuManager
// -----------------------------------------------------------------------------
class MenuManager {
public:
    MenuManager(std::unique_ptr<IUserInterface> ui)
        : userInterface_(std::move(ui)) {}

    void run() {
        bool exitFlag = false;
        while (!exitFlag) {
            try {
                Menu mainMenu("Main Menu", 80, 24);
                mainMenu.addOption(MenuOption("Manage the database", [this]() { runDatabaseMenu(); }));
                mainMenu.addOption(MenuOption("Quit", []() {
                    ConsoleColor::println("Goodbye!", ConsoleColor::TextColor::RED);
                    std::exit(EXIT_SUCCESS);
                }));

                clearScreen();
                const int totalWidth = 50;
                const std::string title = " Main Menu ";
                const std::string border = std::string(totalWidth, '=');

                ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
                std::cout << border << std::endl;
                std::cout << std::setw((totalWidth - title.size()) / 2 + title.size()) << title << std::endl;
                std::cout << border << std::endl;
                ConsoleColor::reset();

                ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
                std::cout << "\nUse arrow keys to navigate and Enter/right arrow to select." << std::endl;
                std::cout << "Use left arrow to go back, or Escape to exit." << std::endl;
                ConsoleColor::reset();
                
                for (const auto& option : mainMenu.getOptions()) {
                    ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
                    std::cout << "| " << std::setw(totalWidth - 4) << std::left
                              << option.getTitle() << " |" << std::endl;
                }
                std::cout << border << std::endl;

                runMenu(mainMenu);
                exitFlag = true;
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Return")
                    throw;
            }
        }
    }

private:
    std::unique_ptr<IUserInterface> userInterface_;

    void clearScreen() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
    }

    void runMenu(Menu &menu) {
        while (true) {
            clearScreen();
            menu.displayMenu(userInterface_.get());
            char input = userInterface_->getInput();

            if (input == -32) {
                char direction = userInterface_->getInput();
                if (direction == 72) {
                    menu.navigateUp();
                    continue;
                }
                else if (direction == 80) {
                    menu.navigateDown();
                    continue;
                }
                else if (direction == 77) {
                    menu.select();
                    continue;
                }
                else if (direction == 75) {
                    throw std::runtime_error("Return");
                }
            }
            
            if (input == 27)
                break;

            try {
                menu.navigate(input);
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) == "Return")
                    return;
                else {
                    ConsoleColor::println("Error: " + std::string(e.what()),
                                            ConsoleColor::TextColor::RED,
                                            ConsoleColor::BackgroundColor::BG_BLACK);
                    std::cout << "Press any key to try again...";
                    userInterface_->getInput();
                }
            }
        }
    }

    void runDatabaseMenu() {
        Database db;
        while (true) {
            clearScreen();
            Menu dbMenu("Database Menu", 80, 24);
            dbMenu.addOption(MenuOption("Create a new table", [&db]() {
                try {
                    auto newTableOpt = db.createTable();
                    if (newTableOpt)
                        db.recordMenu( db.selectTable() ? *db.selectTable() : newTableOpt.value() );
                }
                catch (const std::exception &e) {
                    std::cerr << e.what() << std::endl;
                    system("pause");
                }
            }));
            dbMenu.addOption(MenuOption("Use an existing table", [&db]() {
                Table* table = db.selectTable();
                if (table)
                    db.recordMenu(*table);
            }));
            dbMenu.addOption(MenuOption("Back", []() {
                // Retour simple
            }));

            try {
                runMenu(dbMenu);
                break;
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Return")
                    throw;
                else
                    break;
            }
        }
    }
};

// -----------------------------------------------------------------------------
// Fonction main
// -----------------------------------------------------------------------------
int main() {
    try {
        auto ui = std::make_unique<ConsoleUserInterface>();
        MenuManager manager(std::move(ui));
        manager.run();

        // Exemple d'utilisation directe de la base de données
        Database db;
        auto tableOpt = db.createTable();
        if (tableOpt) {
            Table* table = db.selectTable();
            if (table) {
                db.addRecord(*table);
                db.displayRecords(*table);

                TUID generator;
                int nextID = generator.getNextID();
                std::cout << "\nNext generated ID : " << nextID << "\n";
                auto indices = generator.querySketch(nextID, 3);
                std::cout << "Indices returned by querySketch : ";
                for (const auto idx : indices) {
                    std::cout << idx << " ";
                }
                std::cout << "\nPress any key to close...";
                (void)getchar();
            }
        }
    } catch (const std::exception &ex) {
        std::cerr << "Exception caught in main : " << ex.what() << "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
This morning, I shared my recent improvement to the C++ database management system, where I added Base64 encoding for all data entries. Now, I'm excited to announce that the search results are now automatically copied to the clipboard! This enhancement not only streamlines the user experience but also ensures that users can easily access and share their search results without any hassle.
It seems like I can't stop working on this project! At this rate, I might just end up coding in my sleep. But hey, who needs rest when you can have a clipboard full of search results?
Stay tuned for more updates as I continue to refine and enhance this application!

C++:
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#include <memory>
#include <conio.h> // pour _getch() sur Windows

#ifdef _WIN32
    #include <windows.h>
#else
    #include <unistd.h>
#endif

namespace fs = std::filesystem;

// -----------------------------------------------------------------------------
// Gestion des couleurs de la console
// -----------------------------------------------------------------------------
class ConsoleColor {
public:
    enum class TextColor : int {
        BLACK = 0,
        DARK_BLUE = 1,
        DARK_GREEN = 2,
        LIGHT_BLUE = 3,
        DARK_RED = 4,
        MAGENTA = 5,
        ORANGE = 6,
        LIGHT_GRAY = 7,
        GRAY = 8,
        BLUE = 9,
        GREEN = 10,
        CYAN = 11,
        RED = 12,
        PINK = 13,
        YELLOW = 14,
        WHITE = 15
    };

    enum class BackgroundColor : int {
        BG_BLACK = 0,
        BG_DARK_BLUE = 1,
        BG_DARK_GREEN = 2,
        BG_LIGHT_BLUE = 3,
        BG_DARK_RED = 4,
        BG_MAGENTA = 5,
        BG_ORANGE = 6,
        BG_LIGHT_GRAY = 7,
        BG_GRAY = 8,
        BG_BLUE = 9,
        BG_GREEN = 10,
        BG_CYAN = 11,
        BG_RED = 12,
        BG_PINK = 13,
        BG_YELLOW = 14,
        BG_WHITE = 15
    };

    static void print(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
        std::cout << message;
        SetConsoleTextAttribute(handle, static_cast<int>(TextColor::WHITE));
    #else
        std::cout << message;
    #endif
    }

    static void println(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
        print(std::string(message) + "\n", textColor, bgColor);
    }
    
    static void setColor(TextColor textColor, BackgroundColor bgColor) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
    #endif
    }

    static void reset() {
        setColor(TextColor::WHITE, BackgroundColor::BG_BLACK);
    }
};

// -----------------------------------------------------------------------------
// Interface et classes pour le menu interactif
// -----------------------------------------------------------------------------
class IUserInterface {
public:
    virtual void display(std::string_view message) = 0;
    virtual char getInput() = 0;
    virtual ~IUserInterface() = default;
};

class ConsoleUserInterface : public IUserInterface {
public:
    void display(std::string_view message) override {
        displayMessage(std::string(message), ConsoleColor::TextColor::WHITE);
    }
    char getInput() override {
        return _getch();
    }
private:
    void displayMessage(const std::string& message, ConsoleColor::TextColor textColor = ConsoleColor::TextColor::WHITE) {
        const std::string border = std::string(message.size() + 4, '*');
        ConsoleColor::println(border, textColor);
        ConsoleColor::println("* " + message + " *", textColor);
        ConsoleColor::println(border, textColor);
    }
};

class MenuOption {
public:
    MenuOption(std::string title, std::function<void()> action)
        : title_(std::move(title)), action_(std::move(action)) {}

    void select() {
        action_();
    }

    std::string getTitle() const {
        return title_;
    }

private:
    std::string title_;
    std::function<void()> action_;
};

class Menu {
public:
    Menu(std::string title, int width, int height)
        : title_(std::move(title)), width_(width), height_(height) {}
    void addOption(const MenuOption& option) {
        options_.push_back(option);
    }
    void displayMenu(IUserInterface* ui) {
        ConsoleColor::println("=== " + title_ + " ===", ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        for (std::size_t i = 0; i < options_.size(); ++i) {
            if (currentSelection_ == i) {
                ConsoleColor::print("> " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::print("  " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
            std::cout << std::endl;
        }
        ConsoleColor::println("====================", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        ConsoleColor::println("Up/Down arrows to navigate, Left arrow to go back, Right arrow or Enter to select, Escape to exit.", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
    }

    void navigate(char input) {
        if (input == -32) { // touche spéciale
            char direction = _getch();
            if (direction == 72) { // flèche haut
                currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
            } else if (direction == 80) { // flèche bas
                currentSelection_ = (currentSelection_ + 1) % options_.size();
            } else if (direction == 75) { // flèche gauche
                throw std::runtime_error("Return");
            } else if (direction == 77) { // flèche droite
                select();
            }
        } else if (input == 13) { // touche Entrée
            select();
        }
    }

    void select() {
        options_[currentSelection_].select();
    }
    
    void navigateUp() {
        currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
    }

    void navigateDown() {
        currentSelection_ = (currentSelection_ + 1) % options_.size();
    }

    const std::vector<MenuOption>& getOptions() const {
        return options_;
    }

private:
    std::string title_;
    std::vector<MenuOption> options_;
    std::size_t currentSelection_ = 0;
    int width_;
    int height_;
};

// -----------------------------------------------------------------------------
// Utilitaires CSV et hachage
// -----------------------------------------------------------------------------
inline std::string trim(std::string_view s) noexcept {
    const auto begin = s.find_first_not_of(" \t\n\r");
    if (begin == std::string_view::npos)
        return "";
    const auto end = s.find_last_not_of(" \t\n\r");
    return std::string(s.substr(begin, end - begin + 1));
}

inline std::vector<std::string> parseCSVLine(const std::string& line, char delimiter = ';') noexcept {
    std::vector<std::string> tokens;
    std::istringstream ss(line);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        tokens.push_back(trim(token));
    }
    return tokens;
}

/// Permet de réaliser un hachage 2-universel simple.
class Hasher {
public:
    template <typename T>
    static std::size_t hash(std::size_t seed, const T& val) noexcept {
        std::hash<T> hasher;
        return hasher(val) ^ (seed * 0x9e3779b97f4a7c15ULL);
    }
};

// -----------------------------------------------------------------------------
// Classe Sketch (Count-Min Sketch avec logique de médiane)
// -----------------------------------------------------------------------------
class Sketch {
public:
    Sketch(std::size_t d, std::size_t w, std::size_t r) noexcept
        : d_(d), w_(w), r_(r),
          counters_(d, std::vector<std::vector<int>>(w, std::vector<int>(r, 0))) {}

    template <typename T>
    void update(const T& x) noexcept {
        for (std::size_t o = 0; o < r_; ++o) {
            for (std::size_t i = 0; i < d_; ++i) {
                std::size_t j = Hasher::hash(i, x) % w_;
                ++counters_[i][j][o];
            }
        }
    }

    int medianOfMeans(const std::vector<int>& values) const noexcept {
        std::vector<int> temp = values;
        std::sort(temp.begin(), temp.end());
        return temp[temp.size() / 2];
    }

    template <typename T>
    std::vector<std::size_t> query(const T& q, std::size_t topV) const noexcept {
        std::vector<int> similarities;
        similarities.reserve(w_);
        for (std::size_t j = 0; j < w_; ++j) {
            std::vector<int> temps;
            for (std::size_t i = 0; i < d_; ++i) {
                temps.insert(temps.end(), counters_[i][j].begin(), counters_[i][j].end());
            }
            similarities.push_back(medianOfMeans(temps));
        }
        std::vector<std::size_t> indices(w_);
        for (std::size_t i = 0; i < w_; ++i) {
            indices[i] = i;
        }
        std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) {
            return similarities[a] > similarities[b];
        });
        if (topV > indices.size())
            topV = indices.size();
        indices.resize(topV);
        return indices;
    }

private:
    std::size_t d_;
    std::size_t w_;
    std::size_t r_;
    std::vector<std::vector<std::vector<int>>> counters_;
};

// -----------------------------------------------------------------------------
// Classe TUID : gestion d'ID et du Sketch
// -----------------------------------------------------------------------------
class TUID {
public:
    TUID() noexcept
        : lastID_(0), sketch_(4, 100, 5) {}

    int getNextID() noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        int next = ++lastID_;
        sketch_.update(next);
        return next;
    }

    void reset(int value = 0) noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        lastID_ = value;
    }

    std::vector<std::size_t> querySketch(const int& q, std::size_t topV = 5) const noexcept {
        return sketch_.query(q, topV);
    }

private:
    int lastID_;
    Sketch sketch_;
    mutable std::mutex mutex_;
};

// -----------------------------------------------------------------------------
// Structures pour les enregistrements et la table
// -----------------------------------------------------------------------------
struct Record {
    int id;  // ID auto-généré unique
    std::vector<std::string> data;  // Données pour les colonnes hors CleID et ID
};

struct Table {
    std::string name;                         // Nom de la table
    std::vector<std::string> columns;         // Liste des colonnes (les deux premières étant réservées)
    std::map<std::string, Record> records;    // Clé format "0001" -> enregistrement
};

// -----------------------------------------------------------------------------
// Fonctions Base64 pour encoder/décoder les chaînes
// -----------------------------------------------------------------------------
static const unsigned char base64_table[65] =

    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";


std::string base64_encode(const unsigned char *src, size_t len) {

    unsigned char *out, *pos;

    const unsigned char *end, *in;


    size_t olen;

    olen = 4 * ((len + 2) / 3); /* 3-byte blocks to 4-byte */


    if (olen < len)

        return std::string(); /* integer overflow */


    std::string outStr;

    outStr.resize(olen);

    out = (unsigned char*)&outStr[0];


    end = src + len;

    in = src;

    pos = out;

    while (end - in >= 3) {

        *pos++ = base64_table[in[0] >> 2];

        *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];

        *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];

        *pos++ = base64_table[in[2] & 0x3f];

        in += 3;

    }


    if (end - in) {

        *pos++ = base64_table[in[0] >> 2];

        if (end - in == 1) {

            *pos++ = base64_table[(in[0] & 0x03) << 4];

            *pos++ = '=';

        } else {

            *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];

            *pos++ = base64_table[(in[1] & 0x0f) << 2];

        }

        *pos++ = '=';

    }


    return outStr;

}


static const int B64index[256] = { 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,

0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,

0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 62, 63, 62, 62, 63, 52, 53, 54, 55,

56, 57, 58, 59, 60, 61,  0,  0,  0,  0,  0,  0,  0,  0,  1,  2,  3,  4,  5,  6,

7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  0,

0,  0,  0, 63,  0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,

41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 };


std::string b64decode(const void* data, const size_t len)
{
    unsigned char* p = (unsigned char*)data;
    int pad = len > 0 && (len % 4 || p[len - 1] == '=');
    const size_t L = ((len + 3) / 4 - pad) * 4;
    std::string str(L / 4 * 3 + pad, '\0');

    for (size_t i = 0, j = 0; i < L; i += 4)
    {
        int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]];
        str[j++] = n >> 16;
        str[j++] = n >> 8 & 0xFF;
        str[j++] = n & 0xFF;
    }
    if (pad)
    {
        int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12;
        str[str.size() - 1] = n >> 16;

        if (len > L + 2 && p[L + 2] != '=')
        {
            n |= B64index[p[L + 2]] << 6;
            str.push_back(n >> 8 & 0xFF);
        }
    }
    return str;
}


// -----------------------------------------------------------------------------
// Classe Database avec sauvegarde et chargement en Base64
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Classe Database avec sauvegarde et chargement en Base64 partiel
// -----------------------------------------------------------------------------
class Database {
public:
Database() : tableDir_("db_tables") {
initializeFolder();
loadExistingTables();
}



// Crée une nouvelle table et l'enregistre
std::optional<Table> createTable() {
    Table newTable;
    std::cout << "Enter the name of the new table: ";
    std::getline(std::cin, newTable.name);
    if (newTable.name.empty()) {
        throw std::runtime_error("The name of the table cannot be empty.");
    }
    if (tables_.find(newTable.name) != tables_.end()) {
        throw std::runtime_error("A table with the same name already exists.");
    }

    std::cout << "Enter the names of the columns (separated by commas): ";
    std::string columnsInput;
    std::getline(std::cin, columnsInput);
    // Colonnes réservées
    newTable.columns.push_back("KeyID");
    newTable.columns.push_back("ID");

    auto tokens = parseCSVLine(columnsInput, ',');
    for (const auto &col : tokens) {
        if (!col.empty())
            newTable.columns.push_back(col);
    }

    try {
        saveTable(newTable);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    tables_[newTable.name] = newTable;
    std::cout << "Table '" << newTable.name << "' created successfully." << std::endl;
    return newTable;
}

// Sélectionne une table existante via son nom
Table* selectTable() {
    if (tables_.empty()) {
        std::cout << "No table exists. Please create one first." << std::endl;
        return nullptr;
    }
    std::vector<std::string> tableNames;
    for (const auto &entry : tables_) {
        tableNames.push_back(entry.first);
    }
    size_t currentSelection = 0;
    while (true) {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        ConsoleColor::println("\n===== TABLE SELECTION =====", ConsoleColor::TextColor::YELLOW);
        for (size_t i = 0; i < tableNames.size(); ++i) {
            if (i == currentSelection) {
                ConsoleColor::println("> " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::println("  " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
        }
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (ch == 27) { // Escape
            return nullptr;
        } else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 72) {
                currentSelection = (currentSelection == 0) ? tableNames.size() - 1 : currentSelection - 1;
            } else if (arrow == 80) {
                currentSelection = (currentSelection + 1) % tableNames.size();
            }
        } else if (ch == 13) { // Enter
            const std::string& name = tableNames[currentSelection];
            auto it = tables_.find(name);
            if (it == tables_.end()) {
                std::cout << "Table not found." << std::endl;
                return nullptr;
            }
            try {
                loadTable(it->second);
            } catch (const std::exception &e) {
                throw std::runtime_error("Error while loading the table: " + std::string(e.what()));
            }
            return &it->second;
        }
    }
}

void displayOptions(const std::vector<std::string>& options, size_t currentSelection) {
    for (size_t i = 0; i < options.size(); ++i) {
        if (i == currentSelection) {
            ConsoleColor::println("> " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        } else {
            ConsoleColor::println("  " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        }
    }
}

bool handleInput(int ch, size_t& currentSelection, size_t optionsCount, Table& table) {
    if (ch == 27) { // Escape
        return true;
    } else if (ch == 224) {
        int arrow = _getch();
        if (arrow == 72) {
            currentSelection = (currentSelection == 0) ? optionsCount - 1 : currentSelection - 1;
        } else if (arrow == 80) {
            currentSelection = (currentSelection + 1) % optionsCount;
        }
    } else if (ch == 13) {
        switch (currentSelection) {
            case 0: addRecord(table); break;
            case 1: displayRecords(table); break;
            case 2: searchRecord(table); break;
            case 3: updateRecord(table); break;
            case 4: deleteRecord(table); break;
            case 5: return true;
        }
        ConsoleColor::println("\nPress any key to continue...");
        _getch();
    }
    return false;
}
void displayHelp(const std::string& option) {
    if (option == "Add a record") {
        ConsoleColor::println("This option allows you to add a new record to the selected table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Display all records") {
        ConsoleColor::println("This option displays all records in the selected table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Search for a record (by KeyID)") {
        ConsoleColor::println("This option lets you search for a record by entering its KeyID.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Edit a record") {
        ConsoleColor::println("This option allows you to modify an existing record after selecting it.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Delete a record") {
        ConsoleColor::println("This option permits you to delete an existing record from the table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Return to the main menu") {
        ConsoleColor::println("This option will take you back to the main menu.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else {
        ConsoleColor::println("No help available for this option.", ConsoleColor::TextColor::LIGHT_GRAY);
    }
}
// Menu interactif pour gérer les enregistrements
void recordMenu(Table& table) {
    const std::vector<std::string> options = {
        "Add a record",
        "Display all records",
        "Search for a record (by KeyID)",
        "Edit a record",
        "Delete a record",
        "Return to the main menu"
    };

    size_t currentSelection = 0;
    while (true) {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        ConsoleColor::println("\n===== RECORDS MENU =====", ConsoleColor::TextColor::YELLOW);
        displayOptions(options, currentSelection);
        // Afficher l'aide pour l'option actuellement survolée
        ConsoleColor::println("");
        displayHelp(options[currentSelection]);
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (handleInput(ch, currentSelection, options.size(), table)) {
            break;
        }
    }
}

// -------------------------------------------------------------------------
// Fonctions de gestion des enregistrements
// -------------------------------------------------------------------------
std::string getInputForColumn(const std::string& columnName) {
    std::string input;
    std::cout << "Enter the value for '" << columnName << "' : ";
    std::getline(std::cin, input);
    if (input.find_first_not_of(" \t\n\r") == std::string::npos) {
        throw std::runtime_error("The value for '" + columnName + "' cannot be empty.");
    }
    return input;
}

void addRecord(Table& table) {
    Record newRecord;
    newRecord.id = uidGenerator_.getNextID();
    const std::string key = generateKeyID(newRecord.id);
    // Saisie pour chaque colonne (à partir de la 3ème colonne)
    for (std::size_t i = 2; i < table.columns.size(); ++i) {
        newRecord.data.push_back(getInputForColumn(table.columns[i]));
    }
    if (table.records.find(key) != table.records.end()) {
        throw std::runtime_error("Error: a record with this ID already exists.");
    }
    table.records[key] = std::move(newRecord);
    saveTable(table);
    std::cout << "Record added successfully. Generated key: " << key << std::endl;
}

void displayRecords(const Table& table) const noexcept {
    if (table.records.empty()) {
        std::cout << "No records exist in this table." << std::endl;
        return;
    }
    
    std::vector<std::pair<std::string, Record>> recVector(table.records.begin(), table.records.end());
    std::size_t currentIndex = 0;
    auto displayRecordAt = [&]() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        const auto& key = recVector[currentIndex].first;
        const Record& rec = recVector[currentIndex].second;
        const int labelWidth = 12;
        const int fieldWidth = 30;
        const int totalWidth = 2 + labelWidth + fieldWidth + 2;
        
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        std::cout << "\n" << std::string(totalWidth, '=') << std::endl;
        std::cout << std::left << std::setw(totalWidth)
                  << "  Record (" + std::to_string(currentIndex + 1) + " of " + std::to_string(recVector.size()) + ")  "
                  << std::endl;
        std::cout << std::string(totalWidth, '=') << std::endl;
        ConsoleColor::reset();
        
        std::string borderLine = "+" + std::string(totalWidth - 2, '-') + "+";
        auto printField = [&](const std::string &label, const auto &value) {
            std::ostringstream oss;
            oss << value;
            std::cout << "| " << std::left << std::setw(labelWidth) << label
                      << " : " << std::left << std::setw(fieldWidth) << oss.str() << " |" << std::endl;
        };
        
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        printField("KEY", key);
        printField("ID", rec.id);
        ConsoleColor::reset();
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            std::string columnTitle = (i + 2 < table.columns.size())
                                      ? table.columns[i + 2]
                                      : ("Field" + std::to_string(i));
            printField(columnTitle, rec.data[i]);
        }
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
        std::cout << "\nUse ←/→ to navigate, or type 'q' to quit." << std::endl;
        ConsoleColor::reset();
    };

    // Afficher le premier record
    displayRecordAt();
    bool quit = false;
    while (!quit) {
    #if defined(_WIN32)
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 75) {
                if (currentIndex > 0) {
                    --currentIndex;
                    displayRecordAt();
                }
            }
            else if (arrow == 77) {
                if (currentIndex + 1 < recVector.size()) {
                    ++currentIndex;
                    displayRecordAt();
                }
            }
        }
    #else
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 27) {
            int ch1 = _getch();
            int ch2 = _getch();
            if(ch1 == 91) {
                if(ch2 == 68) {
                    if (currentIndex > 0) {
                        --currentIndex;
                        displayRecordAt();
                    }
                }
                else if(ch2 == 67) {
                    if (currentIndex + 1 < recVector.size()) {
                        ++currentIndex;
                        displayRecordAt();
                    }
                }
            }
        }
    #endif
    }
}

static void toClipboard(HWND hwnd, const std::string &s) {
        if (!OpenClipboard(hwnd)) {
            return;
        }
        EmptyClipboard();
        HGLOBAL hg = GlobalAlloc(GMEM_MOVEABLE, s.size() + 1);
        if (!hg) {
            CloseClipboard();
            return;
        }
        char* dest = static_cast<char*>(GlobalLock(hg));
        memcpy(dest, s.c_str(), s.size() + 1);
        GlobalUnlock(hg);
        SetClipboardData(CF_TEXT, hg);
        CloseClipboard();
        GlobalFree(hg);
    }

void searchRecord(const Table& table) /* const */ {
    std::cout << "Enter the key of the record to search for: ";
    std::string key;
    std::getline(std::cin, key);

    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    const Record& rec = it->second;

    // Affichage du message dans la console pour le record trouvé
    ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
    std::cout << "\n=== Record found ===" << std::endl;
    ConsoleColor::reset();

    const int labelWidth = 10;
    std::cout << std::left << std::setw(labelWidth) << "KEY:" << key << std::endl;
    std::cout << std::left << std::setw(labelWidth) << "ID:"  << rec.id << std::endl;

    // Création d'une chaîne pour copier dans le presse-papier
    std::ostringstream clipboardStream;
    clipboardStream << "KEY: " << key << "\n";
    clipboardStream << "ID: " << rec.id << "\n";

    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        std::string columnTitle = (i + 2 < table.columns.size())
                                  ? table.columns[i + 2]
                                  : "Field" + std::to_string(i);
        std::cout << std::left << std::setw(labelWidth) << (columnTitle + ":")
                  << rec.data[i] << std::endl;
        clipboardStream << columnTitle << ": " << rec.data[i] << "\n";
    }
    std::cout << "\n";

    // Récupération du handle de la fenêtre console
    HWND hwnd = GetConsoleWindow();
    std::string clipboardData = clipboardStream.str();
    toClipboard(hwnd, clipboardData);

    // Affichage du message en anglais en utilisant ConsoleColor::setColor
    // Nous choisissons ici le texte jaune sur fond noir par exemple.
    ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
    std::cout << "The search result has been copied to the clipboard." << std::endl;
    ConsoleColor::reset();
}

void updateRecord(Table& table) {
    std::cout << "Enter the key of the record to modify: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    Record& rec = it->second;
    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        std::cout << "Current value for '" << table.columns[i + 2] << "' : " << rec.data[i] << std::endl;
        std::cout << "Enter the new value (leave empty to keep) : ";
        std::string newValue;
        std::getline(std::cin, newValue);
        if (!newValue.empty()) {
            rec.data[i] = newValue;
        }
    }
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record updated successfully." << std::endl;
}

void deleteRecord(Table& table) {
    std::cout << "Enter the key of the record to delete: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    table.records.erase(it);
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record deleted successfully." << std::endl;
}

private:
const std::string tableDir_;
std::map<std::string, Table> tables_;
TUID uidGenerator_;



// Initialise le dossier de sauvegarde
void initializeFolder() {
    try {
        if (!fs::exists(tableDir_)) {
            fs::create_directory(tableDir_);
        }
    } catch (const fs::filesystem_error &e) {
        throw std::runtime_error("Error while creating the folder: " + std::string(e.what()));
    }
}

// Génère une clé au format "0001"
std::string generateKeyID(int id) const {
    std::ostringstream oss;
    oss << std::setw(4) << std::setfill('0') << id;
    return oss.str();
}

// Sauvegarde d'une table dans un fichier CSV.
// Ici, l'en-tête est en clair;
// pour chaque enregistrement, la clé et l'ID sont écrits en clair,
// puis chaque donnée (à partir de la 3ème colonne) est encodée en Base64.
void saveTable(const Table &table) const {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ofstream file(path, std::ios::out);
    if (!file.is_open()) {
        throw std::runtime_error("Error while opening the file " + path + " for writing.");
    }

    // Écriture de l'en-tête (les colonnes) en clair
    {
        std::ostringstream oss;
        for (std::size_t i = 0; i < table.columns.size(); ++i) {
            oss << table.columns[i];
            if (i + 1 < table.columns.size()) {
                oss << ";";
            }
        }
        file << oss.str() << "\n";
    }

    // Écriture des enregistrements
    for (const auto& [key, rec] : table.records) {
        std::ostringstream line;
        // La clé et l'ID en clair
        line << key << ";" << rec.id;
        // Pour chaque donnée, on encode en Base64
        for (const auto& d : rec.data) {
            std::string encodedData = base64_encode(reinterpret_cast<const unsigned char*>(d.c_str()), d.size());
            line << ";" << encodedData;
        }
        file << line.str() << "\n";
    }
    file.close();
    if (file.fail()) {
        throw std::runtime_error("Error while writing to the file " + path);
    }
}

// Charge une table depuis un fichier CSV.
// Les colonnes (en-tête) sont lues en clair.
// Pour chaque enregistrement, la clé et l'ID sont lus en clair,
// et chaque donnée (à partir de la 3ème colonne) est décodée de Base64.
void loadTable(Table &table) {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ifstream file(path);
    if (!file.is_open()) {
       throw std::runtime_error("File " + path + " not found.");
    }
    table.records.clear();
    std::string line;
    bool firstLine = true;
    while (std::getline(file, line)) {
        if (line.empty())
            continue;
        if (firstLine) {
            table.columns = parseCSVLine(line, ';'); // Lire l'en-tête en clair
            firstLine = false;
            continue;
        }
        auto tokens = parseCSVLine(line, ';');
        if (tokens.size() < 2)
            continue;
        Record rec;
        const std::string key = tokens[0]; // Clé en clair
        try {
            rec.id = std::stoi(tokens[1]); // ID en clair
        } catch (const std::exception&) {
            std::cerr << "Error converting the ID for the key " << key << std::endl;
            continue;
        }
        // Pour chaque donnée, décodage depuis Base64
        for (std::size_t i = 2; i < tokens.size(); ++i) {
            rec.data.push_back(b64decode(tokens[i].c_str(), tokens[i].size()));
        }
        table.records[key] = std::move(rec);
    }
    file.close();

    // Mise à jour du TUID avec le maximum des IDs
    int maxID = 0;
    for (const auto &entry : table.records) {
        maxID = std::max(maxID, entry.second.id);
    }
    uidGenerator_.reset(maxID);
}

// Charge toutes les tables existantes dans le dossier
void loadExistingTables() {
    try {
        if (!fs::exists(tableDir_))
            return;
        for (const auto &entry : fs::directory_iterator(tableDir_)) {
            if (entry.is_regular_file() && entry.path().extension() == ".csv") {
                Table table;
                table.name = entry.path().stem().string();
                loadTable(table);
                tables_[table.name] = std::move(table);
            }
        }
    } catch (const fs::filesystem_error &e) {
        std::cerr << "Error while reading the directory: " << e.what() << std::endl;
    }
}

};

// -----------------------------------------------------------------------------
// Classe MenuManager
// -----------------------------------------------------------------------------
class MenuManager {
public:
    MenuManager(std::unique_ptr<IUserInterface> ui)
        : userInterface_(std::move(ui)) {}

    void run() {
        bool exitFlag = false;
        while (!exitFlag) {
            try {
                Menu mainMenu("Main Menu", 80, 24);
                mainMenu.addOption(MenuOption("Manage the database", [this]() { runDatabaseMenu(); }));
                mainMenu.addOption(MenuOption("Quit", []() {
                    ConsoleColor::println("Goodbye!", ConsoleColor::TextColor::RED);
                    std::exit(EXIT_SUCCESS);
                }));

                clearScreen();
                const int totalWidth = 50;
                const std::string title = " Main Menu ";
                const std::string border = std::string(totalWidth, '=');

                ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
                std::cout << border << std::endl;
                std::cout << std::setw((totalWidth - title.size()) / 2 + title.size()) << title << std::endl;
                std::cout << border << std::endl;
                ConsoleColor::reset();

                ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
                std::cout << "\nUse arrow keys to navigate and Enter/right arrow to select." << std::endl;
                std::cout << "Use left arrow to go back, or Escape to exit." << std::endl;
                ConsoleColor::reset();
                
                for (const auto& option : mainMenu.getOptions()) {
                    ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
                    std::cout << "| " << std::setw(totalWidth - 4) << std::left 
                              << option.getTitle() << " |" << std::endl;
                }
                std::cout << border << std::endl;

                runMenu(mainMenu);
                exitFlag = true;
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Return")
                    throw;
            }
        }
    }

private:
    std::unique_ptr<IUserInterface> userInterface_;

    void clearScreen() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
    }

    void runMenu(Menu &menu) {
        while (true) {
            clearScreen();
            menu.displayMenu(userInterface_.get());
            char input = userInterface_->getInput();

            if (input == -32) {
                char direction = userInterface_->getInput();
                if (direction == 72) {
                    menu.navigateUp();
                    continue;
                }
                else if (direction == 80) {
                    menu.navigateDown();
                    continue;
                }
                else if (direction == 77) {
                    menu.select();
                    continue;
                }
                else if (direction == 75) {
                    throw std::runtime_error("Return");
                }
            }
            
            if (input == 27)
                break;

            try {
                menu.navigate(input);
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) == "Return")
                    return;
                else {
                    ConsoleColor::println("Error: " + std::string(e.what()), 
                                            ConsoleColor::TextColor::RED, 
                                            ConsoleColor::BackgroundColor::BG_BLACK);
                    std::cout << "Press any key to try again...";
                    userInterface_->getInput();
                }
            }
        }
    }

    void runDatabaseMenu() {
        Database db;
        while (true) {
            clearScreen();
            Menu dbMenu("Database Menu", 80, 24);
            dbMenu.addOption(MenuOption("Create a new table", [&db]() {
                try {
                    auto newTableOpt = db.createTable();
                    if (newTableOpt)
                        db.recordMenu( db.selectTable() ? *db.selectTable() : newTableOpt.value() );
                }
                catch (const std::exception &e) {
                    std::cerr << e.what() << std::endl;
                    system("pause");
                }
            }));
            dbMenu.addOption(MenuOption("Use an existing table", [&db]() {
                Table* table = db.selectTable();
                if (table)
                    db.recordMenu(*table);
            }));
            dbMenu.addOption(MenuOption("Back", []() {
                // Retour simple
            }));

            try {
                runMenu(dbMenu);
                break;
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Return")
                    throw;
                else
                    break;
            }
        }
    }
};

// -----------------------------------------------------------------------------
// Fonction main
// -----------------------------------------------------------------------------
int main() {
    try {
        auto ui = std::make_unique<ConsoleUserInterface>();
        MenuManager manager(std::move(ui));
        manager.run();

        // Exemple d'utilisation directe de la base de données
        Database db;
        auto tableOpt = db.createTable();
        if (tableOpt) {
            Table* table = db.selectTable();
            if (table) {
                db.addRecord(*table);
                db.displayRecords(*table);

                TUID generator;
                int nextID = generator.getNextID();
                std::cout << "\nNext generated ID : " << nextID << "\n";
                auto indices = generator.querySketch(nextID, 3);
                std::cout << "Indices returned by querySketch : ";
                for (const auto idx : indices) {
                    std::cout << idx << " ";
                }
                std::cout << "\nPress any key to close...";
                (void)getchar();
            }
        }
    } catch (const std::exception &ex) {
        std::cerr << "Exception caught in main : " << ex.what() << "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
 
Last edited:
Joined
Sep 20, 2022
Messages
237
Reaction score
39
I think the base64 is overkill.

Converting 3 bytes into 4 characters was a good idea back when email transmission speeds were slow and storage was expensive.

If you want to store any sequence of bytes inside a string, converting 1 byte into 2 characters is simpler, the extra space is no big deal these days, and the conversion functions come standard with every language.

Just my opinion.
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
Thank you for your feedback! I used this to avoid special characters, in fact. But I have nothing against hexadecimal, for example. It's just a matter of opportunity.
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
Thank you for your feedback on the Base64 approach. I'm pleased to share that the new version now uses hexadecimal encoding instead. This change effectively avoids any issues with special characters and simplifies the conversion process, without a significant impact on performance or storage. I appreciate your input and hope you find this update practical and efficient.
C++:
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#include <memory>
#include <conio.h> // pour _getch() sur Windows

#ifdef _WIN32
    #include <windows.h>
#else
    #include <unistd.h>
#endif

namespace fs = std::filesystem;

// -----------------------------------------------------------------------------
// Gestion des couleurs de la console
// -----------------------------------------------------------------------------
class ConsoleColor {
public:
    enum class TextColor : int {
        BLACK = 0,
        DARK_BLUE = 1,
        DARK_GREEN = 2,
        LIGHT_BLUE = 3,
        DARK_RED = 4,
        MAGENTA = 5,
        ORANGE = 6,
        LIGHT_GRAY = 7,
        GRAY = 8,
        BLUE = 9,
        GREEN = 10,
        CYAN = 11,
        RED = 12,
        PINK = 13,
        YELLOW = 14,
        WHITE = 15
    };

    enum class BackgroundColor : int {
        BG_BLACK = 0,
        BG_DARK_BLUE = 1,
        BG_DARK_GREEN = 2,
        BG_LIGHT_BLUE = 3,
        BG_DARK_RED = 4,
        BG_MAGENTA = 5,
        BG_ORANGE = 6,
        BG_LIGHT_GRAY = 7,
        BG_GRAY = 8,
        BG_BLUE = 9,
        BG_GREEN = 10,
        BG_CYAN = 11,
        BG_RED = 12,
        BG_PINK = 13,
        BG_YELLOW = 14,
        BG_WHITE = 15
    };

    static void print(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
        std::cout << message;
        SetConsoleTextAttribute(handle, static_cast<int>(TextColor::WHITE));
    #else
        std::cout << message;
    #endif
    }

    static void println(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
        print(std::string(message) + "\n", textColor, bgColor);
    }
   
    static void setColor(TextColor textColor, BackgroundColor bgColor) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
    #endif
    }

    static void reset() {
        setColor(TextColor::WHITE, BackgroundColor::BG_BLACK);
    }
};

// -----------------------------------------------------------------------------
// Interface et classes pour le menu interactif
// -----------------------------------------------------------------------------
class IUserInterface {
public:
    virtual void display(std::string_view message) = 0;
    virtual char getInput() = 0;
    virtual ~IUserInterface() = default;
};

class ConsoleUserInterface : public IUserInterface {
public:
    void display(std::string_view message) override {
        displayMessage(std::string(message), ConsoleColor::TextColor::WHITE);
    }
    char getInput() override {
        return _getch();
    }
private:
    void displayMessage(const std::string& message, ConsoleColor::TextColor textColor = ConsoleColor::TextColor::WHITE) {
        const std::string border = std::string(message.size() + 4, '*');
        ConsoleColor::println(border, textColor);
        ConsoleColor::println("* " + message + " *", textColor);
        ConsoleColor::println(border, textColor);
    }
};

class MenuOption {
public:
    MenuOption(std::string title, std::function<void()> action)
        : title_(std::move(title)), action_(std::move(action)) {}

    void select() {
        action_();
    }

    std::string getTitle() const {
        return title_;
    }

private:
    std::string title_;
    std::function<void()> action_;
};

class Menu {
public:
    Menu(std::string title, int width, int height)
        : title_(std::move(title)), width_(width), height_(height) {}
    void addOption(const MenuOption& option) {
        options_.push_back(option);
    }
    void displayMenu(IUserInterface* ui) {
        ConsoleColor::println("=== " + title_ + " ===", ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        for (std::size_t i = 0; i < options_.size(); ++i) {
            if (currentSelection_ == i) {
                ConsoleColor::print("> " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::print("  " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
            std::cout << std::endl;
        }
        ConsoleColor::println("====================", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        ConsoleColor::println("Up/Down arrows to navigate, Left arrow to go back, Right arrow or Enter to select, Escape to exit.", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
    }

    void navigate(char input) {
        if (input == -32) { // touche spéciale
            char direction = _getch();
            if (direction == 72) { // flèche haut
                currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
            } else if (direction == 80) { // flèche bas
                currentSelection_ = (currentSelection_ + 1) % options_.size();
            } else if (direction == 75) { // flèche gauche
                throw std::runtime_error("Return");
            } else if (direction == 77) { // flèche droite
                select();
            }
        } else if (input == 13) { // touche Entrée
            select();
        }
    }

    void select() {
        options_[currentSelection_].select();
    }
   
    void navigateUp() {
        currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
    }

    void navigateDown() {
        currentSelection_ = (currentSelection_ + 1) % options_.size();
    }

    const std::vector<MenuOption>& getOptions() const {
        return options_;
    }

private:
    std::string title_;
    std::vector<MenuOption> options_;
    std::size_t currentSelection_ = 0;
    int width_;
    int height_;
};

// -----------------------------------------------------------------------------
// Utilitaires CSV et hachage
// -----------------------------------------------------------------------------
inline std::string trim(std::string_view s) noexcept {
    const auto begin = s.find_first_not_of(" \t\n\r");
    if (begin == std::string_view::npos)
        return "";
    const auto end = s.find_last_not_of(" \t\n\r");
    return std::string(s.substr(begin, end - begin + 1));
}

inline std::vector<std::string> parseCSVLine(const std::string& line, char delimiter = ';') noexcept {
    std::vector<std::string> tokens;
    std::istringstream ss(line);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        tokens.push_back(trim(token));
    }
    return tokens;
}

/// Permet de réaliser un hachage 2-universel simple.
class Hasher {
public:
    template <typename T>
    static std::size_t hash(std::size_t seed, const T& val) noexcept {
        std::hash<T> hasher;
        return hasher(val) ^ (seed * 0x9e3779b97f4a7c15ULL);
    }
};

// -----------------------------------------------------------------------------
// Classe Sketch (Count-Min Sketch avec logique de médiane)
// -----------------------------------------------------------------------------
class Sketch {
public:
    Sketch(std::size_t d, std::size_t w, std::size_t r) noexcept
        : d_(d), w_(w), r_(r),
          counters_(d, std::vector<std::vector<int>>(w, std::vector<int>(r, 0))) {}

    template <typename T>
    void update(const T& x) noexcept {
        for (std::size_t o = 0; o < r_; ++o) {
            for (std::size_t i = 0; i < d_; ++i) {
                std::size_t j = Hasher::hash(i, x) % w_;
                ++counters_[i][j][o];
            }
        }
    }

    int medianOfMeans(const std::vector<int>& values) const noexcept {
        std::vector<int> temp = values;
        std::sort(temp.begin(), temp.end());
        return temp[temp.size() / 2];
    }

    template <typename T>
    std::vector<std::size_t> query(const T& q, std::size_t topV) const noexcept {
        std::vector<int> similarities;
        similarities.reserve(w_);
        for (std::size_t j = 0; j < w_; ++j) {
            std::vector<int> temps;
            for (std::size_t i = 0; i < d_; ++i) {
                temps.insert(temps.end(), counters_[i][j].begin(), counters_[i][j].end());
            }
            similarities.push_back(medianOfMeans(temps));
        }
        std::vector<std::size_t> indices(w_);
        for (std::size_t i = 0; i < w_; ++i) {
            indices[i] = i;
        }
        std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) {
            return similarities[a] > similarities[b];
        });
        if (topV > indices.size())
            topV = indices.size();
        indices.resize(topV);
        return indices;
    }

private:
    std::size_t d_;
    std::size_t w_;
    std::size_t r_;
    std::vector<std::vector<std::vector<int>>> counters_;
};

// -----------------------------------------------------------------------------
// Classe TUID : gestion d'ID et du Sketch
// -----------------------------------------------------------------------------
class TUID {
public:
    TUID() noexcept
        : lastID_(0), sketch_(4, 100, 5) {}

    int getNextID() noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        int next = ++lastID_;
        sketch_.update(next);
        return next;
    }

    void reset(int value = 0) noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        lastID_ = value;
    }

    std::vector<std::size_t> querySketch(const int& q, std::size_t topV = 5) const noexcept {
        return sketch_.query(q, topV);
    }

private:
    int lastID_;
    Sketch sketch_;
    mutable std::mutex mutex_;
};

// -----------------------------------------------------------------------------
// Structures pour les enregistrements et la table
// -----------------------------------------------------------------------------
struct Record {
    int id;  // ID auto-généré unique
    std::vector<std::string> data;  // Données pour les colonnes hors CleID et ID
};

struct Table {
    std::string name;                         // Nom de la table
    std::vector<std::string> columns;         // Liste des colonnes (les deux premières étant réservées)
    std::map<std::string, Record> records;    // Clé format "0001" -> enregistrement
};

// -----------------------------------------------------------------------------
// Fonctions Hex pour encoder/décoder les chaînes - https://stackoverflow.com/questions/3381614/c-convert-string-to-hexadecimal-and-vice-versa
// -----------------------------------------------------------------------------

std::string string_to_hex(const std::string& input)
{
    static const char hex_digits[] = "0123456789ABCDEF";

    std::string output;
    output.reserve(input.length() * 2);
    for (unsigned char c : input)
    {
        output.push_back(hex_digits[c >> 4]);
        output.push_back(hex_digits[c & 15]);
    }
    return output;
}

#include <stdexcept>

int hex_value(unsigned char hex_digit)
{
    static const signed char hex_values[256] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    };
    int value = hex_values[hex_digit];
    if (value == -1) throw std::invalid_argument("invalid hex digit");
    return value;
}

std::string hex_to_string(const std::string& input)
{
    const auto len = input.length();
    if (len & 1) throw std::invalid_argument("odd length");

    std::string output;
    output.reserve(len / 2);
    for (auto it = input.begin(); it != input.end(); )
    {
        int hi = hex_value(*it++);
        int lo = hex_value(*it++);
        output.push_back(hi << 4 | lo);
    }
    return output;
}

// -----------------------------------------------------------------------------
// Classe Database avec sauvegarde et chargement en Base64
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Classe Database avec sauvegarde et chargement en Base64 partiel
// -----------------------------------------------------------------------------
class Database {
public:
Database() : tableDir_("db_tables") {
initializeFolder();
loadExistingTables();
}



// Crée une nouvelle table et l'enregistre
std::optional<Table> createTable() {
    Table newTable;
    std::cout << "Enter the name of the new table: ";
    std::getline(std::cin, newTable.name);
    if (newTable.name.empty()) {
        throw std::runtime_error("The name of the table cannot be empty.");
    }
    if (tables_.find(newTable.name) != tables_.end()) {
        throw std::runtime_error("A table with the same name already exists.");
    }

    std::cout << "Enter the names of the columns (separated by commas): ";
    std::string columnsInput;
    std::getline(std::cin, columnsInput);
    // Colonnes réservées
    newTable.columns.push_back("KeyID");
    newTable.columns.push_back("ID");

    auto tokens = parseCSVLine(columnsInput, ',');
    for (const auto &col : tokens) {
        if (!col.empty())
            newTable.columns.push_back(col);
    }

    try {
        saveTable(newTable);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    tables_[newTable.name] = newTable;
    std::cout << "Table '" << newTable.name << "' created successfully." << std::endl;
    return newTable;
}

// Sélectionne une table existante via son nom
Table* selectTable() {
    if (tables_.empty()) {
        std::cout << "No table exists. Please create one first." << std::endl;
        return nullptr;
    }
    std::vector<std::string> tableNames;
    for (const auto &entry : tables_) {
        tableNames.push_back(entry.first);
    }
    size_t currentSelection = 0;
    while (true) {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        ConsoleColor::println("\n===== TABLE SELECTION =====", ConsoleColor::TextColor::YELLOW);
        for (size_t i = 0; i < tableNames.size(); ++i) {
            if (i == currentSelection) {
                ConsoleColor::println("> " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::println("  " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
        }
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (ch == 27) { // Escape
            return nullptr;
        } else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 72) {
                currentSelection = (currentSelection == 0) ? tableNames.size() - 1 : currentSelection - 1;
            } else if (arrow == 80) {
                currentSelection = (currentSelection + 1) % tableNames.size();
            }
        } else if (ch == 13) { // Enter
            const std::string& name = tableNames[currentSelection];
            auto it = tables_.find(name);
            if (it == tables_.end()) {
                std::cout << "Table not found." << std::endl;
                return nullptr;
            }
            try {
                loadTable(it->second);
            } catch (const std::exception &e) {
                throw std::runtime_error("Error while loading the table: " + std::string(e.what()));
            }
            return &it->second;
        }
    }
}

void displayOptions(const std::vector<std::string>& options, size_t currentSelection) {
    for (size_t i = 0; i < options.size(); ++i) {
        if (i == currentSelection) {
            ConsoleColor::println("> " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        } else {
            ConsoleColor::println("  " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        }
    }
}

bool handleInput(int ch, size_t& currentSelection, size_t optionsCount, Table& table) {
    if (ch == 27) { // Escape
        return true;
    } else if (ch == 224) {
        int arrow = _getch();
        if (arrow == 72) {
            currentSelection = (currentSelection == 0) ? optionsCount - 1 : currentSelection - 1;
        } else if (arrow == 80) {
            currentSelection = (currentSelection + 1) % optionsCount;
        }
    } else if (ch == 13) {
        switch (currentSelection) {
            case 0: addRecord(table); break;
            case 1: displayRecords(table); break;
            case 2: searchRecord(table); break;
            case 3: updateRecord(table); break;
            case 4: deleteRecord(table); break;
            case 5: return true;
        }
        ConsoleColor::println("\nPress any key to continue...");
        _getch();
    }
    return false;
}
void displayHelp(const std::string& option) {
    if (option == "Add a record") {
        ConsoleColor::println("This option allows you to add a new record to the selected table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Display all records") {
        ConsoleColor::println("This option displays all records in the selected table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Search for a record (by KeyID)") {
        ConsoleColor::println("This option lets you search for a record by entering its KeyID.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Edit a record") {
        ConsoleColor::println("This option allows you to modify an existing record after selecting it.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Delete a record") {
        ConsoleColor::println("This option permits you to delete an existing record from the table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Return to the main menu") {
        ConsoleColor::println("This option will take you back to the main menu.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else {
        ConsoleColor::println("No help available for this option.", ConsoleColor::TextColor::LIGHT_GRAY);
    }
}
// Menu interactif pour gérer les enregistrements
void recordMenu(Table& table) {
    const std::vector<std::string> options = {
        "Add a record",
        "Display all records",
        "Search for a record (by KeyID)",
        "Edit a record",
        "Delete a record",
        "Return to the main menu"
    };

    size_t currentSelection = 0;
    while (true) {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        ConsoleColor::println("\n===== RECORDS MENU =====", ConsoleColor::TextColor::YELLOW);
        displayOptions(options, currentSelection);
        // Afficher l'aide pour l'option actuellement survolée
        ConsoleColor::println("");
        displayHelp(options[currentSelection]);
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (handleInput(ch, currentSelection, options.size(), table)) {
            break;
        }
    }
}

// -------------------------------------------------------------------------
// Fonctions de gestion des enregistrements
// -------------------------------------------------------------------------
std::string getInputForColumn(const std::string& columnName) {
    std::string input;
    std::cout << "Enter the value for '" << columnName << "' : ";
    std::getline(std::cin, input);
    if (input.find_first_not_of(" \t\n\r") == std::string::npos) {
        throw std::runtime_error("The value for '" + columnName + "' cannot be empty.");
    }
    return input;
}

void addRecord(Table& table) {
    Record newRecord;
    newRecord.id = uidGenerator_.getNextID();
    const std::string key = generateKeyID(newRecord.id);
    // Saisie pour chaque colonne (à partir de la 3ème colonne)
    for (std::size_t i = 2; i < table.columns.size(); ++i) {
        newRecord.data.push_back(getInputForColumn(table.columns[i]));
    }
    if (table.records.find(key) != table.records.end()) {
        throw std::runtime_error("Error: a record with this ID already exists.");
    }
    table.records[key] = std::move(newRecord);
    saveTable(table);
    std::cout << "Record added successfully. Generated key: " << key << std::endl;
}

void displayRecords(const Table& table) const noexcept {
    if (table.records.empty()) {
        std::cout << "No records exist in this table." << std::endl;
        return;
    }
   
    std::vector<std::pair<std::string, Record>> recVector(table.records.begin(), table.records.end());
    std::size_t currentIndex = 0;
    auto displayRecordAt = [&]() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
        const auto& key = recVector[currentIndex].first;
        const Record& rec = recVector[currentIndex].second;
        const int labelWidth = 12;
        const int fieldWidth = 30;
        const int totalWidth = 2 + labelWidth + fieldWidth + 2;
       
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        std::cout << "\n" << std::string(totalWidth, '=') << std::endl;
        std::cout << std::left << std::setw(totalWidth)
                  << "  Record (" + std::to_string(currentIndex + 1) + " of " + std::to_string(recVector.size()) + ")  "
                  << std::endl;
        std::cout << std::string(totalWidth, '=') << std::endl;
        ConsoleColor::reset();
       
        std::string borderLine = "+" + std::string(totalWidth - 2, '-') + "+";
        auto printField = [&](const std::string &label, const auto &value) {
            std::ostringstream oss;
            oss << value;
            std::cout << "| " << std::left << std::setw(labelWidth) << label
                      << " : " << std::left << std::setw(fieldWidth) << oss.str() << " |" << std::endl;
        };
       
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        printField("KEY", key);
        printField("ID", rec.id);
        ConsoleColor::reset();
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            std::string columnTitle = (i + 2 < table.columns.size())
                                      ? table.columns[i + 2]
                                      : ("Field" + std::to_string(i));
            printField(columnTitle, rec.data[i]);
        }
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
        std::cout << "\nUse left arrow and right arrow to navigate, or type the letter q to quit." << std::endl;
        ConsoleColor::reset();
    };

    // Afficher le premier record
    displayRecordAt();
    bool quit = false;
    while (!quit) {
    #if defined(_WIN32)
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 75) {
                if (currentIndex > 0) {
                    --currentIndex;
                    displayRecordAt();
                }
            }
            else if (arrow == 77) {
                if (currentIndex + 1 < recVector.size()) {
                    ++currentIndex;
                    displayRecordAt();
                }
            }
        }
    #else
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 27) {
            int ch1 = _getch();
            int ch2 = _getch();
            if(ch1 == 91) {
                if(ch2 == 68) {
                    if (currentIndex > 0) {
                        --currentIndex;
                        displayRecordAt();
                    }
                }
                else if(ch2 == 67) {
                    if (currentIndex + 1 < recVector.size()) {
                        ++currentIndex;
                        displayRecordAt();
                    }
                }
            }
        }
    #endif
    }
}

static void toClipboard(HWND hwnd, const std::string &s) {
        if (!OpenClipboard(hwnd)) {
            return;
        }
        EmptyClipboard();
        HGLOBAL hg = GlobalAlloc(GMEM_MOVEABLE, s.size() + 1);
        if (!hg) {
            CloseClipboard();
            return;
        }
        char* dest = static_cast<char*>(GlobalLock(hg));
        memcpy(dest, s.c_str(), s.size() + 1);
        GlobalUnlock(hg);
        SetClipboardData(CF_TEXT, hg);
        CloseClipboard();
        GlobalFree(hg);
    }

void searchRecord(const Table& table) /* const */ {
    std::cout << "Enter the key of the record to search for: ";
    std::string key;
    std::getline(std::cin, key);

    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    const Record& rec = it->second;

    // Affichage du message dans la console pour le record trouvé
    ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
    std::cout << "\n=== Record found ===" << std::endl;
    ConsoleColor::reset();

    const int labelWidth = 10;
    std::cout << std::left << std::setw(labelWidth) << "KEY:" << key << std::endl;
    std::cout << std::left << std::setw(labelWidth) << "ID:"  << rec.id << std::endl;

    // Création d'une chaîne pour copier dans le presse-papier avec des retours chariot Windows ("\r\n")
    std::ostringstream clipboardStream;
    clipboardStream << "KEY: " << key << "\r\n";
    clipboardStream << "ID: " << rec.id << "\r\n";

    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        std::string columnTitle = (i + 2 < table.columns.size())
                                  ? table.columns[i + 2]
                                  : "Field" + std::to_string(i);
        std::cout << std::left << std::setw(labelWidth) << (columnTitle + ":")
                  << rec.data[i] << std::endl;
        clipboardStream << columnTitle << ": " << rec.data[i] << "\r\n";
    }
    std::cout << "\n";

    // Récupération du handle de la fenêtre console
    HWND hwnd = GetConsoleWindow();
    std::string clipboardData = clipboardStream.str();
    toClipboard(hwnd, clipboardData);

    // Affichage du message en anglais en utilisant ConsoleColor::setColor
    // (par exemple, texte jaune sur fond noir)
    ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
    std::cout << "The search result has been copied to the clipboard." << std::endl;
    ConsoleColor::reset();
}

void updateRecord(Table& table) {
    std::cout << "Enter the key of the record to modify: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    Record& rec = it->second;
    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        std::cout << "Current value for '" << table.columns[i + 2] << "' : " << rec.data[i] << std::endl;
        std::cout << "Enter the new value (leave empty to keep) : ";
        std::string newValue;
        std::getline(std::cin, newValue);
        if (!newValue.empty()) {
            rec.data[i] = newValue;
        }
    }
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record updated successfully." << std::endl;
}

void deleteRecord(Table& table) {
    std::cout << "Enter the key of the record to delete: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    table.records.erase(it);
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record deleted successfully." << std::endl;
}

private:
const std::string tableDir_;
std::map<std::string, Table> tables_;
TUID uidGenerator_;



// Initialise le dossier de sauvegarde
void initializeFolder() {
    try {
        if (!fs::exists(tableDir_)) {
            fs::create_directory(tableDir_);
        }
    } catch (const fs::filesystem_error &e) {
        throw std::runtime_error("Error while creating the folder: " + std::string(e.what()));
    }
}

// Génère une clé au format "0001"
std::string generateKeyID(int id) const {
    std::ostringstream oss;
    oss << std::setw(4) << std::setfill('0') << id;
    return oss.str();
}

// Sauvegarde d'une table dans un fichier CSV.
// Ici, l'en-tête est en clair;
// pour chaque enregistrement, la clé et l'ID sont écrits en clair,
// puis chaque donnée (à partir de la 3ème colonne) est encodée en Base64.
void saveTable(const Table &table) const {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ofstream file(path, std::ios::out);
    if (!file.is_open()) {
        throw std::runtime_error("Error while opening the file " + path + " for writing.");
    }

    // Écriture de l'en-tête (les colonnes) en clair
    {
        std::ostringstream oss;
        for (std::size_t i = 0; i < table.columns.size(); ++i) {
            oss << table.columns[i];
            if (i + 1 < table.columns.size()) {
                oss << ";";
            }
        }
        file << oss.str() << "\n";
    }

    // Écriture des enregistrements
    for (const auto& [key, rec] : table.records) {
        std::ostringstream line;
        // La clé et l'ID en clair
        line << key << ";" << rec.id;
        // Pour chaque donnée, on encode en Hexadecimal (via string_to_hex)
        for (const auto& d : rec.data) {
            // Passage direct de la chaîne, sans reinterpret_cast
            std::string encodedData = string_to_hex(d);
            line << ";" << encodedData;
        }
        file << line.str() << "\n";
    }
    file.close();
    if (file.fail()) {
        throw std::runtime_error("Error while writing to the file " + path);
    }
}

// Charge une table depuis un fichier CSV.
// Les colonnes (en-tête) sont lues en clair.
// Pour chaque enregistrement, la clé et l'ID sont lus en clair,
// et chaque donnée (à partir de la 3ème colonne) est décodée de Base64.
void loadTable(Table &table) {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ifstream file(path);
    if (!file.is_open()) {
       throw std::runtime_error("File " + path + " not found.");
    }
    table.records.clear();
    std::string line;
    bool firstLine = true;
    while (std::getline(file, line)) {
        if (line.empty())
            continue;
        if (firstLine) {
            table.columns = parseCSVLine(line, ';'); // Lire l'en-tête en clair
            firstLine = false;
            continue;
        }
        auto tokens = parseCSVLine(line, ';');
        if (tokens.size() < 2)
            continue;
        Record rec;
        const std::string key = tokens[0]; // Clé en clair
        try {
            rec.id = std::stoi(tokens[1]); // ID en clair
        } catch (const std::exception&) {
            std::cerr << "Error converting the ID for the key " << key << std::endl;
            continue;
        }
        // Pour chaque donnée, décodage depuis Hexadecimal
        for (std::size_t i = 2; i < tokens.size(); ++i) {
            rec.data.push_back(hex_to_string(tokens[i]));
        }
        table.records[key] = std::move(rec);
    }
    file.close();

    // Mise à jour du TUID avec le maximum des IDs
    int maxID = 0;
    for (const auto &entry : table.records) {
        maxID = std::max(maxID, entry.second.id);
    }
    uidGenerator_.reset(maxID);
}

// Charge toutes les tables existantes dans le dossier
void loadExistingTables() {
    try {
        if (!fs::exists(tableDir_))
            return;
        for (const auto &entry : fs::directory_iterator(tableDir_)) {
            if (entry.is_regular_file() && entry.path().extension() == ".csv") {
                Table table;
                table.name = entry.path().stem().string();
                loadTable(table);
                tables_[table.name] = std::move(table);
            }
        }
    } catch (const fs::filesystem_error &e) {
        std::cerr << "Error while reading the directory: " << e.what() << std::endl;
    }
}

};

// -----------------------------------------------------------------------------
// Classe MenuManager
// -----------------------------------------------------------------------------
class MenuManager {
public:
    MenuManager(std::unique_ptr<IUserInterface> ui)
        : userInterface_(std::move(ui)) {}

    void run() {
    bool exitFlag = false;
    while (!exitFlag) {
        try {
            Menu mainMenu("Main Menu", 80, 24);
            mainMenu.addOption(MenuOption("Manage the database", [this]() { runDatabaseMenu(); }));
            mainMenu.addOption(MenuOption("Quit", []() {
                ConsoleColor::println("Goodbye!", ConsoleColor::TextColor::RED);
                std::exit(EXIT_SUCCESS);
            }));

            clearScreen();

            // Définition de la largeur totale pour le menu (style QBasic)
            const int totalWidth = 50;
            const std::string title = " Main Menu ";
            const std::string borderLine(totalWidth, '=');

            // Affichage du titre du menu avec fond coloré
            ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            std::cout << "\n" << borderLine << std::endl;
            std::cout << std::left << std::setw(totalWidth)
                      << "  " + title + "  " << std::endl;
            std::cout << borderLine << std::endl;
            ConsoleColor::reset();

            // Affichage des instructions pour l'utilisateur dans le style QBasic vintage
            ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
            std::cout << "\nUse the arrow keys to navigate and press Enter or the right arrow to select an option." << std::endl;
            std::cout << "To exit the program, choose the 'Quit' option from the menu." << std::endl;
            ConsoleColor::reset();

            // Affichage des options du menu dans un encadrement similaire aux anciennes fenêtres QBasic
            for (const auto& option : mainMenu.getOptions()) {
                ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
                std::cout << "| " << std::setw(totalWidth - 4) << std::left
                          << option.getTitle() << " |" << std::endl;
            }
            // Réaffichage d'une bordure de fin pour la cohérence visuelle
            ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            std::cout << borderLine << std::endl;
            ConsoleColor::reset();

            runMenu(mainMenu);
            exitFlag = true;
        }
        catch (const std::runtime_error &e) {
            if (std::string(e.what()) != "Return")
                throw;
        }
    }
}

private:
    std::unique_ptr<IUserInterface> userInterface_;

    void clearScreen() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
    }

    void runMenu(Menu &menu) {
        while (true) {
            clearScreen();
            menu.displayMenu(userInterface_.get());
            char input = userInterface_->getInput();

            if (input == -32) {
                char direction = userInterface_->getInput();
                if (direction == 72) {
                    menu.navigateUp();
                    continue;
                }
                else if (direction == 80) {
                    menu.navigateDown();
                    continue;
                }
                else if (direction == 77) {
                    menu.select();
                    continue;
                }
                else if (direction == 75) {
                    throw std::runtime_error("Return");
                }
            }
           
            if (input == 27)
                break;

            try {
                menu.navigate(input);
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) == "Return")
                    return;
                else {
                    ConsoleColor::println("Error: " + std::string(e.what()),
                                            ConsoleColor::TextColor::RED,
                                            ConsoleColor::BackgroundColor::BG_BLACK);
                    std::cout << "Press any key to try again...";
                    userInterface_->getInput();
                }
            }
        }
    }

    void runDatabaseMenu() {
        Database db;
        while (true) {
            clearScreen();
            Menu dbMenu("Database Menu", 80, 24);
            dbMenu.addOption(MenuOption("Create a new table", [&db]() {
                try {
                    auto newTableOpt = db.createTable();
                    if (newTableOpt)
                        db.recordMenu( db.selectTable() ? *db.selectTable() : newTableOpt.value() );
                }
                catch (const std::exception &e) {
                    std::cerr << e.what() << std::endl;
                    system("pause");
                }
            }));
            dbMenu.addOption(MenuOption("Use an existing table", [&db]() {
                Table* table = db.selectTable();
                if (table)
                    db.recordMenu(*table);
            }));
            dbMenu.addOption(MenuOption("Back", []() {
                // Retour simple
            }));

            try {
                runMenu(dbMenu);
                break;
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Return")
                    throw;
                else
                    break;
            }
        }
    }
};

// -----------------------------------------------------------------------------
// Fonction main
// -----------------------------------------------------------------------------
int main() {
    try {
        auto ui = std::make_unique<ConsoleUserInterface>();
        MenuManager manager(std::move(ui));
        manager.run();

        // Exemple d'utilisation directe de la base de données
        Database db;
        auto tableOpt = db.createTable();
        if (tableOpt) {
            Table* table = db.selectTable();
            if (table) {
                db.addRecord(*table);
                db.displayRecords(*table);

                TUID generator;
                int nextID = generator.getNextID();
                std::cout << "\nNext generated ID : " << nextID << "\n";
                auto indices = generator.querySketch(nextID, 3);
                std::cout << "Indices returned by querySketch : ";
                for (const auto idx : indices) {
                    std::cout << idx << " ";
                }
                std::cout << "\nPress any key to close...";
                (void)getchar();
            }
        }
    } catch (const std::exception &ex) {
        std::cerr << "Exception caught in main : " << ex.what() << "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
Thank you for your feedback. I'm excited to announce that the new version now features an enhanced search functionality. By tokenizing and normalizing search terms and using fuzzy matching with the Levenshtein distance, the system now finds records even with minor typos or partial matches. Results are neatly displayed, and the top match is automatically copied to your clipboard for convenience. Enjoy the update!

C++:
/*
MIT License
Copyright (c) 2025 CoTon_TiGe_MoUaRf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <mutex>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <vector>
#include <memory>
#include <cmath>
#include <queue>
#include <set>
#include <conio.h> // pour _getch() sur Windows

#ifdef _WIN32
    #include <windows.h>
#else
    #include <unistd.h>
#endif

namespace fs = std::filesystem;

// -----------------------------------------------------------------------------
// Gestion des couleurs de la console
// -----------------------------------------------------------------------------
class ConsoleColor {
public:
    enum class TextColor : int {
        BLACK = 0,
        DARK_BLUE = 1,
        DARK_GREEN = 2,
        LIGHT_BLUE = 3,
        DARK_RED = 4,
        MAGENTA = 5,
        ORANGE = 6,
        LIGHT_GRAY = 7,
        GRAY = 8,
        BLUE = 9,
        GREEN = 10,
        CYAN = 11,
        RED = 12,
        PINK = 13,
        YELLOW = 14,
        WHITE = 15
    };

    enum class BackgroundColor : int {
        BG_BLACK = 0,
        BG_DARK_BLUE = 1,
        BG_DARK_GREEN = 2,
        BG_LIGHT_BLUE = 3,
        BG_DARK_RED = 4,
        BG_MAGENTA = 5,
        BG_ORANGE = 6,
        BG_LIGHT_GRAY = 7,
        BG_GRAY = 8,
        BG_BLUE = 9,
        BG_GREEN = 10,
        BG_CYAN = 11,
        BG_RED = 12,
        BG_PINK = 13,
        BG_YELLOW = 14,
        BG_WHITE = 15
    };

    static void print(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
        std::cout << message;
        SetConsoleTextAttribute(handle, static_cast<int>(TextColor::WHITE));
    #else
        std::cout << message;
    #endif
    }

    static void println(std::string_view message, TextColor textColor = TextColor::WHITE, BackgroundColor bgColor = BackgroundColor::BG_BLACK) {
        print(std::string(message) + "\n", textColor, bgColor);
    }
    
    static void setColor(TextColor textColor, BackgroundColor bgColor) {
    #if defined(_WIN32)
        HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(handle, (static_cast<int>(bgColor) << 4) | static_cast<int>(textColor));
    #endif
    }

    static void reset() {
        setColor(TextColor::WHITE, BackgroundColor::BG_BLACK);
    }
};

// -----------------------------------------------------------------------------
// Interface et classes pour le menu interactif
// -----------------------------------------------------------------------------
class IUserInterface {
public:
    virtual void display(std::string_view message) = 0;
    virtual char getInput() = 0;
    virtual ~IUserInterface() = default;
};

class ConsoleUserInterface : public IUserInterface {
public:
    void display(std::string_view message) override {
        displayMessage(std::string(message), ConsoleColor::TextColor::WHITE);
    }
    char getInput() override {
        return _getch();
    }
private:
    void displayMessage(const std::string& message, ConsoleColor::TextColor textColor = ConsoleColor::TextColor::WHITE) {
        const std::string border = std::string(message.size() + 4, '*');
        ConsoleColor::println(border, textColor);
        ConsoleColor::println("* " + message + " *", textColor);
        ConsoleColor::println(border, textColor);
    }
};

class MenuOption {
public:
    MenuOption(std::string title, std::function<void()> action)
        : title_(std::move(title)), action_(std::move(action)) {}

    void select() {
        action_();
    }

    std::string getTitle() const {
        return title_;
    }

private:
    std::string title_;
    std::function<void()> action_;
};

class Menu {
public:
    Menu(std::string title, int width, int height)
        : title_(std::move(title)), width_(width), height_(height) {}
    void addOption(const MenuOption& option) {
        options_.push_back(option);
    }
    void displayMenu(IUserInterface* ui) {
        ConsoleColor::println("=== " + title_ + " ===", ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        for (std::size_t i = 0; i < options_.size(); ++i) {
            if (currentSelection_ == i) {
                ConsoleColor::print("> " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::print("  " + options_[i].getTitle(), ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
            std::cout << std::endl;
        }
        ConsoleColor::println("====================", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        ConsoleColor::println("Up/Down arrows to navigate, Left arrow to go back, Right arrow or Enter to select, Escape to exit.", ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
    }

    void navigate(char input) {
        if (input == -32) { // touche spéciale
            char direction = _getch();
            if (direction == 72) { // flèche haut
                currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
            } else if (direction == 80) { // flèche bas
                currentSelection_ = (currentSelection_ + 1) % options_.size();
            } else if (direction == 75) { // flèche gauche
                throw std::runtime_error("Return");
            } else if (direction == 77) { // flèche droite
                select();
            }
        } else if (input == 13) { // touche Entrée
            select();
        }
    }

    void select() {
        options_[currentSelection_].select();
    }
    
    void navigateUp() {
        currentSelection_ = (currentSelection_ + options_.size() - 1) % options_.size();
    }

    void navigateDown() {
        currentSelection_ = (currentSelection_ + 1) % options_.size();
    }

    const std::vector<MenuOption>& getOptions() const {
        return options_;
    }

private:
    std::string title_;
    std::vector<MenuOption> options_;
    std::size_t currentSelection_ = 0;
    int width_;
    int height_;
};

// -----------------------------------------------------------------------------
// Utilitaires CSV et hachage
// -----------------------------------------------------------------------------
inline std::string trim(std::string_view s) noexcept {
    const auto begin = s.find_first_not_of(" \t\n\r");
    if (begin == std::string_view::npos)
        return "";
    const auto end = s.find_last_not_of(" \t\n\r");
    return std::string(s.substr(begin, end - begin + 1));
}

inline std::vector<std::string> parseCSVLine(const std::string& line, char delimiter = ';') noexcept {
    std::vector<std::string> tokens;
    std::istringstream ss(line);
    std::string token;
    while (std::getline(ss, token, delimiter)) {
        tokens.push_back(trim(token));
    }
    return tokens;
}

/// Permet de réaliser un hachage 2-universel simple.
class Hasher {
public:
    template <typename T>
    static std::size_t hash(std::size_t seed, const T& val) noexcept {
        std::hash<T> hasher;
        return hasher(val) ^ (seed * 0x9e3779b97f4a7c15ULL);
    }
};

// -----------------------------------------------------------------------------
// Classe Sketch (Count-Min Sketch avec logique de médiane)
// -----------------------------------------------------------------------------
class Sketch {
public:
    Sketch(std::size_t d, std::size_t w, std::size_t r) noexcept
        : d_(d), w_(w), r_(r),
          counters_(d, std::vector<std::vector<int>>(w, std::vector<int>(r, 0))) {}

    template <typename T>
    void update(const T& x) noexcept {
        for (std::size_t o = 0; o < r_; ++o) {
            for (std::size_t i = 0; i < d_; ++i) {
                std::size_t j = Hasher::hash(i, x) % w_;
                ++counters_[i][j][o];
            }
        }
    }

    int medianOfMeans(const std::vector<int>& values) const noexcept {
        std::vector<int> temp = values;
        std::sort(temp.begin(), temp.end());
        return temp[temp.size() / 2];
    }

    template <typename T>
    std::vector<std::size_t> query(const T& q, std::size_t topV) const noexcept {
        std::vector<int> similarities;
        similarities.reserve(w_);
        for (std::size_t j = 0; j < w_; ++j) {
            std::vector<int> temps;
            for (std::size_t i = 0; i < d_; ++i) {
                temps.insert(temps.end(), counters_[i][j].begin(), counters_[i][j].end());
            }
            similarities.push_back(medianOfMeans(temps));
        }
        std::vector<std::size_t> indices(w_);
        for (std::size_t i = 0; i < w_; ++i) {
            indices[i] = i;
        }
        std::sort(indices.begin(), indices.end(), [&](std::size_t a, std::size_t b) {
            return similarities[a] > similarities[b];
        });
        if (topV > indices.size())
            topV = indices.size();
        indices.resize(topV);
        return indices;
    }

private:
    std::size_t d_;
    std::size_t w_;
    std::size_t r_;
    std::vector<std::vector<std::vector<int>>> counters_;
};

// -----------------------------------------------------------------------------
// Classe TUID : gestion d'ID et du Sketch
// -----------------------------------------------------------------------------
class TUID {
public:
    TUID() noexcept
        : lastID_(0), sketch_(4, 100, 5) {}

    int getNextID() noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        int next = ++lastID_;
        sketch_.update(next);
        return next;
    }

    void reset(int value = 0) noexcept {
        std::lock_guard<std::mutex> lock(mutex_);
        lastID_ = value;
    }

    std::vector<std::size_t> querySketch(const int& q, std::size_t topV = 5) const noexcept {
        return sketch_.query(q, topV);
    }

private:
    int lastID_;
    Sketch sketch_;
    mutable std::mutex mutex_;
};

// -----------------------------------------------------------------------------
// Structures pour les enregistrements et la table
// -----------------------------------------------------------------------------
struct Record {
    int id;  // ID auto-généré unique
    std::vector<std::string> data;  // Données pour les colonnes hors CleID et ID
};

struct Table {
    std::string name;                         // Nom de la table
    std::vector<std::string> columns;         // Liste des colonnes (les deux premières étant réservées)
    std::map<std::string, Record> records;    // Clé format "0001" -> enregistrement
};

// -----------------------------------------------------------------------------
// Fonctions Base64 pour encoder/décoder les chaînes
// -----------------------------------------------------------------------------

std::string string_to_hex(const std::string& input)
{
    static const char hex_digits[] = "0123456789ABCDEF";

    std::string output;
    output.reserve(input.length() * 2);
    for (unsigned char c : input)
    {
        output.push_back(hex_digits[c >> 4]);
        output.push_back(hex_digits[c & 15]);
    }
    return output;
}

#include <stdexcept>

int hex_value(unsigned char hex_digit)
{
    static const signed char hex_values[256] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    };
    int value = hex_values[hex_digit];
    if (value == -1) throw std::invalid_argument("invalid hex digit");
    return value;
}

std::string hex_to_string(const std::string& input)
{
    const auto len = input.length();
    if (len & 1) throw std::invalid_argument("odd length");

    std::string output;
    output.reserve(len / 2);
    for (auto it = input.begin(); it != input.end(); )
    {
        int hi = hex_value(*it++);
        int lo = hex_value(*it++);
        output.push_back(hi << 4 | lo);
    }
    return output;
}

// ----------------------------------------------------------
// Fonctions utilitaires pour la recherche améliorée et Levenshtein

// Fonction utilitaire pour arrondir (alternative à std::round)
inline int round_to_int(double x) {
    return static_cast<int>(x + (x >= 0 ? 0.5 : -0.5));
}

// Exemple de fonction de distance entre deux touches basée sur un clavier QWERTY simplifié.
int getKeyboardDist(char a, char b) {
auto getCoord = [](char c) -> std::pair<int, int> {
c = std::toupper(static_cast<unsigned char>(c));
std::string line1 = "QWERTYUIOP";
std::string line2 = "ASDFGHJKL";
std::string line3 = "ZXCVBNM";
size_t pos = line1.find(c);
if (pos != std::string::npos)
return { static_cast<int>(pos), 0 };
pos = line2.find(c);
if (pos != std::string::npos)
return { static_cast<int>(pos), 1 };
pos = line3.find(c);
if (pos != std::string::npos)
return { static_cast<int>(pos), 2 };
return { 0, 0 };
};



auto p1 = getCoord(a);
auto p2 = getCoord(b);
double dx = static_cast<double>(p1.first - p2.first);
double dy = static_cast<double>(p1.second - p2.second);
 return round_to_int(std::sqrt(dx * dx + dy * dy));

}

// Coût d'insertion pouvant être amélioré.
int getInsertionCost(size_t j, const std::string &s) {
    // Coût de base pour une insertion.
    const int baseCost = 1;
    // Dans cet exemple, nous renvoyons toujours le coût de base.
    // Vous pouvez améliorer cette fonction en vous basant sur le contexte (par exemple, voisinnage)
    return baseCost;
}

// Structures et fonctions pour l'arbre rouge-noir (simplifié)
struct rb_node {
rb_node *rb_left = nullptr;
rb_node *rb_right = nullptr;
};

struct rb_root {
rb_node *rb_node = nullptr;
};

struct kdata {
char k1;
char k2;
int cost;
rb_node node; // Le membre node permet d'insérer kdata dans l'arbre
};

void rbinsert(struct rb_root *root, struct kdata *kd) {
    struct rb_node **p = &root->rb_node;
    struct rb_node *parent = nullptr;

    while (*p) {
        struct kdata *d = reinterpret_cast<struct kdata*>(*p);
        parent = *p;
        if (kd->k1 > d->k1) {
            p = &((*p)->rb_right);
        } else {
            p = &((*p)->rb_left);
        }
    }
    // Insérer le nœud
    // Ajoutez ici le code pour lier le nœud et équilibrer l'arbre
}

int rbfind(struct rb_root *root, char c1, char c2) {
    struct rb_node *n = root->rb_node;
    while (n) {
        struct kdata *d = reinterpret_cast<struct kdata*>(n);
        if (c1 > d->k1) {
            n = n->rb_right;
        } else if (c1 < d->k1) {
            n = n->rb_left;
        } else {
            // Retourner le coût de substitution
            return d->cost; // Assurez-vous que d->cost est un int
        }
    }
    return 1; // Coût par défaut si non trouvé
}

void initializeSubstitutionCosts(rb_root *root) {
for (char c1 = 'a'; c1 <= 'z'; ++c1) {
for (char c2 = 'a'; c2 <= 'z'; ++c2) {
kdata *kd = new kdata;
kd->k1 = c1;
kd->k2 = c2;
kd->cost = (c1 == c2) ? 0 : 1;
rbinsert(root, kd);
}
}
// Note : Dans une version complète, il faudra prévoir la libération de ces allocations.
}

int LevenshteinDistance(const std::string &s1, const std::string &s2) {
const size_t len1 = s1.size();
const size_t len2 = s2.size();



if (len1 == 0) return static_cast<int>(len2);
if (len2 == 0) return static_cast<int>(len1);

std::vector<int> previous(len2 + 1), current(len2 + 1);

for (size_t j = 0; j <= len2; ++j) {
    previous[j] = j;
}

rb_root substitutionCosts = { nullptr };
initializeSubstitutionCosts(&substitutionCosts);

for (size_t i = 1; i <= len1; ++i) {
    current[0] = i;
    for (size_t j = 1; j <= len2; ++j) {
        int substitutionCost = (s1[i - 1] == s2[j - 1])
                                 ? 0
                                 : rbfind(&substitutionCosts, s1[i - 1], s2[j - 1]);
        int deletionCost = 1;
        int insertionCost = getInsertionCost(j - 1, s2);
        current[j] = std::min({ previous[j] + deletionCost,
                                 current[j - 1] + insertionCost,
                                 previous[j - 1] + substitutionCost });
    }
    previous.swap(current);
}
// Dans une version complète, libérez l'arbre substitutionCosts.
return previous[len2];

}

std::string normalize(const std::string &str) {
    std::string normalized;
    normalized.reserve(str.size());

    for (char c : str) {
        if (!std::isspace(static_cast<unsigned char>(c))) {
            normalized.push_back(std::tolower(static_cast<unsigned char>(c)));
        }
    }
    return normalized;
}

std::vector<std::string> tokenize(const std::string &str) {
    std::vector<std::string> tokens;
    std::istringstream stream(str);
    std::string token;

    while (stream >> token) {
        tokens.push_back(normalize(token));
    }
    return tokens;
}

// ----------------------------------------------------------
// Classe Database

class Database {
public:
Database() : tableDir_("db_tables") {
initializeFolder();
loadExistingTables();
}



// Crée une nouvelle table et l'enregistre
std::optional<Table> createTable() {
    Table newTable;
    std::cout << "Enter the name of the new table: ";
    std::getline(std::cin, newTable.name);
    if (newTable.name.empty()) {
        throw std::runtime_error("The name of the table cannot be empty.");
    }
    if (tables_.find(newTable.name) != tables_.end()) {
        throw std::runtime_error("A table with the same name already exists.");
    }

    std::cout << "Enter the names of the columns (separated by commas): ";
    std::string columnsInput;
    std::getline(std::cin, columnsInput);
    // Colonnes réservées
    newTable.columns.push_back("KeyID");
    newTable.columns.push_back("ID");

    auto tokens = parseCSVLine(columnsInput, ',');
    for (const auto &col : tokens) {
        if (!col.empty())
            newTable.columns.push_back(col);
    }

    try {
        saveTable(newTable);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    tables_[newTable.name] = newTable;
    std::cout << "Table '" << newTable.name << "' created successfully." << std::endl;
    return newTable;
}

// Sélectionne une table existante via son nom
Table* selectTable() {
    if (tables_.empty()) {
        std::cout << "No table exists. Please create one first." << std::endl;
        return nullptr;
    }
    std::vector<std::string> tableNames;
    for (const auto &entry : tables_) {
        tableNames.push_back(entry.first);
    }
    size_t currentSelection = 0;
    while (true) {
#if defined(_WIN32)
        system("cls");
#else
        system("clear");
#endif
        ConsoleColor::println("\n===== TABLE SELECTION =====", ConsoleColor::TextColor::YELLOW);
        for (size_t i = 0; i < tableNames.size(); ++i) {
            if (i == currentSelection) {
                ConsoleColor::println("> " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            } else {
                ConsoleColor::println("  " + tableNames[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
            }
        }
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (ch == 27) { // Escape
            return nullptr;
        } else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 72) {
                currentSelection = (currentSelection == 0) ? tableNames.size() - 1 : currentSelection - 1;
            } else if (arrow == 80) {
                currentSelection = (currentSelection + 1) % tableNames.size();
            }
        } else if (ch == 13) { // Enter
            const std::string& name = tableNames[currentSelection];
            auto it = tables_.find(name);
            if (it == tables_.end()) {
                std::cout << "Table not found." << std::endl;
                return nullptr;
            }
            try {
                loadTable(it->second);
            } catch (const std::exception &e) {
                throw std::runtime_error("Error while loading the table: " + std::string(e.what()));
            }
            return &it->second;
        }
    }
}

void displayOptions(const std::vector<std::string>& options, size_t currentSelection) {
    for (size_t i = 0; i < options.size(); ++i) {
        if (i == currentSelection) {
            ConsoleColor::println("> " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        } else {
            ConsoleColor::println("  " + options[i], ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLACK);
        }
    }
}

bool handleInput(int ch, size_t& currentSelection, size_t optionsCount, Table& table) {
    if (ch == 27) { // Escape
        return true;
    } else if (ch == 224) {
        int arrow = _getch();
        if (arrow == 72) {
            currentSelection = (currentSelection == 0) ? optionsCount - 1 : currentSelection - 1;
        } else if (arrow == 80) {
            currentSelection = (currentSelection + 1) % optionsCount;
        }
    } else if (ch == 13) {
        switch (currentSelection) {
            case 0: addRecord(table); break;
            case 1: displayRecords(table); break;
            case 2: searchRecord(table); break;
            case 3: updateRecord(table); break;
            case 4: deleteRecord(table); break;
            case 5: return true;
        }
        ConsoleColor::println("\nPress any key to continue...");
        _getch();
    }
    return false;
}

void displayHelp(const std::string& option) {
    if (option == "Add a record") {
        ConsoleColor::println("This option allows you to add a new record to the selected table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Display all records") {
        ConsoleColor::println("This option displays all records in the selected table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Search for a record (by KeyID)") {
        ConsoleColor::println("This option lets you search for a record by entering its KeyID or a keyword.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Edit a record") {
        ConsoleColor::println("This option allows you to modify an existing record after selecting it.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Delete a record") {
        ConsoleColor::println("This option permits you to delete an existing record from the table.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else if (option == "Return to the main menu") {
        ConsoleColor::println("This option will take you back to the main menu.", ConsoleColor::TextColor::LIGHT_GRAY);
    } else {
        ConsoleColor::println("No help available for this option.", ConsoleColor::TextColor::LIGHT_GRAY);
    }
}

// Menu interactif pour gérer les enregistrements
void recordMenu(Table& table) {
    const std::vector<std::string> options = {
        "Add a record",
        "Display all records",
        "Search for a record (by KeyID)",
        "Edit a record",
        "Delete a record",
        "Return to the main menu"
    };

    size_t currentSelection = 0;
    while (true) {
#if defined(_WIN32)
        system("cls");
#else
        system("clear");
#endif
        ConsoleColor::println("\n===== RECORDS MENU =====", ConsoleColor::TextColor::YELLOW);
        displayOptions(options, currentSelection);
        // Afficher l'aide pour l'option actuellement survolée
        std::cout << std::endl;
        displayHelp(options[currentSelection]);
        ConsoleColor::println("\nUse the up and down arrows to navigate, press Enter to select, and Escape to go back.", ConsoleColor::TextColor::GRAY);
        int ch = _getch();
        if (handleInput(ch, currentSelection, options.size(), table)) {
            break;
        }
    }
}

// -------------------------------------------------------------------------
// Fonctions de gestion des enregistrements
// -------------------------------------------------------------------------
std::string getInputForColumn(const std::string& columnName) {
    std::string input;
    std::cout << "Enter the value for '" << columnName << "' : ";
    std::getline(std::cin, input);
    if (input.find_first_not_of(" \t\n\r") == std::string::npos) {
        throw std::runtime_error("The value for '" + columnName + "' cannot be empty.");
    }
    return input;
}

void addRecord(Table& table) {
    Record newRecord;
    newRecord.id = uidGenerator_.getNextID();
    const std::string key = generateKeyID(newRecord.id);
    // Saisie pour chaque colonne (à partir de la 3ème colonne)
    for (std::size_t i = 2; i < table.columns.size(); ++i) {
        newRecord.data.push_back(getInputForColumn(table.columns[i]));
    }
    if (table.records.find(key) != table.records.end()) {
        throw std::runtime_error("Error: a record with this ID already exists.");
    }
    table.records[key] = std::move(newRecord);
    saveTable(table);
    std::cout << "Record added successfully. Generated key: " << key << std::endl;
}

void displayRecords(const Table& table) const noexcept {
    if (table.records.empty()) {
        std::cout << "No records exist in this table." << std::endl;
        return;
    }
    
    std::vector<std::pair<std::string, Record>> recVector(table.records.begin(), table.records.end());
    std::size_t currentIndex = 0;
    auto displayRecordAt = [&]() {
#if defined(_WIN32)
        system("cls");
#else
        system("clear");
#endif
        const auto& key = recVector[currentIndex].first;
        const Record& rec = recVector[currentIndex].second;
        const int labelWidth = 12;
        const int fieldWidth = 30;
        const int totalWidth = 2 + labelWidth + fieldWidth + 2;
        
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        std::cout << "\n" << std::string(totalWidth, '=') << std::endl;
        std::cout << std::left << std::setw(totalWidth)
                  << "  Record (" + std::to_string(currentIndex + 1) + " of " + std::to_string(recVector.size()) + ")  "
                  << std::endl;
        std::cout << std::string(totalWidth, '=') << std::endl;
        ConsoleColor::reset();
        
        std::string borderLine = "+" + std::string(totalWidth - 2, '-') + "+";
        auto printField = [&](const std::string &label, const auto &value) {
            std::ostringstream oss;
            oss << value;
            std::cout << "| " << std::left << std::setw(labelWidth) << label
                      << " : " << std::left << std::setw(fieldWidth) << oss.str() << " |" << std::endl;
        };
        
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        printField("KEY", key);
        printField("ID", rec.id);
        ConsoleColor::reset();
        for (std::size_t i = 0; i < rec.data.size(); ++i) {
            std::string columnTitle = (i + 2 < table.columns.size())
                                      ? table.columns[i + 2]
                                      : ("Field" + std::to_string(i));
            printField(columnTitle, rec.data[i]);
        }
        std::cout << borderLine << std::endl;
        ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
        std::cout << "\nUse left arrow and right arrow to navigate, or type the letter q to quit." << std::endl;
        ConsoleColor::reset();
    };

    // Afficher le premier record
    displayRecordAt();
    bool quit = false;
    while (!quit) {
#if defined(_WIN32)
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 224) {
            int arrow = _getch();
            if (arrow == 75) {
                if (currentIndex > 0) {
                    --currentIndex;
                    displayRecordAt();
                }
            }
            else if (arrow == 77) {
                if (currentIndex + 1 < recVector.size()) {
                    ++currentIndex;
                    displayRecordAt();
                }
            }
        }
#else
        int ch = _getch();
        if (ch == 'q' || ch == 'Q') {
            quit = true;
        }
        else if (ch == 27) {
            int ch1 = _getch();
            int ch2 = _getch();
            if(ch1 == 91) {
                if(ch2 == 68) {
                    if (currentIndex > 0) {
                        --currentIndex;
                        displayRecordAt();
                    }
                }
                else if(ch2 == 67) {
                    if (currentIndex + 1 < recVector.size()) {
                        ++currentIndex;
                        displayRecordAt();
                    }
                }
            }
        }
#endif
    }
}

// Recherche améliorée via Levenshtein intégrée dans searchRecord().
// Signature inchangée : void searchRecord(const Table& table)
void searchRecord(const Table& table) {
    std::cout << "Enter the search term (keyword or part of the KeyID): ";
    std::string searchTerm;
    std::getline(std::cin, searchTerm);

    // Mise en œuvre de la recherche améliorée
    std::map<std::string, Record> result;
    std::queue<std::pair<std::string, Record>> fringe;
    std::set<std::string> explored;

    // Tokenize et normalise le terme de recherche
    std::vector<std::string> tokens = tokenize(searchTerm);

    // Ajouter tous les enregistrements à la file
    for (const auto &pair : table.records) {
        fringe.push(pair);
    }

    // Lambda de validation d'un token sur une chaîne donnée
    auto tokenMatches = [&tokens](const std::string &str) -> bool {
        std::string normalizedStr = normalize(str);
        for (const auto &token : tokens) {
            // Vérification par sous-chaîne
            if (normalizedStr.find(token) != std::string::npos)
                return true;
            // Vérification avec distance de Levenshtein (tolérance de 1)
            if (LevenshteinDistance(token, normalizedStr) <= 1)
                return true;
        }
        return false;
    };

    // Parcours des enregistrements
    while (!fringe.empty()) {
        auto current = fringe.front();
        fringe.pop();

        const std::string &idStr = current.first;
        const Record &rec = current.second;

        if (explored.find(idStr) != explored.end())
            continue;

        // Recherche dans l'ID (KeyID)
        if (tokenMatches(idStr)) {
            result[idStr] = rec;
            explored.insert(idStr);
            continue;
        }

        // Recherche dans les champs de données
        for (const auto &dataField : rec.data) {
            if (tokenMatches(dataField)) {
                result[idStr] = rec;
                break;
            }
        }
        explored.insert(idStr);
    }

    // Affichage du résultat amélioré
    if (result.empty()) {
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        std::cout << "\n=== Record not found. ===\n" << std::endl;
        ConsoleColor::reset();
    } else {
        ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
        std::cout << "\n=== Records matching the search ===" << std::endl;
        ConsoleColor::reset();

        const int labelWidth = 10;
        std::ostringstream clipboardStream;

        // Affichage de chaque enregistrement trouvé
        for (const auto &entry : result) {
            const std::string &key = entry.first;
            const Record &rec = entry.second;
            std::cout << std::left << std::setw(labelWidth) << "KEY:" << key << std::endl;
            std::cout << std::left << std::setw(labelWidth) << "ID:"  << rec.id << std::endl;
            clipboardStream << "KEY: " << key << "\r\n";
            clipboardStream << "ID: " << rec.id << "\r\n";
            
            for (std::size_t i = 0; i < rec.data.size(); ++i) {
                std::string columnTitle = (i + 2 < table.columns.size())
                                          ? table.columns[i + 2]
                                          : ("Field" + std::to_string(i));
                std::cout << std::left << std::setw(labelWidth) << (columnTitle + ":")
                          << rec.data[i] << std::endl;
                clipboardStream << columnTitle << ": " << rec.data[i] << "\r\n";
            }
            std::cout << "\n";  // séparation entre les records
            clipboardStream << "\r\n";
        }

        // Copie de l'affichage du premier résultat dans le presse-papier
        auto it = result.begin();
        std::string clipboardData = clipboardStream.str();
#ifdef _WIN32
        HWND hwnd = GetConsoleWindow();
        toClipboard(hwnd, clipboardData);
#endif

        ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
        std::cout << "The search result(s) has been copied to the clipboard." << std::endl;
        ConsoleColor::reset();
    }
}

void updateRecord(Table& table) {
    std::cout << "Enter the key of the record to modify: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    Record& rec = it->second;
    for (std::size_t i = 0; i < rec.data.size(); ++i) {
        std::cout << "Current value for '" << table.columns[i + 2] << "' : " << rec.data[i] << std::endl;
        std::cout << "Enter the new value (leave empty to keep) : ";
        std::string newValue;
        std::getline(std::cin, newValue);
        if (!newValue.empty()) {
            rec.data[i] = newValue;
        }
    }
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record updated successfully." << std::endl;
}

void deleteRecord(Table& table) {
    std::cout << "Enter the key of the record to delete: ";
    std::string key;
    std::getline(std::cin, key);
    auto it = table.records.find(key);
    if (it == table.records.end()) {
        std::cout << "Record not found." << std::endl;
        return;
    }
    table.records.erase(it);
    try {
        saveTable(table);
    } catch (const std::exception &e) {
        throw std::runtime_error("Error while saving the table: " + std::string(e.what()));
    }
    std::cout << "Record deleted successfully." << std::endl;
}

// -------------------------------------------------------------------------
// Fonctions de gestion du dossier et de la sauvegarde
// -------------------------------------------------------------------------
static void toClipboard(HWND hwnd, const std::string &s) {
    if (!OpenClipboard(hwnd)) {
        return;
    }
    EmptyClipboard();
    HGLOBAL hg = GlobalAlloc(GMEM_MOVEABLE, s.size() + 1);
    if (!hg) {
        CloseClipboard();
        return;
    }
    char* dest = static_cast<char*>(GlobalLock(hg));
    memcpy(dest, s.c_str(), s.size() + 1);
    GlobalUnlock(hg);
    SetClipboardData(CF_TEXT, hg);
    CloseClipboard();
    GlobalFree(hg);
}

private:
const std::string tableDir_;
std::map<std::string, Table> tables_;
TUID uidGenerator_;


// Initialise le dossier de sauvegarde
void initializeFolder() {
    try {
        if (!fs::exists(tableDir_)) {
            fs::create_directory(tableDir_);
        }
    } catch (const fs::filesystem_error &e) {
        throw std::runtime_error("Error while creating the folder: " + std::string(e.what()));
    }
}

// Génère une clé au format "0001"
std::string generateKeyID(int id) const {
    std::ostringstream oss;
    oss << std::setw(4) << std::setfill('0') << id;
    return oss.str();
}

// Sauvegarde d'une table dans un fichier CSV.
// L'en-tête est en clair, puis pour chaque enregistrement, la clé et l'ID sont écrits en clair,
// et chaque donnée (à partir de la 3ème colonne) est encodée en Hexadecimal.
void saveTable(const Table &table) const {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ofstream file(path, std::ios::out);
    if (!file.is_open()) {
        throw std::runtime_error("Error while opening the file " + path + " for writing.");
    }

    // Écriture de l'en-tête (les colonnes) en clair
    {
        std::ostringstream oss;
        for (std::size_t i = 0; i < table.columns.size(); ++i) {
            oss << table.columns[i];
            if (i + 1 < table.columns.size()) {
                oss << ";";
            }
        }
        file << oss.str() << "\n";
    }

    // Écriture des enregistrements
    for (const auto& [key, rec] : table.records) {
        std::ostringstream line;
        line << key << ";" << rec.id;
        for (const auto& d : rec.data) {
            std::string encodedData = string_to_hex(d);
            line << ";" << encodedData;
        }
        file << line.str() << "\n";
    }
    file.close();
    if (file.fail()) {
        throw std::runtime_error("Error while writing to the file " + path);
    }
}

// Charge une table depuis un fichier CSV.
// L'en-tête et la clé/ID sont lus en clair, et chaque donnée est décodée depuis Hexadecimal.
void loadTable(Table &table) {
    const std::string path = tableDir_ + "/" + table.name + ".csv";
    std::ifstream file(path);
    if (!file.is_open()) {
       throw std::runtime_error("File " + path + " not found.");
    }
    table.records.clear();
    std::string line;
    bool firstLine = true;
    while (std::getline(file, line)) {
        if (line.empty())
            continue;
        if (firstLine) {
            table.columns = parseCSVLine(line, ';');
            firstLine = false;
            continue;
        }
        auto tokens = parseCSVLine(line, ';');
        if (tokens.size() < 2)
            continue;
        Record rec;
        const std::string key = tokens[0];
        try {
            rec.id = std::stoi(tokens[1]);
        } catch (const std::exception&) {
            std::cerr << "Error converting the ID for the key " << key << std::endl;
            continue;
        }
        for (std::size_t i = 2; i < tokens.size(); ++i) {
            rec.data.push_back(hex_to_string(tokens[i]));
        }
        table.records[key] = std::move(rec);
    }
    file.close();

    // Mise à jour du TUID avec le maximum des IDs
    int maxID = 0;
    for (const auto &entry : table.records) {
        maxID = std::max(maxID, entry.second.id);
    }
    uidGenerator_.reset(maxID);
}

// Charge toutes les tables existantes dans le dossier
void loadExistingTables() {
    try {
        if (!fs::exists(tableDir_))
            return;
        for (const auto &entry : fs::directory_iterator(tableDir_)) {
            if (entry.is_regular_file() && entry.path().extension() == ".csv") {
                Table table;
                table.name = entry.path().stem().string();
                loadTable(table);
                tables_[table.name] = std::move(table);
            }
        }
    } catch (const fs::filesystem_error &e) {
        std::cerr << "Error while reading the directory: " << e.what() << std::endl;
    }
}

};

// -----------------------------------------------------------------------------
// Classe MenuManager
// -----------------------------------------------------------------------------
class MenuManager {
public:
    MenuManager(std::unique_ptr<IUserInterface> ui)
        : userInterface_(std::move(ui)) {}

    void run() {
    bool exitFlag = false;
    while (!exitFlag) {
        try {
            Menu mainMenu("Main Menu", 80, 24);
            mainMenu.addOption(MenuOption("Manage the database", [this]() { runDatabaseMenu(); }));
            mainMenu.addOption(MenuOption("Quit", []() {
                ConsoleColor::println("Goodbye!", ConsoleColor::TextColor::RED);
                std::exit(EXIT_SUCCESS);
            }));

            clearScreen();

            // Définition de la largeur totale pour le menu (style QBasic)
            const int totalWidth = 50;
            const std::string title = " Main Menu ";
            const std::string borderLine(totalWidth, '=');

            // Affichage du titre du menu avec fond coloré
            ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            std::cout << "\n" << borderLine << std::endl;
            std::cout << std::left << std::setw(totalWidth)
                      << "  " + title + "  " << std::endl;
            std::cout << borderLine << std::endl;
            ConsoleColor::reset();

            // Affichage des instructions pour l'utilisateur dans le style QBasic vintage
            ConsoleColor::setColor(ConsoleColor::TextColor::LIGHT_BLUE, ConsoleColor::BackgroundColor::BG_BLACK);
            std::cout << "\nUse the arrow keys to navigate and press Enter or the right arrow to select an option." << std::endl;
            std::cout << "To exit the program, choose the 'Quit' option from the menu." << std::endl;
            ConsoleColor::reset();

            // Affichage des options du menu dans un encadrement similaire aux anciennes fenêtres QBasic
            for (const auto& option : mainMenu.getOptions()) {
                ConsoleColor::setColor(ConsoleColor::TextColor::YELLOW, ConsoleColor::BackgroundColor::BG_BLACK);
                std::cout << "| " << std::setw(totalWidth - 4) << std::left
                          << option.getTitle() << " |" << std::endl;
            }
            // Réaffichage d'une bordure de fin pour la cohérence visuelle
            ConsoleColor::setColor(ConsoleColor::TextColor::WHITE, ConsoleColor::BackgroundColor::BG_BLUE);
            std::cout << borderLine << std::endl;
            ConsoleColor::reset();

            runMenu(mainMenu);
            exitFlag = true;
        }
        catch (const std::runtime_error &e) {
            if (std::string(e.what()) != "Return")
                throw;
        }
    }
}

private:
    std::unique_ptr<IUserInterface> userInterface_;

    void clearScreen() {
    #if defined(_WIN32)
        system("cls");
    #else
        system("clear");
    #endif
    }

    void runMenu(Menu &menu) {
        while (true) {
            clearScreen();
            menu.displayMenu(userInterface_.get());
            char input = userInterface_->getInput();

            if (input == -32) {
                char direction = userInterface_->getInput();
                if (direction == 72) {
                    menu.navigateUp();
                    continue;
                }
                else if (direction == 80) {
                    menu.navigateDown();
                    continue;
                }
                else if (direction == 77) {
                    menu.select();
                    continue;
                }
                else if (direction == 75) {
                    throw std::runtime_error("Return");
                }
            }
            
            if (input == 27)
                break;

            try {
                menu.navigate(input);
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) == "Return")
                    return;
                else {
                    ConsoleColor::println("Error: " + std::string(e.what()),
                                            ConsoleColor::TextColor::RED,
                                            ConsoleColor::BackgroundColor::BG_BLACK);
                    std::cout << "Press any key to try again...";
                    userInterface_->getInput();
                }
            }
        }
    }

    void runDatabaseMenu() {
        Database db;
        while (true) {
            clearScreen();
            Menu dbMenu("Database Menu", 80, 24);
            dbMenu.addOption(MenuOption("Create a new table", [&db]() {
                try {
                    auto newTableOpt = db.createTable();
                    if (newTableOpt)
                        db.recordMenu( db.selectTable() ? *db.selectTable() : newTableOpt.value() );
                }
                catch (const std::exception &e) {
                    std::cerr << e.what() << std::endl;
                    system("pause");
                }
            }));
            dbMenu.addOption(MenuOption("Use an existing table", [&db]() {
                Table* table = db.selectTable();
                if (table)
                    db.recordMenu(*table);
            }));
            dbMenu.addOption(MenuOption("Back", []() {
                // Retour simple
            }));

            try {
                runMenu(dbMenu);
                break;
            }
            catch (const std::runtime_error &e) {
                if (std::string(e.what()) != "Return")
                    throw;
                else
                    break;
            }
        }
    }
};

// -----------------------------------------------------------------------------
// Fonction main
// -----------------------------------------------------------------------------
int main() {
    try {
        auto ui = std::make_unique<ConsoleUserInterface>();
        MenuManager manager(std::move(ui));
        manager.run();

        // Exemple d'utilisation directe de la base de données
        Database db;
        auto tableOpt = db.createTable();
        if (tableOpt) {
            Table* table = db.selectTable();
            if (table) {
                db.addRecord(*table);
                db.displayRecords(*table);

                TUID generator;
                int nextID = generator.getNextID();
                std::cout << "\nNext generated ID : " << nextID << "\n";
                auto indices = generator.querySketch(nextID, 3);
                std::cout << "Indices returned by querySketch : ";
                for (const auto idx : indices) {
                    std::cout << idx << " ";
                }
                std::cout << "\nPress any key to close...";
                (void)getchar();
            }
        }
    } catch (const std::exception &ex) {
        std::cerr << "Exception caught in main : " << ex.what() << "\n";
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
 
Joined
Jun 12, 2020
Messages
28
Reaction score
1
To free memory in relation to the previous code, you can use the following function:
Code:
// ----------------------------------------------------------
// Fonctions utilitaires pour la recherche améliorée et Levenshtein

// Fonction utilitaire pour arrondir (alternative à std::round)
inline int round_to_int(double x) {
    return static_cast<int>(x + (x >= 0 ? 0.5 : -0.5));
}

// Exemple de fonction de distance entre deux touches basée sur un clavier QWERTY simplifié.
int getKeyboardDist(char a, char b) {
    auto getCoord = [](char c) -> std::pair<int, int> {
        c = std::toupper(static_cast<unsigned char>(c));
        std::string line1 = "QWERTYUIOP";
        std::string line2 = "ASDFGHJKL";
        std::string line3 = "ZXCVBNM";
        size_t pos = line1.find(c);
        if (pos != std::string::npos)
            return { static_cast<int>(pos), 0 };
        pos = line2.find(c);
        if (pos != std::string::npos)
            return { static_cast<int>(pos), 1 };
        pos = line3.find(c);
        if (pos != std::string::npos)
            return { static_cast<int>(pos), 2 };
        return { 0, 0 };
    };

    auto p1 = getCoord(a);
    auto p2 = getCoord(b);
    double dx = static_cast<double>(p1.first - p2.first);
    double dy = static_cast<double>(p1.second - p2.second);
    return round_to_int(std::sqrt(dx * dx + dy * dy));
}

// Coût d'insertion pouvant être amélioré.
int getInsertionCost(size_t j, const std::string &s) {
    const int baseCost = 1;
    // Dans cet exemple, on renvoie toujours le coût de base.
    return baseCost;
}

// ----------------------------------------------------------
// Structures et fonctions pour l'arbre rouge-noir (simplifié)

// On fait en sorte que kdata hérite de rb_node.
// Cela permet d'utiliser directement l'objet kdata comme noeud dans l'arbre.
// Attention : Dans un vrai arbre rouge-noir, il faudrait ajouter d'autres champs (ex. couleur).
struct rb_node {
    rb_node *rb_left = nullptr;
    rb_node *rb_right = nullptr;
};

struct rb_root {
    rb_node *rb_node = nullptr;
};

// kdata hérite de rb_node, ce qui garantit que l'adresse d'un kdata est identique
// à l'adresse de sa partie rb_node. Ainsi, on pourra faire des casts en toute sécurité.
struct kdata : public rb_node {
    char k1;
    char k2;
    int cost;
};

// Fonction d'insertion dans l'arbre binaire simplifié.
// On insère le noeud (qui est un kdata) en fonction d'un critère de comparaison.
// Ici nous comparons d'abord k1, puis (si nécessaire) k2.
void rbinsert(rb_root *root, kdata *kd) {
    rb_node **p = &root->rb_node;
    while (*p) {
        kdata *d = static_cast<kdata*>(*p);
        // Choix de l'ordre : on compare k1 puis k2
        if ((kd->k1 < d->k1)
            || (kd->k1 == d->k1 && kd->k2 < d->k2)) {
            p = &((*p)->rb_left);
        } else {
            p = &((*p)->rb_right);
        }
    }
    *p = kd;
    // Dans un arbre rouge-noir complet, il faudrait ici rééquilibrer l'arbre.
}

// Fonction de recherche dans l'arbre pour trouver un kdata donné.
// Ici, nous recherchons de manière simplifiée uniquement via k1 (et potentiellement k2) selon votre besoin.
// Pour cet exemple, la recherche est faite par k1 uniquement.
int rbfind(rb_root *root, char c1, char /* c2 */) {
    rb_node *n = root->rb_node;
    while (n) {
        kdata *d = static_cast<kdata*>(n);
        if (c1 > d->k1) {
            n = n->rb_right;
        } else if (c1 < d->k1) {
            n = n->rb_left;
        } else {
            return d->cost; // Retourne le coût de substitution
        }
    }
    return 1; // Coût par défaut si non trouvé
}

// Fonction pour initialiser les coûts de substitution et insérer les kdata dans l'arbre
void initializeSubstitutionCosts(rb_root *root) {
    for (char c1 = 'a'; c1 <= 'z'; ++c1) {
        for (char c2 = 'a'; c2 <= 'z'; ++c2) {
            kdata *kd = new kdata;
            kd->k1 = c1;
            kd->k2 = c2;
            kd->cost = (c1 == c2) ? 0 : 1;
            // Assurez-vous que rb_left et rb_right sont nullptr (grâce à l'initialisation dans rb_node)
            rbinsert(root, kd);
        }
    }
    // Les allocations seront libérées par freeRbTree().
}

// Fonction récursive pour libérer l'arbre.
// Puisque chaque noeud dans l'arbre est un objet kdata (via héritage),
// on peut convertir le rb_node en kdata* en toute sécurité.
void freeTree(rb_node *node) {
    if (!node)
        return;
    freeTree(node->rb_left);
    freeTree(node->rb_right);
    kdata* kd = static_cast<kdata*>(node);
    delete kd;
}

// Fonction pour libérer entièrement l'arbre
void freeRbTree(rb_root *root) {
    if (root) {
        freeTree(root->rb_node);
        root->rb_node = nullptr;
    }
}

// ----------------------------------------------------------
// Implémentation de la fonction LevenshteinDistance

int LevenshteinDistance(const std::string &s1, const std::string &s2) {
    const size_t len1 = s1.size();
    const size_t len2 = s2.size();
    if (len1 == 0) return static_cast<int>(len2);
    if (len2 == 0) return static_cast<int>(len1);

    std::vector<int> previous(len2 + 1), current(len2 + 1);
    for (size_t j = 0; j <= len2; ++j) {
        previous[j] = j;
    }

    rb_root substitutionCosts;
    substitutionCosts.rb_node = nullptr;
    initializeSubstitutionCosts(&substitutionCosts);

    for (size_t i = 1; i <= len1; ++i) {
        current[0] = i;
        for (size_t j = 1; j <= len2; ++j) {
            int substitutionCost = (s1[i - 1] == s2[j - 1])
                ? 0
                : rbfind(&substitutionCosts, s1[i - 1], s2[j - 1]);
            int deletionCost = 1;
            int insertionCost = getInsertionCost(j - 1, s2);
            current[j] = std::min({ previous[j] + deletionCost,
                                     current[j - 1] + insertionCost,
                                     previous[j - 1] + substitutionCost });
        }
        previous.swap(current);
    }

    // Libérer l'arbre alloué
    freeRbTree(&substitutionCosts);
    return previous[len2];
}

std::string normalize(const std::string &str) {
    std::string normalized;
    normalized.reserve(str.size());

    for (char c : str) {
        if (!std::isspace(static_cast<unsigned char>(c))) {
            normalized.push_back(std::tolower(static_cast<unsigned char>(c)));
        }
    }
    return normalized;
}

std::vector<std::string> tokenize(const std::string &str) {
    std::vector<std::string> tokens;
    std::istringstream stream(str);
    std::string token;

    while (stream >> token) {
        tokens.push_back(normalize(token));
    }
    return tokens;
}
 
Joined
Sep 20, 2022
Messages
237
Reaction score
39
This program is getting quite long.

I'm confused about one thing, what's the purpose of the Count-Min Sketch in a database editor?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,274
Messages
2,571,146
Members
48,775
Latest member
satman49

Latest Threads

Top