#pragma once
#include <fstream>
#include <string>
#include <Windows.h>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <filesystem>
#include <deque>
#include <unordered_set>
#include <imgui.h>

class Logger
{
public:
    /**
     * Get the singleton instance of the Logger class.
     * @return A reference to the Logger instance.
     */
    static Logger& Get()
    {
        static Logger instance;
        return instance;
    }

    /**
     * Delete the copy constructor and assignment operator to prevent copying.
     */
    Logger(Logger const&) = delete;
    void operator=(Logger const&) = delete;

    /**
     * Enum class representing different log levels.
     * Each log level has a corresponding color for display purposes.
     * The last entry, Count, is used to determine the number of log levels.
     */
    enum class LogLevel
    {
        Info,
        Warning,
        Error,
        Shutdown,
        Initialize,
        Update,
        Render,
        Input,
        Physics,
        Audio,
        Network,
        Scripting,
        AI,
        Resource,
        Memory,
        Debug,
		Count // Do not use this, it's just to get the number of log levels it must at the end
    };

	// Return the size of the enum class LogLevel as a constant integer
	static constexpr int LogLevelCount = static_cast<int>(LogLevel::Count);

    /**
     * Struct representing a log entry.
     * Contains the log message and its associated log level.
     */
    struct LogEntry
    {
        std::string message;
        LogLevel level;
    };

    /** 
     * Struct containing information about a log level.
     * This includes the name of the log level, its value, and its color for display.
     */
    struct LogLevelInfo
    {
        const char* name;
        int value;
        ImVec4 color;
    };

