/**************************************************************************
 * 
 * Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas.
 * All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 * 
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 **************************************************************************/


/**
 * This is an EGL driver that wraps GLX. This gives the benefit of being
 * completely agnostic of the direct rendering implementation.
 *
 * Authors: Alan Hourihane <alanh@tungstengraphics.com>
 */

/*
 * TODO: 
 *
 * Add GLXFBConfig support
 * Pbuffer & Pixmap support
 * test eglBind/ReleaseTexImage
 */


#include <assert.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "dlfcn.h"
#include <X11/Xlib.h>
#include <GL/gl.h>
#include "glxclient.h"

#define _EGL_PLATFORM_X

#include "eglconfig.h"
#include "eglcontext.h"
#include "egldisplay.h"
#include "egldriver.h"
#include "eglglobals.h"
#include "eglhash.h"
#include "egllog.h"
#include "eglsurface.h"

#include <GL/gl.h>

#define CALLOC_STRUCT(T)   (struct T *) calloc(1, sizeof(struct T))

static const EGLint all_apis = (EGL_OPENGL_ES_BIT |
                                EGL_OPENGL_ES2_BIT |
                                EGL_OPENVG_BIT |
                                EGL_OPENGL_BIT);

struct visual_attribs
{
   /* X visual attribs */
   int id;
   int klass;
   int depth;
   int redMask, greenMask, blueMask;
   int colormapSize;
   int bitsPerRGB;

   /* GL visual attribs */
   int supportsGL;
   int transparentType;
   int transparentRedValue;
   int transparentGreenValue;
   int transparentBlueValue;
   int transparentAlphaValue;
   int transparentIndexValue;
   int bufferSize;
   int level;
   int render_type;
   int doubleBuffer;
   int stereo;
   int auxBuffers;
   int redSize, greenSize, blueSize, alphaSize;
   int depthSize;
   int stencilSize;
   int accumRedSize, accumGreenSize, accumBlueSize, accumAlphaSize;
   int numSamples, numMultisample;
   int visualCaveat;
};

/** subclass of _EGLDriver */
struct GLX_egl_driver
{
   _EGLDriver Base;   /**< base class */

   XVisualInfo *visuals;

   /* GLXFBConfig *fbconfigs - todo */
};


/** subclass of _EGLContext */
struct GLX_egl_context
{
   _EGLContext Base;   /**< base class */

   GLXContext context;
};


/** subclass of _EGLSurface */
struct GLX_egl_surface
{
   _EGLSurface Base;   /**< base class */

   GLXDrawable drawable;
};


/** subclass of _EGLConfig */
struct GLX_egl_config
{
   _EGLConfig Base;   /**< base class */
};

/** cast wrapper */
static struct GLX_egl_driver *
GLX_egl_driver(_EGLDriver *drv)
{
   return (struct GLX_egl_driver *) drv;
}

static struct GLX_egl_context *
GLX_egl_context(_EGLContext *ctx)
{
   return (struct GLX_egl_context *) ctx;
}

static struct GLX_egl_surface *
GLX_egl_surface(_EGLSurface *surf)
{
   return (struct GLX_egl_surface *) surf;
}

static GLboolean
get_visual_attribs(Display *dpy, XVisualInfo *vInfo,
                   struct visual_attribs *attribs)
{
   const char *ext = glXQueryExtensionsString(dpy, vInfo->screen);
   int rgba;

   memset(attribs, 0, sizeof(struct visual_attribs));

   attribs->id = vInfo->visualid;
#if defined(__cplusplus) || defined(c_plusplus)
   attribs->klass = vInfo->c_class;
#else
   attribs->klass = vInfo->class;
#endif
   attribs->depth = vInfo->depth;
   attribs->redMask = vInfo->red_mask;
   attribs->greenMask = vInfo->green_mask;
   attribs->blueMask = vInfo->blue_mask;
   attribs->colormapSize = vInfo->colormap_size;
   attribs->bitsPerRGB = vInfo->bits_per_rgb;

