#include "app.h"
#include "main_loop.h"

#include "runtime/aio.h"
#include "runtime/buffer_manager.h"
#include "runtime/config.h"

#include "renderer/common/renderer_api.h"

#include <stdbool.h>

RT_CVAR_I(rt_Fullscreen, "Show window in fullscreen mode. [0/1] Default: 0", 1);
RT_CVAR_I(rt_WindowWidth, "Window width. Default: 1024", 1024);
RT_CVAR_I(rt_WindowHeight, "Window height. Default: 768", 768);

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

static LRESULT CALLBACK win32WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProcW(hWnd, uMsg, wParam, lParam);
    }
}

RT_DLLEXPORT int rtWin32Entry(HINSTANCE hInstance,
                              HINSTANCE hPrevInstance,
                              PWSTR pCmdLine,
                              int nCmdShow,
                              rt_app_callbacks app_callbacks) {
    if (rtInitRuntime() != RT_SUCCESS)
        return 1;

    rtRegisterCVAR(&rt_Fullscreen);
    rtRegisterCVAR(&rt_WindowWidth);
    rtRegisterCVAR(&rt_WindowHeight);

    if (app_callbacks.RegisterCVars)
        app_callbacks.RegisterCVars();

    WNDCLASSEXW wndclass = {
        .cbSize        = sizeof(wndclass),
        .hInstance     = hInstance,
        .lpszClassName = L"rtWndClass",
        .style         = CS_OWNDC,
        .lpfnWndProc   = win32WndProc,
    };
    if (!RegisterClassExW(&wndclass)) {
        rtReportError("CORE", "RegisterClassEx failed: %u", GetLastError());
        return 1;
    }

    HWND wnd = NULL;
    if (rt_Fullscreen.i) {
        /* Fullscreen window */
        int w = GetSystemMetrics(SM_CXSCREEN);
        int h = GetSystemMetrics(SM_CYSCREEN);
        wnd   = CreateWindowExW(WS_EX_APPWINDOW,
                              L"rtWndClass",
                              L"Voyage",
                              WS_POPUP,
                              0,
                              0,
                              w,
                              h,
                              NULL,
                              NULL,
                              hInstance,
                              NULL);
        ShowWindow(wnd, SW_SHOW);
    } else {
        /* Windowed mode */
        int w = rt_WindowWidth.i;
        int h = rt_WindowHeight.i;
        wnd   = CreateWindowExW(WS_EX_APPWINDOW,
                              L"rtWndClass",
                              L"Voyage",
                              WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU,
                              CW_USEDEFAULT,
                              CW_USEDEFAULT,
                              w,
                              h,
                              NULL,
                              NULL,
                              hInstance,
                              NULL);
        ShowWindow(wnd, SW_SHOW);
    }

    if (!wnd) {
        rtReportError("CORE", "Failed to create the game window: %u", GetLastError());
        return 1;
    }

    unsigned int window_width, window_height;
    {
        RECT r;
        GetClientRect(wnd, &r);
        window_width = r.right;
        window_height = r.bottom;
    }
    rt_renderer_init_info renderer_info = {.hWnd          = wnd,
                                           .hInstance     = hInstance,
                                           .width         = window_width,
                                           .height        = window_height,
                                           .is_fullscreen = rt_Fullscreen.i};
    if (rtLoadRenderer() != RT_SUCCESS) {
        return 1;
    }
    if (g_renderer.Init(&renderer_info) != RT_SUCCESS) {
        return 1;
    }

    app_callbacks.Init();

    if (rtInitMainLoop(app_callbacks.Update, app_callbacks.Render) != RT_SUCCESS) {
        return 1;
    }

    /* Main Loop */
    bool keep_running = true;
    while (keep_running) {
        MSG msg;
        while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
            if (msg.message == WM_QUIT) {
                keep_running = false;
            } else {
                TranslateMessage(&msg);
                DispatchMessageW(&msg);
            }
        }
    }

    app_callbacks.Shutdown();
    rtShutdownMainLoop();
    g_renderer.Shutdown();
    DestroyWindow(wnd);
    UnregisterClassW(L"rtWndClass", hInstance);

    rtShutdownRuntime();

    return 0;
}

