typedef unsigned int GLenum;
typedef unsigned char GLboolean;
typedef unsigned int GLbitfield;
typedef signed char GLbyte;
typedef short GLshort;
typedef int GLint;
typedef int GLsizei;
typedef unsigned char GLubyte;
typedef unsigned short GLushort;
typedef unsigned int GLuint;
typedef float GLfloat;
typedef float GLclampf;
typedef double GLdouble;
typedef double GLclampd;
typedef void GLvoid;

%include simple_typemaps.inc
%include complex_typemaps.inc


#define DOC(NAME, VALUE)\
%{\
static char _doc_##NAME[] = VALUE;\
%}

%{

#define PyErr_XPrint() if (PyErr_Occurred()) PyErr_Print()


#if HAS_DYNAMIC_EXT

#define DECLARE_EXT(PROC_NAME, RET, ERROR_RET, PROTO, CALL)\
RET PROC_NAME PROTO\
{\
	typedef RET (APIENTRY *proc_##PROC_NAME) PROTO;\
	proc_##PROC_NAME proc = (proc_##PROC_NAME)GL_GetProcAddress(#PROC_NAME);\
	if (proc) return proc CALL;\
	PyErr_SetGLerror(GL_INVALID_OPERATION);\
	return ERROR_RET;\
}

#define DECLARE_VOID_EXT(PROC_NAME, PROTO, CALL)\
void PROC_NAME PROTO\
{\
	typedef void (APIENTRY *proc_##PROC_NAME) PROTO;\
	proc_##PROC_NAME proc = (proc_##PROC_NAME)GL_GetProcAddress(#PROC_NAME);\
	if (proc) proc CALL;\
	else PyErr_SetGLerror(GL_INVALID_OPERATION);\
}

#else

#define DECLARE_EXT(PROC_NAME, RET, ERROR_RET, PROTO, CALL)\
RET PROC_NAME PROTO\
{\
	PyErr_SetGLerror(GL_INVALID_OPERATION);\
	return ERROR_RET;\
}

#define DECLARE_VOID_EXT(PROC_NAME, PROTO, CALL)\
void PROC_NAME PROTO\
{\
	PyErr_SetGLerror(GL_INVALID_OPERATION);\
}

#endif


#define _PyTuple_From(NAME, BASE) PyObject* _PyTuple_From##NAME(int len, BASE* data);

_PyTuple_From(UnsignedCharArray, unsigned char)

_PyTuple_From(CharArray, signed char)

_PyTuple_From(UnsignedShortArray, unsigned short)

_PyTuple_From(ShortArray, short)

_PyTuple_From(UnsignedIntArray, unsigned int)

_PyTuple_From(IntArray, int)

_PyTuple_From(FloatArray, float)

_PyTuple_From(DoubleArray, double)


#define _PyObject_From(NAME, BASE) PyObject* _PyObject_From##NAME(int nd, int* dims, BASE* data, int own);

_PyObject_From(UnsignedCharArray, unsigned char)

_PyObject_From(CharArray, signed char)

_PyObject_From(UnsignedShortArray, unsigned short)

_PyObject_From(ShortArray, short)

_PyObject_From(UnsignedIntArray, unsigned int)

_PyObject_From(IntArray, int)

_PyObject_From(FloatArray, float)

_PyObject_From(DoubleArray, double)


PyObject* _PyObject_FromArray(GLenum type, int nd, int *dims, void* data, int own);

void* SetupPixelRead(int rank, GLenum format, GLenum type, int *dims);

void SetupPixelWrite(int rank);

void* SetupRawPixelRead(GLenum format, GLenum type, int n, const int *dims, int* size);

void* _PyObject_AsPointer(PyObject* x);

void init_util();

typedef void *PTR;

typedef struct
{
	void (*_decrement)(void* pointer);
	void (*_decrementPointer)(GLenum pname);
	int (*_incrementLock)(void *pointer);
	int (*_incrementPointerLock)(GLenum pname);
	void (*_acquire)(void* pointer);
	void (*_acquirePointer)(GLenum pname);
#if HAS_DYNAMIC_EXT
	PTR (*GL_GetProcAddress)(const char* name);
#endif
	int (*InitExtension)(const char *name, const char** procs);
	PyObject *_GLerror;
	PyObject *_GLUerror;
}  util_API;

%}

#ifdef EXPORT_UTIL
%{

#ifndef GL_VERSION_1_1
PyObject *contexts;

typedef struct
{
	PyObject_HEAD
	GLenum feedback_type;
	GLfloat *feedback_buffer;
	GLuint *selection_buffer;
} PyGLcontext;


static void PyGLcontext_Del(PyObject *self)
{
	PyObject_Del(self);
}


PyTypeObject PyGLcontext_Type =
{
    PyObject_HEAD_INIT(0)
    0,									// ob_size
    "GLcontext",					// tp_name
    sizeof(PyGLcontext),			// tp_basicsize
    0,									// tp_itemsize
    PyGLcontext_Del,				// tp_dealloc
};


PyGLcontext* PyGLcontext_New()
{
	PyGLcontext *self = PyObject_NEW(PyGLcontext, &PyGLcontext_Type);

	self->feedback_buffer = NULL;
	self->selection_buffer = NULL;

	return self;
}


PyGLcontext *GetGLcontext()
{
	if (CurrentContextIsValid())
	{
		PyObject *key = PyInt_FromLong(GetCurrentContext());
		PyGLcontext *context = (PyGLcontext*)PyDict_GetItem(contexts, key);
		if (!context)
		{
			context = PyGLcontext_New();
			PyDict_SetItem(contexts, key, (PyObject*)context);
		}
		Py_DECREF(key);
		return context;
	}
	return NULL;
}

#endif

#define POINTER_COUNT 6

typedef struct
{
	void *pointers[POINTER_COUNT+1];
	int ref_count;
} PointerLock;

PointerLock *PointerLocks = NULL;
int PointerLockCount = 0;

GLenum *PointerTypes = NULL;
int PointerTypeCount = 0;

#ifndef GL_VERSION_1_1
#if !EXT_DEFINES_PROTO || !defined(GL_EXT_vertex_array)
DECLARE_VOID_EXT(glGetPointervEXT, (GLenum pname, void** params)), (GLenum pname, void** params))
#endif

static char *va_proc_names[] =
{
#if !EXT_DEFINES_PROTO || !defined(GL_EXT_vertex_array)
	"glGetPointervEXT",
#endif
	NULL
};

int InitExtension(const char* name, const char** procs);

void glGetPointerv(GLenum pname, void** params)
{
	if (InitExtension("GL_EXT_vertex_array", va_proc_names))
	{
		glGetPointervEXT(pname, params);
	}
}
#endif

/* decrementLock a pointer */
void decrementLock(void* pointer)
{
	if (pointer && PointerLocks)
	{
		int i, j;
		for (i = 0; i < PointerLockCount; i++)
		{
			for (j = 0; j < POINTER_COUNT+1; j++)
			{
				if (PointerLocks[i].pointers[j] == pointer && --PointerLocks[i].ref_count == 0)
				{
					int k;
					PyMem_Free(PointerLocks[i].pointers[0]);
					PointerLockCount--;
					for (k = i; k < PointerLockCount; k++) PointerLocks[k] = PointerLocks[k+1];
					return;
				}
			}
		}
	}
}


/* decrementLock a pointer referenced by glSelectBuffer, glFeedbackBuffer, glColorPointer, etc. */
void decrementPointerLock(GLenum pname)
{
	void* pointer = NULL;
	glGetPointerv(pname, &pointer);
	decrementLock(pointer);
}


void decrementAllLocks()
{
	int i;
	
	for (i = 0; i < PointerTypeCount; i++) decrementPointerLock(PointerTypes[i]);
}


/* increment pointer lock */
int incrementLock(void *pointer)
{
	if (pointer && PointerLocks)
	{
		int i, j;
		
		for (i = 0; i < PointerLockCount; i++)
		{
			for (j = 0; j < POINTER_COUNT+1; j++)
			{
				if (PointerLocks[i].pointers[j] == pointer)
				{
					PointerLocks[i].ref_count++;
					return 1;
				}
			}
		}
	}

	return 0;
}


void addPointerType(GLenum type)
{
	int i;
	
	for (i = 0; i < PointerTypeCount; i++) if (PointerTypes[i] == type) return;
	
	PointerTypes = (PointerTypes) ? PyMem_Resize(PointerTypes, GLenum, ++PointerTypeCount) : PyMem_New(GLenum, PointerTypeCount = 1);
	
	PointerTypes[PointerTypeCount-1] = type;
}


int incrementPointerLock(GLenum pname)
{
	void* pointer = NULL;
	int found;
	
	glGetPointerv(pname, &pointer);
	found = incrementLock(pointer);
	if (found) addPointerType(pname);

	return found;
}


void incrementAllLocks()
{
	int i;
	
	for (i = 0; i < PointerTypeCount; i++) incrementPointerLock(PointerTypes[i]);
}


/* acquire a pointer */
void acquire(void* pointer)
{
	if (pointer)
	{
		int i;
		PointerLock *lock;

		PointerLocks = (PointerLocks) ? PyMem_Resize(PointerLocks, PointerLock, ++PointerLockCount) : PyMem_New(PointerLock, PointerLockCount = 1);
		lock = PointerLocks + PointerLockCount - 1;
		(*lock).pointers[0] = pointer;
		(*lock).ref_count = 1;

		for (i = 1; i < POINTER_COUNT+1; i++) (*lock).pointers[i] = NULL;
	}
}

void acquirePointer(GLenum pname)
{
	void* pointer = NULL;
	glGetPointerv(pname, &pointer);
	acquire(pointer);
	addPointerType(pname);
}

void acquireInterleavedPointer(void* pointer, GLenum pnames[POINTER_COUNT])
{
	if (pointer)
	{
		int i;
		PointerLock *lock;

		PointerLocks = (PointerLocks) ? PyMem_Resize(PointerLocks, PointerLock, ++PointerLockCount) : PyMem_New(PointerLock, PointerLockCount = 1);
		lock = PointerLocks + PointerLockCount - 1;
		(*lock).pointers[0] = pointer;
		(*lock).ref_count = 0;

		for (i = 0; i < POINTER_COUNT; i++)
		{
			(*lock).pointers[i+1] = NULL;
			if (pnames[i])
			{
				glGetPointerv(pnames[i], (*lock).pointers + i + 1);
				if ((*lock).pointers[i+1])
				{
					addPointerType(pnames[i]);
					(*lock).ref_count++;
				}
			}
		}
	}
}

int in_extension_list(const char* name, char *list)
{
	size_t len = strlen(name);
	
	while (list && strlen(list))
	{
		if (strlen(list) >= len && !strncmp(list, name, len)) return 1;
		list = strchr(list, ' ');
		if (!list) return 0;
		list++;
	}

	return 0;
}


const char* GetPlatformExtensionsString();
void* GetExtProc(const char* name);

int has_extension(const char* name)
{
	if (CurrentContextIsValid())
	{
		return in_extension_list(name, (char*)glGetString(GL_EXTENSIONS)) ||
		       in_extension_list(name, (char*)gluGetString(GLU_EXTENSIONS)) ||
		       in_extension_list(name, (char*)GetPlatformExtensionsString());
	}

	return 0;
}


#if HAS_DYNAMIC_EXT
PyObject *ProcAddresses = NULL;

void *GL_GetProcAddress(const char* name)
{
	void *address = NULL;
	if (CurrentContextIsValid())
	{
		PyObject *key = Py_BuildValue("ls", GetCurrentContext(), name);
		PyObject *py_address = PyDict_GetItem(ProcAddresses, key);
		if (py_address) address = PyCObject_AsVoidPtr(py_address);
		Py_DECREF(key);
	}
	return address;
}
#endif

int InitExtension(const char* name, const char** procs)
{
	if (has_extension(name))
	{
		int success = 1;
		void *proc;
		PyObject *key;
#if HAS_DYNAMIC_EXT
		while (*procs)
		{
			key = Py_BuildValue("ls", GetCurrentContext(), *procs);
			if (PyDict_GetItem(ProcAddresses, key)) break;
			proc = (void*)GetExtProc(*procs);
			if (proc)
			{
				PyDict_SetItem(ProcAddresses, key, PyCObject_FromVoidPtr(proc, NULL));
			}
			Py_DECREF(key);
			procs++;
		}
#endif
		return success;
	}
	return 0;
}

static util_API _util_API;

PyObject *GLerror = NULL;
PyObject *GLUerror = NULL;

%}
#else
%{
static util_API *_util_API = NULL;

#define decrementLock(x) (*_util_API)._decrement(x)
#define decrementPointerLock(x) (*_util_API)._decrementPointer(x)
#define incrementLock(x) (*_util_API)._incrementLock(x)
#define incrementPointerLock(x) (*_util_API)._incrementPointerLock(x)
#define acquire(x) (*_util_API)._acquire(x)
#define acquirePointer(x) (*_util_API)._acquirePointer(x)
#define GLerror (*_util_API)._GLerror
#define GLUerror (*_util_API)._GLUerror
#if HAS_DYNAMIC_EXT
#define GL_GetProcAddress(x) (*_util_API).GL_GetProcAddress(x)
#endif
#define InitExtension(x, y) (*_util_API).InitExtension(x, (const char**)y)

%}
#endif

%{
#define PyErr_SetGLerror(code) PyErr_SetObject(GLerror, Py_BuildValue("is", code, gluErrorString(code)));
#define PyErr_SetGLUerror(code) PyErr_SetObject(GLUerror, Py_BuildValue("is", code, gluErrorString(code)));

int _PyObject_Dimension(PyObject* x, int rank);

#define ERROR_MSG_SEP ", "
#define ERROR_MSG_SEP_LEN 2

int GLErrOccurred()
{
	if (PyErr_Occurred()) return 1;

	if (CurrentContextIsValid())
	{
		GLenum error, *errors = NULL;
		char *msg = NULL;
		const char *this_msg;
		int count = 0;
	
		error = glGetError();

		while (error != GL_NO_ERROR)
		{
			this_msg = gluErrorString(error);
			if (count)
			{
				msg = realloc(msg, (strlen(msg) + strlen(this_msg) + ERROR_MSG_SEP_LEN + 1)*sizeof(char));
				strcat(msg, ERROR_MSG_SEP);
				strcat(msg, this_msg);
				
				errors = realloc(errors, (count + 1)*sizeof(GLenum));
			}
			else
			{
				msg = malloc((strlen(this_msg) + 1)*sizeof(char));
				strcpy(msg, this_msg);
				
				errors = malloc(sizeof(GLenum));
			}
			
			errors[count++] = error;
			error = glGetError();
		}
		
		if (count)
		{
			PyErr_SetObject(GLerror, Py_BuildValue("Os", _PyTuple_FromIntArray(count, (int*)errors), msg));
			free(errors);
			free(msg);
			return 1;
		}
	}

	return 0;
}

%}

#define EXCEPTION_HANDLER()\
%except\
{\
	$function\
	if (PyErr_Occurred()) return NULL;\
}

#define GL_EXCEPTION_HANDLER()\
%except\
{\
	$function\
	if (GLErrOccurred()) return NULL;\
}

%init
%{
#ifdef NUMERIC
	PyArray_API = NULL;
	import_array();
	init_util();
	PyErr_Clear();
#endif
%}

#ifdef EXPORT_UTIL
%pragma(python) code = "__numeric_present__ = __init___.__numeric_present__
__numeric_support__ = __init___.__numeric_support__"

%init
%{
	GLerror = PyErr_NewException("OpenGL.GL.GLerror", PyExc_EnvironmentError, NULL);
	GLUerror = PyErr_NewException("OpenGL.GLU.GLUerror", PyExc_EnvironmentError, NULL);
#if HAS_DYNAMIC_EXT
	ProcAddresses = PyDict_New();
	PyDict_SetItemString(d, "proc", ProcAddresses);
#endif
#ifndef GL_VERSION_1_1
	contexts = PyDict_New();
#endif
	_util_API._decrement = decrementLock;
	_util_API._decrementPointer = decrementPointerLock;
	_util_API._incrementLock = incrementLock;
	_util_API._incrementPointerLock = incrementPointerLock;
	_util_API._acquire = acquire;
	_util_API._acquirePointer = acquirePointer;
	_util_API.InitExtension = InitExtension;
#if HAS_DYNAMIC_EXT
	_util_API.GL_GetProcAddress = GL_GetProcAddress;
#endif
	_util_API._GLerror = GLerror;
	_util_API._GLUerror = GLUerror;
	PyDict_SetItemString(d, "_util_API", PyCObject_FromVoidPtr((void*)&_util_API, NULL));
#ifdef NUMERIC
	PyDict_SetItemString(d, "__numeric_support__", PyInt_FromLong(1));
	PyDict_SetItemString(d, "__numeric_present__", PyInt_FromLong((PyArray_API) ? 1 : 0));
#else
	PyDict_SetItemString(d, "__numeric_support__", PyInt_FromLong(0));
	PyDict_SetItemString(d, "__numeric_present__", PyInt_FromLong(0));
#endif
%}
#else
%init
%{
{
	PyObject *util = PyImport_ImportModule("OpenGL.GL.__init___");
	if (util)
	{
		PyObject *api_object = PyDict_GetItemString(PyModule_GetDict(util), "_util_API");
		if (PyCObject_Check(api_object)) _util_API = (util_API*)PyCObject_AsVoidPtr(api_object);
	}
}
%}
#endif

