
#ifndef B3_USE_GLFW

#include "tiny_x11_opengl_window.h"
#include "tiny_opengl_include.h"

#include <stdio.h>
#include <stdlib.h>
#include "glad/gl.h"

#ifdef GLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS
#include "glad/glx.h"
#else
#include <GL/glx.h>
#endif  // GLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS
#include <assert.h>

//#define DYNAMIC_LOAD_X11_FUNCTIONS
#ifdef DYNAMIC_LOAD_X11_FUNCTIONS
#include <dlfcn.h>
#endif  // DYNAMIC_LOAD_X11_FUNCTIONS

//#include<X11/X.h>
//#include<X11/Xlib.h>
//#include<GL/gl.h>

// defined in GL/glxew.h
//#include<GL/glu.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <pthread.h>

GLint att[] = {GLX_RGBA, GLX_DEPTH_SIZE,   24,  GLX_RED_SIZE,
               8,        GLX_GREEN_SIZE,   8,   GLX_BLUE_SIZE,
               8,        GLX_ALPHA_SIZE,   8,   GLX_STENCIL_SIZE,
               8,        GLX_DOUBLEBUFFER, None};
/*
 static int att[] =
            {
                GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None

              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,
              None
            };
*/
static bool forceOpenGL3 = true;

#ifdef DYNAMIC_LOAD_X11_FUNCTIONS

/// our X11 function typedefs

typedef int (*PFNXFREE)(void*);
typedef XErrorHandler (*PFNXSETERRORHANDLER)(XErrorHandler);
typedef int (*PFNXSYNC)(Display* a, Bool b);
typedef Display* (*PFNXOPENDISPLAY)(_Xconst char* a);
typedef Colormap (*PFNXCREATECOLORMAP)(Display* a, Window b, Visual* c, int d);
typedef Window (*PFNXCREATEWINDOW)(Display* a, Window b, int c, int d,
                                   unsigned int e, unsigned int f,
                                   unsigned int g, int h, unsigned int i,
                                   Visual* j, unsigned long k,
                                   XSetWindowAttributes* l);
typedef int (*PFNXMAPWINDOW)(Display*, Window);
typedef int (*PFNXSTORENAME)(Display* a, Window b, _Xconst char* c);
typedef int (*PFNXCLOSEDISPLAY)(Display* a);
typedef int (*PFNXDESTROYWINDOW)(Display* a, Window b);
typedef int (*PFNXRAISEWINDOW)(Display* a, Window b);

#if NeedWidePrototypes
typedef KeySym* (*PFNXGETKEYBOARDMAPPING)(Display*, unsigned int, int, int*);
typedef KeySym (*PFNXKEYCODETOKEYSYM)(Display* a, unsigned int b, int c);
#else
typedef KeySym* (*PFNXGETKEYBOARDMAPPING)(Display*, KeyCode, int, int*);
typedef KeySym (*PFNXKEYCODETOKEYSYM)(Display* a, KeyCode b, int c);
#endif
typedef void (*PFNXCONVERTCASE)(KeySym /* sym */, KeySym* /* lower */,
                                KeySym* /* upper */);
typedef int (*PFNXPENDING)(Display* a);
typedef int (*PFNXNEXTEVENT)(Display* a, XEvent* b);
typedef int (*PFNXEVENTSQUEUED)(Display* a, int b);
typedef int (*PFNXPEEKEVENT)(Display* a, XEvent* b);
typedef KeySym (*PFNXLOOKUPKEYSYM)(XKeyEvent* a, int b);
typedef Status (*PFNXGETWINDOWATTRIBUTES)(Display* a, Window b,
                                          XWindowAttributes* c);

#define X11_LIBRARY "libX11.so.6"

#define MyXSync m_data->m_x11_XSync
#define MyXGetKeyboardMapping m_data->m_x11_XGetKeyboardMapping
#define MyXSetErrorHandler m_data->m_x11_XSetErrorHandler
#define MyXOpenDisplay m_data->m_x11_XOpenDisplay
#define MyXCreateColormap m_data->m_x11_XCreateColormap
#define MyXCreateWindow m_data->m_x11_XCreateWindow
#define MyXMapWindow m_data->m_x11_XMapWindow
#define MyXStoreName m_data->m_x11_XStoreName
#define MyXDestroyWindow m_data->m_x11_XDestroyWindow
#define MyXRaiseWindow m_data->m_x11_XRaiseWindow
#define MyXCloseDisplay m_data->m_x11_XCloseDisplay
#define MyXKeycodeToKeysym m_data->m_x11_XKeycodeToKeysym
#define MyXConvertCase m_data->m_x11_XConvertCase
#define MyXPending m_data->m_x11_XPending
#define MyXNextEvent m_data->m_x11_XNextEvent
#define MyXEventsQueued m_data->m_x11_XEventsQueued
#define MyXPeekEvent m_data->m_x11_XPeekEvent
#define MyXNextEvent m_data->m_x11_XNextEvent
#define MyXGetWindowAttributes m_data->m_x11_XGetWindowAttributes
#define MyXStoreName m_data->m_x11_XStoreName
#define MyXFree m_data->m_x11_XFree
#define MyXMapWindow m_data->m_x11_XMapWindow
#define MyXStoreName m_data->m_x11_XStoreName
#define MyXLookupKeysym m_data->m_x11_XLookupKeysym

