/*
	GLXPrivate.c
	Author: Patrick Baggett <ptbaggett@762studios.com>
	Created: 1/11/2013

	Purpose:

	OpenGL on X Windows (GLX) utility code

	License:

	This program is free software. It comes without any warranty, to
	the extent permitted by applicable law. You can redistribute it
	and/or modify it under the terms of the Do What The Fuck You Want
	To Public License, Version 2, as published by Sam Hocevar. See
	http://sam.zoy.org/wtfpl/COPYING for more details.

*/

#include <SST/SST_WMOpenGL.h>
#include "XlibPrivate.h"
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>

/* GL constants */
#define GL_VERSION 0x1F02

/* GLX constants */
#define GLX_DOUBLEBUFFER		5
#define GLX_STEREO				6
#define GLX_AUX_BUFFERS			7
#define GLX_RED_SIZE			8
#define GLX_GREEN_SIZE			9
#define GLX_BLUE_SIZE			10
#define GLX_ALPHA_SIZE			11
#define GLX_DEPTH_SIZE			12
#define GLX_STENCIL_SIZE		13
#define GLX_DRAWABLE_TYPE		0x8010
#define GLX_RGBA_TYPE			0x8014
#define GLX_WINDOW_BIT			0x00000001
#define GLX_PIXMAP_BIT			0x00000002
#define GLX_PBUFFER_BIT			0x00000004

/* GLX_ARB_multisample */
#define GLX_SAMPLE_BUFFERS_ARB	100000
#define GLX_SAMPLES_ARB			100001

#define GLX_PBUFFER_HEIGHT		0x8040
#define GLX_PBUFFER_WIDTH		0x8041

/* GLX_ARB_create_context[_profile] */
#define GLX_CONTEXT_MAJOR_VERSION_ARB				0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB				0x2092
#define GLX_CONTEXT_FLAGS_ARB						0x2094
#define GLX_CONTEXT_PROFILE_MASK_ARB				0x9126
#define GLX_CONTEXT_DEBUG_BIT_ARB					0x0001
#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB		0x0002
#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB			0x00000001
#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB	0x00000002

GLXFunctions glX = { 0 };

static void setupAttribs(int* glxAttr, const SST_OpenGLContextAttributes* attribs, Bool supportsMultisample);
static int* addAttr(int* ptr, int attr, int value)
{
	ptr[0] = attr;
	ptr[1] = value;
	return ptr + 2;
}

/******************************************************************************/

int loadGLX()
{
	const int flags = RTLD_LAZY | RTLD_GLOBAL;
	void* libGL;

	/* Set all values to null/0/false */
	memset(&glX, 0, sizeof(GLXFunctions));

	/* Load OpenGL library with proper DT_SONAME */
	libGL = dlopen("libGL.so.1", flags);
	if(libGL == NULL)
	{
		/* Failed...try generic name for it */
		libGL = dlopen("libGL.so", flags);
		if(libGL == NULL)
			return 0;
	}

	/* Resolve symbols */
	glX.QueryExtension = dlsym(libGL, "glXQueryExtension");
	glX.QueryVersion = dlsym(libGL, "glXQueryVersion");
	glX.QueryExtensionsString = dlsym(libGL, "glXQueryExtensionsString");
	glX.ChooseFBConfig = dlsym(libGL, "glXChooseFBConfig");
	glX.CreateNewContext = dlsym(libGL, "glXCreateNewContext");
	glX.DestroyContext = dlsym(libGL, "glXDestroyContext");
	glX.MakeContextCurrent = dlsym(libGL, "glXMakeContextCurrent");
	glX.GetCurrentContext = dlsym(libGL, "glXGetCurrentContext");
	glX.SwapBuffers = dlsym(libGL, "glXSwapBuffers");
	glX.CreatePbuffer = dlsym(libGL, "glXCreatePbuffer");
	glX.DestroyPbuffer = dlsym(libGL, "glXDestroyPbuffer");

	/* On Linux, glXGetProcAddressARB() is statically exported from libGL.so as part of LSB. However,
	on non-LSB compliant or other UNIX systems, the -ARB version may not exist. Try the unsuffixed
	form if the -ARB variant does not exist. */
	glX.GetProcAddressARB = dlsym(libGL, "glXGetProcAddressARB");
	if(glX.GetProcAddressARB == NULL)
		glX.GetProcAddressARB = dlsym(libGL, "glXGetProcAddress");
	
	/* We dont resolve  glXCreateContextAttribsARB yet; first check GLX extension strings. */
	glX.CreateContextAttribsARB = NULL;

	/* Save library handle */
	glX.libGL = libGL;
	return 1;
}

