#include "glut4gcj/GLUT.h"

#include "glut4gcj/DisplayFunc.h"
#include "glut4gcj/EntryFunc.h"
#include "glut4gcj/IdleFunc.h"
#include "glut4gcj/KeyboardFunc.h"
#include "glut4gcj/MenuFunc.h"
#include "glut4gcj/MenuStateFunc.h"
#include "glut4gcj/MotionFunc.h"
#include "glut4gcj/MouseFunc.h"
#include "glut4gcj/PassiveMotionFunc.h"
#include "glut4gcj/ReshapeFunc.h"
#include "glut4gcj/TimerFunc.h"
#include "glut4gcj/VisibilityFunc.h"

#include "glut4gcj/SpecialFunc.h"
#include "glut4gcj/SpaceballMotionFunc.h"
#include "glut4gcj/SpaceballRotateFunc.h"
#include "glut4gcj/SpaceballButtonFunc.h"

#include <gcj/cni.h>
#include <java/lang/Object.h>
#include <java/lang/Integer.h>
#include <java/util/Hashtable.h>

#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>

using namespace ::glut4gcj;
using namespace ::java::lang;
using namespace ::java::util;

JArray< ::java::lang::String *>*
GLUT::glutInit (JArray< ::java::lang::String *> *argArray)
{
	JvInitClass(&GLUT::class$);

	int argc = JvGetArrayLength(argArray)+1;
	int argc_backup = argc;
	char** argv = (char**)malloc(sizeof(char*) * argc);
	char** argv_backup = (char**)malloc(sizeof(char*) * argc);
	if (argv == NULL || argv_backup == NULL)
	{
		fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
		abort();
	}
	argv[0] = "glut4gcj";
	String** args = elements(argArray);
	for (int i = 0;i < argArray->length;i++)
	{
		jbyteArray byteArray = args[i]->getBytes();
		jbyte* bytes = elements(byteArray);
		char* buffer = (char*)malloc(sizeof(char) * (byteArray->length+1));
		if(buffer == NULL);
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		memcpy(buffer, bytes, byteArray->length);
		bytes[byteArray->length] = '\0';
		argv[i] = buffer;
		argv_backup[i] = buffer;
	}
//	for (int i = 0;i < argc;i++) printf("argv[%d] : %s\n", i, argv[0]);

	::glutInit(&argc, argv);

	jstringArray result = JvConvertArgv(argc, (const char**)argv);

	for (int i = 1;i < argc_backup;i++) free(argv_backup[i]);
	free(argv_backup);
	free(argv);


	return result;
}

void
GLUT::glutInitDisplayMode (jint mode)
{
	JvInitClass(&GLUT::class$);
	::glutInitDisplayMode(mode);
}

void
GLUT::glutInitDisplayString (::java::lang::String * string)
{
	JvInitClass(&GLUT::class$);

#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	jbyteArray byteArray = string->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutInitDisplayString(buffer);
#endif
}

void
GLUT::glutInitWindowPosition (jint x, jint y)
{
	JvInitClass(&GLUT::class$);
	::glutInitWindowPosition(x, y);
}

void
GLUT::glutInitWindowSize (jint width, jint height)
{
	JvInitClass(&GLUT::class$);
	::glutInitWindowSize(width, height);
}

void
GLUT::glutMainLoop ()
{
	JvInitClass(&GLUT::class$);
	::glutMainLoop();
}

jint
GLUT::glutCreateWindow (::java::lang::String * name)
{
	JvInitClass(&GLUT::class$);

	jbyteArray byteArray = name->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	return ::glutCreateWindow(buffer);
}

jint
GLUT::glutCreateSubWindow (jint win, jint x, jint y, jint width, jint height)
{
	JvInitClass(&GLUT::class$);
	jint subWin = ::glutCreateSubWindow(win, x, y, width, height);
	return subWin;
}

void
GLUT::glutDestroyWindow (jint win)
{
	JvInitClass(&GLUT::class$);
	::glutDestroyWindow(win);
}

void
GLUT::glutPostRedisplay ()
{
	JvInitClass(&GLUT::class$);
	::glutPostRedisplay();
}

void
GLUT::glutPostWindowRedisplay (jint win)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 11)
	::glutPostWindowRedisplay(win);
#endif
}

