/*
 * $Id$
 *
 * $Date$
 * $Revision$
 *
 * (C) 1999 by Hyperion
 * All rights reserved
 *
 * This file is part of the MiniGL library project
 * See the file Licence.txt for more details
 *
 */

#include "mgl/gl.h"
#include "GL/glut.h"
#include <math.h>
#include "requesters.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sysinc.h"
#include <proto/timer.h>
#include <proto/intuition.h>
#include <proto/keymap.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <devices/timer.h>
#include <interfaces/glut.h>

static int __glutWindow = 1;

extern struct MiniGLIFace *IMiniGL;
extern struct IntuitionIFace *IIntuition;
extern struct TimerIFace *__ITimer;
extern struct KeymapIFace *__IKeymap;

/* From glut_gamemode.c */
void glut_GLUTLeaveGameMode(struct GlutIFace *Self);

TimerHook* timerhook_new(struct MsgPort *timerPort);
void timerhook_delete(TimerHook *timerHook);

char *mystrdup(char *other)
{
	char *c = other;
	int len = 0;
	char *copy;
	
	if (!other)
		return 0;
	
	while(other && *c)
	{
		len++;
		c++;
	}
	
	copy = IExec->AllocVec(len+1, MEMF_ANY);
	while (!copy)
		return 0;
	
	c = copy;
	while (*other)
		*c++ = *other++;
		
	*c++ = 0;
	
	return copy;
}
	
void glut_GLUTExit(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

    if(ctx->gameModeData.gameModeActive)
	{
		glut_GLUTLeaveGameMode(Self);
	}

	if (ctx->__glutContext)
	{
		ctx->__glutContext->DeleteContext();
		ctx->__glutContext = 0;
	}

	if(ctx->warpPointerEvent)
	{
		IExec->FreeVec(ctx->warpPointerEvent);
		ctx->warpPointerEvent = NULL;
	}
	if(ctx->warpPointerIOReq)
	{
		IExec->CloseDevice((struct IORequest *)ctx->warpPointerIOReq);
		IExec->DeleteIORequest((struct IORequest *)ctx->warpPointerIOReq);
		ctx->warpPointerIOReq = NULL;
	}
	if(ctx->warpPointerMP);
	{
		IExec->DeleteMsgPort(ctx->warpPointerMP);
		ctx->warpPointerMP = NULL;
	}

	TimerHook *timerHook = (TimerHook*)IExec->RemTail((struct List*) &(ctx->freeTimerHookList));
	while(timerHook)
	{
		timerhook_delete(timerHook);
		timerHook = (TimerHook*)IExec->RemTail((struct List*) &(ctx->freeTimerHookList));
	}
	timerHook = (TimerHook*)IExec->RemTail((struct List*) &(ctx->activeTimerHookList));
	while(timerHook)
	{
		timerhook_delete(timerHook);
		timerHook = (TimerHook*)IExec->RemTail((struct List*) &(ctx->activeTimerHookList));
	}
}

void glut_GLUTInit(struct GlutIFace *Self, int *argc, char **argv)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);
	
	__ITimer->GetSysTime(&ctx->__glutStartTime);
	
	ctx->__glutWindowWidth = 300;
	ctx->__glutWindowHeight = 300;
	ctx->__glutWindowX = 10;
	ctx->__glutWindowY = 10;
	ctx->__glutDisplayMode = GLUT_RGBA|GLUT_SINGLE;
	ctx->__glutContext = 0;
	ctx->ignoreKeyRepeat = GL_FALSE;
	ctx->keyRepeatMode = GLUT_KEY_REPEAT_ON;

	ctx->gameModeData.gameModeActive = GL_FALSE;
	ctx->gameModeSize.X = 640;
	ctx->gameModeSize.Y = 480;
	ctx->gameModeDepth = 16;
	ctx->gameModeRefresh = 72;

	ctx->modifiers = MODIFIERS_INVALID;
	ctx->actionOnWindowClose = GLUT_ACTION_GLUTMAINLOOP_RETURNS;

	ctx->warpPointerMP = NULL;
	ctx->warpPointerIOReq = NULL;
	ctx->warpPointerEvent = NULL;

    struct EClockVal eClkVal;
	ULONG eFreq = __ITimer->ReadEClock(&eClkVal);
	ctx->ticksPerMSec = eFreq / 1000.0;
}