   if (glXGetConfig(dpy, vInfo, GLX_USE_GL, &attribs->supportsGL) != 0 ||
       !attribs->supportsGL)
      return GL_FALSE;
   glXGetConfig(dpy, vInfo, GLX_BUFFER_SIZE, &attribs->bufferSize);
   glXGetConfig(dpy, vInfo, GLX_LEVEL, &attribs->level);
   glXGetConfig(dpy, vInfo, GLX_RGBA, &rgba);
   if (!rgba)
      return GL_FALSE;
   attribs->render_type = GLX_RGBA_BIT;
   
   glXGetConfig(dpy, vInfo, GLX_DOUBLEBUFFER, &attribs->doubleBuffer);
   if (!attribs->doubleBuffer)
      return GL_FALSE;

   glXGetConfig(dpy, vInfo, GLX_STEREO, &attribs->stereo);
   glXGetConfig(dpy, vInfo, GLX_AUX_BUFFERS, &attribs->auxBuffers);
   glXGetConfig(dpy, vInfo, GLX_RED_SIZE, &attribs->redSize);
   glXGetConfig(dpy, vInfo, GLX_GREEN_SIZE, &attribs->greenSize);
   glXGetConfig(dpy, vInfo, GLX_BLUE_SIZE, &attribs->blueSize);
   glXGetConfig(dpy, vInfo, GLX_ALPHA_SIZE, &attribs->alphaSize);
   glXGetConfig(dpy, vInfo, GLX_DEPTH_SIZE, &attribs->depthSize);
   glXGetConfig(dpy, vInfo, GLX_STENCIL_SIZE, &attribs->stencilSize);
   glXGetConfig(dpy, vInfo, GLX_ACCUM_RED_SIZE, &attribs->accumRedSize);
   glXGetConfig(dpy, vInfo, GLX_ACCUM_GREEN_SIZE, &attribs->accumGreenSize);
   glXGetConfig(dpy, vInfo, GLX_ACCUM_BLUE_SIZE, &attribs->accumBlueSize);
   glXGetConfig(dpy, vInfo, GLX_ACCUM_ALPHA_SIZE, &attribs->accumAlphaSize);

   /* get transparent pixel stuff */
   glXGetConfig(dpy, vInfo,GLX_TRANSPARENT_TYPE, &attribs->transparentType);
   if (attribs->transparentType == GLX_TRANSPARENT_RGB) {
     glXGetConfig(dpy, vInfo, GLX_TRANSPARENT_RED_VALUE, &attribs->transparentRedValue);
     glXGetConfig(dpy, vInfo, GLX_TRANSPARENT_GREEN_VALUE, &attribs->transparentGreenValue);
     glXGetConfig(dpy, vInfo, GLX_TRANSPARENT_BLUE_VALUE, &attribs->transparentBlueValue);
     glXGetConfig(dpy, vInfo, GLX_TRANSPARENT_ALPHA_VALUE, &attribs->transparentAlphaValue);
   }
   else if (attribs->transparentType == GLX_TRANSPARENT_INDEX) {
     glXGetConfig(dpy, vInfo, GLX_TRANSPARENT_INDEX_VALUE, &attribs->transparentIndexValue);
   }

   /* multisample attribs */
#ifdef GLX_ARB_multisample
   if (ext && strstr(ext, "GLX_ARB_multisample")) {
      glXGetConfig(dpy, vInfo, GLX_SAMPLE_BUFFERS_ARB, &attribs->numMultisample);
      glXGetConfig(dpy, vInfo, GLX_SAMPLES_ARB, &attribs->numSamples);
   }
#endif
   else {
      attribs->numSamples = 0;
      attribs->numMultisample = 0;
   }

#if defined(GLX_EXT_visual_rating)
   if (ext && strstr(ext, "GLX_EXT_visual_rating")) {
      glXGetConfig(dpy, vInfo, GLX_VISUAL_CAVEAT_EXT, &attribs->visualCaveat);
   }
   else {
      attribs->visualCaveat = GLX_NONE_EXT;
   }
#else
   attribs->visualCaveat = 0;
#endif

   return GL_TRUE;
}

