////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014-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 <GLES3/gl31.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <sys/keycodes.h>

screen_window_t screen_window;
screen_context_t screen_context;
screen_event_t screen_ev;

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

void error_exit(const char* format, ... )
{
    va_list args;
    va_start( args, format );
    vfprintf( stderr, format, args );
    va_end( args );
    exit(1);
}

enum 
{
    NvGlDemoKeyCode_Escape = 27
};

typedef void (*GlCloseCB)(void);
typedef void (*GlKeyCB)(char key, int state);
GlCloseCB closeCB = NULL;
GlKeyCB keyCB = NULL;

void CHECK_GLERROR()
{
    GLenum err = glGetError();

    if (err != GL_NO_ERROR)
    {
        fprintf(stderr, "[%s line %d] OpenGL Error: 0x%x ", __FILE__, __LINE__, err);

        switch (err)
        {
        case GL_INVALID_ENUM:
            fprintf(stderr, "(GL_INVALID_ENUM)\n");
            break;
        case GL_INVALID_VALUE:
            fprintf(stderr, "(GL_INVALID_VALUE)\n");
            break;
        case GL_INVALID_OPERATION:
            fprintf(stderr, "(GL_INVALID_OPERATION)\n");
            break;
        case GL_OUT_OF_MEMORY:
            fprintf(stderr, "(GL_OUT_OF_MEMORY)\n");
            break;
        case GL_INVALID_FRAMEBUFFER_OPERATION:
            fprintf(stderr, "(GL_INVALID_FRAMEBUFFER_OPERATION)\n");
            break;
        default:
            break;
        }

        fflush(stderr);
    }
}

static void UpdateEventMask(void)
{
    static int rc = 1;

    if(rc) {
       rc = screen_create_event(&screen_ev);
    }
}


void SetCloseCB(GlCloseCB cb)
{
    // Call the eglQnxScreenConsumer module if option is enabled
    closeCB = cb;
    UpdateEventMask();
}

void SetKeyCB(GlKeyCB cb)
{
    keyCB = cb;
    UpdateEventMask();
}

// Add keys here, that are used in demo apps.
static unsigned char GetKeyPress(int *screenKey)
{
    unsigned char key = '\0';

    switch (*screenKey) {

        case KEYCODE_ESCAPE:
            key = NvGlDemoKeyCode_Escape;
        break;
        default:
            /* For "normal" keys, Screen KEYCODE is just ASCII. */
            if (*screenKey <= 127) {
                key = *screenKey;
            }
        break;
    }

    return key;
}


void CheckEvents(void)
{

    static int vis = 1, val = 1;
    int rc;

    /**
     ** We start the loop by processing any events that might be in our
     ** queue. The only event that is of interest to us are the resize
     ** and close events. The timeout variable is set to 0 (no wait) or
     ** forever depending if the window is visible or invisible.
     **/

    while (!screen_get_event(screen_context, screen_ev, vis ? 0ull : ~0ull)) 
    {
           // Get QNX CAR 2.1 event property
           rc = screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_TYPE, &val);
           if (rc || val == SCREEN_EVENT_NONE) {
               break;
           }

           switch (val) {

               case SCREEN_EVENT_CLOSE:
                    /**
                     ** All we have to do when we receive the close event is
                     ** exit the application loop.
                     **/
                    if (closeCB) {
                        closeCB();
                    }
                    break;

               case SCREEN_EVENT_KEYBOARD:
                    rc = screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_FLAGS, &val);
                    if (rc || val == SCREEN_EVENT_NONE) {
                        break;
                    }
                    if (val & KEY_DOWN) {
                        rc = screen_get_event_property_iv(screen_ev, SCREEN_PROPERTY_SYM, &val);
                        if (rc || val == SCREEN_EVENT_NONE) {
                            break;
                        }
                        unsigned char key;
                        key = GetKeyPress(&val);
                        if (key != '\0') {
                            keyCB(key, 1);
                        }
                    }
                    break;

               default:
                    break;
           }
    }
}


