#include <Core/Logging/LogManager.h>
#include <Core/Logging/LogTypes.h>

#include <chrono>

// Begin Todo JULIET debug output
#include <cinttypes>
#include <Core/Memory/MemoryArena.h>

#ifdef JULIET_WIN32
#include <Core/HAL/Win32.h>
#include <debugapi.h>
#endif
// End Todo

// TODO Juliet chrono

namespace Juliet
{
    namespace
    {
        struct LogsEntry : public QueueNode<LogsEntry>
        {
            String                                Value;
            std::chrono::system_clock::time_point Time;
            LogLevel                              Level;
            LogCategory                           Category;
        };
        DECLARE_QUEUE(LogsEntry);

        // TODO: Debug level per category
        const bool kPrintDebugLog = false;

        // A log scope accumulates log until end of scope.
        // Can be used to accumulate then write in a log file each frame.
        struct LogScope
        {
            LogScope*      Next;
            size_t         Position;
            LogsEntryQueue Entries;
        };

        // Logs contains the current scopes and the arena
        struct Logs
        {
            Arena*    Arena    = nullptr;
            LogScope* TopScope = nullptr;
        };

        Logs* ActiveLog = nullptr;

        Logs* LogAllocate()
        {
            Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Log Manager"));
            Logs*  logs  = ArenaPushStruct<Logs>(arena);
            logs->Arena  = arena;
            return logs;
        }

        void LogRelease(NonNullPtr<Logs> logs)
        {
            ArenaRelease(logs->Arena);
        }

        void OutputLog(LogsEntry& entry)
        {
            // TODO Juliet Output io for each platform

            // {:%F} is YYYY-MM-DD, {:%T} is HH:MM:SS.ffffff
            std::string timestamp   = std::format("[{:%F %T}] ", entry.Time);
            std::string fullMessage = timestamp + CStr(entry.Value) + "\n";

#ifdef JULIET_WIN32
            OutputDebugStringA(fullMessage.c_str());
#endif
            printf("%s", fullMessage.c_str());
        }

        void PushLogEntry(NonNullPtr<Logs> logs, String value, LogLevel level, LogCategory category)
        {
            Arena* arena = logs->Arena;
            String str   = StringCopy(arena, value);

            // Push onto the current scope
            LogsEntry* newEntry = ArenaPushStruct<LogsEntry>(arena);
            newEntry->Time      = std::chrono::system_clock::now();
            newEntry->Level     = level;
            newEntry->Category  = category;
            newEntry->Value     = str;

            Enqueue(logs->TopScope->Entries, newEntry);
            logs->TopScope->Entries.Size += sizeof(LogsEntry) + str.Size;

            OutputLog(*newEntry);
        }
    } // namespace

    void InitializeLogManager()
    {
#ifdef JULIET_WIN32
        SetConsoleOutputCP(CP_UTF8);
        SetConsoleCP(CP_UTF8);
#endif

        ActiveLog = LogAllocate();
        LogScopeBegin();
    }

    void ShutdownLogManager()
    {
        LogScopeEnd();
        LogRelease(ActiveLog);
    }

    void LogScopeBegin()
    {
        Assert(ActiveLog != nullptr);

        size_t    position = ArenaPos(ActiveLog->Arena);
        LogScope* scope    = ArenaPushStruct<LogScope>(ActiveLog->Arena);
        scope->Position    = position;
        SingleLinkedListPushNext(ActiveLog->TopScope, scope);
    }

    void LogScopeEnd()
    {
        Assert(ActiveLog != nullptr);

        LogScope* scope = ActiveLog->TopScope;
        Assert(scope != nullptr);

        SingleLinkedListPopNext(ActiveLog->TopScope);

        ArenaPopTo(ActiveLog->Arena, scope->Position);
    }

    void Log(LogLevel level, LogCategory category, const char* fmt, va_list args)
    {
        if (level == LogLevel::Debug && kPrintDebugLog == false)
        {
            return;
        }

        // TODO : Revisit, copy from https://github.com/Eclmist/Ether/blob/develop/src/common/logging/loggingmanager.cpp
        char formattedBuffer[4096];

        (void)vsprintf_s(formattedBuffer, fmt, args); // Cast to void to ignore the return type. TODO : Juliet format function

        std::string formattedText(formattedBuffer);

        std::stringstream ss(formattedText);
        std::string       individualLine;

        while (std::getline(ss, individualLine, '\n'))
        {
            // Logs can happen before the active log is set, in that case, we can write to the console but not register the entry.
            // One case is the Arena Allocate that calls logs and will happen before the logs are properly set because logs need to allocate an arena.
            if (ActiveLog != nullptr)
            {
                String julietstr = WrapString(individualLine.c_str());
                PushLogEntry(ActiveLog, julietstr, level, category);
            }
        }
    }

    void Log(LogLevel level, LogCategory category, const char* fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
        Log(level, category, fmt, args);
        va_end(args);
    }

    void LogDebug(LogCategory category, const char* fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
        Log(LogLevel::Debug, category, fmt, args);
        va_end(args);
    }

    void LogMessage(LogCategory category, const char* fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
        Log(LogLevel::Message, category, fmt, args);
        va_end(args);
    }

    void LogWarning(LogCategory category, const char* fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
        Log(LogLevel::Warning, category, fmt, args);
        va_end(args);
    }

    void LogError(LogCategory category, const char* fmt, ...)
    {
        va_list args;
        va_start(args, fmt);
        Log(LogLevel::Error, category, fmt, args);
        va_end(args);
    }
} // namespace Juliet
