/* -----------------------------------------------------------------------------
 *
 * Module      :  C support for Graphics.Rendering.OpenGL.Raw.GetProcAddress.hs
 * Copyright   :  (c) Sven Panne 2009-2019
 * License     :  BSD3
 *
 * Maintainer  :  Sven Panne <svenpanne@gmail.com>
 * Stability   :  stable
 * Portability :  portable
 *
 * -------------------------------------------------------------------------- */

#if defined(USE_WGLGETPROCADDRESS)

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

static void*
getProcAddress(const char *name)
{
  static int firstTime = 1;
  static HMODULE handle = NULL;

  if (firstTime) {
    firstTime = 0;
    handle = LoadLibrary(TEXT("opengl32"));
  }
  return handle ? GetProcAddress(handle, name) : NULL;
}


void*
hs_OpenGLRaw_getProcAddress(const char *name)
{
  void *apiEntry = wglGetProcAddress(name);
  /* Sometimes core API entries are not returned via wglGetProcAddress, so
     we fall back to GetProcAddress in these cases. */
  return (apiEntry == NULL) ? getProcAddress(name) : apiEntry;
}

/* -------------------------------------------------------------------------- */
#elif defined(USE_NSADDRESSOFSYMBOL)

#include <mach-o/dyld.h>
#include <stdlib.h>
#include <string.h>

void*
hs_OpenGLRaw_getProcAddress(const char *name)
{
  NSSymbol symbol;

  /* Prepend a '_' for the Unix C symbol mangling convention */
  char* symbolName = (char*)malloc(strlen(name) + 2);
  if (!symbolName) {
    return NULL;
  }
  symbolName[0] = '_';
  strcpy(symbolName + 1, name);

  if (!NSIsSymbolNameDefined(symbolName)) {
    free(symbolName);
    return NULL;
  }

  symbol = NSLookupAndBindSymbol(symbolName);
  free(symbolName);
  if (!symbol) {
    return NULL;
  }

  return NSAddressOfSymbol(symbol);
}

/* -------------------------------------------------------------------------- */
#elif defined(USE_DLSYM)

#include <stdlib.h>
#include <dlfcn.h>

/* Do not depend on <GL/gl.h> */
typedef unsigned char GLubyte;

/* Do not depend on <GL/glx.h> */
typedef void (*genericFunctionPointer)(void);
typedef genericFunctionPointer (*PFNGLXGETPROCADDRESSARB)(const GLubyte *);

static const char* gpaNames[] = {
  "glXGetProcAddress", "glXGetProcAddressARB", "glXGetProcAddressEXT",
  "_glXGetProcAddress", "_glXGetProcAddressARB", "_glXGetProcAddressEXT"
};

void*
hs_OpenGLRaw_getProcAddress(const char *name)
{
  static int firstTime = 1;
  static void *handle = NULL;
  static void *gpa = NULL;

  if (firstTime) {
    firstTime = 0;

    /* Get a handle for our executable. */
    handle = dlopen(NULL, RTLD_LAZY);
    /* If fail this early, there's not much we can do about it. */
    if (!handle) {
      return NULL;
    }

    {
      /* Let's see if our platform supports a glXGetProcAddress() variant. */
      int numNames = (int)(sizeof(gpaNames) / sizeof(gpaNames[0]));
      int i;
      for (i = 0;   (!gpa) && (i < numNames);   ++i) {
        gpa = dlsym(handle, gpaNames[i]);
      }
    }
  }

  if (gpa) {
    /* Fine, we seem to have some kind of glXGetProcAddress(), so use it. */
    return ((PFNGLXGETPROCADDRESSARB)gpa)(name);
  } else if (handle) {
    /* Fallback to dlsym() if we have no glXGetProcAddress(), although we then
       ignore the fact that OpenGL entry points could be context dependent. */
    return dlsym(handle, name);
  } else {
    return NULL;
  }
}

/* -------------------------------------------------------------------------- */
#elif defined(USE_GLXGETPROCADDRESS)

/* Do not depend on <GL/gl.h> */
typedef unsigned char GLubyte;

/* Do not depend on <GL/glx.h> */
typedef void (*genericFunctionPointer)(void);
extern genericFunctionPointer glXGetProcAddressARB(const GLubyte *);

void*
hs_OpenGLRaw_getProcAddress(const char *name)
{
  return glXGetProcAddressARB((const GLubyte*)name);
}

/* -------------------------------------------------------------------------- */
#else

#error "Don't know how to retrieve OpenGL extension entries"

#endif
