/**
 * Functions for choosing and opening/loading device drivers.
 */


#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "eglconfig.h"
#include "eglcontext.h"
#include "egldefines.h"
#include "egldisplay.h"
#include "egldriver.h"
#include "eglglobals.h"
#include "egllog.h"
#include "eglmisc.h"
#include "eglmode.h"
#include "eglscreen.h"
#include "eglstring.h"
#include "eglsurface.h"

#if defined(_EGL_PLATFORM_X)
#include <dlfcn.h>
#elif defined(_EGL_PLATFORM_WINDOWS)
/* Use static linking on Windows for now */
#define WINDOWS_STATIC_LINK
#endif

/**
 * Wrappers for dlopen/dlclose()
 */
#if defined(_EGL_PLATFORM_WINDOWS)
#ifdef WINDOWS_STATIC_LINK
   static const char *DefaultDriverName = "Windows EGL Static Library";
#else
   /* XXX Need to decide how to do dynamic name lookup on Windows */
   static const char *DefaultDriverName = "TBD";
#endif
   typedef HMODULE lib_handle;

   static HMODULE
   open_library(const char *filename)
   {
#ifdef WINDOWS_STATIC_LINK
      return 0;
#else
      return LoadLibrary(filename);
#endif
   }

   static void
   close_library(HMODULE lib)
   {
#ifdef WINDOWS_STATIC_LINK
#else
      FreeLibrary(lib);
#endif
   }

#elif defined(_EGL_PLATFORM_X)
   static const char *DefaultDriverName = "egl_softpipe";

   typedef void * lib_handle;

   static void *
   open_library(const char *filename)
   {
      return dlopen(filename, RTLD_LAZY);
   }

   static void
   close_library(void *lib)
   {
      dlclose(lib);
   }
   
#endif


/**
 * Choose a driver for a given display.
 * The caller may free() the returned strings.
 */
static char *
_eglChooseDriver(_EGLDisplay *dpy, char **argsRet)
{
   char *path = NULL;
   const char *args = NULL;
   const char *suffix = NULL;
   const char *p;

   path = getenv("EGL_DRIVER");
   if (path)
      path = _eglstrdup(path);

#if defined(_EGL_PLATFORM_X)
   if (!path && dpy->NativeDisplay) {
      /* assume (wrongly!) that the native display is a display string */
      path = _eglSplitDisplayString((const char *) dpy->NativeDisplay, &args);
   }
   suffix = "so";
#elif defined(_EGL_PLATFORM_WINDOWS)
   suffix = "dll";
#endif /* _EGL_PLATFORM_X */

   if (!path)
      path = _eglstrdup(DefaultDriverName);

   /* append suffix if there isn't */
   p = strrchr(path, '.');
   if (!p && suffix) {
      size_t len = strlen(path);
      char *tmp = malloc(len + strlen(suffix) + 2);
      if (tmp) {
         memcpy(tmp, path, len);
         tmp[len++] = '.';
         tmp[len] = '\0';
         strcat(tmp + len, suffix);

         free(path);
         path = tmp;
      }
   }

   if (argsRet)
      *argsRet = (args) ? _eglstrdup(args) : NULL;

   return path;
}


/**
 * Open the named driver and find its bootstrap function: _eglMain().
 */
static _EGLMain_t
_eglOpenLibrary(const char *driverPath, lib_handle *handle)
{
   _EGLMain_t mainFunc;
   lib_handle lib;

   assert(driverPath);

#if defined(_EGL_PLATFORM_WINDOWS)
/* Use static linking on Windows for now */
#ifdef WINDOWS_STATIC_LINK
   lib = 0;
   mainFunc = (_EGLMain_t)_eglMain;
#else
   /* XXX untested */
   _eglLog(_EGL_DEBUG, "dlopen(%s)", driverPath);
   lib = open_library(driverPath);
   if (!lib) {
      _eglLog(_EGL_WARNING, "Could not open %s",
              driverPath);
      return NULL;
   }
   mainFunc = (_EGLMain_t) GetProcAddress(lib, "_eglMain");
#endif
#elif defined(_EGL_PLATFORM_X)
   _eglLog(_EGL_DEBUG, "dlopen(%s)", driverPath);
   lib = open_library(driverPath);
   if (!lib) {
      _eglLog(_EGL_WARNING, "Could not open %s (%s)",
              driverPath, dlerror());
      if (!getenv("EGL_DRIVER"))
         _eglLog(_EGL_WARNING,
                 "The driver can be overridden by setting EGL_DRIVER");
      return NULL;
   }
   mainFunc = (_EGLMain_t) dlsym(lib, "_eglMain");
#endif

   if (!mainFunc) {
      _eglLog(_EGL_WARNING, "_eglMain not found in %s", driverPath);
      if (lib)
         close_library(lib);
      return NULL;
   }

   *handle = lib;
   return mainFunc;
}


