/*
 * $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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "sysinc.h"
#include <proto/timer.h>
#include <proto/intuition.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;


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->__glutContext)
	{
		ctx->__glutContext->DeleteContext();
		ctx->__glutContext = 0;
	}
}

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;
}


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, 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_Windowed,			TRUE,
				MGLCC_Buffers,			ctx->__glutDisplayMode & GLUT_DOUBLE ? 2 : 1,
				MGLCC_PixelDepth,		16,
				MGLCC_CloseGadget,		TRUE,
				MGLCC_SizeGadget,		TRUE,
				MGLCC_StencilBuffer,	TRUE,
			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("Moving Window to start position\n");
	__IIntuition->MoveWindow(window, ctx->__glutWindowX - window->LeftEdge, 
			ctx->__glutWindowY -window->TopEdge);

		
	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();
	}
}

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_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_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_GLUTIdleFunc(struct GlutIFace *Self, void (*func)(void))
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	ctx->glutIdleFunc = func;
}

/* FIXME: Implement glutTimeFunc */

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

	return 0;
}

int glut_GLUTGet(struct GlutIFace *Self, GLenum state)
{
	GLUTcontext ctx = (GLUTcontext)GET_INSTANCE(Self);

	switch (state)
	{
		case GLUT_ELAPSED_TIME:
		{
			struct TimeVal now;
			__ITimer->GetSysTime(&now);
			__ITimer->SubTime(&now, &ctx->__glutStartTime);
			return 1000 * now.Seconds + now.Microseconds/1000;
		}
		break;
	}
	
	return 0;
}
			

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


	window = (struct Window *)ctx->__glutContext->GetWindowHandle();
	__IIntuition->ModifyIDCMP(window, 
			IDCMP_VANILLAKEY|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);
		
	/* Do an initial visibiliy */
	if (ctx->glutVisibilityFunc)
		ctx->glutVisibilityFunc(GLUT_VISIBLE);
	
	while (1 /*ctx->Running == GL_TRUE*/)
	{
		idle = TRUE;
		window = (struct Window *)ctx->__glutContext->GetWindowHandle();
		
		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->glutKeyboardFunc)
						ctx->glutKeyboardFunc(27, MouseX, MouseY);
					break;
					
				case IDCMP_VANILLAKEY:
					if (ctx->glutKeyboardFunc)
						ctx->glutKeyboardFunc((unsigned char)Code, MouseX, MouseY);
					break;

				case IDCMP_RAWKEY:
					if (ctx->glutSpecialFunc)
					{
						int key = -1;
						
						switch (Code)
						{
							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)
							ctx->glutSpecialFunc(key, MouseX, MouseY);
					}
					break;

				case IDCMP_MOUSEBUTTONS:
					if (ctx->glutMouseFunc)
					{
						switch (Code)
						{
							case SELECTDOWN:
								ctx->glutMouseFunc(GLUT_LEFT_BUTTON, GLUT_DOWN,
														MouseX, MouseY);
								buttons |= 1;
								break;
							case SELECTUP:
								ctx->glutMouseFunc(GLUT_LEFT_BUTTON, GLUT_UP,
														MouseX, MouseY);
								buttons &= ~1;
								break;
							case MENUDOWN:
								ctx->glutMouseFunc(GLUT_RIGHT_BUTTON, GLUT_DOWN,
														MouseX, MouseY);
								buttons |= 2;
								break;
							case MENUUP:
								ctx->glutMouseFunc(GLUT_RIGHT_BUTTON, GLUT_UP,
														MouseX, MouseY);
								buttons &= ~2;
								break;
							case MIDDLEDOWN:
								ctx->glutMouseFunc(GLUT_MIDDLE_BUTTON, GLUT_DOWN,
														MouseX, MouseY);
								buttons |= 4;
								break;
							case MIDDLEUP:
								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;
			} /* switch (Class) */
		} /* while (imsg = ...) */
			
		/* Handle outstanding events */
		if (ctx->GlutEvent)
		{
			if (ctx->GlutEvent & GLUTEVENT_RESHAPE)
			{
//				ctx->__glutContext->ChooseWindowMode(GL_TRUE);
				ctx->GlutEvent &= ~GLUTEVENT_RESHAPE;
				ctx->GlutEvent |= GLUTEVENT_REDISPLAY;
				ctx->__glutContext->ResizeContext(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_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->__glutContext->ChooseWindowMode(GL_FALSE);
				ctx->__glutContext->ResizeContext(window->Width - window->BorderLeft - window->BorderRight,
							 window->Height - window->BorderTop - window->BorderBottom);
				
				if (ctx->glutReshapeFunc)
					ctx->glutReshapeFunc(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) */
			
		if (idle && ctx->glutIdleFunc)
			ctx->glutIdleFunc();

	} /* while (1) */			
}