void
GLUT::glutSwapBuffers ()
{
	JvInitClass(&GLUT::class$);
	::glutSwapBuffers();
}

jint
GLUT::glutGetWindow ()
{
	JvInitClass(&GLUT::class$);
	return ::glutGetWindow();
}

void
GLUT::glutSetWindow (jint win)
{
	JvInitClass(&GLUT::class$);
	::glutSetWindow(win);
}

void
GLUT::glutSetWindowTitle (::java::lang::String *name)
{
	JvInitClass(&GLUT::class$);

	jbyteArray byteArray = name->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutSetWindowTitle(buffer);
}

void
GLUT::glutSetIconTitle (::java::lang::String * name)
{
	JvInitClass(&GLUT::class$);

	jbyteArray byteArray = name->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutSetIconTitle(buffer);
}

void
GLUT::glutPositionWindow (jint x, jint y)
{
	JvInitClass(&GLUT::class$);
	::glutPositionWindow(x, y);
}

void
GLUT::glutReshapeWindow (jint width, jint height)
{
	JvInitClass(&GLUT::class$);
	::glutReshapeWindow(width, height);
}

void
GLUT::glutPopWindow ()
{
	JvInitClass(&GLUT::class$);
	::glutPopWindow();
}

void
GLUT::glutPushWindow ()
{
	JvInitClass(&GLUT::class$);
	::glutPushWindow();
}

void
GLUT::glutIconifyWindow ()
{
	JvInitClass(&GLUT::class$);
	::glutIconifyWindow();
}

void
GLUT::glutShowWindow ()
{
	JvInitClass(&GLUT::class$);
	::glutShowWindow();
}

void
GLUT::glutHideWindow ()
{
	JvInitClass(&GLUT::class$);
	::glutHideWindow();
}

void
GLUT::glutFullScreen ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutFullScreen();
#endif
}

void
GLUT::glutSetCursor (jint cursor)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutSetCursor(cursor);
#endif
}

void
GLUT::glutWarpPointer (jint x, jint y)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	::glutWarpPointer(x, y);
#endif
}

void
GLUT::glutEstablishOverlay ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutEstablishOverlay();
#endif
}

void
GLUT::glutRemoveOverlay ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutRemoveOverlay();
#endif
}

void
GLUT::glutUseLayer (jint layer)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutUseLayer(layer);
#endif
}

void
GLUT::glutPostOverlayRedisplay ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutPostOverlayRedisplay();
#endif
}

void
GLUT::glutPostWindowOverlayRedisplay (jint win)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 11)
	::glutPostWindowOverlayRedisplay(win);
#endif
}

void
GLUT::glutShowOverlay ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutShowOverlay();
#endif
}

void
GLUT::glutHideOverlay ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 3)
	::glutHideOverlay();
#endif
}

static MenuFunc** menuFuncs = NULL;
static int menuFuncsLength = 0;

static 
void 
menu(int value)
{
	int menuID = ::glutGetMenu();
	MenuFunc* menuFunc = menuFuncs[menuID];
	if (menuFunc != NULL) menuFunc->menu(value);
}


