////////////////////////////////////////////////////////////////////////////
//
// Copyright 1993-2015 NVIDIA Corporation.  All rights reserved.
//
// Please refer to the NVIDIA end user license agreement (EULA) associated
// with this source code for terms and conditions that govern your use of
// this software. Any use, reproduction, disclosure, or distribution of
// this software and related documentation outside the terms of the EULA
// is strictly prohibited.
//
////////////////////////////////////////////////////////////////////////////

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>

Display *display;
int screen;
Window win = 0; 

#include <GLES3/gl31.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>

#define GET_GLERROR(ret)\
{                                       \
    GLenum err = glGetError();           \
    if (err != GL_NO_ERROR) \
      {                      \
    fprintf(stderr, "[%s line %d] OpenGL Error: 0x%x\n", __FILE__, __LINE__, err);  \
    fflush(stderr);			\
    \
    switch (err)\
    { \
      case GL_INVALID_ENUM: printf("GL_INVALID_ENUM\n"); break;	\
      case GL_INVALID_VALUE: printf("GL_INVALID_VALUE\n"); break;	\
      case GL_INVALID_OPERATION: printf("GL_INVALID_OPERATION\n"); break; \
      case GL_OUT_OF_MEMORY: printf("GL_OUT_OF_MEMORY\n"); break;	\
      case GL_INVALID_FRAMEBUFFER_OPERATION: printf("GL_INVALID_FRAMEBUFFER_OPERATION\n"); break; \
      default: printf("UKNOWN OPENGL ERROR CODE 0x%x\n", err);	\
      };\
      }\
}

EGLDisplay eglDisplay = EGL_NO_DISPLAY;
EGLSurface eglSurface = EGL_NO_SURFACE;
EGLContext eglContext = EGL_NO_CONTEXT;

int graphics_setup_window(int xpos, int ypos, int width, int height, const char *windowname)
{
    // OpenGL ES 3.1
    EGLint configAttrs[] = {
        EGL_RED_SIZE,        1,
        EGL_GREEN_SIZE,      1,
        EGL_BLUE_SIZE,       1,
        EGL_DEPTH_SIZE,     16,
        EGL_SAMPLE_BUFFERS,  0,
        EGL_SAMPLES,         0,
	//EGL_CONFORMANT,      EGL_OPENGL_BIT,
	    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,  //3_BIT_KHR,
        EGL_NONE
    };
    EGLint contextAttrs[] = {
      EGL_CONTEXT_CLIENT_VERSION, 3,
      EGL_NONE
    };

    EGLConfig* configList = NULL;
    EGLint     configCount;

    display = XOpenDisplay(NULL);
    if (!display) 
      error_exit("Error opening X display.\n");

    screen = DefaultScreen(display);

    eglDisplay = eglGetDisplay(0);

    if (eglDisplay == EGL_NO_DISPLAY)
      error_exit("EGL failed to obtain display\n");

    if (!eglInitialize(eglDisplay, 0, 0))
      error_exit("EGL failed to initialize\n");

    if (!eglChooseConfig(eglDisplay, configAttrs,
                         NULL, 0, &configCount) || !configCount)
      error_exit("EGL failed to return any matching configurations\n");

    configList = (EGLConfig*)malloc(configCount * sizeof(EGLConfig));

    if (!eglChooseConfig(eglDisplay, configAttrs,
                         configList, configCount, &configCount) || !configCount)
        error_exit("EGL failed to populate configuration list\n");

    Window xRootWindow  = DefaultRootWindow(display);
    XSetWindowAttributes xCreateWindowAttributes;
    xCreateWindowAttributes.event_mask = ExposureMask;
    win = XCreateWindow(
        display,
        xRootWindow,
        0,
        0,
        width,
        height,
        0,
        CopyFromParent,
        InputOutput,
        CopyFromParent,
        CWEventMask,
        &xCreateWindowAttributes);
    XMapWindow(display, win);
    Atom netWmStateAtom = XInternAtom(display, "_NET_WM_STATE", false);
    XEvent xEvent;
    memset(&xEvent, 0, sizeof(xEvent));
    xEvent.type                 = ClientMessage;
    xEvent.xclient.window       = win;
    xEvent.xclient.message_type = netWmStateAtom;
    xEvent.xclient.format       = 32;
    xEvent.xclient.data.l[0]    = 1;
    xEvent.xclient.data.l[1]    = false;
    XSendEvent(
        display,
        xRootWindow,
        false,
        SubstructureNotifyMask,
        &xEvent);

    XStoreName( display, win, windowname);
    
    XSelectInput(display, win, ExposureMask |  KeyPressMask | ButtonPressMask | ButtonReleaseMask | 
                          KeyReleaseMask | VisibilityChangeMask | PointerMotionMask );

    EGLint windowAttrs[] =  {EGL_NONE};

    eglSurface = eglCreateWindowSurface(eglDisplay, configList[0], (EGLNativeWindowType) win, windowAttrs);


    if (!eglSurface) 
      error_exit("EGL couldn't create window\n");

    eglBindAPI(EGL_OPENGL_ES_API);

    eglContext = eglCreateContext(eglDisplay, configList[0], NULL, contextAttrs);
    if (!eglContext) 
      error_exit("EGL couldn't create context\n");
    
    if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) 
      error_exit("EGL couldn't make context/surface current\n");

    EGLint Context_RendererType;
    eglQueryContext(eglDisplay, eglContext,EGL_CONTEXT_CLIENT_TYPE, &Context_RendererType);
    
    switch (Context_RendererType)
    {
        case EGL_OPENGL_API:
            printf("Using OpenGL API is not supported\n");
            exit(EXIT_FAILURE);
            break;
        case EGL_OPENGL_ES_API:
            printf("Using OpenGL ES API");
            break;
        case EGL_OPENVG_API:
            error_exit("Context Query Returned OpenVG. This is Unsupported\n");
        default:
            error_exit("Unknown Context Type. %04X\n", Context_RendererType);
    }

    return 1;
}

void graphics_set_windowtitle(const char *windowname)
{
    XStoreName( display, win, windowname);
}

void graphics_swap_buffers()
{
    eglSwapBuffers(eglDisplay, eglSurface);
}

void graphics_close_window()
{
    if (eglDisplay != EGL_NO_DISPLAY)
    {
        eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

        if (eglContext != EGL_NO_CONTEXT)
            eglDestroyContext(eglDisplay, eglContext);

        if (eglSurface != EGL_NO_SURFACE)
            eglDestroySurface(eglDisplay, eglSurface);

        eglTerminate(eglDisplay);
    }

    if (win)
        XDestroyWindow(display, win);

    XCloseDisplay(display); 
}