#else
#define MyXSync XSync
#define MyXGetKeyboardMapping XGetKeyboardMapping
#define MyXSetErrorHandler XSetErrorHandler
#define MyXOpenDisplay XOpenDisplay
#define MyXCreateColormap XCreateColormap
#define MyXCreateWindow XCreateWindow
#define MyXMapWindow XMapWindow
#define MyXStoreName XStoreName
#define MyXDestroyWindow XDestroyWindow
#define MyXRaiseWindow XRaiseWindow
#define MyXCloseDisplay XCloseDisplay
#define MyXKeycodeToKeysym XKeycodeToKeysym
#define MyXConvertCase XConvertCase
#define MyXPending XPending
#define MyXNextEvent XNextEvent
#define MyXEventsQueued XEventsQueued
#define MyXPeekEvent XPeekEvent
#define MyXNextEvent XNextEvent
#define MyXGetWindowAttributes XGetWindowAttributes
#define MyXStoreName XStoreName
#define MyXFree XFree
#define MyXMapWindow XMapWindow
#define MyXStoreName XStoreName
#define MyXLookupKeysym XLookupKeysym

#endif  // DYNAMIC_LOAD_X11_FUNCTIONS

enum { MY_X11_ALT_KEY = 1, MY_X11_SHIFT_KEY = 2, MY_X11_CONTROL_KEY = 4 };

struct InternalData2 {
  Display* m_dpy;
  Window m_root;
  XVisualInfo* m_vi;
  Colormap m_cmap;
  XSetWindowAttributes m_swa;
  Window m_win;
  GLXContext m_glc;
  XWindowAttributes m_gwa;
  XEvent m_xev;
  GLXFBConfig m_bestFbc;
  int m_modifierFlags;
  int m_glWidth;
  int m_glHeight;

#ifdef DYNAMIC_LOAD_X11_FUNCTIONS
  // dynamically load stuff
  void* m_x11_library;
  PFNXFREE m_x11_XFree;
  PFNXSETERRORHANDLER m_x11_XSetErrorHandler;
  PFNXSYNC m_x11_XSync;
  PFNXOPENDISPLAY m_x11_XOpenDisplay;
  PFNXCREATECOLORMAP m_x11_XCreateColormap;
  PFNXCREATEWINDOW m_x11_XCreateWindow;
  PFNXMAPWINDOW m_x11_XMapWindow;
  PFNXSTORENAME m_x11_XStoreName;
  PFNXCLOSEDISPLAY m_x11_XCloseDisplay;
  PFNXDESTROYWINDOW m_x11_XDestroyWindow;
  PFNXRAISEWINDOW m_x11_XRaiseWindow;
  PFNXKEYCODETOKEYSYM m_x11_XKeycodeToKeysym;
  PFNXGETKEYBOARDMAPPING m_x11_XGetKeyboardMapping;
  PFNXCONVERTCASE m_x11_XConvertCase;
  PFNXPENDING m_x11_XPending;
  PFNXNEXTEVENT m_x11_XNextEvent;
  PFNXEVENTSQUEUED m_x11_XEventsQueued;
  PFNXPEEKEVENT m_x11_XPeekEvent;
  PFNXLOOKUPKEYSYM m_x11_XLookupKeysym;
  PFNXGETWINDOWATTRIBUTES m_x11_XGetWindowAttributes;
#endif  // DYNAMIC_LOAD_X11_FUNCTIONS

  TinyWheelCallback m_wheelCallback;
  TinyMouseMoveCallback m_mouseMoveCallback;
  TinyMouseButtonCallback m_mouseButtonCallback;
  TinyResizeCallback m_resizeCallback;
  TinyKeyboardCallback m_keyboardCallback;