jint
GLUT::glutCreateMenu (MenuFunc* func)
{
	JvInitClass(&GLUT::class$);

	void (*callback)(int value) = menu;
	if (func == NULL) callback = NULL;

	int menuID = ::glutCreateMenu(callback);
	if (menuID >= menuFuncsLength)
	{
		int newLength = menuFuncsLength * 2;
		if (menuID >= newLength) newLength = menuID * 2 + 2;
		MenuFunc** newFuncs = (MenuFunc**)malloc(sizeof(MenuFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, menuFuncs, sizeof(MenuFunc*) * menuFuncsLength);
		free(menuFuncs);
		menuFuncs = newFuncs;
		menuFuncsLength = newLength;
	}

	menuFuncs[menuID] = func;

	return menuID;
}


void
GLUT::glutDestroyMenu (jint menu)
{
	JvInitClass(&GLUT::class$);
	::glutDestroyMenu(menu);
	if (menu < menuFuncsLength)
	{
		menuFuncs[menu] = NULL;
	}
}

jint
GLUT::glutGetMenu ()
{
	JvInitClass(&GLUT::class$);
	jint menu = ::glutGetMenu();;
	return menu;
}

void
GLUT::glutSetMenu (jint menu)
{
	JvInitClass(&GLUT::class$);
	::glutSetMenu(menu);
}

void
GLUT::glutAddMenuEntry (::java::lang::String *name, jint value)
{
	JvInitClass(&GLUT::class$);

	jbyteArray byteArray = name->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutAddMenuEntry(buffer, value);
}

void
GLUT::glutAddSubMenu (::java::lang::String *name, jint menu)
{
	JvInitClass(&GLUT::class$);

	jbyteArray byteArray = name->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutAddSubMenu(buffer, menu);
}

void
GLUT::glutChangeToMenuEntry (jint entry, ::java::lang::String *name, jint value)
{
	JvInitClass(&GLUT::class$);

	jbyteArray byteArray = name->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutChangeToMenuEntry(entry, buffer, value);
}

void
GLUT::glutChangeToSubMenu (jint entry, ::java::lang::String *name, jint menu)
{
	JvInitClass(&GLUT::class$);

	jbyteArray byteArray = name->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutChangeToSubMenu(entry, buffer, menu);
}

void
GLUT::glutRemoveMenuItem (jint entry)
{
	JvInitClass(&GLUT::class$);
	::glutRemoveMenuItem(entry);
}

void
GLUT::glutAttachMenu (jint button)
{
	JvInitClass(&GLUT::class$);
	::glutAttachMenu(button);
}

void
GLUT::glutDetachMenu (jint button)
{
	JvInitClass(&GLUT::class$);
	::glutDetachMenu(button);
}



static DisplayFunc** displayFuncs = NULL;
static int displayFuncsLength = 0;

static 
void 
display()
{
	int win = ::glutGetWindow();
	DisplayFunc* displayFunc = displayFuncs[win];
	if (displayFunc != NULL) displayFunc->display();
}

void 
GLUT::glutDisplayFunc (::glut4gcj::DisplayFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= displayFuncsLength)
	{
		int newLength = displayFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		DisplayFunc** newFuncs = (DisplayFunc**)malloc(sizeof(DisplayFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, displayFuncs, sizeof(DisplayFunc*) * displayFuncsLength);
		free(displayFuncs);
		displayFuncs = newFuncs;
		displayFuncsLength = newLength;
	}

	if (func != NULL)
	{
		displayFuncs[win] = func;
		::glutDisplayFunc(display);
	}
}

static ReshapeFunc** reshapeFuncs = NULL;
static int reshapeFuncsLength = 0;

static 
void 
reshape(int width, int height)
{
	int win = ::glutGetWindow();
	ReshapeFunc* reshapeFunc = reshapeFuncs[win];
	if (reshapeFunc != NULL) reshapeFunc->reshape(width, height);
}

void 
GLUT::glutReshapeFunc (::glut4gcj::ReshapeFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= reshapeFuncsLength)
	{
		int newLength = reshapeFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		ReshapeFunc** newFuncs = (ReshapeFunc**)malloc(sizeof(ReshapeFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, reshapeFuncs, sizeof(ReshapeFunc*) * reshapeFuncsLength);
		free(reshapeFuncs);
		reshapeFuncs = newFuncs;
		reshapeFuncsLength = newLength;
	}

	if (func != NULL)
	{
		reshapeFuncs[win] = func;
		::glutReshapeFunc(reshape);
	}
	else
	{
		reshapeFuncs[win] = NULL;
		::glutReshapeFunc(NULL);
	}
}


static KeyboardFunc** keyboardFuncs = NULL;
static int keyboardFuncsLength = 0;

static 
void 
keyboard(unsigned char key, int x, int y)
{
	int win = ::glutGetWindow();
	KeyboardFunc* keyboardFunc = keyboardFuncs[win];
	if (keyboardFunc != NULL) keyboardFunc->keyboard(key, x, y);
}

void 
GLUT::glutKeyboardFunc (::glut4gcj::KeyboardFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= keyboardFuncsLength)
	{
		int newLength = keyboardFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		KeyboardFunc** newFuncs = (KeyboardFunc**)malloc(sizeof(KeyboardFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, keyboardFuncs, sizeof(KeyboardFunc*) * keyboardFuncsLength);
		free(keyboardFuncs);
		keyboardFuncs = newFuncs;
		keyboardFuncsLength = newLength;
	}

	if (func != NULL)
	{
		keyboardFuncs[win] = func;
		::glutKeyboardFunc(keyboard);
	}
	else
	{
		reshapeFuncs[win] = NULL;
		::glutKeyboardFunc(NULL);
	}
}


static MouseFunc** mouseFuncs = NULL;
static int mouseFuncsLength = 0;

static 
void 
mouse(int button, int state, int x, int y)
{
	int win = ::glutGetWindow();
	MouseFunc* mouseFunc = mouseFuncs[win];
	if (mouseFunc != NULL) mouseFunc->mouse(button, state, x, y);
}

void 
GLUT::glutMouseFunc (::glut4gcj::MouseFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= mouseFuncsLength)
	{
		int newLength = mouseFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		MouseFunc** newFuncs = (MouseFunc**)malloc(sizeof(MouseFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, mouseFuncs, sizeof(MouseFunc*) * mouseFuncsLength);
		free(mouseFuncs);
		mouseFuncs = newFuncs;
		mouseFuncsLength = newLength;
	}

	if (func != NULL)
	{
		mouseFuncs[win] = func;
		::glutMouseFunc(mouse);
	}
	else
	{
		mouseFuncs[win] = NULL;
		::glutMouseFunc(NULL);
	}
}

static MotionFunc** motionFuncs = NULL;
static int motionFuncsLength = 0;

static 
void 
motion(int x, int y)
{
	int win = ::glutGetWindow();
	MotionFunc* motionFunc = motionFuncs[win];
	if (motionFunc != NULL) motionFunc->motion(x, y);
}

void 
GLUT::glutMotionFunc (::glut4gcj::MotionFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= motionFuncsLength)
	{
		int newLength = motionFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		MotionFunc** newFuncs = (MotionFunc**)malloc(sizeof(MotionFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, motionFuncs, sizeof(MotionFunc*) * motionFuncsLength);
		free(motionFuncs);
		motionFuncs = newFuncs;
		motionFuncsLength = newLength;
	}

	if (func != NULL)
	{
		motionFuncs[win] = func;
		::glutMotionFunc(motion);
	}
	else
	{
		motionFuncs[win] = NULL;
		::glutMotionFunc(NULL);
	}
}

static PassiveMotionFunc** passiveMotionFuncs = NULL;
static int passiveMotionFuncsLength = 0;

static 
void 
passiveMotion(int x, int y)
{
	int win = ::glutGetWindow();
	PassiveMotionFunc* passiveMotionFunc = passiveMotionFuncs[win];
	if (passiveMotionFunc != NULL) passiveMotionFunc->passiveMotion(x, y);
}

void 
GLUT::glutPassiveMotionFunc (::glut4gcj::PassiveMotionFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= passiveMotionFuncsLength)
	{
		int newLength = passiveMotionFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		PassiveMotionFunc** newFuncs = (PassiveMotionFunc**)malloc(sizeof(PassiveMotionFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, passiveMotionFuncs, sizeof(PassiveMotionFunc*) * passiveMotionFuncsLength);
		free(passiveMotionFuncs);
		passiveMotionFuncs = newFuncs;
		passiveMotionFuncsLength = newLength;
	}

	if (func != NULL)
	{
		passiveMotionFuncs[win] = func;
		::glutPassiveMotionFunc(passiveMotion);
	}
	else
	{
		passiveMotionFuncs[win] = NULL;
		::glutPassiveMotionFunc(NULL);
	}
}


static EntryFunc** entryFuncs = NULL;
static int entryFuncsLength = 0;

static 
void 
entry(int state)
{
	int win = ::glutGetWindow();
	EntryFunc* entryFunc = entryFuncs[win];
	if (entryFunc != NULL) entryFunc->entry(state);
}

void 
GLUT::glutEntryFunc (::glut4gcj::EntryFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= entryFuncsLength)
	{
		int newLength = entryFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		EntryFunc** newFuncs = (EntryFunc**)malloc(sizeof(EntryFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, entryFuncs, sizeof(EntryFunc*) * entryFuncsLength);
		free(entryFuncs);
		entryFuncs = newFuncs;
		entryFuncsLength = newLength;
	}

	if (func != NULL)
	{
		entryFuncs[win] = func;
		::glutEntryFunc(entry);
	}
	else
	{
		entryFuncs[win] = NULL;
		::glutEntryFunc(NULL);
	}
}


static VisibilityFunc** visibilityFuncs = NULL;
static int visibilityFuncsLength = 0;

static 
void 
visibility(int state)
{
	int win = ::glutGetWindow();
	VisibilityFunc* visibilityFunc = visibilityFuncs[win];
	if (visibilityFunc != NULL) visibilityFunc->visibility(state);
}

void 
GLUT::glutVisibilityFunc (::glut4gcj::VisibilityFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= visibilityFuncsLength)
	{
		int newLength = visibilityFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		VisibilityFunc** newFuncs = (VisibilityFunc**)malloc(sizeof(VisibilityFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, visibilityFuncs, sizeof(VisibilityFunc*) * visibilityFuncsLength);
		free(visibilityFuncs);
		visibilityFuncs = newFuncs;
		visibilityFuncsLength = newLength;
	}

	if (func != NULL)
	{
		visibilityFuncs[win] = func;
		::glutVisibilityFunc(visibility);
	}
	else
	{
		visibilityFuncs[win] = NULL;
		::glutVisibilityFunc(NULL);
	}
}


static IdleFunc* idleFunc = NULL;

static 
void 
idle()
{
	if (idleFunc != NULL) idleFunc->idle();
}

void 
GLUT::glutIdleFunc (::glut4gcj::IdleFunc *func)
{
	JvInitClass(&GLUT::class$);

	idleFunc = func;
	if (func != NULL)
	{
		::glutIdleFunc(idle);
	}
	else
	{
		::glutIdleFunc(NULL);
	}
}


static TimerFunc** timerFuncs = NULL;
static jint* timerValues = NULL;
static int timerFuncsLength = 0;

static 
void 
timer(int funcID)
{
	JvSynchronize dummy (&GLUT::class$);

	TimerFunc* timerFunc = timerFuncs[funcID];
	if (timerFunc != NULL)
	{
		jint value = timerValues[funcID];
		timerFuncs[funcID] = NULL;
		timerValues[funcID] = 0;
		timerFunc->timer(value);
	}
}

void 
GLUT::glutTimerFunc (jint msec, ::glut4gcj::TimerFunc *func, jint value)
{
	JvInitClass(&GLUT::class$);
	JvSynchronize dummy (&GLUT::class$);
//	fprintf(stderr, "GLUT::glutTimerFunc() is called.\n");

	int timerFuncID = timerFuncsLength;
	for (int i = 0;i < timerFuncsLength;i++)
	{
		if (timerFuncs[i] == NULL)
		{
			timerFuncID = i;
			break;
		}
	}

	if (timerFuncID == timerFuncsLength)
	{
		int newLength = timerFuncsLength * 2 + 2;
//		fprintf(stderr, "Allocate timerFuncs[] and timerValues[]. newLength = %d\n", newLength);

		TimerFunc** newFuncs = (TimerFunc**)malloc(sizeof(TimerFunc*) * newLength);  
		jint* newValues = (jint*)malloc(sizeof(jint) * newLength);  
		if(newFuncs == NULL || newValues == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, timerFuncs, sizeof(TimerFunc*) * timerFuncsLength);
		memcpy(newValues, timerValues, sizeof(jint) * timerFuncsLength);
		free(timerFuncs);
		free(timerValues);
		timerFuncs = newFuncs;
		timerValues = newValues;
		timerFuncsLength = newLength;
	}

	if (func != NULL && msec >= 0)
	{
		timerFuncs[timerFuncID] = func;
		timerValues[timerFuncID] = value;
		::glutTimerFunc(msec, timer, timerFuncID);
	}
}


static MenuStateFunc** menuStateFuncs = NULL;
static int menuStateFuncsLength = 0;

static 
void 
menuState(int state)
{
	int win = ::glutGetWindow();
	MenuStateFunc* menuStateFunc = menuStateFuncs[win];
	if (menuStateFunc != NULL) menuStateFunc->menuState(state);
}

void 
GLUT::glutMenuStateFunc (::glut4gcj::MenuStateFunc *func)
{
	JvInitClass(&GLUT::class$);

	int win = ::glutGetWindow();
	if (win >= menuStateFuncsLength)
	{
		int newLength = menuStateFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		MenuStateFunc** newFuncs = (MenuStateFunc**)malloc(sizeof(MenuStateFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, menuStateFuncs, sizeof(MenuStateFunc*) * menuStateFuncsLength);
		free(menuStateFuncs);
		menuStateFuncs = newFuncs;
		menuStateFuncsLength = newLength;
	}

	if (func != NULL)
	{
		menuStateFuncs[win] = func;
		::glutMenuStateFunc(menuState);
	}
	else
	{
		menuStateFuncs[win] = NULL;
		::glutMenuStateFunc(NULL);
	}
}

#if (GLUT_API_VERSION >= 2)
static SpecialFunc** specialFuncs = NULL;
static int specialFuncsLength = 0;

static 
void 
special(int key, int x, int y)
{
	int win = ::glutGetWindow();
	SpecialFunc* specialFunc = specialFuncs[win];
	if (specialFunc != NULL) specialFunc->special(key, x, y);
}
#endif

void 
GLUT::glutSpecialFunc (::glut4gcj::SpecialFunc *func)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 2)
	int win = ::glutGetWindow();
	if (win >= specialFuncsLength)
	{
		int newLength = specialFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		SpecialFunc** newFuncs = (SpecialFunc**)malloc(sizeof(SpecialFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, specialFuncs, sizeof(SpecialFunc*) * specialFuncsLength);
		free(specialFuncs);
		specialFuncs = newFuncs;
		specialFuncsLength = newLength;
	}

	if (func != NULL)
	{
		specialFuncs[win] = func;
		::glutSpecialFunc(special);
	}
	else
	{
		specialFuncs[win] = NULL;
		::glutSpecialFunc(NULL);
	}
#endif
}


#if (GLUT_API_VERSION >= 2)
static SpaceballMotionFunc** spaceballMotionFuncs = NULL;
static int spaceballMotionFuncsLength = 0;

static 
void 
spaceballMotion(int x, int y, int z)
{
	int win = ::glutGetWindow();
	SpaceballMotionFunc* spaceballMotionFunc = spaceballMotionFuncs[win];
	if (spaceballMotionFunc != NULL) spaceballMotionFunc->spaceballMotion(x, y, z);
}
#endif

void 
GLUT::glutSpaceballMotionFunc (::glut4gcj::SpaceballMotionFunc *func)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 2)
	int win = ::glutGetWindow();
	if (win >= spaceballMotionFuncsLength)
	{
		int newLength = spaceballMotionFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		SpaceballMotionFunc** newFuncs = (SpaceballMotionFunc**)malloc(sizeof(SpaceballMotionFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, spaceballMotionFuncs, sizeof(SpaceballMotionFunc*) * spaceballMotionFuncsLength);
		free(spaceballMotionFuncs);
		spaceballMotionFuncs = newFuncs;
		spaceballMotionFuncsLength = newLength;
	}

	if (func != NULL)
	{
		spaceballMotionFuncs[win] = func;
		::glutSpaceballMotionFunc(spaceballMotion);
	}
	else
	{
		spaceballMotionFuncs[win] = NULL;
		::glutSpaceballMotionFunc(NULL);
	}
#endif
}

