/** Copyright (C) 2011 by Jason L. McKesson **/
/** This file is licensed by the MIT License. **/



#include <stdlib.h>
#include <string.h>
#include "gll_gl_ext.h"
#include "gll_gl_ext_base.h"
#include "glload/gll.h"

#ifdef WIN32
#undef APIENTRY
#include <windows.h>
#include "wgll_ext.h"
#include "wgll_ext_base.h"
#define strcasecmp(lhs, rhs) _stricmp((lhs), (rhs))
#endif

#ifdef LOAD_X11
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <GL/glx.h>
#include "glxl_ext.h"
#endif


static StrToExtMap *GetExtEntry(const char *strExtensionName, StrToExtMap *pMapArray, int iNumEntries)
{
	int iLoop;
	StrToExtMap *pCurrLoc = pMapArray;
	for(iLoop = 0; iLoop < iNumEntries; iLoop++, pCurrLoc++)
	{
		if(strcasecmp(strExtensionName, pCurrLoc->strExtensionName) == 0)
			return pCurrLoc;
	}

	return NULL;
}

static void ProcExt(StrToExtMap *pMapLoc)
{
	if(pMapLoc)
	{
		//Only try to load the extension if the extension variable is not set.
		if(!(*pMapLoc->pExtensionVariable))
		{
			if(pMapLoc->LoadExtension)
				pMapLoc->LoadExtension();

			*pMapLoc->pExtensionVariable = 1;
		}
	}
}

static void ProcExtFromExtString(const char *strExtList, StrToExtMap *pMapArray, int iNumEntries)
{
	size_t iExtListLen = strlen(strExtList);
	const char *strExtListEnd = strExtList + iExtListLen;
	const char *strCurrPos = strExtList;
	char strWorkBuff[256];

	while(*strCurrPos)
	{
		//Get the extension at our position.
		int iStrLen = 0;
		const char *strEndStr = strchr(strCurrPos, ' ');
		int iStop = 0;
		if(strEndStr == NULL)
		{
			strEndStr = strExtListEnd;
			iStop = 1;
		}

		iStrLen = (int)((ptrdiff_t)strEndStr - (ptrdiff_t)strCurrPos);

		if(iStrLen > 255)
			return;

		strncpy(strWorkBuff, strCurrPos, iStrLen);
		strWorkBuff[iStrLen] = '\0';

		ProcExt(GetExtEntry(strWorkBuff, pMapArray, iNumEntries));

		strCurrPos = strEndStr + 1;
		if(iStop) break;
	}
}

//This will load the extension.
static void ProcExtFromList()
{
	GLint iLoop;
	GLint iNumExtensions = 0;
	glGetIntegerv(GL_NUM_EXTENSIONS, &iNumExtensions);

	for(iLoop = 0; iLoop < iNumExtensions; iLoop++)
	{
		const char *strExtensionName = (const char *)glGetStringi(GL_EXTENSIONS, iLoop);
		ProcExt(GetExtEntry(strExtensionName, gleIntExtensionMap, gleIntExtensionMapSize));
	}
}

static void GetGLVersion(int *pOutMajor, int *pOutMinor)
{
	const char *strVersion = NULL;
	const char *strDotPos = NULL;
	int iLength = 0;
	char strWorkBuff[10];
	*pOutMinor = 0;
	*pOutMajor = 0;

	strVersion = (const char *)glGetString(GL_VERSION);
	strDotPos = strchr(strVersion, '.');
	if(!strDotPos)
		return;

	iLength = (int)((ptrdiff_t)strDotPos - (ptrdiff_t)strVersion);
	strncpy(strWorkBuff, strVersion, iLength);
	strWorkBuff[iLength] = '\0';

	*pOutMajor = atoi(strWorkBuff);
	strDotPos = strchr(strVersion + iLength + 1, ' ');
	if(!strDotPos)
	{
		//No extra data. Take the whole rest of the string.
		strcpy(strWorkBuff, strVersion + iLength + 1);
	}
	else
	{
		//Copy only up until the space.
		int iLengthMinor = (int)((ptrdiff_t)strDotPos - (ptrdiff_t)strVersion);
		iLengthMinor = iLengthMinor - (iLength + 1);
		strncpy(strWorkBuff, strVersion + iLength + 1, iLengthMinor);
		strWorkBuff[iLengthMinor] = '\0';
	}

	*pOutMinor = atoi(strWorkBuff);
}

