#include <Core/Common/CoreTypes.h>
#include <Core/HAL/Display/Display_Private.h>
#include <Core/HAL/Display/DisplayDevice.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/EngineArena.h>
#include <Core/Memory/MemoryArena.h>
#include <format>

namespace Juliet
{
    namespace
    {
        DisplayDevice* g_CurrentDisplayDevice = nullptr;
    }

    namespace Internal::Display
    {
        // TODO : IfDef new factories that are not compatible
        constexpr DisplayDeviceFactory* Factories[] = { &Win32DisplayDeviceFactory, nullptr };
    } // namespace Internal::Display

    void InitializeDisplaySystem()
    {
        Assert(!g_CurrentDisplayDevice);

        DisplayDevice*        candidateDevice  = nullptr;
        DisplayDeviceFactory* candidateFactory = nullptr;
        for (DisplayDeviceFactory* factory : Internal::Display::Factories)
        {
            if (factory)
            {
                candidateDevice = factory->CreateDevice();
                if (candidateDevice)
                {
                    candidateFactory = factory;
                    break;
                }
            }
        }

        // TODO : handle error instead of crashing
        Assert(candidateDevice);

        g_CurrentDisplayDevice       = candidateDevice;
        g_CurrentDisplayDevice->Name = candidateFactory->Name;

        if (!g_CurrentDisplayDevice->Initialize(g_CurrentDisplayDevice))
        {
            ShutdownDisplaySystem();
        }
    }

    void ShutdownDisplaySystem()
    {
        if (!g_CurrentDisplayDevice)
        {
            return;
        }

        // Destroy all Windows that are still alive
        if (g_CurrentDisplayDevice->MainWindow)
        {
            DestroyPlatformWindow(g_CurrentDisplayDevice->MainWindow);
        }

        g_CurrentDisplayDevice->Shutdown(g_CurrentDisplayDevice);
        // Free anything that was freed by the shutdown and then free the display
        // no op for now
        g_CurrentDisplayDevice->Free(g_CurrentDisplayDevice);
        g_CurrentDisplayDevice = nullptr;
    }

    Window* CreatePlatformWindow(const char* title, uint16 width, uint16 height, int /*flags*/ /* = 0 unused */)
    {
        Assert(g_CurrentDisplayDevice->CreatePlatformWindow);

        MemoryArena* arena  = GetEngineArena();
        auto         window = ArenaPushType<Window>(arena, ConstString("Window"));
        if (!window)
        {
            return nullptr;
        }
        window->Width  = width;
        window->Height = height;

        auto titleLen = StringLength(title);
        auto buffer   = ArenaPushArray<char>(arena, titleLen, ConstString("Window Title Array"));
        MemCopy(buffer, title, titleLen);

        window->Title.Data = buffer;
        window->Title.Size = titleLen;

        g_CurrentDisplayDevice->MainWindow = window;
        if (!g_CurrentDisplayDevice->CreatePlatformWindow(g_CurrentDisplayDevice, window))
        {
            // Note: We don't "free" from arena easily, but since this is catastrophic
            // and persistent, we just leak the small amount of arena space or handle it if we had a marker.
            return nullptr;
        }

        // TODO : make SHOW optional on creation with a flag
        g_CurrentDisplayDevice->ShowWindow(g_CurrentDisplayDevice, window);

        return window;
    }

    void DestroyPlatformWindow(NonNullPtr<Window> window)
    {
        Assert(g_CurrentDisplayDevice->MainWindow == window.Get());

        HideWindow(window);

        // We don't free from arena, these are persistent until shutdown.
        window->Title.Data = nullptr;
        window->Title.Size = 0;

        g_CurrentDisplayDevice->DestroyPlatformWindow(g_CurrentDisplayDevice, window);

        g_CurrentDisplayDevice->MainWindow = nullptr;
    }

    void ShowWindow(NonNullPtr<Window> window)
    {
        g_CurrentDisplayDevice->ShowWindow(g_CurrentDisplayDevice, window);
    }

    void HideWindow(NonNullPtr<Window> window)
    {
        g_CurrentDisplayDevice->HideWindow(g_CurrentDisplayDevice, window);
    }

    WindowID GetWindowID(NonNullPtr<Window> window)
    {
        return window->ID;
    }

    // Display Device Utils. Not exposed in the API
    DisplayDevice* GetDisplayDevice()
    {
        return g_CurrentDisplayDevice;
    }
} // namespace Juliet