    /**
     * Get the LogLevelInfo for a given log level.
     * This function returns a LogLevelInfo struct containing the name, value, and color of the log level.
     * @param level The log level for which to get the information.
     * @return A LogLevelInfo struct containing the information for the specified log level.
     */
    static const LogLevelInfo GetLogLevelInfo(LogLevel level)
    {
		switch (level)
		{
		case LogLevel::Info: return LogLevelInfo{ "Info", 0, ImVec4(0.0f, 1.0f, 0.0f, 1.0f) };
		case LogLevel::Warning: return LogLevelInfo{ "Warning", 1, ImVec4(1.0f, 1.0f, 0.0f, 1.0f) };
		case LogLevel::Error: return LogLevelInfo{ "Error", 2, ImVec4(1.0f, 0.0f, 0.0f, 1.0f) };
		case LogLevel::Shutdown: return LogLevelInfo{ "shutdown", 3, ImVec4(0.5f, 0.0f, 0.0f, 1.0f) };
		case LogLevel::Initialize: return LogLevelInfo{ "initialize", 4, ImVec4(0.0f, 1.0f, 1.0f, 1.0f) };
		case LogLevel::Update: return LogLevelInfo{ "Update", 5, ImVec4(1.0f, 0.0f, 1.0f, 1.0f) };
		case LogLevel::Render: return LogLevelInfo{ "render", 6, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) };
		case LogLevel::Input: return LogLevelInfo{ "Input", 7, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::Physics: return LogLevelInfo{ "physics", 8, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::Audio: return LogLevelInfo{ "Audio", 9, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::Network: return LogLevelInfo{ "Network", 10, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::Scripting: return LogLevelInfo{ "Scripting", 11, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::AI: return LogLevelInfo{ "AI", 12, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::Resource: return LogLevelInfo{ "Resource", 13, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::Memory: return LogLevelInfo{ "Memory", 14, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		case LogLevel::Debug: return LogLevelInfo{ "Debug", 15, ImVec4(0.5f, 0.5f, 0.5f, 1.0f) };
		default: return LogLevelInfo{ "Unknown", 16, ImVec4(1.0f, 1.0f, 1.0f, 1.0f) };
		}
	}

    /**
     * Constructor for the Logger class.
     * Initializes the logger, sets up the log file path, and manages log files.
     */
    Logger()
    {
        char* appdata = nullptr;
        size_t len;
        _dupenv_s(&appdata, &len, "APPDATA");
        if (appdata == nullptr)
        {
            m_appdataPath = "log.log";
        }
        else
        {
            m_appdataPath = appdata;
        }
        free(appdata);
        std::string directoryPath = m_appdataPath + "\\Khaotic Engine";
        CreateDirectoryA(directoryPath.c_str(), NULL);

        ManageLogFiles(directoryPath);

        m_logFilePath = directoryPath + "\\" + m_logFileName;

		// Enable only the Error warning and shutdown log levels
        for (int i = 0; i < LogLevelCount; i++)
        {
            m_disabledLogLevels[i] = true;

            if (i == static_cast<int>(LogLevel::Error) || i == static_cast<int>(LogLevel::Warning) || i == static_cast<int>(LogLevel::Shutdown))
            {
                m_disabledLogLevels[i] = false;

            }
        }

    }

    /**
     * Write a log message to the log file and console.
     * @param message 
     * @param fileName 
     * @param lineNumber 
     * @param level 
     */
    void Log(const std::string& message, const std::string& fileName, int lineNumber, LogLevel level = LogLevel::Info)
    {

        auto now = std::chrono::system_clock::now();
        auto in_time_t = std::chrono::system_clock::to_time_t(now);

        std::tm buf;
        localtime_s(&buf, &in_time_t);

        // Obtenez les millisecondes à partir de maintenant
        auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;

        // Utilisez LogLevelToString pour obtenir la chaîne de caractères du niveau de log
        std::string levelStr = GetLogLevelInfo(level).name;

        std::stringstream ss;
        ss << "[" << std::put_time(&buf, "%Y-%m-%d") << "] "
            << "[" << std::put_time(&buf, "%X") << "." << std::setfill('0') << std::setw(3) << ms.count() << "] "
            << "[" << levelStr << "] "
            << "[" << fileName << ":" << lineNumber << "] "
            << message;

        Log(ss.str(), level);

        std::ofstream file(m_logFilePath, std::ios::app);
        if (file.is_open())
        {
            file << ss.str() << std::endl;
            file.close();
        }
    }

	/**
     * Write a log message to the log buffer.
     * This is the fonction wich is used to send log messages to the gui.
     * It's using a buffer to store the log messages with a maximum size.
     * The buffer default size is 100 messages.
     * This limit can be changed by changing the logBufferSize variable.
     * But it is not recommended to change it because it can cause performance issues.
     * @param message The log message to write.
     * @param level The log level for the message.
     */
    void Log(const std::string& message, LogLevel level)
    {

		// Si le niveau de log est désactivé, ne faites rien
		if (m_disabledLogLevels[GetLogLevelInfo(level).value])
		{
			return;
		}

        if (logBuffer.size() >= logBufferSize)
        {
            logBuffer.pop_front();
        }
        logBuffer.push_back({ message, level });
    }

    /**
     * Get the loggBuffer deque.
     * @return A constant reference to the logBuffer deque containing log entries.
     */
    const std::deque<LogEntry>& GetLogBuffer() const { return logBuffer; }

    /**
     * This function manages log files in the specified directory.
     * It checks for log files with the ".log" extension,
     * Then it keeps only the three most recent log files,
     * deleting the oldest ones if there are more than three.
     * It also creates a new log file for the current execution with a timestamp in its name.
     * @param directoryPath 
     */
    void ManageLogFiles(const std::string& directoryPath)
    {
        std::vector<std::filesystem::path> logFiles;

        // Parcourez tous les fichiers dans le dossier
        for (const auto& entry : std::filesystem::directory_iterator(directoryPath))
        {
            // Si le fichier est un fichier de log, ajoutez-le à la liste
            if (entry.path().extension() == ".log")
            {
                logFiles.push_back(entry.path());
            }
        }

        // Si nous avons plus de trois fichiers de log, supprimez le plus ancien
        while (logFiles.size() >= 3)
        {
            // Triez les fichiers par date de modification, le plus ancien en premier
            std::sort(logFiles.begin(), logFiles.end(), [](const std::filesystem::path& a, const std::filesystem::path& b)
                {
                    return std::filesystem::last_write_time(a) < std::filesystem::last_write_time(b);
                });

            // Supprimez le fichier le plus ancien
            std::filesystem::remove(logFiles[0]);

            // Supprimez-le de la liste
            logFiles.erase(logFiles.begin());
        }

        // Créez un nouveau fichier de log pour cette exécution
        auto now = std::chrono::system_clock::now();
        auto in_time_t = std::chrono::system_clock::to_time_t(now);
        std::tm buf;
        localtime_s(&buf, &in_time_t);

        std::stringstream ss;
        ss << "Khaotic_log_" << std::put_time(&buf, "%Y_%m_%d_%Hh%Mm%Ss") << ".log";
        m_logFileName = ss.str();
    }

    bool m_disabledLogLevels[LogLevelCount];
    std::string m_logFilePath;

private:
    std::string m_filename;
    std::string m_appdataPath;
    std::string m_logFileName;

    std::deque<LogEntry> logBuffer;
    const size_t logBufferSize = 100;

};