#if (GLUT_API_VERSION >= 2)
static SpaceballRotateFunc** spaceballRotateFuncs = NULL;
static int spaceballRotateFuncsLength = 0;

static 
void 
spaceballRotate(int x, int y, int z)
{
	int win = ::glutGetWindow();
	SpaceballRotateFunc* spaceballRotateFunc = spaceballRotateFuncs[win];
	if (spaceballRotateFunc != NULL) spaceballRotateFunc->spaceballRotate(x, y, z);
}
#endif

void 
GLUT::glutSpaceballRotateFunc (::glut4gcj::SpaceballRotateFunc *func)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 2)
	int win = ::glutGetWindow();
	if (win >= spaceballRotateFuncsLength)
	{
		int newLength = spaceballRotateFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		SpaceballRotateFunc** newFuncs = (SpaceballRotateFunc**)malloc(sizeof(SpaceballRotateFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, spaceballRotateFuncs, sizeof(SpaceballRotateFunc*) * spaceballRotateFuncsLength);
		free(spaceballRotateFuncs);
		spaceballRotateFuncs = newFuncs;
		spaceballRotateFuncsLength = newLength;
	}

	if (func != NULL)
	{
		spaceballRotateFuncs[win] = func;
		::glutSpaceballRotateFunc(spaceballRotate);
	}
	else
	{
		spaceballRotateFuncs[win] = NULL;
		::glutSpaceballRotateFunc(NULL);
	}
#endif
}