/**
 * Load the named driver.  The path and args passed will be
 * owned by the driver and freed.
 */
static _EGLDriver *
_eglLoadDriver(char *path, char *args)
{
   _EGLMain_t mainFunc;
   lib_handle lib;
   _EGLDriver *drv = NULL;

   mainFunc = _eglOpenLibrary(path, &lib);
   if (!mainFunc)
      return NULL;

   drv = mainFunc(args);
   if (!drv) {
      if (lib)
         close_library(lib);
      return NULL;
   }

   if (!drv->Name) {
      _eglLog(_EGL_WARNING, "Driver loaded from %s has no name", path);
      drv->Name = "UNNAMED";
   }

   drv->Path = path;
   drv->Args = args;
   drv->LibHandle = lib;

   return drv;
}


/**
 * Match a display to a preloaded driver.
 */
static _EGLDriver *
_eglMatchDriver(_EGLDisplay *dpy)
{
   _EGLDriver *defaultDriver = NULL;
   EGLint i;

   for (i = 0; i < _eglGlobal.NumDrivers; i++) {
      _EGLDriver *drv = _eglGlobal.Drivers[i];

      /* display specifies a driver */
      if (dpy->DriverName) {
         if (strcmp(dpy->DriverName, drv->Name) == 0)
            return drv;
      }
      else if (drv->Probe) {
         if (drv->Probe(drv, dpy))
            return drv;
      }
      else {
         if (!defaultDriver)
            defaultDriver = drv;
      }
   }

   return defaultDriver;
}


/**
 * Load a driver and save it.
 */
const char *
_eglPreloadDriver(_EGLDisplay *dpy)
{
   char *path, *args;
   _EGLDriver *drv;
   EGLint i;

   path = _eglChooseDriver(dpy, &args);
   if (!path)
      return NULL;

   for (i = 0; i < _eglGlobal.NumDrivers; i++) {
      drv = _eglGlobal.Drivers[i];
      if (strcmp(drv->Path, path) == 0) {
         _eglLog(_EGL_DEBUG, "Driver %s is already preloaded",
                 drv->Name);
         free(path);
         if (args)
            free(args);
         return drv->Name;
      }
   }

   drv = _eglLoadDriver(path, args);
   if (!drv)
      return NULL;

   _eglGlobal.Drivers[_eglGlobal.NumDrivers++] = drv;

   return drv->Name;
}


/**
 * Open a preloaded driver.
 */
_EGLDriver *
_eglOpenDriver(_EGLDisplay *dpy)
{
   _EGLDriver *drv = _eglMatchDriver(dpy);
   return drv;
}


/**
 * Close a preloaded driver.
 */
EGLBoolean
_eglCloseDriver(_EGLDriver *drv, _EGLDisplay *dpy)
{
   return EGL_TRUE;
}


/**
 * Unload preloaded drivers.
 */
void
_eglUnloadDrivers(void)
{
   EGLint i;
   for (i = 0; i < _eglGlobal.NumDrivers; i++) {
      _EGLDriver *drv = _eglGlobal.Drivers[i];
      lib_handle handle = drv->LibHandle;

      if (drv->Path)
         free((char *) drv->Path);
      if (drv->Args)
         free((char *) drv->Args);

      /* destroy driver */
      if (drv->Unload)
         drv->Unload(drv);

      if (handle)
         close_library(handle);
      _eglGlobal.Drivers[i] = NULL;
   }

   _eglGlobal.NumDrivers = 0;
}


/**
 * Given a display handle, return the _EGLDriver for that display.
 */
_EGLDriver *
_eglLookupDriver(EGLDisplay dpy)
{
   _EGLDisplay *d = _eglLookupDisplay(dpy);
   if (d)
      return d->Driver;
   else
      return NULL;
}