void glut_GLUTInitWindowSize(struct GlutIFace *Self, int width, int height)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->__glutWindowWidth = width;
	ctx->__glutWindowHeight = height;
}


void glut_GLUTInitWindowPosition(struct GlutIFace *Self, int x, int y)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->__glutWindowX = x;
	ctx->__glutWindowY = y;
}


void glut_GLUTInitDisplayMode(struct GlutIFace *Self, unsigned int mode)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->__glutDisplayMode = mode;
}

int glut_GLUTCreateWindow(struct GlutIFace *Self, const char *name)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	struct Window *window;
	GLcontext context;
	
	ctx->__glutContext = IMiniGL->CreateContextTags(
				MGLCC_Width,			ctx->__glutWindowWidth,
				MGLCC_Height,			ctx->__glutWindowHeight,
				MGLCC_WindowLeft, 		ctx->__glutWindowX,
				MGLCC_WindowTop, 	    ctx->__glutWindowY,
				MGLCC_Windowed,			TRUE,
				MGLCC_Buffers,			ctx->__glutDisplayMode & GLUT_DOUBLE ? 2 : 1,
				MGLCC_PixelDepth,		16,
				MGLCC_CloseGadget,		TRUE,
				MGLCC_SizeGadget,		TRUE,
				MGLCC_StencilBuffer,	ctx->__glutDisplayMode & GLUT_STENCIL ? TRUE : FALSE,
			TAG_DONE);
	
	if (!ctx->__glutContext)
	{
		Self->GLUTExit();
		return -1;
	}
	
	dprintf("Window created\n");
		
	context = (GLcontext)GET_INSTANCE(ctx->__glutContext);
	ctx->glctx = context;

	dprintf("Context = %p, if = %p\n", context, ctx->__glutContext);
	
	window = (struct Window *)ctx->__glutContext->GetWindowHandle();

	dprintf("Window handle = %p\n", window);

	dprintf("Setting smart lock mode\n");
	ctx->__glutContext->LockMode(MGL_LOCK_SMART);

		
	IIntuition->SetWindowTitles(window, name, name);
		
	dprintf("Setting up a redisplay event\n");
	ctx->GlutEvent = GLUTEVENT_REDISPLAY;
	ctx->IconName = 0;
	ctx->GlutSingle = ctx->__glutDisplayMode & GLUT_DOUBLE ? GL_FALSE : GL_TRUE;
	ctx->GlutW = ctx->__glutWindowWidth;
	ctx->GlutH = ctx->__glutWindowHeight;
	
	dprintf("Done\n");
	
	return __glutWindow++;
}

/* FIXME: Implement 
 * glutCreateSubWindow
 * glutSetWindow
 * glutGetWindow
 */

void glut_GLUTDestroyWindow(struct GlutIFace *Self, int window)
{
  	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	/* FIXME: window id is ignored */
	if (window == 1)
	{
  		ctx->__glutContext->DeleteContext();
		ctx->__glutContext = NULL;
	}
}

void glut_GLUTPostRedisplay(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->GlutEvent |= GLUTEVENT_REDISPLAY;
}

void glut_GLUTSwapBuffers(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->__glutContext->SwitchDisplay();
}

void glut_GLUTPositionWindow(struct GlutIFace *Self, int x, int y)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->newX = x;
	ctx->newY = y;
	ctx->GlutEvent |= GLUTEVENT_REPOSITION;
}

void glut_GLUTReshapeWindow(struct GlutIFace *Self, int width, int height)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->newW = width;
	ctx->newH = height;
	ctx->GlutEvent |= GLUTEVENT_RESHAPE;
}

void glut_GLUTFullScreen(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->GlutEvent |= GLUTEVENT_FULLSCREEN;
}

void glut_GLUTPushWindow(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->GlutEvent |= GLUTEVENT_PUSHWINDOW;
	ctx->GlutEvent &= ~GLUTEVENT_POPWINDOW;
}

void glut_GLUTPopWindow(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->GlutEvent |= GLUTEVENT_POPWINDOW;
	ctx->GlutEvent &= ~GLUTEVENT_PUSHWINDOW;
}

void glut_GLUTShowWindow(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->GlutEvent |= GLUTEVENT_SHOWWINDOW;
	ctx->GlutEvent &= ~GLUTEVENT_HIDEWINDOW;
}