static EGLBoolean
create_configs(_EGLDisplay *disp, struct GLX_egl_driver *GLX_drv)
{
   XVisualInfo theTemplate;
   int numVisuals;
   long mask;
   int i;
   struct visual_attribs attribs;

   /* get list of all visuals on this screen */
   theTemplate.screen = DefaultScreen(disp->Xdpy);
   mask = VisualScreenMask;
   GLX_drv->visuals = XGetVisualInfo(disp->Xdpy, mask, &theTemplate, &numVisuals);

   for (i = 0; i < numVisuals; i++) {
      struct GLX_egl_config *config;

      if (!get_visual_attribs(disp->Xdpy, &GLX_drv->visuals[i], &attribs))
	 continue;

      config = CALLOC_STRUCT(GLX_egl_config);

      _eglInitConfig(&config->Base, i+1);
      SET_CONFIG_ATTRIB(&config->Base, EGL_NATIVE_VISUAL_ID, attribs.id);
      SET_CONFIG_ATTRIB(&config->Base, EGL_BUFFER_SIZE, attribs.bufferSize);
      SET_CONFIG_ATTRIB(&config->Base, EGL_RED_SIZE, attribs.redSize);
      SET_CONFIG_ATTRIB(&config->Base, EGL_GREEN_SIZE, attribs.greenSize);
      SET_CONFIG_ATTRIB(&config->Base, EGL_BLUE_SIZE, attribs.blueSize);
      SET_CONFIG_ATTRIB(&config->Base, EGL_ALPHA_SIZE, attribs.alphaSize);
      SET_CONFIG_ATTRIB(&config->Base, EGL_DEPTH_SIZE, attribs.depthSize);
      SET_CONFIG_ATTRIB(&config->Base, EGL_STENCIL_SIZE, attribs.stencilSize);
      SET_CONFIG_ATTRIB(&config->Base, EGL_SAMPLES, attribs.numSamples);
      SET_CONFIG_ATTRIB(&config->Base, EGL_SAMPLE_BUFFERS, attribs.numMultisample);
      SET_CONFIG_ATTRIB(&config->Base, EGL_CONFORMANT, all_apis);
      SET_CONFIG_ATTRIB(&config->Base, EGL_RENDERABLE_TYPE, all_apis);
      SET_CONFIG_ATTRIB(&config->Base, EGL_SURFACE_TYPE,
                        (EGL_WINDOW_BIT /*| EGL_PBUFFER_BIT | EGL_PIXMAP_BIT*/));

      /* XXX possibly other things to init... */

      _eglAddConfig(disp, &config->Base);
   }

   return EGL_TRUE;
}

/**
 * Called via eglInitialize(), GLX_drv->API.Initialize().
 */
static EGLBoolean
GLX_eglInitialize(_EGLDriver *drv, EGLDisplay dpy,
                   EGLint *minor, EGLint *major)
{
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   _EGLDisplay *disp = _eglLookupDisplay(dpy);

   _eglLog(_EGL_DEBUG, "XDRI: eglInitialize");

   if (!disp->Xdpy) {
      disp->Xdpy = XOpenDisplay(NULL);
      if (!disp->Xdpy) {
         _eglLog(_EGL_WARNING, "XDRI: XOpenDisplay failed");
         return EGL_FALSE;
      }
   }

   GLX_drv->Base.Initialized = EGL_TRUE;

   GLX_drv->Base.Name = "GLX";

   /* we're supporting EGL 1.4 */
   *minor = 1;
   *major = 4;

   create_configs(disp, GLX_drv);

   return EGL_TRUE;
}


/**
 * Called via eglTerminate(), drv->API.Terminate().
 */
static EGLBoolean
GLX_eglTerminate(_EGLDriver *drv, EGLDisplay dpy)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);

   _eglLog(_EGL_DEBUG, "XDRI: eglTerminate");

//   XCloseDisplay(disp->Xdpy);

   return EGL_TRUE;
}


/**
 * Called via eglCreateContext(), drv->API.CreateContext().
 */
