#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/MemoryArena.h>

namespace Juliet
{
    namespace
    {
        DisplayDevice* g_CurrentDisplayDevice = nullptr;

        void DestroyPlatformWindow(index_t windowIndex);
    } // namespace

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

    void InitializeDisplaySystem()
    {
        Assert(!g_CurrentDisplayDevice);

        Arena* arena = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Display System"));

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

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

        g_CurrentDisplayDevice        = candidateDevice;
        g_CurrentDisplayDevice->Arena = arena;
        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
        for (index_t idx = g_CurrentDisplayDevice->Windows.Size(); idx-- > 0;)
        {
            DestroyPlatformWindow(idx);
        }

        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);

        ArenaRelease(g_CurrentDisplayDevice->Arena);
        g_CurrentDisplayDevice = nullptr;
    }

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

        Window window = {};
        window.Arena  = ArenaAllocate({} JULIET_DEBUG_ONLY(, "Window"));
        window.Width  = width;
        window.Height = height;

        window.Title = StringCopy(window.Arena, WrapString(title));

        g_CurrentDisplayDevice->Windows.PushBack(window);

        auto* pWindow = g_CurrentDisplayDevice->Windows.Last();
        if (!g_CurrentDisplayDevice->CreatePlatformWindow(g_CurrentDisplayDevice, pWindow))
        {
            ArenaRelease(window.Arena);
            return nullptr;
        }

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

        return pWindow;
    }

    namespace
    {
        void DestroyPlatformWindow(index_t windowIndex)
        {
            VectorArena<Window>& windows = g_CurrentDisplayDevice->Windows;
            Window*              window  = &windows[windowIndex];

            HideWindow(window);

            g_CurrentDisplayDevice->DestroyPlatformWindow(g_CurrentDisplayDevice, window);

            ArenaClear(window->Arena);
            ArenaRelease(window->Arena);

            windows.RemoveAtFast(windowIndex);
        }
    } // namespace

    void DestroyPlatformWindow(NonNullPtr<Window> window)
    {
        // Find and destroy
        VectorArena<Window>& windows = g_CurrentDisplayDevice->Windows;
        for (index_t idx = windows.Size(); idx-- > 0;)
        {
            Window& windowRef = windows[idx];
            if (windowRef.ID == window->ID)
            {
                DestroyPlatformWindow(idx);
                break;
            }
        }
    }

    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;
    }

    void SetWindowTitle(NonNullPtr<Window> window, String title)
    {
        g_CurrentDisplayDevice->SetWindowTitle(g_CurrentDisplayDevice, window, title);
    }

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