#include <stdint.h>

#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <GL/glx.h>
#include <GL/gl.h>
#include <dlfcn.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <time.h>

#define SCRW 800
#define SCRH 600

static __inline long __syscall2(long n, long a1, long a2)
{
    unsigned long ret;
    __asm__ __volatile__ ("syscall" : "=a"(ret) : "a"(n), "D"(a1), "S"(a2)
            : "rcx", "r11", "memory");
    return ret;
}

static __inline uint64_t time_monotonic(void)
{
    struct timespec now;
    __syscall2(SYS_clock_gettime, CLOCK_MONOTONIC_RAW, (long)&now);
    return now.tv_sec * 1000000 + now.tv_nsec / 1000;
}

static int visual_hints[] = {
    GLX_RGBA,
    GLX_DEPTH_SIZE, 24,
    GLX_DOUBLEBUFFER,
    None
};

static const char *sym_names =
"libX11.so\0"
"XOpenDisplay\0"
"XCreateSimpleWindow\0"
"XSelectInput\0"
"XMapWindow\0"
"XPending\0"
"XNextEvent\0"
"XkbKeycodeToKeysym\0"
"\0"
"libGL.so\0"
"glXChooseVisual\0"
"glXCreateContext\0"
"glXMakeCurrent\0"
"glXSwapBuffers\0"
"glClear\0\0\0";

static void *syms[12];

#define _XOpenDisplay        ((Display *(*)(const char *))syms[0])
#define _XCreateSimpleWindow ((Window (*)(Display *, Window, int, int, unsigned, unsigned, unsigned, unsigned long, unsigned long))syms[1])
#define _XSelectInput        ((int (*)(Display *, Window, long))syms[2])
#define _XMapWindow          ((int (*)(Display *, Window))syms[3])
#define _XPending            ((int (*)(Display *))syms[4])
#define _XNextEvent          ((int (*)(Display *, XEvent *))syms[5])
#define _XkbKeycodeToKeysym  ((KeySym (*)(Display *, KeyCode, int, int))syms[6])

#define _glXChooseVisual     ((XVisualInfo *(*)(Display *, int, int *))syms[7])
#define _glXCreateContext    ((GLXContext (*)(Display *, XVisualInfo *, GLXContext, Bool))syms[8])
#define _glXMakeCurrent      ((Bool (*)(Display *, GLXDrawable, GLXContext))syms[9])
#define _glXSwapBuffers      ((void (*)(Display *, GLXDrawable))syms[10])

#define _glClear             ((void (*)(GLbitfield))syms[11])

static void dl_load(void)
{
    const char *name = sym_names;
    void **sym = syms;

    do
    {
        void *dl = dlopen(name, RTLD_GLOBAL);
        for(;;)
        {
            while(*name++);
            if(*name == 0) break;
            *sym++ = dlsym(dl, name);
        }
    } while(*++name);
}

void my_main(int argc, const char **argv)
{
    dl_load();

    Display *display = _XOpenDisplay(NULL);
    int screen = DefaultScreen(display);
    Window window = _XCreateSimpleWindow(display, RootWindow(display, screen), 0, 0, SCRW, SCRH, 0, 0, 0);
    _XSelectInput(display, window, KeyPressMask | KeyReleaseMask);
    XVisualInfo *visual_info = _glXChooseVisual(display, screen, visual_hints);
    GLXContext context = _glXCreateContext(display, visual_info, NULL, True);
    _glXMakeCurrent(display, window, context);

    _XMapWindow(display, window);

    uint64_t prev = time_monotonic();
    uint64_t t = 0;
    for(;;)
    {
        XEvent event;
        while(_XPending(display))
        {
            _XNextEvent(display, &event);
            if(event.type == KeyPress)
            {
                switch(_XkbKeycodeToKeysym(display, event.xkey.keycode, 0, 0))
                {
                    case XK_Escape: goto shutdown;
                }
            }
        }
        // draw demo

        _glClear(GL_COLOR_BUFFER_BIT);

        _glXSwapBuffers(display, window);

        t += time_monotonic() - prev;
    }

shutdown:
    return;
}

asm (
    ".text\n"
    ".global _start\n"
    "_start:\n"
    "xor %rbp,%rbp\n"
    "pop %rdi\n"
    "mov %rsp,%rsi\n"
    "andq $-16,%rsp\n"
    "call my_main\n"
    "movq $60,%rax\n"
    "xor %rdi,%rdi\n"
    "syscall\n"
);
