#include "opengl.hpp"

#include <iostream>
#include <sys/time.h>
#include <unistd.h>
#include "options.hpp"

#define UNUSED(foo) (void) foo;

timespec diff(timespec start, timespec end) {
    timespec result;
    if ((end.tv_nsec - start.tv_nsec) < 0) {
        result.tv_sec = end.tv_sec - start.tv_sec - 1;
        result.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
    } else {
        result.tv_sec = end.tv_sec - start.tv_sec;
        result.tv_nsec = end.tv_nsec - start.tv_nsec;
    }
    return result;
}

namespace endofthejedi {

    GLWindow::GLWindow(unsigned int width, unsigned int height) : m_width(width), m_height(height)
    {
    }

    void GLWindow::open()
    {
        m_display = XOpenDisplay(NULL);

        if (m_display == NULL) {
            throw std::runtime_error("XOpenDisplay NULL");
        }

        m_rootwnd = DefaultRootWindow(m_display);

        m_visualinfo = glXChooseVisual(m_display, 0, m_attributes);

        if (m_visualinfo == NULL) {
            throw std::runtime_error("glXChooseVisual NULL");
        }

        m_colormap =
            XCreateColormap(m_display, m_rootwnd, m_visualinfo->visual, AllocNone);

        m_swa.colormap = m_colormap;
        m_swa.event_mask = ExposureMask | KeyPressMask;

        m_window = XCreateWindow(
            m_display, m_rootwnd, 0, 0, m_width, m_height, 0, m_visualinfo->depth,
            InputOutput, m_visualinfo->visual, CWColormap | CWEventMask, &m_swa);

        m_atomWmDeleteWindow = XInternAtom(m_display, "WM_DELETE_WINDOW", False);
        XSetWMProtocols(m_display, m_window, &m_atomWmDeleteWindow, 1);

        XMapWindow(m_display, m_window);
        XStoreName(m_display, m_window, "NAME");

        m_glcontext = glXCreateContext(m_display, m_visualinfo, NULL, GL_TRUE);

        XSync(m_display, False);

        glXMakeCurrent(m_display, m_window, m_glcontext);

        std::cout << "GL Renderer: " << glGetString(GL_RENDERER) << "\n";
        std::cout << "GL Version: " << glGetString(GL_VERSION) << "\n";
        std::cout << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION)
                  << "\n";

        m_running = true;

        init();

        XClearWindow(m_display, m_window);
        XMapRaised(m_display, m_window);
    }

    GLWindow::~GLWindow() {
        glXMakeCurrent(m_display, None, NULL);
        glXDestroyContext(m_display, m_glcontext);
        XFree(m_visualinfo);
        XDestroyWindow(m_display, m_window);
        XCloseDisplay(m_display);
    }

    void GLWindow::handleevents() {
        if (XPending(m_display) > 0) {
            XEvent xev;
            XNextEvent(m_display, &xev);
            handle(xev);
        }
    }

    void GLWindow::handle(XEvent event) {
        if (event.type == Expose) {
            XWindowAttributes attribs;
            XGetWindowAttributes(m_display, m_window, &attribs);
            m_width = attribs.width;
            m_height = attribs.height;
            resize();

        } else if (event.type == ClientMessage) {
            if (event.xclient.data.l[0] == (int) m_atomWmDeleteWindow) {
                stop();
            }
        } else if (event.type == DestroyNotify) {
            stop();
        }
    }

    void GLWindow::swap() { glXSwapBuffers(m_display, m_window); }

    void GLWindow::poll() {

        clock_gettime(CLOCK_MONOTONIC_RAW, &prev);
        clock_gettime(CLOCK_MONOTONIC_RAW, &current);

        handleevents();
        render(delta);
        swap();

        if (m_maxfps > 0) {
          sleeptime = ((1000000000.0/m_maxfps) - delta) + sleeptime;
          if (sleeptime > 0.0) {
            usleep((unsigned int)(sleeptime/1000.0));
            sleeptime = sleeptime - (unsigned int)(sleeptime/1000.0);
          }
        }

        clock_gettime(CLOCK_MONOTONIC_RAW, &current);
        delta = diff(prev, current).tv_nsec;
        prev = current;
        if(delta > 0.0) {
          m_fps = (1000000000.0/delta);
          //std::cout << m_fps << "\n";
        }
    }

    void GLWindow::stop() { m_running = false; }
    bool GLWindow::running() { return m_running; }
    void GLWindow::init() { }
    void GLWindow::render(double time) { UNUSED(time) }
    void GLWindow::resize() {}
}