/**
 * Plug all the available fallback routines into the given driver's
 * dispatch table.
 */
void
_eglInitDriverFallbacks(_EGLDriver *drv)
{
   /* If a pointer is set to NULL, then the device driver _really_ has
    * to implement it.
    */
   drv->API.Initialize = NULL;
   drv->API.Terminate = NULL;

   drv->API.GetConfigs = _eglGetConfigs;
   drv->API.ChooseConfig = _eglChooseConfig;
   drv->API.GetConfigAttrib = _eglGetConfigAttrib;

   drv->API.CreateContext = _eglCreateContext;
   drv->API.DestroyContext = _eglDestroyContext;
   drv->API.MakeCurrent = _eglMakeCurrent;
   drv->API.QueryContext = _eglQueryContext;

   drv->API.CreateWindowSurface = _eglCreateWindowSurface;
   drv->API.CreatePixmapSurface = _eglCreatePixmapSurface;
   drv->API.CreatePbufferSurface = _eglCreatePbufferSurface;
   drv->API.DestroySurface = _eglDestroySurface;
   drv->API.QuerySurface = _eglQuerySurface;
   drv->API.SurfaceAttrib = _eglSurfaceAttrib;
   drv->API.BindTexImage = _eglBindTexImage;
   drv->API.ReleaseTexImage = _eglReleaseTexImage;
   drv->API.SwapInterval = _eglSwapInterval;
   drv->API.SwapBuffers = _eglSwapBuffers;
   drv->API.CopyBuffers = _eglCopyBuffers;

   drv->API.QueryString = _eglQueryString;
   drv->API.WaitGL = _eglWaitGL;
   drv->API.WaitNative = _eglWaitNative;

#ifdef EGL_MESA_screen_surface
   drv->API.ChooseModeMESA = _eglChooseModeMESA; 
   drv->API.GetModesMESA = _eglGetModesMESA;
   drv->API.GetModeAttribMESA = _eglGetModeAttribMESA;
   drv->API.GetScreensMESA = _eglGetScreensMESA;
   drv->API.CreateScreenSurfaceMESA = _eglCreateScreenSurfaceMESA;
   drv->API.ShowScreenSurfaceMESA = _eglShowScreenSurfaceMESA;
   drv->API.ScreenPositionMESA = _eglScreenPositionMESA;
   drv->API.QueryScreenMESA = _eglQueryScreenMESA;
   drv->API.QueryScreenSurfaceMESA = _eglQueryScreenSurfaceMESA;
   drv->API.QueryScreenModeMESA = _eglQueryScreenModeMESA;
   drv->API.QueryModeStringMESA = _eglQueryModeStringMESA;
#endif /* EGL_MESA_screen_surface */

#ifdef EGL_VERSION_1_2
   drv->API.CreatePbufferFromClientBuffer = _eglCreatePbufferFromClientBuffer;
#endif /* EGL_VERSION_1_2 */
}



/**
 * Try to determine which EGL APIs (OpenGL, OpenGL ES, OpenVG, etc)
 * are supported on the system by looking for standard library names.
 */
EGLint
_eglFindAPIs(void)
{
   EGLint mask = 0x0;
   lib_handle lib;
#if defined(_EGL_PLATFORM_WINDOWS)
   /* XXX not sure about these names */
   const char *es1_libname = "libGLESv1_CM.dll";
   const char *es2_libname = "libGLESv2.dll";
   const char *gl_libname = "OpenGL32.dll";
   const char *vg_libname = "libOpenVG.dll";
#elif defined(_EGL_PLATFORM_X)
   const char *es1_libname = "libGLESv1_CM.so";
   const char *es2_libname = "libGLESv2.so";
   const char *gl_libname = "libGL.so";
   const char *vg_libname = "libOpenVG.so";
#endif

   if ((lib = open_library(es1_libname))) {
      close_library(lib);
      mask |= EGL_OPENGL_ES_BIT;
   }

   if ((lib = open_library(es2_libname))) {
      close_library(lib);
      mask |= EGL_OPENGL_ES2_BIT;
   }

   if ((lib = open_library(gl_libname))) {
      close_library(lib);
      mask |= EGL_OPENGL_BIT;
   }

   if ((lib = open_library(vg_libname))) {
      close_library(lib);
      mask |= EGL_OPENVG_BIT;
   }

   return mask;
}