#if (GLUT_API_VERSION >= 2)
static SpaceballButtonFunc** spaceballButtonFuncs = NULL;
static int spaceballButtonFuncsLength = 0;

static 
void 
spaceballButton(int button, int state)
{
	int win = ::glutGetWindow();
	SpaceballButtonFunc* spaceballButtonFunc = spaceballButtonFuncs[win];
	if (spaceballButtonFunc != NULL) spaceballButtonFunc->spaceballButton(button, state);
}
#endif

void 
GLUT::glutSpaceballButtonFunc (::glut4gcj::SpaceballButtonFunc *func)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 2)
	int win = ::glutGetWindow();
	if (win >= spaceballButtonFuncsLength)
	{
		int newLength = spaceballButtonFuncsLength * 2;
		if (win >= newLength) newLength = win * 2 + 2;
		SpaceballButtonFunc** newFuncs = (SpaceballButtonFunc**)malloc(sizeof(SpaceballButtonFunc*) * newLength);  
		if(newFuncs == NULL)
		{
			fprintf(stderr, "%s: %d: malloc() fail.\n", __FILE__, __LINE__);
			abort();
		}
		
		memcpy(newFuncs, spaceballButtonFuncs, sizeof(SpaceballButtonFunc*) * spaceballButtonFuncsLength);
		free(spaceballButtonFuncs);
		spaceballButtonFuncs = newFuncs;
		spaceballButtonFuncsLength = newLength;
	}

	if (func != NULL)
	{
		spaceballButtonFuncs[win] = func;
		::glutSpaceballButtonFunc(spaceballButton);
	}
	else
	{
		spaceballButtonFuncs[win] = NULL;
		::glutSpaceballButtonFunc(NULL);
	}