void glut_GLUTHideWindow(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->GlutEvent |= GLUTEVENT_HIDEWINDOW;
	ctx->GlutEvent &= ~GLUTEVENT_SHOWWINDOW;
}

void glut_GLUTIconifyWindow(struct GlutIFace *Self)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->GlutEvent |= GLUTEVENT_ICONIFY;
}

void glut_GLUTSetWindowTitle(struct GlutIFace *Self, char *name)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	struct Window *window = (struct Window *)ctx->__glutContext->GetWindowHandle();
	IIntuition->SetWindowTitles(window, name, name);
}

void glut_GLUTSetIconTitle(struct GlutIFace *Self, char *name)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	if (ctx->IconName)
		IExec->FreeVec(ctx->IconName);
		
	ctx->IconName = mystrdup(name);
}

/* FIXME: Implement glutSetCursor */

/* FIXME: Implement Menus */

void glut_GLUTWarpPointer(struct GlutIFace *Self,  int x, int y )
{
    GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	// Initialize fields if necessary
	if(!ctx->warpPointerMP)
	{
		ctx->warpPointerMP = IExec->CreateMsgPort();
		if(!ctx->warpPointerMP)
		{
			dprintf("Error: Could not create a message port\n");
			return;
		}
	}
	if(!ctx->warpPointerEvent)
	{
		// We're using a single memory allocation for both the event and the pixel-pointer
		ctx->warpPointerEvent = IExec->AllocVec(sizeof(struct InputEvent) +
									sizeof(struct IEPointerPixel),MEMF_PUBLIC);
		if(!ctx->warpPointerEvent)
		{
			dprintf("Error: Could not allocate memory for this operation\n");
			return;
		}
		ctx->warpPointerEvent->ie_EventAddress = (APTR)ctx->warpPointerEvent + sizeof(struct InputEvent);
        ctx->warpPointerEvent->ie_NextEvent = NULL;
        ctx->warpPointerEvent->ie_Class = IECLASS_NEWPOINTERPOS;
        ctx->warpPointerEvent->ie_SubClass = IESUBCLASS_PIXEL;
        ctx->warpPointerEvent->ie_Code = IECODE_NOBUTTON;
		ctx->warpPointerEvent->ie_Qualifier = 0;
	}
	if(!ctx->warpPointerIOReq)
	{
		// Open the input device and initialize the I/O request
		ctx->warpPointerIOReq = (struct IOStdReq*)
								IExec->CreateIORequest(ctx->warpPointerMP,sizeof(struct IOStdReq));
		if(!ctx->warpPointerIOReq)
		{
			dprintf("Error: Could not create IORequest.\n");
			return;
		}
		if(IExec->OpenDevice("input.device",0, (struct IORequest *)ctx->warpPointerIOReq, 0) != 0)
		{
			IExec->DeleteIORequest((struct IORequest*)ctx->warpPointerIOReq);
			ctx->warpPointerIOReq = NULL;
			dprintf("Error: Could not open input device.\n");
			return;
		}

        ctx->warpPointerIOReq->io_Data = (APTR)ctx->warpPointerEvent;
        ctx->warpPointerIOReq->io_Length = sizeof(struct InputEvent);
        ctx->warpPointerIOReq->io_Command = IND_WRITEEVENT;
	}

	// Get the necessary information
	struct IEPointerPixel *pixel = ctx->warpPointerEvent->ie_EventAddress;
	struct Window *window = (struct Window *)ctx->__glutContext->GetWindowHandle();
	LONG left, top;
	IIntuition->GetWindowAttrs(window, WA_Left, &left, WA_Top, &top, TAG_END);
	pixel->iepp_Screen=(struct Screen *)window->WScreen;
	pixel->iepp_Position.X = left + window->BorderLeft + x;
	pixel->iepp_Position.Y = top + window->BorderTop + y;

	// Execute!
    IExec->DoIO((struct IORequest *)ctx->warpPointerIOReq);
}

