#include <Core/HAL/Display/Win32/Win32DisplayEvent.h>
#include <Core/HAL/Display/Win32/Win32Window.h>
#include <Core/HAL/Display/Window.h>
#include <Core/Memory/Allocator.h>
#include <Core/Memory/MemoryArena.h>

namespace Juliet::Win32
{
    namespace
    {
        constexpr auto   WindowClassName = "JulietWindowClass";
        constexpr LPCSTR WindowClassPtr  = WindowClassName;

        bool SetupWindowState(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window, HWND handle)
        {
            auto* state = ArenaPushStruct<Window32State>(window->Arena);

            window->State = state;
            state->Handle = handle;
            state->Window = window;
            state->HDC    = GetDC(handle);

            state->IsMouseTracked = false;

            return true;
        }

        void CleanUpWindowState(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window)
        {
            auto* state = static_cast<Window32State*>(window->State);
            Assert(state);
            ReleaseDC(state->Handle, state->HDC);
            DestroyWindow(state->Handle);
            window->State = nullptr;
        }
    } // namespace

    bool CreatePlatformWindow(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window)
    {
        // TODO : save the instance in app state or something
        HINSTANCE instance = GetModuleHandle(nullptr);

        // TODO : Put outside, we should not create a new class for each new window
        WNDCLASSEXA WindowClass   = {};
        WindowClass.cbSize        = sizeof(WNDCLASSEX);
        WindowClass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
        WindowClass.lpfnWndProc   = Win32MainWindowCallback;
        WindowClass.hInstance     = instance;
        WindowClass.hCursor       = LoadCursor(nullptr, IDC_ARROW);
        WindowClass.hbrBackground = static_cast<HBRUSH>(GetStockObject(LTGRAY_BRUSH));
        WindowClass.lpszClassName = WindowClassName;
        if (!RegisterClassExA(&WindowClass))
        {
            return false;
        }

        DWORD style   = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW;
        DWORD styleEx = 0;

        int       x = CW_USEDEFAULT, y = CW_USEDEFAULT;
        const int w = window->Width, h = window->Height;
        HWND handle = CreateWindowExA(styleEx, WindowClassPtr, window->Title.Data, style, x, y, w, h, nullptr, nullptr,
                                      instance, nullptr);

        PumpEvents(self);

        if (!SetupWindowState(self, window, handle))
        {
            return false;
        }
        Assert(window->State);

        return true;
    }

    void DestroyPlatformWindow(NonNullPtr<DisplayDevice> self, NonNullPtr<Window> window)
    {
        CleanUpWindowState(self, window);
    }

    void ShowWindow(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window)
    {
        Assert(window);
        Assert(window->State);

        auto& win32State = static_cast<Window32State&>(*window->State);
        ::ShowWindow(win32State.Handle, SW_SHOW);
    }

    void HideWindow(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window)
    {
        auto& win32State = static_cast<Window32State&>(*window->State);
        ::ShowWindow(win32State.Handle, SW_HIDE);
    }

    void SetWindowTitle(NonNullPtr<DisplayDevice> /*self*/, NonNullPtr<Window> window, String title)
    {
        Assert(window);
        Assert(window->State);

        auto& win32State = static_cast<Window32State&>(*window->State);
        SetWindowTextA(win32State.Handle, CStr(title));
    }
} // namespace Juliet::Win32
