#include "../include/mgl/mgl.h"
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/Xrandr.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

static mgl_context context;
static int init_count = 0;
static XErrorHandler prev_xerror = NULL;
static XIOErrorHandler prev_xioerror = NULL;
static bool connected_to_x_server = false;

static int mgl_x_error_handler(Display *display, XErrorEvent *ee) {
    (void)display;
    (void)ee;
    return 0;
}

static int mgl_x_io_error_handler(Display *display) {
    (void)display;
    connected_to_x_server = false;
    return 0;
}

static bool xrender_is_supported(Display *display, int *event_base, int *error_base) {
    *event_base = 0;
    *error_base = 0;
    if(!XRenderQueryExtension(display, event_base, error_base))
        return false;

    int major_version = 0;
    int minor_version = 0;
    if(!XRenderQueryVersion(display, &major_version, &minor_version))
        return false;

    return major_version > 0 || (major_version == 0 && minor_version >= 7);
}

static bool xrandr_is_supported(Display *display, int *event_base, int *error_base) {
    *event_base = 0;
    *error_base = 0;
    if(!XRRQueryExtension(display, event_base, error_base))
        return false;

    int major_version = 0;
    int minor_version = 0;
    if(!XRRQueryVersion(display, &major_version, &minor_version))
        return false;

    return major_version > 1 || (major_version == 1 && minor_version >= 2);
}

int mgl_init(void) {
    ++init_count;
    if(init_count == 1) {
        setenv("__GL_MaxFramesAllowed", "1", true);
        memset(&context, 0, sizeof(context));

        context.connection = XOpenDisplay(NULL);
        if(!context.connection) {
            fprintf(stderr, "mgl error: XOpenDisplay failed\n");
            mgl_deinit();
            return -1;
        }
        connected_to_x_server = true;

        prev_xerror = XSetErrorHandler(mgl_x_error_handler);
        prev_xioerror = XSetIOErrorHandler(mgl_x_io_error_handler);

        if(!xrender_is_supported(context.connection, &context.render_event_base, &context.render_error_base)) {
            fprintf(stderr, "mgl error: x11 render extension is not supported by your X server\n");
            mgl_deinit();
            return -1;
        }

        if(!xrandr_is_supported(context.connection, &context.randr_event_base, &context.randr_error_base)) {
            fprintf(stderr, "mgl error: x11 randr extension is not supported by your X server\n");
            mgl_deinit();
            return -1;
        }

        XRRSelectInput(context.connection, DefaultRootWindow(context.connection), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask | RROutputChangeNotifyMask);

        XInitThreads();
        XkbSetDetectableAutoRepeat(context.connection, True, NULL);

        context.wm_delete_window_atom = XInternAtom(context.connection, "WM_DELETE_WINDOW", False);
        context.net_wm_ping_atom = XInternAtom(context.connection, "_NET_WM_PING", False);
        context.net_wm_pid_atom = XInternAtom(context.connection, "_NET_WM_PID", False);

        if(mgl_gl_load(&context.gl) != 0) {
            mgl_deinit();
            return -1;
        }
    }
    return 0;
}

void mgl_deinit(void) {
    if(init_count == 1) {
        if(context.connection) {
            XCloseDisplay(context.connection);
            context.connection = NULL;
            connected_to_x_server = false;

            /*
                GLX needs to be unloaded after closing the display on nvidia because
                nvidia registers cleanup callbacks on exit, that uses the x11 display.
            */
            mgl_gl_unload(&context.gl);
        }

        if(prev_xioerror) {
            XSetIOErrorHandler(prev_xioerror);
            prev_xioerror = NULL;
        }

        if(prev_xerror) {
            XSetErrorHandler(prev_xerror);
            prev_xerror = NULL;
        }

        context.current_window = NULL;
    }

    if(init_count > 0)
        --init_count;
}

mgl_context* mgl_get_context(void) {
#ifndef NDEBUG
    if(init_count == 0) {
        fprintf(stderr, "mgl error: mgl_get_context was called before mgl_init\n");
        abort();
    }
#endif
    return &context;
}

bool mgl_is_connected_to_display_server(void) {
    return connected_to_x_server;
}

void mgl_ping_display_server(void) {
    if(context.connection) {
        XNoOp(context.connection);
        XFlush(context.connection);
    }
}