#endif
}


void
GLUT::glutSetColor (jint cell, jfloat red, jfloat green, jfloat blue)
{
	JvInitClass(&GLUT::class$);
	::glutSetColor(cell, red, green, blue);
}

jfloat
GLUT::glutGetColor (jint cell, jint component)
{
	JvInitClass(&GLUT::class$);
	return ::glutGetColor(cell, component);
}

void
GLUT::glutCopyColormap (jint win)
{
	JvInitClass(&GLUT::class$);
	::glutCopyColormap(win);
}

jint
GLUT::glutGet (jint state)
{
	JvInitClass(&GLUT::class$);
	jint result = ::glutGet(state);
	return result;
}

jint
GLUT::glutDeviceGet (jint info)
{
	JvInitClass(&GLUT::class$);
	jint result = ::glutDeviceGet(info);
	return result;
}

jboolean
GLUT::glutExtensionSupported (::java::lang::String *extension)
{
	JvInitClass(&GLUT::class$);
	jboolean result = false;

#if (GLUT_API_VERSION >= 2)
	jbyteArray byteArray = extension->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	if (::glutExtensionSupported(buffer) != 0) result = true;
#endif

	return result;
}

jint
GLUT::glutGetModifiers ()
{
	JvInitClass(&GLUT::class$);
	jint result = 0;
#if (GLUT_API_VERSION >= 3)
	result = ::glutGetModifiers();;
#endif
	return result;
}