#elif defined(RT_USE_XLIB)

#include <X11/Xlib.h>
#include <assert.h>

static void xlibSetFullscreen(Display *dpy, int screen, Window window, bool enable) {
    Atom wm_state      = XInternAtom(dpy, "_NET_W_STATE", False);
    Atom wm_fullscreen = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
    if (wm_state == None || wm_fullscreen == None) {
        rtLog("CORE", "Window manager does not support fullscreen mode.");
        return;
    }

#define _NET_WM_STATE_REMOVE     0
#define _NET_WM_STATE_ADD        1
#define EVENT_SOURCE_APPLICATION 1
    XEvent ev;
    ev.type                 = ClientMessage;
    ev.xclient.display      = dpy;
    ev.xclient.window       = window;
    ev.xclient.message_type = wm_state;
    ev.xclient.format       = 32;
    ev.xclient.data.l[0]    = (enable) ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
    ev.xclient.data.l[1]    = wm_fullscreen;
    ev.xclient.data.l[2]    = 0;
    ev.xclient.data.l[3]    = EVENT_SOURCE_APPLICATION;
    ev.xclient.data.l[4]    = 0;

    Window root_window = XRootWindow(dpy, screen);
    long ev_mask       = SubstructureRedirectMask;

    if (!XSendEvent(dpy, root_window, False, ev_mask, &ev)) {
        rtReportError("CORE", "Failed to send x11 fullscreen event.");
    }

#undef _NET_WM_STATE_ADD
#undef _NET_WM_STATE_REMOVE
#undef EVENT_SOURCE_APPLICATION
}

RT_DLLEXPORT int rtXlibEntry(int argc, char **argv, rt_app_callbacks app_callbacks) {
    if (rtInitRuntime() != RT_SUCCESS)
        return 1;

    if (app_callbacks.RegisterCVars)
        app_callbacks.RegisterCVars();

    Display *dpy = XOpenDisplay(NULL);
    if (!dpy) {
        rtReportError("CORE", "Failed to open default display");
        return 1;
    }
    int screen = DefaultScreen(dpy);
    Window window;
    int w  = rt_WindowWidth.i;
    int h  = rt_WindowHeight.i;
    window = XCreateSimpleWindow(dpy,
                                 RootWindow(dpy, screen),
                                 10,
                                 10,
                                 w,
                                 h,
                                 1,
                                 BlackPixel(dpy, screen),
                                 WhitePixel(dpy, screen));
    XSelectInput(dpy, window, KeyPressMask);
    XMapWindow(dpy, window);
    XStoreName(dpy, window, "Voyage");

    Atom wm_close = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    if (!wm_close) {
        rtReportError("CORE", "Failed to find WM_DELETE_WINDOW atom.");
        XDestroyWindow(dpy, window);
        XCloseDisplay(dpy);
        return 1;
    }
    XSetWMProtocols(dpy, window, &wm_close, 1);

    if (rt_Fullscreen.i)
        xlibSetFullscreen(dpy, screen, window, true);

    rt_renderer_init_info renderer_info = {.display = dpy, .window = window};
    if (!rtInitGFX(&renderer_info)) {
        rtReportError("GFX", "Init failed.");
        return 1;
    }

    app_callbacks.Init();

    /* Main Loop */
    bool keep_running = true;
    while (keep_running) {
        while (XEventsQueued(dpy, QueuedAlready) > 0) {
            XEvent event;
            XNextEvent(dpy, &event);
            switch (event.type) {
            case KeyPress:
                break;
            case ButtonPressMask:
                /* Mouse down */
                break;
            case PointerMotionMask:
                /* Mouse movement */
                break;
            case ClientMessage:
                if (event.xclient.data.l[0] == (long)wm_close) {
                    rtLog("CORE", "Received WM_DELETE_WINDOW");
                    keep_running = false;
                }
                break;
            }
        }
    }

    app_callbacks.Shutdown();

    XDestroyWindow(dpy, window);
    XCloseDisplay(dpy);
    rtShutdownRuntime();

    return 0;
}

#endif