void glut_GLUTSpaceballMotionFunc(struct GlutIFace *Self,  void (* callback)( int, int, int ) )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTSpaceballRotateFunc(struct GlutIFace *Self,  void (* callback)( int, int, int ) )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTSpaceballButtonFunc(struct GlutIFace *Self,  void (* callback)( int, int ) )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTButtonBoxFunc(struct GlutIFace *Self,  void (* callback)( int, int ) )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTDialsFunc(struct GlutIFace *Self,  void (* callback)( int, int ) )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTTabletMotionFunc(struct GlutIFace *Self,  void (* callback)( int, int ) )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTTabletButtonFunc(struct GlutIFace *Self,  void (* callback)( int, int, int, int ) )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTOverlayDisplayFunc(struct GlutIFace *Self,  void (* callback)( void ) )
{
	// Overlay is not actually supported
	dprintf("Overlay is not supported\n");
}

void glut_GLUTJoystickFunc(struct GlutIFace *Self,  void (* callback)( unsigned int, int, int, int ), int pollInterval )
{
	// FIXME: this is not supported yet
	dprintf("unsupported callback\n");
}

void glut_GLUTDisplayFunc(struct GlutIFace *Self, void (*func)())
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutDisplayFunc = func;
}

void glut_GLUTReshapeFunc(struct GlutIFace *Self, void (*func)(int width, int height))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutReshapeFunc = func;
}

void glut_GLUTKeyboardFunc(struct GlutIFace *Self, void (*func)(unsigned char key, int x, int y))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutKeyboardFunc = func;
}

void glut_GLUTKeyboardUpFunc(struct GlutIFace *Self, void (*func)(unsigned char key, int x, int y))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutKeyboardUpFunc = func;
}

void glut_GLUTIgnoreKeyRepeat(struct GlutIFace *Self, int ignore)
{
    GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->ignoreKeyRepeat = ignore;
}

void glut_GLUTMouseFunc(struct GlutIFace *Self, void (*func)(int button, int state, int x, int y))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutMouseFunc = func;
}

void glut_GLUTMotionFunc(struct GlutIFace *Self, void (*func)(int x, int y))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutMotionFunc = func;
}

void glut_GLUTPassiveMotionFunc(struct GlutIFace *Self, void (*func)(int x, int y))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutPassiveMotionFunc = func;
}

void glut_GLUTVisibilityFunc(struct GlutIFace *Self, void (*func)(int state))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutVisibilityFunc = func;
}

void glut_GLUTEntryFunc(struct GlutIFace *Self, void (*func)(int state))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);
	
	ctx->glutEntryFunc = func;
}

void glut_GLUTSpecialFunc(struct GlutIFace *Self, void (*func)(int key, int x, int y))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutSpecialFunc = func;
}

void glut_GLUTSpecialUpFunc(struct GlutIFace *Self, void (*func)(int key, int x, int y))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutSpecialUpFunc = func;
}

void glut_GLUTIdleFunc(struct GlutIFace *Self, void (*func)(void))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutIdleFunc = func;
}

void glut_GLUTTimerFunc(struct GlutIFace *Self, unsigned int msecs, void (*func)(int value), int value)
{
    GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	if(!ctx->timerHookPort)
	{
		ctx->timerHookPort = IExec->CreatePort( 0, 0 );
		if(!ctx->timerHookPort)
		{
			dprintf("Error: Could not allocate a message port\n");
			return;
		}
	}

	TimerHook *timerHook = (TimerHook*)
								IExec->RemTail((struct List*) &(ctx->freeTimerHookList));
	if(!timerHook)
	{
		timerHook = timerhook_new(ctx->timerHookPort);
		if(!timerHook)
		{
			dprintf("Error: Could not allocate a new timer request object\n");
			return;
		}
	}

	uint64 ticks = msecs * ctx->ticksPerMSec;
	// Note, this is actually the e-clock so Seconds and Microseconds mean ev_hi & ev_lo
	timerHook->timerIO->Time.Seconds = ticks >> 32;
	timerHook->timerIO->Time.Microseconds = ticks & 0x00000000FFFFFFFF;
	timerHook->func = func;
	timerHook->value = value;

	IExec->SendIO((struct IORequest*)timerHook->timerIO);
	IExec->AddHead((struct List*)&(ctx->activeTimerHookList), (struct Node*)timerHook);
}