static int CheckCompatibilityExt()
{
	int iLoop = 0;
	int iNumExtensions = 0;
	glGetIntegerv(GL_NUM_EXTENSIONS, &iNumExtensions);

	for(iLoop = 0; iLoop < iNumExtensions; iLoop++)
	{
		const char *strExtensionName = (const char *)glGetStringi(GL_EXTENSIONS, iLoop);
		if(strcasecmp(strExtensionName, "GL_ARB_compatibility") == 0)
		{
			return 1;
		}
	}

	return 0;
}

static int iMajorVersion = 0;
static int iMinorVersion = 0;




int LoadFunctions()
{
	int eCurrLoadStatus = LS_LOAD_FUNCTIONS_ALL;

	iMajorVersion = 0;
	iMinorVersion = 0;

	//Clear the extensions, in case we loaded already.
	gleIntClear();

	//Get the base functions that we need just to process OpenGL.
	gleIntLoadBaseFuncs();

	//Get the version numbers.
	GetGLVersion(&iMajorVersion, &iMinorVersion);

	if(iMajorVersion < 3)
	{
		//Load the 2.1 core.
		if(!gleIntLoad_Version_2_1()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
	}
	else if(iMajorVersion == 3 && iMinorVersion < 2)
	{
		switch(iMinorVersion)
		{
		case 0:
			if(!gleIntLoad_Version_3_0()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
			break;
		case 1:
			//Check the ARB_compatibility extension.
			if(CheckCompatibilityExt())
			{
				if(!gleIntLoad_Version_3_1_Comp()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
			}
			else
			{
				if(!gleIntLoad_Version_3_1()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
			}
			break;
		}
	}
	else
	{
		int iProfileMask = 0;
		glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &iProfileMask);
		if(iProfileMask)
		{
			if(iProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT)
			{
				if(!gleIntLoad_Version_3_2_Comp()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
			}
			else
			{
				if(!gleIntLoad_Version_3_2()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
			}
		}
		else
		{
			//Hack to fix NVIDIA stupidity.
			if(CheckCompatibilityExt())
			{
				if(!gleIntLoad_Version_3_2_Comp()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
			}
			else
			{
				if(!gleIntLoad_Version_3_2()) eCurrLoadStatus = LS_LOAD_FUNCTIONS_SOME;
			}
		}
	}

	//Now, process the extensions.
	//Use different methods if glGetStringi is available.
	if(iMajorVersion < 3)
	{
		ProcExtFromExtString((const char *)glGetString(GL_EXTENSIONS), gleIntExtensionMap, gleIntExtensionMapSize);
	}
	else
	{
		ProcExtFromList();
	}

	return eCurrLoadStatus;
}

int GetMajorVersion() { return iMajorVersion; }
int GetMinorVersion() { return iMinorVersion; }

int IsVersionGEQ( int iTestMajorVersion, int iTestMinorVersion )
{
	if(iMajorVersion > iTestMajorVersion) return 1;
	if(iMajorVersion < iTestMajorVersion) return 0;
	if(iMinorVersion >= iTestMinorVersion) return 1;
	return 0;
}

#ifdef WIN32
int LoadWinFunctions(void *hdc)
{
	HDC hDC = (HDC)hdc;

	wgleIntClear();
	wgleIntLoadBaseFuncs();

	gleIntLoadBaseFuncs();

	if(!wglGetExtensionsStringARB) return LS_LOAD_FAILED;

	ProcExtFromExtString(wglGetExtensionsStringARB(hDC), wgleIntExtensionMap, wgleIntExtensionMapSize);

	return LS_LOAD_FUNCTIONS_ALL;
}
#endif //WIN32

#ifdef LOAD_X11
int LoadGLXFunctions(void *display, int screen)
{
    Display *dpy = (Display*)display;
    
	glXeIntClear();

    ProcExtFromExtString(glXQueryExtensionsString(dpy, screen),
        glXeIntExtensionMap, glXeIntExtensionMapSize);

	return LS_LOAD_FUNCTIONS_ALL;
}

#endif //LOAD_X11


