#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"

#ifndef PERL_DARWIN
#include <GL/glut.h>
#else
#include <GLUT/glut.h>
#endif

#include "const-c.inc"

HV *TimerFuncs = NULL;
AV *MenuFuncs = NULL;


/*
 * There now follows some CPP macrology in order to reduce cut-and-paste
 * coding to generate the code to handle GLUT callbacks.
 *
 * Most glutFooFunc() callback-setting routines, with the notable
 * exception of glutTimerFunc(), take a single argument: a pointer to
 * the callback function. This function will take a number of arguments
 * which depends on precisely which callback it is handling.
 *
 * However, most of these callbacks are not global: they are per-window.
 * The way this is handled in XS is as follows: take glutDisplayFunc as
 * an example.
 *
 * Define an AV, DisplayFuncs. If glutDisplayFunc() is called, the id
 * of the current window is found from glutGetWindow(); this will be a
 * small integer. This integer is used as an index into DisplayFuncs,
 * and the coderef passed to glutDisplayFuncs is stored at this index.
 *
 * Now, define the function void DisplayCallback(void) , which finds
 * the current window ID, and then calls the appropriate coderef stored
 * in DisplayFuncs. Set *this* function to be the callback, as far as
 * GLUT is concerned.
 *
 * Since there are many callbacks of many types, this procedure has
 * been automated using cpp. The register_callback() macro defines the
 * appropriate AV and FooCallback() function; it takes some code as an
 * argument to set up the perl argument stack.
 *
 * This is in turn called from the macros register_callback_noargs(),
 * register_callback_2i(), etc. These take one argument, the callback ID
 * (such as "Display" to define DisplayFuncs, DisplayCallback() etc),
 * and make the appropriate calls to register_callback to define a
 * callback function which takes no arguments, two integer arguments,
 * etc.
 *
 * To summarize, say you have a new kind of callback, Foo, which is called
 * with two integer arguments. To set this up in XS, you do:
 *
 * (1) in the plain-C section:
 *
 *     register_callback_2i(Foo)
 *
 * (2) in the XS section:
 *
 *      void glutFooFunc(SV *cb)
 *           CODE:
 *                register_callback_xs(Foo)
 *
 *  That's it!
 *
 *
 */