int graphics_setup_window(int xpos, int ypos, int width, int height, const char *windowname , int reqdispno)
{
    EGLint configAttrs[] = {
        EGL_RED_SIZE,        8,
        EGL_GREEN_SIZE,      8,
        EGL_BLUE_SIZE,       8,
        EGL_ALPHA_SIZE,      8,
        EGL_DEPTH_SIZE,      16,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };

    EGLint contextAttrs[] = {
      EGL_CONTEXT_CLIENT_VERSION, 3,
      EGL_NONE
    };

    EGLint windowAttrs[] = { EGL_NONE};
    EGLConfig* configList = NULL;
    EGLint configCount;

    int displayCount = 0;
    int dispno;

    screen_context = 0;

    screen_display_t *screenDisplayHandle = NULL;

    if (screen_create_context(&screen_context, 0))
    {
        error_exit("Error creating screen context.\n");
    }

    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");
    }

    screen_window = 0;
    if (screen_create_window(&screen_window, screen_context))
    {
        error_exit("Error creating screen window.\n");
    }

    // query the total no of display avaibale from QNX CAR2 screen
    if (screen_get_context_property_iv(screen_context, SCREEN_PROPERTY_DISPLAY_COUNT, &displayCount))
    {
        error_exit("Error getting context property\n");
    }

    screenDisplayHandle = (screen_display_t *)malloc(displayCount * sizeof(screen_display_t));
    if (!screenDisplayHandle)
    {
        error_exit("Error allocating screen memory handle is getting failed\n");
    }

    // query the display handle from QNX CAR2 screen
    if (screen_get_context_property_pv(screen_context, SCREEN_PROPERTY_DISPLAYS, (void **)screenDisplayHandle))
    {
        error_exit("Error getting display handle\n");
    }

    for (dispno = 0; dispno < displayCount; dispno++)
    {
        int active = 0;
        // Query the connected status from QNX CAR2 screen
        screen_get_display_property_iv(screenDisplayHandle[dispno], SCREEN_PROPERTY_ATTACHED, &active);
        if (active)
        {
            if (reqdispno == dispno)
            {
                // Map the window buffer to user requested display port
                screen_set_window_property_pv(screen_window, SCREEN_PROPERTY_DISPLAY, (void **)&screenDisplayHandle[reqdispno]);
                break;
            }
        }
    }

    if (dispno == displayCount)
    {
        error_exit("Failed to set the requested display\n");
    }

    free(screenDisplayHandle);

    int format = SCREEN_FORMAT_RGBA8888;
    if (screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_FORMAT, &format))
    {
        error_exit("Error setting SCREEN_PROPERTY_FORMAT\n");
    }

    int usage = SCREEN_USAGE_OPENGL_ES2;
    if (screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_USAGE, &usage))
    {
        error_exit("Error setting SCREEN_PROPERTY_USAGE\n");
    }

    EGLint interval = 1;
    if (screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_SWAP_INTERVAL, &interval))
    {
        error_exit("Error setting SCREEN_PROPERTY_SWAP_INTERVAL\n");
    }

    int windowSize[2];
    windowSize[0] = width;
    windowSize[1] = height;
    if (screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_SIZE, windowSize))
    {
        error_exit("Error setting SCREEN_PROPERTY_SIZE\n");
    }

    int windowOffset[2];
    windowOffset[0] = xpos;
    windowOffset[1] = ypos;
    if (screen_set_window_property_iv(screen_window, SCREEN_PROPERTY_POSITION, windowOffset))
    {
        error_exit("Error setting SCREEN_PROPERTY_POSITION\n");
    }

    if (screen_create_window_buffers(screen_window, 2))
    {
        error_exit("Error creating two window buffers.\n");
    }

    eglSurface = eglCreateWindowSurface(eglDisplay, configList[0], (EGLNativeWindowType)screen_window, 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\n");
        break;
    case EGL_OPENGL_ES_API:
        printf("Using OpenGL ES API\n");
        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)
{
    // Do nothing on screen
}

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 (screen_window)
    {
        screen_destroy_window(screen_window);
        screen_window = NULL;
    }
    if (screen_context)
    {
        screen_destroy_context(screen_context);
    }
    if (screen_ev)
    {
        screen_destroy_event(screen_ev);
    }
}