jint
GLUT::glutLayerGet (jint info)
{
	JvInitClass(&GLUT::class$);
	jint result = 0;
#if (GLUT_API_VERSION >= 3)
	result = ::glutLayerGet(info);
#endif
	return result;
}

void
GLUT::glutWireSphere (jdouble radius, jint slices, jint stacks)
{
	JvInitClass(&GLUT::class$);
	::glutWireSphere(radius, slices, stacks);
}

void
GLUT::glutSolidSphere (jdouble radius, jint slices, jint stacks)
{
	JvInitClass(&GLUT::class$);
	::glutSolidSphere(radius, slices, stacks);
}

void
GLUT::glutWireCone (jdouble base, jdouble height, jint slices, jint stacks)
{
	JvInitClass(&GLUT::class$);
	::glutWireCone(base, height, slices, stacks);
}

void
GLUT::glutSolidCone (jdouble base, jdouble height, jint slices, jint stacks)
{
	JvInitClass(&GLUT::class$);
	::glutSolidCone(base, height, slices, stacks);
}

void
GLUT::glutWireCube (jdouble size)
{
	JvInitClass(&GLUT::class$);
	::glutWireCube(size);
}

void
GLUT::glutSolidCube (jdouble size)
{
	JvInitClass(&GLUT::class$);
	::glutSolidCube(size);
}