TimerHook* timerhook_new(struct MsgPort *timerPort)
{
	TimerHook *timerHook = IExec->AllocVec(sizeof(TimerHook), MEMF_PUBLIC | MEMF_CLEAR);

	struct TimeRequest* timerIO = (struct TimeRequest *)
					IExec->CreateIORequest(timerPort, sizeof(struct TimeRequest));
	if (!timerIO){
		return NULL;
    }

	timerIO->Request.io_Command = TR_ADDREQUEST;
	timerHook->timerIO = timerIO;

	BYTE error = IExec->OpenDevice(TIMERNAME, UNIT_ECLOCK,(struct IORequest *) timerIO, 0L);
	if (error != 0 ){
		timerhook_delete(timerHook);
		return NULL;
    }

	return timerHook;
}

void timerhook_delete(TimerHook *timerHook)
{
	if(!timerHook)
	{
		return;
	}

	struct TimeRequest *timerIO = timerHook->timerIO;

    if(timerIO){
		if(timerIO->Request.io_Command != NT_MESSAGE && IExec->CheckIO((struct IORequest*)timerIO) == NULL){
			IExec->AbortIO((struct IORequest*)timerIO);
			IExec->WaitIO((struct IORequest*)timerIO);
		}

		IExec->CloseDevice((struct IORequest *) timerIO);
		IExec->DeleteIORequest((struct IORequest *) timerIO);
	}

	IExec->FreeVec(timerHook);
}

void glut_GLUTCloseFunc(struct GlutIFace *Self, void (*func)(void))
{
    GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutCloseFunc = func;
}

static uint32 __glutCheckWindowBox(struct Window *win)
{
	if (win->MouseX > win->BorderLeft 
	 && win->MouseX < win->Width - win->BorderRight
	 && win->MouseY > win->BorderTop
	 && win->MouseY < win->Height - win->BorderBottom)
		return 1;

	return 0;
}			 