static EGLContext
GLX_eglCreateContext(_EGLDriver *drv, EGLDisplay dpy, EGLConfig config,
                      EGLContext share_list, const EGLint *attrib_list)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   struct GLX_egl_context *GLX_ctx = CALLOC_STRUCT(GLX_egl_context);
   struct GLX_egl_driver *GLX_drv = GLX_egl_driver(drv);
   struct GLX_egl_context *GLX_ctx_shared = NULL;
   _EGLConfig *conf;

   if (!GLX_ctx)
      return EGL_NO_CONTEXT;

   if (!_eglInitContext(drv, dpy, &GLX_ctx->Base, config, attrib_list)) {
      free(GLX_ctx);
      return EGL_NO_CONTEXT;
   }

   if (share_list != EGL_NO_CONTEXT) {
      _EGLContext *shareCtx = _eglLookupContext(share_list);
      if (!shareCtx) {
         _eglError(EGL_BAD_CONTEXT, "eglCreateContext(share_list)");
         return EGL_FALSE;
      }
      GLX_ctx_shared = GLX_egl_context(shareCtx);
   }

   conf = _eglLookupConfig(drv, dpy, config);
   assert(conf);

   GLX_ctx->context = glXCreateContext(disp->Xdpy, &GLX_drv->visuals[(int)config-1], GLX_ctx_shared ? GLX_ctx_shared->context : NULL, GL_TRUE);
   if (!GLX_ctx->context)
      return EGL_FALSE;

   /* need to have a direct rendering context */
   if (!glXIsDirect(disp->Xdpy, GLX_ctx->context))
      return EGL_FALSE;

   return _eglGetContextHandle(&GLX_ctx->Base);
}


/**
 * Called via eglMakeCurrent(), drv->API.MakeCurrent().
 */
static EGLBoolean
GLX_eglMakeCurrent(_EGLDriver *drv, EGLDisplay dpy, EGLSurface d,
                    EGLSurface r, EGLContext context)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   _EGLContext *ctx = _eglLookupContext(context);
   _EGLSurface *dsurf = _eglLookupSurface(d);
   _EGLSurface *rsurf = _eglLookupSurface(r);
   struct GLX_egl_surface *GLX_dsurf = GLX_egl_surface(dsurf);
   struct GLX_egl_surface *GLX_rsurf = GLX_egl_surface(rsurf);
   struct GLX_egl_context *GLX_ctx = GLX_egl_context(ctx);

   if (!_eglMakeCurrent(drv, dpy, d, r, context))
      return EGL_FALSE;

//   if (!glXMakeContextCurrent(disp->Xdpy, GLX_dsurf->drawable, GLX_rsurf->drawable, GLX_ctx->context))
   if (!glXMakeCurrent(disp->Xdpy, GLX_dsurf ? GLX_dsurf->drawable : 0, GLX_ctx ? GLX_ctx->context : NULL))
      return EGL_FALSE;

   return EGL_TRUE;
}

/** Get size of given window */
static Status
get_drawable_size(Display *dpy, Drawable d, uint *width, uint *height)
{
   Window root;
   Status stat;
   int xpos, ypos;
   unsigned int w, h, bw, depth;
   stat = XGetGeometry(dpy, d, &root, &xpos, &ypos, &w, &h, &bw, &depth);
   *width = w;
   *height = h;
   return stat;
}

/**
 * Called via eglCreateWindowSurface(), drv->API.CreateWindowSurface().
 */
static EGLSurface
GLX_eglCreateWindowSurface(_EGLDriver *drv, EGLDisplay dpy, EGLConfig config,
                            NativeWindowType window, const EGLint *attrib_list)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   struct GLX_egl_surface *GLX_surf;
   uint width, height;

   GLX_surf = CALLOC_STRUCT(GLX_egl_surface);
   if (!GLX_surf)
      return EGL_NO_SURFACE;

   if (!_eglInitSurface(drv, dpy, &GLX_surf->Base, EGL_WINDOW_BIT,
                        config, attrib_list)) {
      free(GLX_surf);
      return EGL_FALSE;
   }

   _eglSaveSurface(&GLX_surf->Base);

   GLX_surf->drawable = window;
   get_drawable_size(disp->Xdpy, window, &width, &height);
   GLX_surf->Base.Width = width;
   GLX_surf->Base.Height = height;

   return _eglGetSurfaceHandle(&GLX_surf->Base);
}

static EGLBoolean
GLX_eglDestroySurface(_EGLDriver *drv, EGLDisplay dpy, EGLSurface surface)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   _EGLSurface *surf = _eglLookupSurface(surface);
   return EGL_TRUE;
   if (surf) {
      _eglHashRemove(_eglGlobal.Surfaces, (EGLuint) surface);
      if (surf->IsBound) {
         surf->DeletePending = EGL_TRUE;
      }
      else {
         free(surf);
      }

      return EGL_TRUE;
   }
   else {
      _eglError(EGL_BAD_SURFACE, "eglDestroySurface");
      return EGL_FALSE;
   }
}