  InternalData2()
      : m_dpy(0),
        m_vi(0),
        m_modifierFlags(0),
        m_glWidth(-1),
        m_glHeight(-1),
        m_wheelCallback(0),
        m_mouseMoveCallback(0),
        m_mouseButtonCallback(0),
        m_resizeCallback(0),
        m_keyboardCallback(0) {
#ifdef DYNAMIC_LOAD_X11_FUNCTIONS
    m_x11_library = dlopen(X11_LIBRARY, RTLD_LOCAL | RTLD_NOW);
    if (!m_x11_library) {
      // TODO: Properly handle this error.
      fprintf(stderr, "Error opening X11 library %s: %s\n", X11_LIBRARY,
              dlerror());
      exit(EXIT_FAILURE);
    }

    bool missingFunc = false;

    missingFunc =
        ((m_x11_XFree = (PFNXFREE)dlsym(m_x11_library, "XFree")) == NULL) |
        missingFunc;
    assert(!missingFunc);
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XFree in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XSetErrorHandler = (PFNXSETERRORHANDLER)dlsym(
                        m_x11_library, "XSetErrorHandler")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XSetErrorHandler in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XSetErrorHandler = (PFNXSETERRORHANDLER)dlsym(
                        m_x11_library, "XSetErrorHandler")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XSetErrorHandler in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc =
        ((m_x11_XSync = (PFNXSYNC)dlsym(m_x11_library, "XSync")) == NULL) |
        missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XSync in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XOpenDisplay = (PFNXOPENDISPLAY)dlsym(
                        m_x11_library, "XOpenDisplay")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XOpenDisplay in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XCreateColormap = (PFNXCREATECOLORMAP)dlsym(
                        m_x11_library, "XCreateColormap")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XCreateColormap in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XCreateWindow = (PFNXCREATEWINDOW)dlsym(
                        m_x11_library, "XCreateWindow")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XCreateWindow in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XMapWindow = (PFNXMAPWINDOW)dlsym(
                        m_x11_library, "XMapWindow")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XMapWindow in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XStoreName = (PFNXSTORENAME)dlsym(
                        m_x11_library, "XStoreName")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XStoreName in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XCloseDisplay = (PFNXCLOSEDISPLAY)dlsym(
                        m_x11_library, "XCloseDisplay")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XCloseDisplay in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XDestroyWindow = (PFNXDESTROYWINDOW)dlsym(
                        m_x11_library, "XDestroyWindow")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XDestroyWindow in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XRaiseWindow = (PFNXRAISEWINDOW)dlsym(
                        m_x11_library, "XRaiseWindow")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XRaiseWindow in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }

    missingFunc = ((m_x11_XGetKeyboardMapping = (PFNXGETKEYBOARDMAPPING)dlsym(
                        m_x11_library, "XGetKeyboardMapping")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr,
              "Error: missing func XGetKeyboardMapping in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XKeycodeToKeysym = (PFNXKEYCODETOKEYSYM)dlsym(
                        m_x11_library, "XKeycodeToKeysym")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XKeycodeToKeysym in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XConvertCase = (PFNXCONVERTCASE)dlsym(
                        m_x11_library, "XConvertCase")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XConvertCase in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XPending = (PFNXPENDING)dlsym(m_x11_library,
                                                        "XPending")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XPending in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XNextEvent = (PFNXNEXTEVENT)dlsym(
                        m_x11_library, "XNextEvent")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XNextEvent in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XEventsQueued = (PFNXEVENTSQUEUED)dlsym(
                        m_x11_library, "XEventsQueued")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XEventsQueued in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XPeekEvent = (PFNXPEEKEVENT)dlsym(
                        m_x11_library, "XPeekEvent")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XPeekEvent in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XLookupKeysym = (PFNXLOOKUPKEYSYM)dlsym(
                        m_x11_library, "XLookupKeysym")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr, "Error: missing func XLookupKeysym in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }
    missingFunc = ((m_x11_XGetWindowAttributes = (PFNXGETWINDOWATTRIBUTES)dlsym(
                        m_x11_library, "XGetWindowAttributes")) == NULL) |
                  missingFunc;
    if (missingFunc) {
      fprintf(stderr,
              "Error: missing func XGetWindowAttributes in %s, exiting!\n",
              X11_LIBRARY);
      exit(EXIT_FAILURE);
    }

    if (missingFunc) {
      fprintf(stderr, "Error: a missing func in %s, exiting!\n", X11_LIBRARY);
      exit(EXIT_FAILURE);
    } else {
      printf("X11 functions dynamically loaded using dlopen/dlsym OK!\n");
    }
#endif  // DYNAMIC_LOAD_X11_FUNCTIONS
  }
};

#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig,
                                                     GLXContext, Bool,
                                                     const int*);

// Helper to check for extension string presence.  Adapted from:
//   http://www.opengl.org/resources/features/OGLextensions/
static bool isExtensionSupported(const char* extList, const char* extension) {
  const char* start;
  const char *where, *terminator;

  /* Extension names should not have spaces. */
  where = strchr(extension, ' ');
  if (where || *extension == '\0') return false;

  /* It takes a bit of care to be fool-proof about parsing the
OpenGL extensions string. Don't be fooled by sub-strings,
etc. */
  for (start = extList;;) {
    where = strstr(start, extension);

    if (!where) break;

    terminator = where + strlen(extension);

    if (where == start || *(where - 1) == ' ')
      if (*terminator == ' ' || *terminator == '\0') return true;

    start = terminator;
  }

  return false;
}

static bool ctxErrorOccurred = false;
static int ctxErrorHandler(Display* dpy, XErrorEvent* ev) {
  ctxErrorOccurred = true;
  return 0;
}