#define register_callback(cbname,prototype,xscode,callflags) \
AV * cbname ## Funcs=NULL; \
void cbname ## Callback prototype \
{ \
	int id; \
	SV **fetchval; \
\
	dSP; \
	id = glutGetWindow(); \
	if (NULL== cbname ## Funcs) { \
	  croak(#cbname "Callback called with id=%d but " \
		#cbname "Funcs is NULL!", id); \
	} \
\
	if (NULL==(fetchval=av_fetch( cbname ## Funcs,id,FALSE))) { \
		croak("No " #cbname "Func defined for id=%d",id); \
	} \
\
	ENTER; \
	SAVETMPS; \
	PUSHMARK(SP); \
	xscode	\
\
 	PUTBACK; \
\
	call_sv((SV *)*fetchval,G_DISCARD|callflags); \
\
	FREETMPS; \
	LEAVE; \
	 \
\
	return; \
\
}

#define register_callback_noargs(cbname) \
	register_callback(cbname,(void),,G_NOARGS)

#define register_callback_1i(cbname) \
	register_callback(cbname,(int a), \
		XPUSHs(sv_2mortal(newSViv(a))); ,0)\

#define register_callback_2i(cbname) \
	register_callback(cbname,(int a, int b), \
		XPUSHs(sv_2mortal(newSViv(a))); \
		XPUSHs(sv_2mortal(newSViv(b))); ,0)\

#define register_callback_3i(cbname) \
	register_callback(cbname,(int a, int b, int c), \
		XPUSHs(sv_2mortal(newSViv(a))); \
		XPUSHs(sv_2mortal(newSViv(b))); \
		XPUSHs(sv_2mortal(newSViv(c))); ,0)\

#define register_callback_4i(cbname) \
	register_callback(cbname,(int a, int b, int c, int d), \
		XPUSHs(sv_2mortal(newSViv(a))); \
		XPUSHs(sv_2mortal(newSViv(b))); \
		XPUSHs(sv_2mortal(newSViv(c))); \
		XPUSHs(sv_2mortal(newSViv(d))); ,0)\

#define register_callback_uc2i(cbname) \
	register_callback(cbname,(unsigned char uc, int a, int b), \
		XPUSHs(sv_2mortal(newSVuv(uc))); \
		XPUSHs(sv_2mortal(newSViv(a))); \
		XPUSHs(sv_2mortal(newSViv(b))); ,0)\

#define register_callback_xs(cbname) \
		if (!SvROK(cb)) { \
                        if (SVt_IV == SvTYPE(cb)) { \
                                if (0==SvIV(cb)) { \
                                        glut ## cbname ## Func(NULL); \
		av_store( cbname ## Funcs,glutGetWindow(),newSViv(0)); \
                                        return; \
                                } \
                        } \
                        croak("Callback must be code reference"); \
                        return; \
		} \
		if (SVt_PVCV != SvTYPE(SvRV(cb))) { \
			croak("Callback must be code reference"); \
			return; \
		} \
\
		if (NULL== cbname ## Funcs) { \
			 cbname ## Funcs = newAV(); \
		} \
\
		av_store( cbname ## Funcs,glutGetWindow(),newSVsv(cb)); \
		glut ## cbname ## Func( cbname ## Callback); \

/*********** End of macro definitions *************/


/* Callback definitions */

register_callback_noargs(Display)
register_callback_noargs(OverlayDisplay)
register_callback_2i(Reshape)
register_callback_uc2i(Keyboard)
register_callback_4i(Mouse)
register_callback_2i(Motion)
register_callback_2i(PassiveMotion)
register_callback_1i(Visibility)
register_callback_1i(Entry)
register_callback_3i(Special)
register_callback_3i(SpaceballMotion)
register_callback_3i(SpaceballRotate)
register_callback_2i(SpaceballButton)
register_callback_2i(ButtonBox)
register_callback_2i(Dials)
register_callback_2i(TabletMotion)
register_callback_4i(TabletButton)
register_callback_3i(MenuStatus)
register_callback_1i(MenuState)
register_callback_noargs(Idle)


/* Plain C code */


void TimerCallback(int value)
{
	HE *hashent;
	SV *hashvalue,*callback,*realvalue;
	AV *myarr;
	dSP;

	hashent = hv_fetch_ent(TimerFuncs,newSViv(value),FALSE,0);

	if (NULL==hashent) {
		croak("TimerCallback(value=%d) has no hash entry!",value);
	}

	hashvalue = HeVAL(hashent);

	/*
	 * Be really anal about what we get back from the hash.
	 * To start with, make sure we're holding a reference to an array.
	 * */

	if (!SvROK(hashvalue)) {
		croak("TimerCallback: hash entry is not a reference!");
		return;
	}
	if (SVt_PVAV != SvTYPE(SvRV(hashvalue))) {
		croak("TimerCallback: hash entry is not an array reference!");
		return;
	}

	myarr = (AV *)SvRV(hashvalue);
	if (1!=av_len(myarr)) {
		croak("TimerCallback: av_len(myarr)=%d not 1!\n",av_len(myarr));
	}

	/* OK, finally grab the coderef and value out of the array */
	callback = *av_fetch(myarr,0,FALSE);
	realvalue = *av_fetch(myarr,1,FALSE);

	/* Delete the hash entry now that we've finished with it */

	hv_delete_ent(TimerFuncs,newSViv(value),G_DISCARD,0);


	ENTER;
	SAVETMPS;
	PUSHMARK(SP);	/* Set up stack frame for routine we'll call */

	XPUSHs(sv_2mortal(newSVsv(realvalue))); /* dump "value" arg on stack */
	PUTBACK;
	call_sv((SV *)callback,G_DISCARD);	/* Call callback */

	FREETMPS;
	LEAVE;

}

void MenuCallback(int value)
{
	int id;
	SV **fetchval;

	dSP;
	id = glutGetMenu();
	if (NULL==MenuFuncs) {
	  croak("MenuCallback called with id=%d but MenuFuncs is NULL!", id);
	}

	if (NULL==(fetchval=av_fetch(MenuFuncs,id,FALSE))) {
		croak("No MenuFunc defined for id=%d",id);
	}

	ENTER;
	SAVETMPS;
	PUSHMARK(SP);
	XPUSHs(sv_2mortal(newSViv(value)));

 	PUTBACK;

	call_sv((SV *)*fetchval,G_DISCARD);

	FREETMPS;
	LEAVE;

	return;

}

MODULE = OpenGL::Simple::GLUT		PACKAGE = OpenGL::Simple::GLUT		

INCLUDE: const-xs.inc

# ###### Initialization

void glutInit()
	PREINIT:
		int argc;
		int i;
		char **argv;
		AV *ARGV;
		SV *ARGV0;
	CODE:
		ARGV = perl_get_av("ARGV",FALSE);
		ARGV0= perl_get_sv("0",FALSE);

		argc = 2 + av_len(ARGV);

		if (NULL==(argv=malloc(sizeof(char *)*argc))) {
			perror("malloc()");
			croak("malloc() failed");
		}

		argv[0] = SvPV_nolen(ARGV0);
		for (i=0;i<(argc-1);i++) {
			argv[i+1] = SvPV_nolen(*av_fetch(ARGV,i,FALSE));
		}

		glutInit(&argc,argv);

		free(argv);


void glutInitWindowSize(int width, int height);

void glutInitWindowPosition(int x, int y);

void glutInitDisplayMode(unsigned int mode);

# ######## Beginning Event Processing

void glutMainLoop();

# ######## Window Management

int glutCreateWindow(char *name);

int glutCreateSubWindow(int win, int x, int y, int width, int height);

void glutSetWindow(int win);

int glutGetWindow();

void glutDestroyWindow(int win);

void glutPostRedisplay();

void glutSwapBuffers();

void glutPositionWindow(int x, int y);

void glutReshapeWindow(int width, int height);

void glutFullScreen();

void glutPopWindow();

void glutPushWindow();

void glutShowWindow();

void glutHideWindow();

void glutIconifyWindow();

void glutSetWindowTitle(char *name);

void glutSetIconTitle(char *name);

void glutSetCursor(int cursor);


# ######## Overlay Management

void glutEstablishOverlay();

void glutUseLayer(GLenum layer)

void glutRemoveOverlay();

void glutPostOverlayRedisplay();

void glutShowOverlay();

void glutHideOverlay();


# ######## Menu Management

int glutCreateMenu (SV *cb)
	PREINIT:
		int newid; /* ID of newly created menu */
	CODE:

		/* Make sure we've been passed a coderef */
		if (!SvROK(cb)) {
			croak("Callback must be code reference");
			return;
		}
		if (SVt_PVCV != SvTYPE(SvRV(cb))) {
			croak("Callback must be code reference");
			return;
		}

		if (NULL== MenuFuncs) { MenuFuncs = newAV(); }

		if (0==(newid = glutCreateMenu(MenuCallback))) {
			croak("glutCreateMenu() failed");
		}

		av_store( MenuFuncs,newid,newSVsv(cb));
		RETVAL = newid;
	OUTPUT:
		RETVAL


void glutSetMenu(int menu);

int glutGetMenu();

void glutDestroyMenu(int menu);

void glutAddMenuEntry(char *name, int value);

void glutAddSubMenu(char *name, int menu);

void glutChangeToMenuEntry(int entry, char *name, int value);

void glutChangeToSubMenu(int entry, char *name, int menu);

void glutRemoveMenuItem(int entry);

void glutAttachMenu(int button);

void glutDetachMenu(int button);



# ######## Callback Registration.

# Register a display callback.
# There can be at most one display callback for each GLUT window.
# We keep an array, DisplayFuncs, such that the Nth element of
# Displayfuncs, if defined, contains a coderef for the Nth window
# display callback.


void glutDisplayFunc(SV *cb)
	CODE:
		register_callback_xs(Display)

void glutOverlayDisplayFunc(SV *cb)
	CODE:
		register_callback_xs(OverlayDisplay)

void glutReshapeFunc(SV *cb)
	CODE:
		register_callback_xs(Reshape)

void glutKeyboardFunc(SV *cb)
	CODE:
		register_callback_xs(Keyboard)

void glutMouseFunc(SV *cb)
	CODE:
		register_callback_xs(Mouse)

void glutMotionFunc(SV *cb)
	CODE:
		register_callback_xs(Motion)

void glutPassiveMotionFunc(SV *cb)
	CODE:
		register_callback_xs(PassiveMotion)

void glutVisibilityFunc(SV *cb)
	CODE:
		register_callback_xs(Visibility)

void glutEntryFunc(SV *cb)
	CODE:
		register_callback_xs(Entry)

void glutSpecialFunc(SV *cb)
	CODE:
		register_callback_xs(Special)

void glutSpaceballMotionFunc(SV *cb)
	CODE:
		register_callback_xs(SpaceballMotion)

void glutSpaceballRotateFunc(SV *cb)
	CODE:
		register_callback_xs(SpaceballRotate)

void glutSpaceballButtonFunc(SV *cb)
	CODE:
		register_callback_xs(SpaceballButton)

void glutButtonBoxFunc(SV *cb)
	CODE:
		register_callback_xs(ButtonBox)

void glutDialsFunc(SV *cb)
	CODE:
		register_callback_xs(Dials)

void glutTabletMotionFunc(SV *cb)
	CODE:
		register_callback_xs(TabletMotion)

void glutTabletButtonFunc(SV *cb)
	CODE:
		register_callback_xs(TabletButton)

void glutMenuStatusFunc(SV *cb)
	CODE:
		register_callback_xs(MenuStatus)

void glutMenuStateFunc(SV *cb)
	CODE:
		register_callback_xs(MenuState)

	
void glutIdleFunc(SV *cb)
	CODE:
		register_callback_xs(Idle)




# glutTimerFunc doesn't care about window IDs, and the callback can be
# called at any time. However, the callback is passed an ID number ("value"),
# which can be used to tell which callback is being called.
# So, we actually create our own unique ID for each timer callback.
# We use that as an index into the TimerFuncs hash, which gives back
# an array containing: (1) the coderef for the callback, and (2) the value
# which the user passed in.

		
void glutTimerFunc(unsigned int msecs, SV *cb, int value)
	PREINIT:
		AV *myav;
		SV *myrv;
		int key;
		SV *keysv;
		int i;
	CODE:
		if (!SvROK(cb)) {
			croak("Callback must be code reference");
			return;
		}
		if (SVt_PVCV != SvTYPE(SvRV(cb))) {
			croak("Callback must be code reference");
			return;
		}

		/* Everything looks OK, so store it in the callback array */
		if (NULL==TimerFuncs) {
			TimerFuncs = newHV();
		}

		/* Create an anonymous array to hold the coderef and value */
		myav = newAV();
		av_store(myav,0,newSVsv(cb));
		av_store(myav,1,newSViv(value));
		myrv = newRV( (SV *)myav ); /* Reference to the array */

		/* Generate a new ID. This algorithm sucks. */

		key=0;
		keysv = newSViv(key);
		while (NULL != hv_fetch_ent(TimerFuncs,keysv,FALSE,0)) {
			key++;
			keysv = newSViv(key);
		}
		/* key is an unique integer ID, and keySV is an SV
		 * containing it.
		 * Store the array in the hash. Effectively, we've just done:
		 *
		 * sub timerfunc {
		 * 	my ($coderef,$val) = @_;
		 * 	my $i;
		 * 	while (exists($TimerFuncs{$i})) {
		 * 		$i++;
		 * 	}
		 * 	$TimerFuncs{$i} = [ $coderef, $val ];
		 * }
		 * 
		 */

		hv_store_ent(TimerFuncs,keysv,myrv,0);

		/* Now register our timer callback handler with glut. */

		glutTimerFunc(msecs,TimerCallback,key);



		 
# ######## Color Index Colormap Management

void glutSetColor(int cell, GLfloat red, GLfloat green, GLfloat blue);

GLfloat glutGetColor(int cell, int component);

void glutCopyColormap(int win);

# ########  State Retrieval

int glutGet(GLenum state);

int glutLayerGet(GLenum info);

int glutDeviceGet(GLenum info);

int glutGetModifiers();

int glutExtensionSupported(char *extension);


# ########   Geometric Object Rendering

void glutSolidSphere(GLdouble radius, GLint slices, GLint stacks);

void glutWireSphere(GLdouble radius, GLint slices, GLint stacks);

void glutSolidCube(GLdouble size);

void glutWireCube(GLdouble size);

void glutSolidCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);

void glutWireCone(GLdouble base, GLdouble height, GLint slices, GLint stacks);

void glutSolidTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings);

void glutWireTorus(GLdouble innerRadius, GLdouble outerRadius, GLint nsides, GLint rings);


void glutSolidDodecahedron();

void glutWireDodecahedron();

void glutSolidOctahedron();

void glutWireOctahedron();

void glutSolidTetrahedron();

void glutWireTetrahedron();

void glutSolidIcosahedron();

void glutWireIcosahedron();

void glutSolidTeapot(GLdouble size);

void glutWireTeapot(GLdouble size);