//========================================================================
// GLFW - An OpenGL library
// Platform:    X11/EGL
// API version: 3.0
// WWW:         http://www.glfw.org/
//------------------------------------------------------------------------
// Copyright (c) 2002-2006 Marcus Geelnard
// Copyright (c) 2006-2010 Camilla Berglund <elmindreda@elmindreda.org>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would
//    be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
//    distribution.
//
//========================================================================

#include "internal.h"

#include <stdio.h>
#include <stdlib.h>


//========================================================================
// Returns the specified attribute of the specified EGLConfig
//========================================================================

static int getConfigAttrib(EGLConfig config, int attrib)
{
    int value;
    eglGetConfigAttrib(_glfwLibrary.EGL.display, config, attrib, &value);
    return value;
}


//========================================================================
// Return a list of available and usable framebuffer configs
//========================================================================

static _GLFWfbconfig* getFBConfigs(_GLFWwindow* window,
                                   const _GLFWwndconfig* wndconfig,
                                   unsigned int* found)
{
    EGLConfig* configs;
    _GLFWfbconfig* result;
    int i, count = 0;

    *found = 0;

    eglGetConfigs(_glfwLibrary.EGL.display, NULL, 0, &count);

    configs = (EGLConfig*) malloc(sizeof(EGLConfig) * count);
    if (!configs)
    {
        _glfwSetError(GLFW_OUT_OF_MEMORY, NULL);
        return NULL;
    }

    eglGetConfigs(_glfwLibrary.EGL.display, configs, count, &count);
    if (!count)
    {
        free(configs);

        _glfwSetError(GLFW_OPENGL_UNAVAILABLE,
                      "EGL: No EGLConfigs returned");
        return NULL;
    }

    result = (_GLFWfbconfig*) malloc(sizeof(_GLFWfbconfig) * count);
    if (!result)
    {
        free(configs);

        _glfwSetError(GLFW_OUT_OF_MEMORY, NULL);
        return NULL;
    }

    for (i = 0;  i < count;  i++)
    {
        _GLFWfbconfig* f = result + *found;

        if (!getConfigAttrib(configs[i], EGL_NATIVE_VISUAL_ID))
        {
            // Only consider EGLConfigs with associated visuals
            continue;
        }

        if (!(getConfigAttrib(configs[i], EGL_COLOR_BUFFER_TYPE) & EGL_RGB_BUFFER))
        {
            // Only consider RGB(A) EGLConfigs
            continue;
        }

        if (!(getConfigAttrib(configs[i], EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
        {
            // Only consider window EGLConfigs
            continue;
        }

        if (wndconfig->clientAPI == GLFW_OPENGL_ES_API)
        {
            if (wndconfig->glMajor == 1)
            {
                if (!(getConfigAttrib(configs[i], EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES_BIT))
                    continue;
            }
            else
            {
                if (!(getConfigAttrib(configs[i], EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES2_BIT))
                    continue;
            }
        }
        else if (wndconfig->clientAPI == GLFW_OPENGL_API)
        {
            if (!(getConfigAttrib(configs[i], EGL_RENDERABLE_TYPE) & EGL_OPENGL_BIT))
                continue;
        }

        f->redBits = getConfigAttrib(configs[i], EGL_RED_SIZE);
        f->greenBits = getConfigAttrib(configs[i], EGL_GREEN_SIZE);
        f->blueBits = getConfigAttrib(configs[i], EGL_BLUE_SIZE);

        f->alphaBits = getConfigAttrib(configs[i], EGL_ALPHA_SIZE);
        f->depthBits = getConfigAttrib(configs[i], EGL_DEPTH_SIZE);
        f->stencilBits = getConfigAttrib(configs[i], EGL_STENCIL_SIZE);

        f->samples = getConfigAttrib(configs[i], EGL_SAMPLES);

        f->platformID = (GLFWintptr) getConfigAttrib(configs[i], EGL_CONFIG_ID);

        (*found)++;
    }

    free(configs);
    return result;
}


//========================================================================
// Create the actual OpenGL(|ES) context
//========================================================================

#define setEGLattrib(attribs, index, attribName, attribValue) \
{ \
    attribs[index++] = attribName; \
    attribs[index++] = attribValue; \
}

static int createContext(_GLFWwindow* window,
                         const _GLFWwndconfig* wndconfig,
                         EGLint fbconfigID)
{
    int attribs[40], visMask;
    EGLint count, index, visualID = 0;
    EGLint redBits, greenBits, blueBits, alphaBits;
    EGLConfig config;
    EGLContext share = NULL;
    XVisualInfo visTemplate;

    if (wndconfig->share)
        share = wndconfig->share->EGL.context;

    // Retrieve the previously selected EGLConfig
    {
        index = 0;

        setEGLattrib(attribs, index, EGL_CONFIG_ID, fbconfigID);
        setEGLattrib(attribs, index, EGL_NONE, EGL_NONE);

        eglChooseConfig(_glfwLibrary.EGL.display, attribs, &config, 1, &count);
        if (!count)
        {
            _glfwSetError(GLFW_PLATFORM_ERROR,
                          "EGL: Failed to retrieve the selected EGLConfig");
            return GL_FALSE;
        }
    }

    // Retrieve the corresponding visual
    // NOTE: This is the only non-portable code in this file.
    // Maybe it would not hurt too much to add #ifdefs for different platforms?
    eglGetConfigAttrib(_glfwLibrary.EGL.display, config, EGL_NATIVE_VISUAL_ID, &visualID);

    // Init visual template
    visTemplate.screen = _glfwLibrary.X11.screen;
    visMask = VisualScreenMask;

    if (visualID)
    {
        // The X window visual must match the EGL config
        visTemplate.visualid = visualID;
        visMask |= VisualIDMask;
    }
    else
    {
        // some EGL drivers don't implement the EGL_NATIVE_VISUAL_ID
        // attribute, so attempt to find the closest match.

        eglGetConfigAttrib(_glfwLibrary.EGL.display, config,
                           EGL_RED_SIZE, &redBits);
        eglGetConfigAttrib(_glfwLibrary.EGL.display, config,
                           EGL_GREEN_SIZE, &greenBits);
        eglGetConfigAttrib(_glfwLibrary.EGL.display, config,
                           EGL_BLUE_SIZE, &blueBits);
        eglGetConfigAttrib(_glfwLibrary.EGL.display, config,
                           EGL_ALPHA_SIZE, &alphaBits);

        visTemplate.depth = redBits + greenBits + blueBits + alphaBits;
        visMask |= VisualDepthMask;
    }

    window->EGL.visual = XGetVisualInfo(_glfwLibrary.X11.display,
                                        visMask, &visTemplate, &count);

    if (window->EGL.visual == NULL)
    {
        _glfwSetError(GLFW_PLATFORM_ERROR,
                      "EGL: Failed to retrieve visual for EGLConfig");
        return GL_FALSE;
    }

    if (wndconfig->clientAPI == GLFW_OPENGL_ES_API)
    {
        if (!eglBindAPI(EGL_OPENGL_ES_API))
        {
            _glfwSetError(GLFW_PLATFORM_ERROR,
                          "EGL: OpenGL ES is not supported");
            return GL_FALSE;
        }
    }
    else
    {
        if (!eglBindAPI(EGL_OPENGL_API))
        {
            _glfwSetError(GLFW_PLATFORM_ERROR,
                          "EGL: OpenGL is not supported");
            return GL_FALSE;
        }
    }

    index = 0;

    if (_glfwLibrary.EGL.KHR_create_context)
    {
        setEGLattrib(attribs, index, EGL_CONTEXT_MAJOR_VERSION_KHR, wndconfig->glMajor);
        setEGLattrib(attribs, index, EGL_CONTEXT_MINOR_VERSION_KHR, wndconfig->glMinor);

        if (wndconfig->glForward || wndconfig->glDebug || wndconfig->glRobustness)
        {
            int flags = 0;

            if (wndconfig->glForward)
                flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;

            if (wndconfig->glDebug)
                flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;

            if (wndconfig->glRobustness)
                flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;

            setEGLattrib(attribs, index, EGL_CONTEXT_FLAGS_KHR, flags);
        }

        if (wndconfig->glProfile)
        {
            int flags = 0;

            if (wndconfig->glProfile == GLFW_OPENGL_CORE_PROFILE)
                flags = EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR;
            else if (wndconfig->glProfile == GLFW_OPENGL_COMPAT_PROFILE)
                flags = EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR;

            setEGLattrib(attribs, index, EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, flags);
        }

        if (wndconfig->glRobustness)
        {
            int strategy;

            if (wndconfig->glRobustness == GLFW_OPENGL_NO_RESET_NOTIFICATION)
                strategy = EGL_NO_RESET_NOTIFICATION_KHR;
            else if (wndconfig->glRobustness == GLFW_OPENGL_LOSE_CONTEXT_ON_RESET)
                strategy = EGL_LOSE_CONTEXT_ON_RESET_KHR;

            setEGLattrib(attribs, index, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, strategy);
        }
    }
    else
    {
        if (wndconfig->clientAPI == GLFW_OPENGL_ES_API)
            setEGLattrib(attribs, index, EGL_CONTEXT_CLIENT_VERSION, wndconfig->glMajor);
    }

    setEGLattrib(attribs, index, EGL_NONE, EGL_NONE);

    window->EGL.context = eglCreateContext(_glfwLibrary.EGL.display,
                                           config, share, attribs);

    if (window->EGL.context == EGL_NO_CONTEXT)
    {
        // TODO: Handle all the various error codes here

        _glfwSetError(GLFW_PLATFORM_ERROR,
                      "EGL: Failed to create context");
        return GL_FALSE;
    }

    window->EGL.config = config;

    return GL_TRUE;
}

#undef setEGLattrib


//////////////////////////////////////////////////////////////////////////
//////                       GLFW internal API                      //////
//////////////////////////////////////////////////////////////////////////

//========================================================================
// Initialize EGL
//========================================================================

int _glfwInitOpenGL(void)
{
#ifdef _GLFW_DLOPEN_LIBEGL
    int i;
    char* libEGL_names[ ] =
    {
        "libEGL.so",
        "libEGL.so.1",
        "/usr/lib/libEGL.so",
        "/usr/lib/libEGL.so.1",
        NULL
    };

    for (i = 0;  libEGL_names[i] != NULL;  i++)
    {
        _glfwLibrary.EGL.libEGL = dlopen(libEGL_names[i], RTLD_LAZY | RTLD_GLOBAL);
        if (_glfwLibrary.EGL.libEGL)
            break;
    }

    if (!_glfwLibrary.EGL.libEGL)
    {
        _glfwSetError(GLFW_PLATFORM_ERROR, "EGL: Failed to find libEGL");
        return GL_FALSE;
    }
#endif

    _glfwLibrary.EGL.display = eglGetDisplay(_GLFW_EGL_NATIVE_DISPLAY);
    if (_glfwLibrary.EGL.display == EGL_NO_DISPLAY)
    {
        _glfwSetError(GLFW_OPENGL_UNAVAILABLE,
                      "EGL: Failed to get EGL display");
        return GL_FALSE;
    }

    if (!eglInitialize(_glfwLibrary.EGL.display,
                       &_glfwLibrary.EGL.majorVersion,
                       &_glfwLibrary.EGL.minorVersion))
    {
        _glfwSetError(GLFW_OPENGL_UNAVAILABLE,
                      "EGL: Failed to initialize EGL");
        return GL_FALSE;
    }

    if (_glfwPlatformExtensionSupported("EGL_KHR_create_context"))
        _glfwLibrary.EGL.KHR_create_context = GL_TRUE;

    return GL_TRUE;
}


//========================================================================
// Terminate EGL
//========================================================================

void _glfwTerminateOpenGL(void)
{
#ifdef _GLFW_DLOPEN_LIBEGL
    if (_glfwLibrary.EGL.libEGL != NULL)
    {
        dlclose(_glfwLibrary.EGL.libEGL);
        _glfwLibrary.EGL.libEGL = NULL;
    }
#endif

    eglTerminate(_glfwLibrary.EGL.display);
}


//========================================================================
// Prepare for creation of the OpenGL context
//========================================================================

int _glfwCreateContext(_GLFWwindow* window,
                       const _GLFWwndconfig* wndconfig,
                       const _GLFWfbconfig* fbconfig)
{
    _GLFWfbconfig closest;

    // Choose the best available fbconfig
    {
        unsigned int fbcount;
        _GLFWfbconfig* fbconfigs;
        const _GLFWfbconfig* result;

        fbconfigs = getFBConfigs(window, wndconfig, &fbcount);
        if (!fbconfigs)
        {
            _glfwSetError(GLFW_PLATFORM_ERROR,
                          "EGL: No usable EGLFBConfigs found");
            return GL_FALSE;
        }

        result = _glfwChooseFBConfig(fbconfig, fbconfigs, fbcount);
        if (!result)
        {
            _glfwSetError(GLFW_PLATFORM_ERROR,
                          "EGL: No EGLFBConfig matched the criteria");

            free(fbconfigs);
            return GL_FALSE;
        }

        closest = *result;
        free(fbconfigs);
    }

    return createContext(window, wndconfig, closest.platformID);
}


//========================================================================
// Destroy the OpenGL context
//========================================================================

void _glfwDestroyContext(_GLFWwindow* window)
{
    if (window->EGL.visual)
    {
       XFree(window->EGL.visual);
       window->EGL.visual = NULL;
    }

    if (window->EGL.surface)
    {
        eglDestroySurface(_glfwLibrary.EGL.display, window->EGL.surface);
        window->EGL.surface = EGL_NO_SURFACE;
    }

    if (window->EGL.context)
    {
        eglDestroyContext(_glfwLibrary.EGL.display, window->EGL.context);
        window->EGL.context = EGL_NO_CONTEXT;
    }
}


//========================================================================
// Return the X visual associated with the specified context
//========================================================================

XVisualInfo* _glfwGetContextVisual(_GLFWwindow* window)
{
    return window->EGL.visual;
}


//========================================================================
// Make the OpenGL context associated with the specified window current
//========================================================================

void _glfwPlatformMakeContextCurrent(_GLFWwindow* window)
{
    if (window)
    {
        if (window->EGL.surface == EGL_NO_SURFACE)
        {
            window->EGL.surface = eglCreateWindowSurface(_glfwLibrary.EGL.display,
                                                         window->EGL.config,
                                                         _GLFW_EGL_NATIVE_WINDOW,
                                                         NULL);
            if (window->EGL.surface == EGL_NO_SURFACE)
            {
                _glfwSetError(GLFW_PLATFORM_ERROR,
                              "EGL: Failed to create window surface");
            }
        }

        eglMakeCurrent(_glfwLibrary.EGL.display,
                       window->EGL.surface,
                       window->EGL.surface,
                       window->EGL.context);
    }
    else
    {
        eglMakeCurrent(_glfwLibrary.EGL.display,
                       EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    }
}


//========================================================================
// Swap OpenGL buffers
//========================================================================

void _glfwPlatformSwapBuffers(_GLFWwindow* window)
{
    eglSwapBuffers(_glfwLibrary.EGL.display, window->EGL.surface);
}


//========================================================================
// Set double buffering swap interval
//========================================================================

void _glfwPlatformSwapInterval(int interval)
{
    eglSwapInterval(_glfwLibrary.EGL.display, interval);
}


//========================================================================
// Check if an OpenGL extension is available at runtime
//========================================================================

int _glfwPlatformExtensionSupported(const char* extension)
{
    const char* extensions;

    extensions = eglQueryString(_glfwLibrary.EGL.display, EGL_EXTENSIONS);
    if (extensions != NULL)
    {
        if (_glfwStringInExtensionString(extension, (unsigned char*) extensions))
            return GL_TRUE;
    }

    return GL_FALSE;
}


//========================================================================
// Get the function pointer to an OpenGL function
//========================================================================

GLFWglproc _glfwPlatformGetProcAddress(const char* procname)
{
    return _glfw_eglGetProcAddress(procname);
}


//========================================================================
// Copies the specified OpenGL state categories from src to dst
//========================================================================

void _glfwPlatformCopyContext(_GLFWwindow* src, _GLFWwindow* dst, unsigned long mask)
{
    _glfwSetError(GLFW_PLATFORM_ERROR,
                  "EGL: Context copying not supported by EGL");
}