TinyX11OpenGLWindow::TinyX11OpenGLWindow()
    : m_OpenGLInitialized(false), m_requestedExit(false) {
  m_data = new InternalData2;
}

TinyX11OpenGLWindow::~TinyX11OpenGLWindow() {
  if (m_OpenGLInitialized) {
    disable_opengl();
  }

  delete m_data;
}

void TinyX11OpenGLWindow::enable_opengl() {
  if (forceOpenGL3) {
    // Get the default screen's GLX extension list
    const char* glxExts =
        glXQueryExtensionsString(m_data->m_dpy, DefaultScreen(m_data->m_dpy));

    // NOTE: It is not necessary to create or make current to a context before
    // calling glXGetProcAddressARB, unless we dynamically load OpenGL/GLX/X11

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

    GLXContext ctx = 0;

    // Install an X error handler so the application won't exit if GL 3.3
    // context allocation fails.
    //
    // Note this error handler is global.  All display connections in all
    // threads of a process use the same error handler, so be sure to guard
    // against other threads issuing X commands while this code is running.
    ctxErrorOccurred = false;
    int (*oldHandler)(Display*, XErrorEvent*) =
        MyXSetErrorHandler(&ctxErrorHandler);

    // Check for the GLX_ARB_create_context extension string and the function.
    // If either is not present, use GLX 1.3 context creation method.
    if (!isExtensionSupported(glxExts, "GLX_ARB_create_context") ||
        !glXCreateContextAttribsARB) {
      printf(
          "glXCreateContextAttribsARB() not found"
          " ... using old-style GLX context\n");
      ctx = glXCreateNewContext(m_data->m_dpy, m_data->m_bestFbc, GLX_RGBA_TYPE,
                                0, True);
    }

    // If it does, try to get a GL 3.3 context!
    else {
      int context_attribs[] = {GLX_CONTEXT_MAJOR_VERSION_ARB,
                               3,
                               GLX_CONTEXT_MINOR_VERSION_ARB,
                               3,
                               GLX_CONTEXT_FLAGS_ARB,
                               GLX_CONTEXT_DEBUG_BIT_ARB,
                               GLX_CONTEXT_PROFILE_MASK_ARB,
                               GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
                               None};
      /*
int context_attribs[] =
{
GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
GLX_CONTEXT_MINOR_VERSION_ARB, 2,

//GLX_CONTEXT_FLAGS_ARB        , GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
None
};
*/
      printf("Creating context\n");
      ctx = glXCreateContextAttribsARB(m_data->m_dpy, m_data->m_bestFbc, 0,
                                       True, context_attribs);

      // Sync to ensure any errors generated are processed.
      MyXSync(m_data->m_dpy, False);
      if (!ctxErrorOccurred && ctx)
        printf("Created GL 3.3 context\n");
      else {
        // Couldn't create GL 3.3 context.  Fall back to old-style 2.x context.
        // When a context version below 3.0 is requested, implementations will
        // return the newest context version compatible with OpenGL versions
        // less than version 3.0. GLX_CONTEXT_MAJOR_VERSION_ARB = 1
        context_attribs[1] = 1;
        // GLX_CONTEXT_MINOR_VERSION_ARB = 0
        context_attribs[3] = 0;

        ctxErrorOccurred = false;

        printf(
            "Failed to create GL 3.3 context"
            " ... using old-style GLX context\n");
        ctx = glXCreateContextAttribsARB(m_data->m_dpy, m_data->m_bestFbc, 0,
                                         True, context_attribs);
      }
    }

    // Sync to ensure any errors generated are processed.
    MyXSync(m_data->m_dpy, False);

    // Restore the original error handler
    MyXSetErrorHandler(oldHandler);

    if (ctxErrorOccurred || !ctx) {
      fprintf(stderr, "Failed to create an OpenGL context\n");
      exit(1);
    }

    // Verifying that context is a direct context
    if (!glXIsDirect(m_data->m_dpy, ctx)) {
      printf("Indirect GLX rendering context obtained\n");
    } else {
      printf("Direct GLX rendering context obtained\n");
    }

    printf("Making context current\n");
    glXMakeCurrent(m_data->m_dpy, m_data->m_win, ctx);
    m_data->m_glc = ctx;
  } else {
    m_data->m_glc =
        glXCreateContext(m_data->m_dpy, m_data->m_vi, NULL, GL_TRUE);
    glXMakeCurrent(m_data->m_dpy, m_data->m_win, m_data->m_glc);
  }

  if (!gladLoaderLoadGL()) {
    printf("gladLoadGL failed!\n");
    exit(-1);
  }

  const GLubyte* ven = glGetString(GL_VENDOR);
  printf("GL_VENDOR=%s\n", ven);

  const GLubyte* ren = glGetString(GL_RENDERER);
  printf("GL_RENDERER=%s\n", ren);
  const GLubyte* ver = glGetString(GL_VERSION);
  printf("GL_VERSION=%s\n", ver);
  const GLubyte* sl = glGetString(GL_SHADING_LANGUAGE_VERSION);
  printf("GL_SHADING_LANGUAGE_VERSION=%s\n", sl);

  // Access pthreads as a workaround for a bug in Linux/Ubuntu
  // See
  // https://bugs.launchpad.net/ubuntu/+source/nvidia-graphics-drivers-319/+bug/1248642

  int i = pthread_getconcurrency();
  printf("pthread_getconcurrency()=%d\n", i);

  //    const GLubyte* ext = glGetString(GL_EXTENSIONS);
  //    printf("GL_EXTENSIONS=%s\n", ext);
}