void glut_GLUTMainLoop(struct GlutIFace *Self)
{
	struct IntuiMessage *imsg;
	struct Message *timerMsg;
	struct Window *window;
	BOOL idle = FALSE;
	uint32 buttons = 0;
	uint32 in = 0, oldin = 0;
	BOOL visible = TRUE;
	UWORD qualifier;
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);


	window = (struct Window *)ctx->__glutContext->GetWindowHandle();
	IIntuition->ModifyIDCMP(window,
			IDCMP_RAWKEY|IDCMP_MOUSEMOVE|IDCMP_MOUSEBUTTONS|
			IDCMP_NEWSIZE|IDCMP_CLOSEWINDOW);

	oldin = in = __glutCheckWindowBox(window);
	
	/* Do an initial reshape */
	if (ctx->glutReshapeFunc)
		ctx->glutReshapeFunc(ctx->GlutW, ctx->GlutH);
	else
		ctx->__glutContext->GLViewport(0, 0, ctx->GlutW, ctx->GlutH);
		
	/* Do an initial visibiliy */
	if (ctx->glutVisibilityFunc)
		ctx->glutVisibilityFunc(GLUT_VISIBLE);

	ctx->running = GL_TRUE;
	while(ctx->running == GL_TRUE)
	{
		idle = TRUE;
		window = (struct Window *)ctx->__glutContext->GetWindowHandle();

		if(!ctx->glutIdleFunc && !ctx->GlutEvent)
		{
			// There's no point in busy waiting when there is no idle function to fill the time
			ULONG signals = (1 << ctx->timerHookPort->mp_SigBit) | (1 << window->UserPort->mp_SigBit);
			IExec->Wait(signals);
		}

		while((imsg = (struct IntuiMessage *)IExec->GetMsg(window->UserPort)))
		{
			uint32 Class = imsg->Class;
			uint16 Code = imsg->Code;
			int16 MouseX = imsg->MouseX - window->BorderLeft;
			int16 MouseY = imsg->MouseY - window->BorderTop;
			idle = FALSE;
			IExec->ReplyMsg((struct Message *)imsg);
			
			/* Dispatch */
			switch (Class)
			{
				case IDCMP_CLOSEWINDOW:
				{
					if(ctx->glutCloseFunc)
					{
						ctx->glutCloseFunc();
					}
					else
					{
						switch(ctx->actionOnWindowClose)
						{
						case  GLUT_ACTION_EXIT:
							exit(0);
							break;
						case GLUT_ACTION_GLUTMAINLOOP_RETURNS:
							ctx->running = GL_FALSE;
							break;
						case GLUT_ACTION_CONTINUE_EXECUTION:
							break;
						default:
	                        if (ctx->glutKeyboardFunc)
							{
								ctx->glutKeyboardFunc(27, MouseX, MouseY);
							}
							break;
						}
						
						break;
					}
				}
				case IDCMP_RAWKEY:
					qualifier = imsg->Qualifier;
					if((ctx->ignoreKeyRepeat || ctx->keyRepeatMode == GLUT_KEY_REPEAT_OFF)
								&& (qualifier & IEQUALIFIER_REPEAT))
					{
						break;
					}

					ctx->modifiers = 0;
					if(qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
					{
						ctx->modifiers |= GLUT_ACTIVE_SHIFT;
					}
					if(qualifier & IEQUALIFIER_CONTROL)
					{
						ctx->modifiers |= GLUT_ACTIVE_CTRL;
					}
					if(qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
                    {
						ctx->modifiers |= GLUT_ACTIVE_ALT;
					}

					if (ctx->glutKeyboardFunc || ctx->glutKeyboardUpFunc)
					{
						struct InputEvent inputEvent;
						TEXT buffer[8];
						WORD numChars;

						inputEvent.ie_Class = IECLASS_RAWKEY;
						inputEvent.ie_SubClass = 0;
						inputEvent.ie_Code = (Code & ~IECODE_UP_PREFIX);
						inputEvent.ie_Qualifier = qualifier;
						inputEvent.ie_EventAddress = (APTR *) *((ULONG *)imsg->IAddress);
						numChars = __IKeymap->MapRawKey(&inputEvent, buffer, 8, NULL);

						// want "vanilla" keys only
						if(numChars == 1)
						{
							if(Code & IECODE_UP_PREFIX)
							{
								if(ctx->glutKeyboardUpFunc)
								{
									ctx->glutKeyboardUpFunc((unsigned char)buffer[0], MouseX, MouseY);
								}
							}
							else 
							{
                                if(ctx->glutKeyboardFunc)
								{
									ctx->glutKeyboardFunc((unsigned char)buffer[0], MouseX, MouseY);
								}
							}
                            ctx->modifiers = MODIFIERS_INVALID;
							break;
						}
					}
				
					if (ctx->glutSpecialFunc || ctx->glutSpecialUpFunc)
					{

						int key = -1;
						
						switch (Code & ~IECODE_UP_PREFIX)
						{
							case 0x50: key = GLUT_KEY_F1; 		break;
							case 0x51: key = GLUT_KEY_F2; 		break;
							case 0x52: key = GLUT_KEY_F3; 		break;
							case 0x53: key = GLUT_KEY_F4; 		break;
							case 0x54: key = GLUT_KEY_F5; 		break;
							case 0x55: key = GLUT_KEY_F6; 		break;
							case 0x56: key = GLUT_KEY_F7; 		break;
							case 0x57: key = GLUT_KEY_F8; 		break;
							case 0x58: key = GLUT_KEY_F9; 		break;
							case 0x59: key = GLUT_KEY_F10; 		break;
							case 0x4b: key = GLUT_KEY_F11; 		break;
							case 0x6f: key = GLUT_KEY_F12; 		break;
							case 0x4f: key = GLUT_KEY_LEFT; 	break;
							case 0x4c: key = GLUT_KEY_UP;		break;
							case 0x4e: key = GLUT_KEY_RIGHT;	break;
							case 0x4d: key = GLUT_KEY_DOWN;		break;
							case 0x48: key = GLUT_KEY_PAGE_UP;	break;
							case 0x49: key = GLUT_KEY_PAGE_DOWN;break;
							case 0x70: key = GLUT_KEY_HOME;		break;
							case 0x71: key = GLUT_KEY_END;		break;
							case 0x47: key = GLUT_KEY_INSERT;	break;
						}
						
						if (key != -1)
						{
							if(Code & IECODE_UP_PREFIX)
							{
                            	if (ctx->glutSpecialUpFunc)
                            		ctx->glutSpecialUpFunc(key, MouseX, MouseY);
							}
							else
							{
								if (ctx->glutSpecialFunc)
									ctx->glutSpecialFunc(key, MouseX, MouseY);
							}
						}
					}
                    ctx->modifiers = MODIFIERS_INVALID;
					break;

				case IDCMP_MOUSEBUTTONS:
					switch (Code)
					{
						case SELECTDOWN:
							if (ctx->glutMouseFunc)
								ctx->glutMouseFunc(GLUT_LEFT_BUTTON, GLUT_DOWN,
													MouseX, MouseY);
							buttons |= 1;
							break;
						case SELECTUP:
							if (ctx->glutMouseFunc)
								ctx->glutMouseFunc(GLUT_LEFT_BUTTON, GLUT_UP,
													MouseX, MouseY);
							buttons &= ~1;
							break;
						case MENUDOWN:
							if (ctx->glutMouseFunc)
								ctx->glutMouseFunc(GLUT_RIGHT_BUTTON, GLUT_DOWN,
													MouseX, MouseY);
							buttons |= 2;
							break;
						case MENUUP:
							if (ctx->glutMouseFunc)
								ctx->glutMouseFunc(GLUT_RIGHT_BUTTON, GLUT_UP,
													MouseX, MouseY);
							buttons &= ~2;
							break;
						case MIDDLEDOWN:
							if (ctx->glutMouseFunc)
								ctx->glutMouseFunc(GLUT_MIDDLE_BUTTON, GLUT_DOWN,
													MouseX, MouseY);
							buttons |= 4;
							break;
						case MIDDLEUP:
							if (ctx->glutMouseFunc)
								ctx->glutMouseFunc(GLUT_MIDDLE_BUTTON, GLUT_UP,
													MouseX, MouseY);
							buttons &= ~4;
							break;
					}
					break;

				case IDCMP_MOUSEMOVE:
					/* Check out-of-bounds */
					in = __glutCheckWindowBox(window);
					if (in != oldin)
					{
						/* We either left or entered */
						if (in && ctx->glutEntryFunc)
							ctx->glutEntryFunc(GLUT_ENTERED);
						else if (!in && ctx->glutEntryFunc)
							ctx->glutEntryFunc(GLUT_LEFT);
					}
					oldin = in;
					
					if (in && buttons && ctx->glutMotionFunc)
						ctx->glutMotionFunc(MouseX, MouseY);
					if (in && !buttons && ctx->glutPassiveMotionFunc)
						ctx->glutPassiveMotionFunc(MouseX, MouseY);
					break;
				
				case IDCMP_NEWSIZE:
					ctx->newW = window->Width - window->BorderLeft 
												  - window->BorderRight;
					ctx->newH = window->Height - window->BorderTop 
												   - window->BorderBottom;
					ctx->GlutEvent |= GLUTEVENT_RESHAPE;
					break;
				case IDCMP_REFRESHWINDOW:
					// Clear the refresh event
					IIntuition->BeginRefresh(window);
					IIntuition->EndRefresh(window, TRUE);

					// Trigger a redraw
                    ctx->GlutEvent |= GLUTEVENT_REDISPLAY;
					break;
			} /* switch (Class) */
		} /* while (imsg = ...) */
			
		/* Handle outstanding events */
		if (ctx->GlutEvent)
		{
			if (ctx->GlutEvent & GLUTEVENT_RESHAPE)
			{
				ctx->GlutEvent &= ~GLUTEVENT_RESHAPE;
				ctx->GlutEvent |= GLUTEVENT_REDISPLAY;
				if(ctx->__glutContext->SetDisplayMode(GL_TRUE, ctx->newW, ctx->newH, 0) == 0)
				{
					dprintf("Set display mode failed.\n");
					displayWarningReq(window, NULL, "Fatal Error",
									  "Error: Out of graphics memory.\n"
									  "This program will now close",
									  REQIMAGE_ERROR);
					if(ctx->actionOnWindowClose == GLUT_ACTION_GLUTMAINLOOP_RETURNS)
					{
						return;
					}
					else
					{
						exit(10);
					}
				}
				window = (struct Window *)ctx->__glutContext->GetWindowHandle();
				if (ctx->glutReshapeFunc)
					ctx->glutReshapeFunc(ctx->newW, ctx->newH);
				else
					ctx->__glutContext->GLViewport(0, 0, ctx->newW, ctx->newH);
			}

			if (ctx->GlutEvent & GLUTEVENT_REPOSITION)
			{
				ctx->GlutEvent &= ~GLUTEVENT_REPOSITION;
				IIntuition->MoveWindow(window, ctx->newX - window->LeftEdge,
								ctx->newY - window->TopEdge);
			}

			if (ctx->GlutEvent & GLUTEVENT_FULLSCREEN)
			{
				ctx->GlutEvent &= ~GLUTEVENT_FULLSCREEN;
                ctx->GlutEvent |= GLUTEVENT_REDISPLAY;
                ctx->newW = window->Width - window->BorderLeft
												  - window->BorderRight;
				ctx->newH = window->Height - window->BorderTop
												   - window->BorderBottom;
				if(ctx->__glutContext->SetDisplayMode(GL_FALSE, ctx->newW, ctx->newH, 0) == 0)
				{
                    dprintf("Set display mode failed.\n");
					displayWarningReq(window, NULL, "Fatal Error",
									  "Error: Out of graphics memory.\n"
									  "This program will now close",
									  REQIMAGE_ERROR);
					if(ctx->actionOnWindowClose == GLUT_ACTION_GLUTMAINLOOP_RETURNS)
					{
						return;
					}
					else
					{
						exit(10);
					}
				}
                window = (struct Window *)ctx->__glutContext->GetWindowHandle();
                ctx->newW = window->Width - window->BorderLeft
												  - window->BorderRight;
				ctx->newH = window->Height - window->BorderTop
												   - window->BorderBottom;
				IExec->DebugPrintF("newW: %d newH: %d\n", ctx->newW, ctx->newH);
				if (ctx->glutReshapeFunc)
					ctx->glutReshapeFunc(ctx->newW, ctx->newH);
                else
					ctx->__glutContext->GLViewport(0, 0, ctx->newW, ctx->newH);
			}

			if (ctx->GlutEvent & GLUTEVENT_POPWINDOW)
			{
				ctx->GlutEvent &= ~GLUTEVENT_POPWINDOW;
				IIntuition->WindowToFront(window);
			}

			if (ctx->GlutEvent & GLUTEVENT_PUSHWINDOW)
			{
				ctx->GlutEvent &= ~GLUTEVENT_PUSHWINDOW;
				IIntuition->WindowToBack(window);
			}

			if (ctx->GlutEvent & GLUTEVENT_SHOWWINDOW)
			{
				ctx->GlutEvent &= ~GLUTEVENT_SHOWWINDOW;
				IIntuition->ShowWindow(window, WINDOW_FRONTMOST);
				visible = TRUE;
			}

			if (ctx->GlutEvent & GLUTEVENT_HIDEWINDOW)
			{
				ctx->GlutEvent &= ~GLUTEVENT_HIDEWINDOW;
				IIntuition->HideWindow(window);
				visible = FALSE;
			}

			if (ctx->GlutEvent & GLUTEVENT_ICONIFY)
			{
				ctx->GlutEvent &= ~GLUTEVENT_ICONIFY;
				/* FIXME: Implement iconification */
			}

			if (ctx->GlutEvent & GLUTEVENT_REDISPLAY)
			{
				ctx->GlutEvent &= ~GLUTEVENT_REDISPLAY;
				if (ctx->glutDisplayFunc && visible)
					ctx->glutDisplayFunc();
				
				/* When a single buffer is used, make the blit implicitly */
				if (ctx->GlutSingle)
				{
					ctx->__glutContext->SwitchDisplay();
				}
			}
		} /* if (ctx->GlutEvent) */

		/* Check for timer hook events */
		if(ctx->timerHookPort)
		{
			while((timerMsg = IExec->GetMsg(ctx->timerHookPort)))
			{
	            idle = FALSE;

				// Find the TimerHook
				TimerHook *currTimerHook = (TimerHook*)ctx->activeTimerHookList.mlh_Head;
				TimerHook *timerHook = NULL;
				while(currTimerHook->node.mln_Succ)
				{
					if(currTimerHook->timerIO == (struct TimeRequest*)timerMsg)
					{
						timerHook = currTimerHook;
						break;
					}
					currTimerHook = (TimerHook*)currTimerHook->node.mln_Succ;
				}
				if(timerHook)
				{
	                // execute the hook
					timerHook->func(timerHook->value);

					// Move the TimerHook object from the active, to the free list
					IExec->Remove((struct Node*)timerHook);
					IExec->AddTail((struct List*)&(ctx->freeTimerHookList), (struct Node*) timerHook);
				}
				else
				{
					dprintf("Internal Error: a timer hook event occurred, but the associated "
							"timer hook was not found\n");
				}
			}
		}
			
		if (idle && ctx->glutIdleFunc)
			ctx->glutIdleFunc();

	} /* while (1) */			
}