static EGLBoolean
GLX_eglBindTexImage(_EGLDriver *drv, EGLDisplay dpy, EGLSurface surface,
                     EGLint buffer)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   _EGLSurface *surf = _eglLookupSurface(surface);
   struct GLX_egl_surface *GLX_surf = GLX_egl_surface(surf);

   /* buffer ?? */
   glXBindTexImageEXT(disp->Xdpy, GLX_surf->drawable, GLX_FRONT_LEFT_EXT, NULL);

   return EGL_TRUE;
}


static EGLBoolean
GLX_eglReleaseTexImage(_EGLDriver *drv, EGLDisplay dpy, EGLSurface surface,
                        EGLint buffer)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   _EGLSurface *surf = _eglLookupSurface(surface);
   struct GLX_egl_surface *GLX_surf = GLX_egl_surface(surf);

   /* buffer ?? */
   glXReleaseTexImageEXT(disp->Xdpy, GLX_surf->drawable, GLX_FRONT_LEFT_EXT);

   return EGL_TRUE;
}


static EGLBoolean
GLX_eglSwapBuffers(_EGLDriver *drv, EGLDisplay dpy, EGLSurface draw)
{
   _EGLDisplay *disp = _eglLookupDisplay(dpy);
   _EGLSurface *surf = _eglLookupSurface(draw);
   struct GLX_egl_surface *GLX_surf = GLX_egl_surface(surf);

   _eglLog(_EGL_DEBUG, "XDRI: EGL SwapBuffers 0x%x",draw);

   /* error checking step: */
   if (!_eglSwapBuffers(drv, dpy, draw))
      return EGL_FALSE;

   glXSwapBuffers(disp->Xdpy, GLX_surf->drawable);

   return EGL_TRUE;
}

/*
 * Called from eglGetProcAddress() via drv->API.GetProcAddress().
 */
static _EGLProc
GLX_eglGetProcAddress(const char *procname)
{
   return (_EGLProc)glXGetProcAddress((const GLubyte *)procname);   
}


/**
 * This is the main entrypoint into the driver, called by libEGL.
 * Create a new _EGLDriver object and init its dispatch table.
 */
_EGLDriver *
_eglMain(_EGLDisplay *disp, const char *args)
{
   struct GLX_egl_driver *GLX_drv = CALLOC_STRUCT(GLX_egl_driver);
   char *env;

   if (!GLX_drv)
      return NULL;

   _eglInitDriverFallbacks(&GLX_drv->Base);
   GLX_drv->Base.API.Initialize = GLX_eglInitialize;
   GLX_drv->Base.API.Terminate = GLX_eglTerminate;
   GLX_drv->Base.API.CreateContext = GLX_eglCreateContext;
   GLX_drv->Base.API.MakeCurrent = GLX_eglMakeCurrent;
   GLX_drv->Base.API.CreateWindowSurface = GLX_eglCreateWindowSurface;
   GLX_drv->Base.API.DestroySurface = GLX_eglDestroySurface;
   GLX_drv->Base.API.BindTexImage = GLX_eglBindTexImage;
   GLX_drv->Base.API.ReleaseTexImage = GLX_eglReleaseTexImage;
   GLX_drv->Base.API.SwapBuffers = GLX_eglSwapBuffers;
   GLX_drv->Base.API.GetProcAddress = GLX_eglGetProcAddress;

   GLX_drv->Base.ClientAPIsMask = all_apis;
   GLX_drv->Base.Name = "GLX";

   _eglLog(_EGL_DEBUG, "GLX: main(%s)", args);

   /* set new DRI path to pick up EGL version (which doesn't contain any mesa 
    * code), but don't override if one is already set.
    */
   env = getenv("LIBGL_DRIVERS_PATH");
   if (env) {
      if (!strstr(env, "egl")) {
         sprintf(env, "%s/egl", env);
         setenv("LIBGL_DRIVERS_PATH", env, 1);
      }
   } else
      setenv("LIBGL_DRIVERS_PATH", DEFAULT_DRIVER_DIR"/egl", 0);

   return &GLX_drv->Base;
}