void TinyX11OpenGLWindow::disable_opengl() {
  glXMakeCurrent(m_data->m_dpy, None, NULL);
  glXDestroyContext(m_data->m_dpy, m_data->m_glc);
}

void TinyX11OpenGLWindow::create_window(const TinyWindowConstructionInfo& ci) {
  m_data->m_dpy = MyXOpenDisplay(NULL);

  m_data->m_glWidth = ci.m_width;
  m_data->m_glHeight = ci.m_height;

  if (m_data->m_dpy == NULL) {
    fprintf(stderr, "\n\tcannot connect to X server\n\n");
    exit(EXIT_FAILURE);
  }

  m_data->m_root = DefaultRootWindow(m_data->m_dpy);

#ifdef GLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS

  int res = gladLoaderLoadGLX(m_data->m_dpy, DefaultScreen(m_data->m_dpy));
  if (!res) {
    printf("Error in gladLoadGLX\n");
    exit(0);
  }

#endif

  if (ci.m_openglVersion < 3) {
    forceOpenGL3 = false;
  }

  if (forceOpenGL3) {
    int glxMinor, glxMajor;
    if (!glXQueryVersion(m_data->m_dpy, &glxMajor, &glxMinor) ||
        (((glxMajor == 1) && (glxMinor < 3)) || (glxMajor < 1))) {
      fprintf(stderr, "Invalid GLX version: major %d, minor %d\n", glxMajor,
              glxMinor);
      exit(EXIT_FAILURE);
    }

    static int visual_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,
                                   None};
    int fbcount;
    GLXFBConfig* fbc = glXChooseFBConfig(
        m_data->m_dpy, DefaultScreen(m_data->m_dpy), visual_attribs, &fbcount);
    if (!fbc) {
      fprintf(stderr, "Failed to retrieve a framebuffer config\n");
      exit(1);
    }
/// don't use highest samples, it is really slow on some NVIDIA Quadro cards
#ifdef USE_HIGHEST_SAMPLES
    int best_fbc = -1, worst_fbc = -1, best_num_samp = -1, worst_num_samp = 999;

    int i;
    for (i = 0; i < fbcount; ++i) {
      XVisualInfo* vi = glXGetVisualFromFBConfig(m_data->m_dpy, fbc[i]);
      if (vi) {
        int samp_buf, samples;
        glXGetFBConfigAttrib(m_data->m_dpy, fbc[i], GLX_SAMPLE_BUFFERS,
                             &samp_buf);
        glXGetFBConfigAttrib(m_data->m_dpy, fbc[i], GLX_SAMPLES, &samples);

        // printf( "  Matching fbconfig %d, visual ID 0x%2x: SAMPLE_BUFFERS =
        // %d,"
        //       " SAMPLES = %d\n",
        //      i, vi -> visualid, samp_buf, samples );

        if (best_fbc < 0 || (samp_buf && (samples > best_num_samp)))
          best_fbc = i, best_num_samp = samples;
        if (worst_fbc < 0 || (!samp_buf || (samples < worst_num_samp)))
          worst_fbc = i, worst_num_samp = samples;
      }
      MyXFree(vi);
    }

    m_data->m_bestFbc = fbc[best_fbc];
#else
    m_data->m_bestFbc = *fbc;
#endif
    // Be sure to free the FBConfig list allocated by glXChooseFBConfig()
    MyXFree(fbc);

    m_data->m_vi = glXGetVisualFromFBConfig(m_data->m_dpy, m_data->m_bestFbc);

    m_data->m_swa.colormap = m_data->m_cmap = MyXCreateColormap(
        m_data->m_dpy, RootWindow(m_data->m_dpy, m_data->m_vi->screen),
        m_data->m_vi->visual, AllocNone);
    m_data->m_swa.background_pixmap = None;
    m_data->m_swa.border_pixel = 0;
    m_data->m_swa.event_mask = ExposureMask | KeyReleaseMask | KeyPressMask |
                               ButtonPressMask | ButtonReleaseMask |
                               PointerMotionMask | StructureNotifyMask;
    ;
    m_data->m_root = RootWindow(m_data->m_dpy, m_data->m_vi->screen);

    m_data->m_win = MyXCreateWindow(
        m_data->m_dpy, m_data->m_root, 0, 0, ci.m_width, ci.m_height, 0,
        m_data->m_vi->depth, InputOutput, m_data->m_vi->visual,
        CWBorderPixel | CWColormap | CWEventMask, &m_data->m_swa);

    // m_data->m_win = m_data->m_x11_XCreateWindow(m_data->m_dpy,
    // m_data->m_root, 0, 0, ci.m_width, ci.m_height, 0, m_data->m_vi->depth,
    // InputOutput, m_data->m_vi->visual, CWColormap | CWEventMask,
    // &m_data->m_swa);

    if (!m_data->m_win) {
      fprintf(stderr, "Cannot create window\n");
      exit(EXIT_FAILURE);
    }

    MyXMapWindow(m_data->m_dpy, m_data->m_win);
    MyXStoreName(m_data->m_dpy, m_data->m_win, "OpenGL3 Window");
  } else {
    m_data->m_vi = glXChooseVisual(m_data->m_dpy, 0, att);

    printf("4\n");

    if (m_data->m_vi == NULL) {
      fprintf(stderr, "\n\tno appropriate visual found\n\n");
      exit(EXIT_FAILURE);
    } else {
      printf(
          "\n\tvisual %p selected\n",
          (void*)m_data->m_vi
              ->visualid); /* %p creates hexadecimal output like in glxinfo */
    }

    m_data->m_cmap = MyXCreateColormap(m_data->m_dpy, m_data->m_root,
                                       m_data->m_vi->visual, AllocNone);
    m_data->m_swa.colormap = m_data->m_cmap;
    m_data->m_swa.event_mask = ExposureMask | KeyReleaseMask | KeyPressMask |
                               ButtonPressMask | ButtonReleaseMask |
                               PointerMotionMask | StructureNotifyMask;
    m_data->m_win = MyXCreateWindow(
        m_data->m_dpy, m_data->m_root, 0, 0, ci.m_width, ci.m_height, 0,
        m_data->m_vi->depth, InputOutput, m_data->m_vi->visual,
        CWColormap | CWEventMask, &m_data->m_swa);

    MyXMapWindow(m_data->m_dpy, m_data->m_win);

    MyXStoreName(m_data->m_dpy, m_data->m_win, "OpenGL2 Window");
  }

  enable_opengl();
}