/******************************************************************************/

void unloadGLX()
{
	dlclose(glX.libGL);
}

/******************************************************************************/

int isGLXLoaded()
{
	return (glX.libGL != NULL);
}

/******************************************************************************/

int isGLXSupported(Display* display)
{
	int major = 0, minor = 0;
	int combined;
	const char* ext;

	/* Query if the X server supports GLX. If it doesn't, then nothing to do */
	if(glX.QueryExtension(display, NULL, NULL) == False)
	{
		fprintf(stderr, "%s:GLX extension not supported on this connection.\n", "libsst-wm");
		return 0;
	}

	/* Query the version; we need 1.3 or later */
	if(glX.QueryVersion(display, &major, &minor) == False)
	{
		fprintf(stderr, "%s:Unable to query GLX version.\n", "libsst-wm");
		return 0;
	}

	/* Fail on less than 1.3 */
	combined = (major << 8) | minor;
	if(combined < 0x13)
	{
		fprintf(stderr, "%s:GLX version 1.3 required, found %d.%d\n", "libsst-wm", major, minor);
		return 0;
	}

	ext = glX.QueryExtensionsString(display, DefaultScreen(display));

	/* "GLX_ARB_create_context" and "GLX_ARB_create_context_profile" might both match the first strstr(),
	but "GLX_ARB_create_context_profile" requires the former, so it's all good.
	TODO: write less stupid extension checker - one day may not be true */
	if(combined >= 0x14) /* GLX 1.4 required for GLX_ARB_create_context */
	{
		glX.supportsCreateContextARB = (Bool)(strstr(ext, "GLX_ARB_create_context") != NULL);
		glX.supportsProfiles = (Bool)(strstr(ext, "GLX_ARB_create_context_profile") != NULL);

		/* Supposedly GLX_ARB_create_context is supported. Try to resolve the symbol. If we can't,
		then backout. */
		if(glX.supportsCreateContextARB) {
			glX.CreateContextAttribsARB = glX.GetProcAddressARB("glXCreateContextAttribsARB");
			if(glX.CreateContextAttribsARB == NULL) {
				glX.supportsCreateContextARB = False;
			}
		}
	}
	else /* Don't have GLX 1.4, so can't possibly have these */
	{
		glX.supportsCreateContextARB = False;
		glX.supportsProfiles = False;
	}

	glX.supportsMultisample = (Bool)(strstr(ext, "GLX_ARB_multisample") != NULL);

	return 1;
}

/******************************************************************************/

