#include "openwindow.h"

#include <stdio.h>

int initOpenGL() {
    if (getOpenGLProcs() == 0) return -1;
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

    return 0;
}

#ifndef _WIN32

#define GLLOADER glXGetProcAddress

typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*);

bool checkGLXVersion(Window window) {
    int glx_major, glx_minor;

    if (!glXQueryVersion(window.x.display, &glx_major, &glx_minor)) {
        return false;
    }

    return (glx_major == 1 && glx_minor >= 3) || ( glx_major > 1);
}

GLXFBConfig getBestFBConfig(Window window, GLXFBConfig * configs, size_t count) {
    int best = 0, best_samp = -1;

    for (size_t i = 0; i < count; ++i)
    {
        XVisualInfo * vi = glXGetVisualFromFBConfig(window.x.display, configs[i]);

        if (!vi) continue;

        int samp_buf, samples;

        glXGetFBConfigAttrib(window.x.display, configs[i], GLX_SAMPLE_BUFFERS, &samp_buf);
        glXGetFBConfigAttrib(window.x.display, configs[i], GLX_SAMPLES       , &samples);

        if (samp_buf && samples > best_samp)
          best = i, best_samp = samples;

        XFree(vi);
    }

    return configs[best];
}

int initGLX(Window * window)
{
    if (!checkGLXVersion(*window))
        return -1;

    int attribs[] = {
        GLX_X_RENDERABLE       , True,
        GLX_DRAWABLE_TYPE    , GLX_WINDOW_BIT,
        GLX_RENDER_TYPE      , GLX_RGBA_BIT,
        GLX_X_VISUAL_TYPE    , GLX_TRUE_COLOR,
        GLX_RED_SIZE         , 8,
        GLX_GREEN_SIZE       , 8,
        GLX_BLUE_SIZE       , 8,
        GLX_ALPHA_SIZE      , 8,
        GLX_DEPTH_SIZE      , 24,
        GLX_STENCIL_SIZE    , 8,
        GLX_DOUBLEBUFFER    , True,
        //GLX_SAMPLE_BUFFERS     , 1,
        //GLX_SAMPLES            , 4,
        None
    };

    int fbcount;
    GLXFBConfig * configs = glXChooseFBConfig(window->x.display, DefaultScreen(window->x.display), attribs, &fbcount);
    if (!configs || !fbcount) {
        fprintf(stderr, "Failed to retrieve a framebuffer config\n");
        return -1;
    }

    window->glx.config = getBestFBConfig(*window, configs, fbcount);

    XFree(configs);

    glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0;
    glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc)
        glXGetProcAddressARB( (const GLubyte *) "glXCreateContextAttribsARB" );

    int context_attribs[] = {
      GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
      GLX_CONTEXT_MINOR_VERSION_ARB, 0,
      //GLX_CONTEXT_FLAGS_ARB        , GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
      None
    };
    window->glx.context = glXCreateContextAttribsARB(window->x.display, window->glx.config, 0, True, context_attribs);

    return 0;
}

int init(Window * window, const char * name, size_t width, size_t height) {
    window->x.display = XOpenDisplay(NULL);
    if (window->x.display == NULL) {
        fprintf(stderr, "Failed to open xdisplay!\n");
        return -1;
    }

    if (initGLX(window))
        return -1;

    XVisualInfo * vi = glXGetVisualFromFBConfig(window->x.display, window->glx.config);

    XSetWindowAttributes wa;

    wa.border_pixel = 0;
    wa.colormap = window->x.cmap = XCreateColormap(window->x.display,
                                                   RootWindow(window->x.display, vi->screen),
                                                   vi->visual, AllocNone);

    window->x.eventMask = wa.event_mask =
        StructureNotifyMask |
        ExposureMask        |
        PointerMotionMask   |
        KeyPressMask        |
        KeyReleaseMask      |
        ButtonPressMask     |
        ButtonReleaseMask;

    X11Window root = RootWindow(window->x.display, vi->screen);

    window->x.window = XCreateWindow(
            window->x.display,
            root,
            0,
            0,
            width,
            height,
            0,
            vi->depth,
            InputOutput,
            vi->visual,
            CWBorderPixel|CWColormap|CWEventMask,
            &wa);

    if (window->x.window == None) {
        fprintf(stderr, "Failed to create x11 window!\n");
        return -1;
    }

    XFree(vi);

    XMapWindow(window->x.display, window->x.window);
    XStoreName(window->x.display, window->x.window, name);

    XSync(window->x.display, False);
    glXMakeCurrent(window->x.display, window->x.window, window->glx.context);

    return 0;
}

Window openWindow(const char * name, size_t width, size_t height) {
    Window w = {0};

    if (init(&w, name, width, height) < 0) {
        fprintf(stderr, "Failed to initialize window: X11 error!\n");
        exit(1);
    }

    if (initOpenGL() < 0) {
        fprintf(stderr, "Failed to initialize window: GLAD/OPENGL error!\n");
        exit(1);
    }

    return w;
}

void windowDraw(Window window) {
    glXSwapBuffers(window.x.display, window.x.window);
}

void windowSetEventHandler(Window * window, WindowEventHandler handler) {
    window->eventHandler = handler;
}

void windowHandleEvents(Window * window) {
    if (window->eventHandler == NULL) {
        fprintf(stderr, "Window event handler is not defined. Use `setEventHandler' to set it!\n");
        exit(1);
    }

    XEvent xev;

    if (XPending(window->x.display))
        if (XCheckWindowEvent(window->x.display, window->x.window, window->x.eventMask, &xev))
            window->eventHandler(window, xev);
}

void closeWindow(Window window) {
    glXMakeCurrent(window.x.display, None, None);
    glXDestroyContext(window.x.display, window.glx.context);

    XFreeColormap(window.x.display, window.x.cmap);
    XDestroyWindow(window.x.display, window.x.window);
    XCloseDisplay(window.x.display);
}

#else

#include <windows.h>

#define GLLOADER wglGetProcAddress

LRESULT CALLBACK wndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
    Window * window = (Window *)GetWindowLong(hwnd, GWLP_USERDATA);

	switch (msg)
	{
		case WM_CLOSE: {
            window->close = true;
			return 0;
		}
	}

	return (DefWindowProc(hwnd, msg, wParam, lParam));
}

int initWGL(Window * window, const char * title, size_t width, size_t height) {
    HINSTANCE hInstance = GetModuleHandle(NULL);
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_OWNDC;
	wcex.lpfnWndProc = &DefWindowProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = NULL;
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = 0;
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = title;
	wcex.hIconSm = NULL;
	wcex.lpfnWndProc = wndProc;

	RegisterClassEx(&wcex);
	RECT rect = { 0, 0, width, height };
	int style = WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME;
	AdjustWindowRect(&rect, style, FALSE);

	window->w.window  = CreateWindow(title, "example", style, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, GetModuleHandle(NULL), window);
	ShowWindow(window->w.window, SW_SHOW);

    SetWindowLong(window->w.window, GWLP_USERDATA, (LONG)window);

    return 0;
}

Window openWindow(const char * name, size_t width, size_t height) {
    Window w = {0};

    if (initWGL(&w, name, width, height) < 0) {
        fprintf(stderr, "Failed to initialize egl!\n");
        exit(1);
    }

    initOpenGL();

    eglMakeCurrent(w.egl.display, w.egl.surface, w.egl.surface, w.egl.context);

    return w;
}

void closeWindow(Window window) {
    DestroyWindow(window.w.window);
}

#endif // _WIN32

int getOpenGLProcs(void) {
    return gladLoadGLLoader((GLADloadproc)GLLOADER);
}