void TinyX11OpenGLWindow::close_window() {
  disable_opengl();

  MyXDestroyWindow(m_data->m_dpy, m_data->m_win);
  MyXCloseDisplay(m_data->m_dpy);
}

int TinyX11OpenGLWindow::get_ascii_code_from_virtual_keycode(int keycode) {
  int result = 0;

  KeySym key, key_lc, key_uc;

  int keysyms_per_keycode_return;
  KeySym* keysym = MyXGetKeyboardMapping(m_data->m_dpy, keycode, 1,
                                         &keysyms_per_keycode_return);

  key = keysym[0];

  // key = MyXKeycodeToKeysym( m_data->m_dpy, keycode, 0 );

  switch (key) {
    case XK_Escape:
      return TINY_KEY_ESCAPE;
    case XK_Return:
      return TINY_KEY_RETURN;

    case XK_Control_L:
    case XK_Control_R: {
      return TINY_KEY_CONTROL;
    }
    case XK_Left:
      return TINY_KEY_LEFT_ARROW;
    case XK_Right:
      return TINY_KEY_RIGHT_ARROW;
    case XK_Up:
      return TINY_KEY_UP_ARROW;
    case XK_Down:
      return TINY_KEY_DOWN_ARROW;

    case XK_Alt_L:
    case XK_Alt_R: {
      return TINY_KEY_ALT;
    }
    case XK_Shift_L:
    case XK_Shift_R:
      return TINY_KEY_SHIFT;
    case XK_F1:
      return TINY_KEY_F1;
    case XK_F2:
      return TINY_KEY_F2;
    case XK_F3:
      return TINY_KEY_F3;
    case XK_F4:
      return TINY_KEY_F4;
    case XK_F5:
      return TINY_KEY_F5;
    case XK_F6:
      return TINY_KEY_F6;
    case XK_F7:
      return TINY_KEY_F7;
    case XK_F8:
      return TINY_KEY_F8;
    case XK_F9:
      return TINY_KEY_F9;
    case XK_F10:
      return TINY_KEY_F10;
    case XK_F11:
      return TINY_KEY_F11;
    case XK_F12:
      return TINY_KEY_F12;
    case XK_F13:
      return TINY_KEY_F13;
    case XK_F14:
      return TINY_KEY_F14;
    case XK_F15:
      return TINY_KEY_F15;
    default:
      // Make lowercase
      MyXConvertCase(key, &key_lc, &key_uc);
      key = key_lc;
      // Valid ISO 8859-1 character?
      if ((key >= 32 && key <= 126) || (key >= 160 && key <= 255)) {
        return (int)key;
      }
      result = -1;
  }

  MyXFree(keysym);

  return result;
}