void
GLUT::glutWireTorus (jdouble innerRadius, jdouble outerRadius, jint nslides, jint rings)
{
	JvInitClass(&GLUT::class$);
	::glutWireTorus(innerRadius, outerRadius, nslides, rings);
}

void
GLUT::glutSolidTorus (jdouble innerRadius, jdouble outerRadius, jint nslides, jint rings)
{
	JvInitClass(&GLUT::class$);
	::glutSolidTorus(innerRadius, outerRadius, nslides, rings);
}

void
GLUT::glutWireDodecahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutWireDodecahedron();
}

void
GLUT::glutSolidDodecahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutSolidDodecahedron();
}

void
GLUT::glutWireTeapot (jdouble size)
{
	JvInitClass(&GLUT::class$);
	::glutWireTeapot(size);
}

void
GLUT::glutSolidTeapot (jdouble size)
{
	JvInitClass(&GLUT::class$);
	::glutSolidTeapot(size);
}

void
GLUT::glutWireOctahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutWireOctahedron();
}

void
GLUT::glutSolidOctahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutSolidOctahedron();
}

void
GLUT::glutWireTetrahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutWireTetrahedron();
}

void
GLUT::glutSolidTetrahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutSolidTetrahedron();
}

void
GLUT::glutWireIcosahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutWireIcosahedron();
}

void
GLUT::glutSolidIcosahedron ()
{
	JvInitClass(&GLUT::class$);
	::glutSolidIcosahedron();
}

jint
GLUT::glutVideoResizeGet (jint param)
{
	JvInitClass(&GLUT::class$);
	jint result = 0;
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	result = ::glutVideoResizeGet(param);
#endif
	return result;
}

void
GLUT::glutSetupVideoResizing ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	::glutSetupVideoResizing();
#endif
}

void
GLUT::glutStopVideoResizing ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	::glutStopVideoResizing();
#endif
}

void
GLUT::glutVideoResize (jint x, jint y, jint width, jint height)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	::glutVideoResize(x, y, width, height);
#endif
}

void
GLUT::glutVideoPan (jint x, jint y, jint width, jint height)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	::glutVideoPan(x, y, width, height);
#endif
}

void
GLUT::glutReportErrors ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 9)
	::glutReportErrors();
#endif
}

void
GLUT::glutIgnoreKeyRepeat (jint ignore)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 13)
	::glutIgnoreKeyRepeat(ignore);
#endif
}

void
GLUT::glutSetKeyRepeat (jint repeatMode)
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 13)
	::glutSetKeyRepeat(repeatMode);
#endif
}

void
GLUT::glutForceJoystickFunc ()
{
	JvInitClass(&GLUT::class$);
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 13)
	::glutForceJoystickFunc();
#endif
}

void
GLUT::glutGameModeString (::java::lang::String *string)
{
	JvInitClass(&GLUT::class$);

#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 13)
	jbyteArray byteArray = string->getBytes();
	jbyte* bytes = elements(byteArray);
	char buffer[byteArray->length+1];
	memcpy(buffer, bytes, byteArray->length);
	buffer[byteArray->length] = '\0';

	::glutGameModeString(buffer);
#endif
}

jint
GLUT::glutEnterGameMode ()
{
	JvInitClass(&GLUT::class$);

	jint fullscreenwin = 0;
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 13)
	fullscreenwin = ::glutEnterGameMode();
#endif
	return fullscreenwin;
}

void
GLUT::glutLeaveGameMode ()
{
	JvInitClass(&GLUT::class$);

#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 13)
	::glutLeaveGameMode();
#endif
}

jint
GLUT::glutGameModeGet (jint mode)
{
	JvInitClass(&GLUT::class$);

	jint result = 0;
#if (GLUT_API_VERSION >= 4 || GLUT_XLIB_IMPLEMENTATION >= 13)
	result = ::glutGameModeGet(mode);
#endif
	return result;
}