SST_OpenGLContext GLXCreateOpenGLContext(SST_DisplayTarget_Xlib* displayTarget, SST_WMOpenGLType apiType, const SST_OpenGLContextAttributes* attribs, SST_OpenGLContextAttributes* selectedAttribsReturn)
{
	int attr[MAX_GL_ATTRS];
	Display* display = displayTarget->display;
	GLXFBConfig* configs;
	GLXFBConfig fbconfig;
	GLXContext ctx;
	int nrConfigs = 0;
	SST_OpenGLContext_Xlib* GLctx;
	uint8_t major, minor;
	GLXPbuffer pb;
	const int pbattr[5] = { GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, None };

	/* GLX cannot be used to do anything other than standard OpenGL */
	if(apiType != SSTGL_OPENGL)
		return NULL;

	/* GLX doesn't support multisampling, but it was explicitly requested */
	if(!glX.supportsMultisample && attribs->multisampleFactor > 1)
		return NULL;

	/* Copy SST attributes to GLX attributes */
	setupAttribs(attr, attribs, glX.supportsMultisample);

	/* Get a list of configs -- we're going to only use the first one though */
	configs = glX.ChooseFBConfig(display, DefaultScreen(display), attr, &nrConfigs);
	if(configs == NULL)
		return NULL;

	/* Copy first FBConfig entry, free array */
	fbconfig = *configs;
	X.Free(configs);

	/* Use the new create context functions? */
	if(glX.supportsCreateContextARB)
	{
		int attr[32];
		int* ptr = &attr[0];
		int ctxFlags = 0;

		/* Set the major/minor version */
		ptr = addAttr(ptr, GLX_CONTEXT_MAJOR_VERSION_ARB, attribs->contextVersionMajor);
		ptr = addAttr(ptr, GLX_CONTEXT_MINOR_VERSION_ARB, attribs->contextVersionMinor);

		/* If GLX_ARB_create_context_profile is supported, add a profile mask */
		if(glX.supportsProfiles)
			ptr = addAttr(ptr, GLX_CONTEXT_PROFILE_MASK_ARB, (attribs->legacyEnabled ? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : GLX_CONTEXT_CORE_PROFILE_BIT_ARB));

		/* Enable/disable forward compatibility */
		if(!attribs->legacyEnabled)
			ctxFlags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;

		/* Debug mode? */
		if(attribs->debugEnabled)
			ctxFlags |= GLX_CONTEXT_DEBUG_BIT_ARB;

		ptr = addAttr(ptr, GLX_CONTEXT_FLAGS_ARB, ctxFlags);
		*ptr = None;


		ctx = glX.CreateContextAttribsARB(display, fbconfig, NULL, True, attr);
		if(ctx == NULL)
			return NULL;

	}
	else /* Use older GLX 1.3 glXCreateNewContext() function */
	{
		/* Attempt to create an OpenGL context */
		ctx = glX.CreateNewContext(display, fbconfig, GLX_RGBA_TYPE, NULL, True);
		if(ctx == NULL)
			return NULL;
	}

	/* In order to check the version number, we're going to need to create
	a temporary context. I'm using a Pbuffer to do so. */
	pb = glX.CreatePbuffer(display, fbconfig, pbattr);
	if(pb == None)
	{
		glX.DestroyContext(display, ctx);
		return NULL;
	}

	/* Activate it */
	if(glX.MakeContextCurrent(display, pb, pb, ctx))
	{
		const char* (*pglGetString)(unsigned int);
		const char* version;

		/* Check libGL for this */
		pglGetString = dlsym(glX.libGL, "glGetString");

		version = pglGetString(GL_VERSION);

		major = (uint8_t)(version[0] - '0'); /* "X.Y" -> [0]:major, [2]:minor */
		minor = (uint8_t)(version[2] - '0');

		/* Save context version information */
		if(selectedAttribsReturn != NULL)
		{
			selectedAttribsReturn->contextVersionMajor = (uint8_t)major;
			selectedAttribsReturn->contextVersionMinor = (uint8_t)minor;
		}

		/* OK, we have the information we need. Unbind the context and destroy the Pbuffer */
		glX.MakeContextCurrent(display, None, None, NULL);
		glX.DestroyPbuffer(display, pb);

		/* If the major version is too low or the major version is OK, but the minor version is lacking, then fail */
		if(major < attribs->contextVersionMajor || (major == attribs->contextVersionMajor && minor < attribs->contextVersionMinor))
		{
			glX.DestroyContext(display, ctx);
			return NULL;
		}
	}
	else /* Failed to make current */
	{
		glX.DestroyContext(display, ctx);
		glX.DestroyPbuffer(display, pb);
		return NULL;
	}



	GLctx = (SST_OpenGLContext_Xlib*)malloc(sizeof(SST_OpenGLContext_Xlib));
	if(GLctx == NULL)
	{
		glX.DestroyContext(display, ctx);
		return NULL;
	}

	GLctx->glxcontext = ctx;
	GLctx->displayTarget = displayTarget;
	GLctx->ctxVersion[0] = major;
	GLctx->ctxVersion[1] = minor;

	return GLctx;
}

/******************************************************************************/

static void setupAttribs(int* glxAttr, const SST_OpenGLContextAttributes* attribs, Bool supportsMultisample)
{
	int* ptr = glxAttr;
	int r, b, g;


	switch(attribs->colorBits)
	{
		/* Since these values are "at least", 5-5-5 and 5-6-5 configurations can use the same number */
		case 15:
		case 16:
			r = g = b = 5;
			break;

		case 24:
			r = g = b = 8;

	}

	ptr = addAttr(ptr, GLX_DOUBLEBUFFER, True);
	ptr = addAttr(ptr, GLX_STEREO, (attribs-> stereoEnabled != 0));
	ptr = addAttr(ptr, GLX_RED_SIZE, r);
	ptr = addAttr(ptr, GLX_GREEN_SIZE, r);
	ptr = addAttr(ptr, GLX_BLUE_SIZE, b);
	ptr = addAttr(ptr, GLX_ALPHA_SIZE, ((int)attribs->alphaBits) & 0xFF);
	ptr = addAttr(ptr, GLX_DEPTH_SIZE, ((int)attribs->depthBits) & 0xFF);
	ptr = addAttr(ptr, GLX_STENCIL_SIZE, ((int)attribs->stencilBits) & 0xFF);

	/* Add multisample attributes as appropriate */
	if(attribs->multisampleFactor > 1 && supportsMultisample)
	{
		ptr = addAttr(ptr, GLX_SAMPLE_BUFFERS_ARB, 1);
		ptr = addAttr(ptr, GLX_SAMPLES_ARB, ((int)attribs->multisampleFactor) & 0xFF);
	}

	/* End attribute list */
	*ptr = None;

}