bool TinyX11OpenGLWindow::is_modifier_key_pressed(int key) {
  bool isPressed = false;

  switch (key) {
    case TINY_KEY_ALT: {
      isPressed = ((m_data->m_modifierFlags & MY_X11_ALT_KEY) != 0);
      break;
    };
    case TINY_KEY_SHIFT: {
      isPressed = ((m_data->m_modifierFlags & MY_X11_SHIFT_KEY) != 0);
      break;
    };
    case TINY_KEY_CONTROL: {
      isPressed = ((m_data->m_modifierFlags & MY_X11_CONTROL_KEY) != 0);
      break;
    };

    default: {}
  };
  return isPressed;
}

void TinyX11OpenGLWindow::pump_message() {
  int buttonState = 1;

  // Process all pending events
  while (MyXPending(m_data->m_dpy)) {
    MyXNextEvent(m_data->m_dpy, &m_data->m_xev);
    //      printf("#");
    //      fflush(stdout);
    switch (m_data->m_xev.type) {
      case KeyPress: {
        int keycode =
            get_ascii_code_from_virtual_keycode(m_data->m_xev.xkey.keycode);
        switch (keycode) {
          case TINY_KEY_ALT:
            m_data->m_modifierFlags |= MY_X11_ALT_KEY;
            break;
          case TINY_KEY_SHIFT:
            m_data->m_modifierFlags |= MY_X11_SHIFT_KEY;
            break;
          case TINY_KEY_CONTROL:
            m_data->m_modifierFlags |= MY_X11_CONTROL_KEY;
            break;
          default: {}
        };
        if (m_data->m_keyboardCallback) {
          int state = 1;
          (*m_data->m_keyboardCallback)(keycode, state);
          //    printf("keycode %d",keycode);
          //  fflush(stdout);
        }
        break;
      }

      case KeyRelease: {
        //           fflush(stdout);
        int keycode =
            get_ascii_code_from_virtual_keycode(m_data->m_xev.xkey.keycode);
        switch (keycode) {
          case TINY_KEY_ALT:
            m_data->m_modifierFlags &= ~MY_X11_ALT_KEY;
            break;
          case TINY_KEY_SHIFT:
            m_data->m_modifierFlags &= ~MY_X11_SHIFT_KEY;
            break;
          case TINY_KEY_CONTROL:
            m_data->m_modifierFlags &= ~MY_X11_CONTROL_KEY;
            break;
          default: {}
        };

        if (m_data->m_keyboardCallback) {
#if 1
          unsigned short is_retriggered = 0;
          /// filter out keyboard repeat
          // see
          // http://stackoverflow.com/questions/2100654/ignore-auto-repeat-in-x11-applications
          if (MyXEventsQueued(m_data->m_dpy, QueuedAfterReading)) {
            XEvent nev;
            MyXPeekEvent(m_data->m_dpy, &nev);

            if (nev.type == KeyPress &&
                nev.xkey.time == m_data->m_xev.xkey.time &&
                nev.xkey.keycode == m_data->m_xev.xkey.keycode) {
              // fprintf (stdout, "key #%ld was retriggered.\n",
              // (long) MyXLookupKeysym(&nev.xkey, 0));

              // delete retriggered KeyPress event
              MyXNextEvent(m_data->m_dpy, &m_data->m_xev);
              is_retriggered = 1;
            }
          }
#endif
          int state = 0;
          if (!is_retriggered) (*m_data->m_keyboardCallback)(keycode, state);
        }

        break;
      }

      case ButtonRelease:
        buttonState = 0;
        // continue with ButtonPress code
      case ButtonPress: {
        //                printf("!");
        //                fflush(stdout);

        int button = -1;

        switch (m_data->m_xev.xbutton.button) {
          case Button1: {
            button = 0;
            break;
          }
          case Button2: {
            button = 1;
            break;
          }
          case Button3: {
            button = 2;
            break;
          }
          case Button4: {
            if (m_data->m_wheelCallback) {
              (*m_data->m_wheelCallback)(0, 10);
            }
            break;
          }
          case Button5: {
            if (m_data->m_wheelCallback) {
              (*m_data->m_wheelCallback)(0, -10);
            }
            break;
          }
        }
        int xpos = m_data->m_xev.xmotion.x;
        int ypos = m_data->m_xev.xmotion.y;

        if (button >= 0 && m_data->m_mouseButtonCallback) {
          //                      printf("xpos = %d, ypos = %d\n",xpos,ypos);

          (*m_data->m_mouseButtonCallback)(button, buttonState, xpos, ypos);
        }
        break;
      }
      case MotionNotify: {
        //                printf("!");
        //                fflush(0);
        if (m_data->m_mouseMoveCallback) {
          int xpos = m_data->m_xev.xmotion.x;
          int ypos = m_data->m_xev.xmotion.y;
          (*m_data->m_mouseMoveCallback)(xpos, ypos);
        }
        break;
      }
      case ConfigureNotify: {
        //              printf("@");
        //              fflush(0);
        m_data->m_glWidth = m_data->m_xev.xconfigure.width;
        m_data->m_glHeight = m_data->m_xev.xconfigure.height;

        if (m_data->m_resizeCallback) {
          (*m_data->m_resizeCallback)(m_data->m_xev.xconfigure.width,
                                      m_data->m_xev.xconfigure.height);
        }
        break;
      }
      case ClientMessage: {
        //              printf("?");
        //              fflush(stdout);
        break;
      }
      case Expose: {
        break;
      }
      case DestroyNotify: {
        break;
      }
      default: {
        // XRRUpdateConfiguration( &event );
      }
    };
  }
}

void TinyX11OpenGLWindow::start_rendering() {
  pump_message();

  MyXGetWindowAttributes(m_data->m_dpy, m_data->m_win, &m_data->m_gwa);
  glViewport(0, 0, m_data->m_gwa.width, m_data->m_gwa.height);

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
          GL_STENCIL_BUFFER_BIT);  // clear buffers

  // glCullFace(GL_BACK);
  // glFrontFace(GL_CCW);
  glEnable(GL_DEPTH_TEST);
}

void TinyX11OpenGLWindow::render_all_objects() {}

void TinyX11OpenGLWindow::end_rendering() {
  glXSwapBuffers(m_data->m_dpy, m_data->m_win);
}

void TinyX11OpenGLWindow::run_main_loop() {}

float TinyX11OpenGLWindow::get_time_in_seconds() { return 0.f; }

bool TinyX11OpenGLWindow::requested_exit() const { return m_requestedExit; }

void TinyX11OpenGLWindow::set_request_exit() { m_requestedExit = true; }

void TinyX11OpenGLWindow::set_render_callback(
    TinyRenderCallback renderCallback) {}

void TinyX11OpenGLWindow::set_window_title(const char* title) {
  MyXStoreName(m_data->m_dpy, m_data->m_win, title);
}

void TinyX11OpenGLWindow::set_wheel_callback(TinyWheelCallback wheelCallback) {
  m_data->m_wheelCallback = wheelCallback;
}

void TinyX11OpenGLWindow::set_mouse_move_callback(
    TinyMouseMoveCallback mouseCallback) {
  m_data->m_mouseMoveCallback = mouseCallback;
}

void TinyX11OpenGLWindow::set_mouse_button_callback(
    TinyMouseButtonCallback mouseCallback) {
  m_data->m_mouseButtonCallback = mouseCallback;
}

void TinyX11OpenGLWindow::set_resize_callback(
    TinyResizeCallback resizeCallback) {
  if (resizeCallback && m_data->m_glWidth > 0 && m_data->m_glHeight > 0) {
    resizeCallback(m_data->m_glWidth, m_data->m_glHeight);
  }
  m_data->m_resizeCallback = resizeCallback;
}

void TinyX11OpenGLWindow::set_keyboard_callback(
    TinyKeyboardCallback keyboardCallback) {
  m_data->m_keyboardCallback = keyboardCallback;
}

TinyMouseMoveCallback TinyX11OpenGLWindow::get_mouse_move_callback() {
  return m_data->m_mouseMoveCallback;
}
TinyMouseButtonCallback TinyX11OpenGLWindow::get_mouse_button_callback() {
  return m_data->m_mouseButtonCallback;
}
TinyResizeCallback TinyX11OpenGLWindow::get_resize_callback() {
  return m_data->m_resizeCallback;
}
TinyWheelCallback TinyX11OpenGLWindow::get_wheel_callback() {
  return m_data->m_wheelCallback;
}

TinyKeyboardCallback TinyX11OpenGLWindow::get_keyboard_callback() {
  return m_data->m_keyboardCallback;
}

int TinyX11OpenGLWindow::get_width() const {
  if (m_data) return m_data->m_glWidth;
  return 0;
}
int TinyX11OpenGLWindow::get_height() const {
  if (m_data) return m_data->m_glHeight;
  return 0;
}

#include <stdio.h>

int TinyX11OpenGLWindow::file_open_dialog(char* filename, int maxNameLength) {
  int len = 0;
  FILE* output = popen(
      "zenity --file-selection --file-filter=\"*.urdf\" "
      "--file-filter=\"*.sdf\"  --file-filter=\"*.obj\"  --file-filter=\"*.*\"",
      "r");
  if (output) {
    while (fgets(filename, maxNameLength - 1, output) != NULL) {
      len = strlen(filename);
      if (len > 0) {
        filename[len - 1] = 0;
        printf("file open (length=%d) = %s\n", len, filename);
      }
    }
    pclose(output);
  } else {
    printf("Error: fileOpenDialog no popen output, perhaps install zenity?\n");
  }
  MyXRaiseWindow(m_data->m_dpy, m_data->m_win);
  return len;
}
#endif
