/* -----------------------------------------------------------------------------
*
*  npglut.c
*
*  ANTz - realtime 3D data visualization tools for the real-world, based on NPE.
*
*  ANTz is hosted at http://openantz.com and NPE at http://neuralphysics.org
*
*  Written in 2010-2019 by Shane Saxon - saxon@openantz.com
*
*  Please see main.c for a complete list of additional code contributors.
*
*  To the extent possible under law, the author(s) have dedicated all copyright 
*  and related and neighboring rights to this software to the public domain
*  worldwide. This software is distributed without any warranty.
*
*  Released under the CC0 license, which is GPL compatible.
*
*  You should have received a copy of the CC0 Public Domain Dedication along
*  with this software (license file named LICENSE.txt). If not, see
*  http://creativecommons.org/publicdomain/zero/1.0/
*
* --------------------------------------------------------------------------- */

#include "npglut.h"

#include "../npio.h"
#include "../npctrl.h"				//remove once npPostMsg is global, debug zz

#include "npgl.h"
#include "npkey.h"
#include "npmouse.h"

#include "../os/npos.h"

#include "../data/npmapfile.h"		//debug, zz

#include "gl/nptexmap.h"		//zz tex


void npGlutInitGL (void);
void npGlutEventFuncs (void);
void npGlutDrawGLScene (void);
void npGlutDrawGLSceneIdle (void);
// void npGlutResizeGLScene(int Width, int Height);

void npGlutKeyDown (unsigned char key, int x, int y);
void npGlutKeyUp (unsigned char key, int x, int y);
void npGlutKeyDownSpecial (int key, int x, int y);
void npGlutKeyUpSpecial (int key, int x, int y);

/* the intent of this file is wrap glut functions and glut specific code
*  its 'npgl.h' that handles the primary OpenGL routines
*  app is designed to be wrapped by GLUT or other OS methods, wgl, Cocoa, etc...]

*  update to allow entering and leaving game mode without re-loading textures
*  wglShareLists() function in MSW and glXCreateContext() under X Windows. zz
*/

/*!
*  @param argc is the command line argument count.
*  @param argv is the list of arguments.
*  @param dataRef is the global map context.
*
*  @return 0 if exited normally, otherwise returns an error number which is 
*    generated by the application framework.
*
* Starts the main app loop using defined (OS specific) app framework.
* Currently using freeglut for MSW and Linux. OSX build uses Apple GLUT.      
*/
//------------------------------------------------------------------------------
void npInitGlut (int argc, char **argv, void* dataRef)
{
	GLboolean stereoSupport = false;
	int depth = 0;
	int result = 0;
	int gMainWindow = 0;
	char msg[256];

	pData data = (pData) dataRef;
	pNPgl gl = &data->io.gl;

	// init glut app framework
//	glutInit (&argc, argv);

	//zz debug stereo3D not yet supported on OSX
	//zz debug move OS specific code to npos.h ? nposPostFramework()
#ifndef NP_OSX_
	sprintf (msg, "freeglut ver: %d", glutGet(GLUT_VERSION));
	npPostMsg (msg, kNPmsgCtrl, data);
	glGetBooleanv (GL_STEREO, (GLboolean*)&gl->stereo3D);
#else
	npPostMsg ("Apple GLUT", kNPmsgCtrl, data);
	gl->stereo3D = false;			
#endif

	if (gl->stereo3D)
		sprintf (msg, "OpenGL Stereo 3D: YES");
	else
		sprintf (msg, "OpenGL Stereo 3D: NO");
	npPostMsg (msg, kNPmsgCtrl, data);

	/// OpenGL stereo 3D is ONLY supported by Quadro and AMD Fire Pro
	if (gl->stereo3D)
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STEREO);
	else
		glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

	glutInitWindowPosition (gl->position.x, gl->position.y);
	glutInitWindowSize (gl->windowSize.x, gl->windowSize.y);
	gl->windowID = glutCreateWindow (gl->name);
	
	glutSetWindow (gl->windowID);			//S3D
	glutHideWindow();						//S3D

	//zz stereo 3D and GameMode stripped out on 2016-07-03

	/// register keyboard, mouse and display events with GLUT
	npGlutEventFuncs();

	/// init OpenGL
	npInitGL (dataRef);

	/// show the window
	glutShowWindow ();

	if (gl->fullscreen)
	{
		npPostMsg ("FullScreen Window", kNPmsgCtrl, data);
		glutFullScreen ();
	}		

	/// System Ready...
	data->ctrl.startup = false;
	npPostMsg( "www.openANTz.com", kNPmsgCtrl, data);
	npPostMsg( "System Ready...", kNPmsgCtrl, data);
	npPostMsg( data->map.startupMsg, kNPmsgCtrl, data);
}

//------------------------------------------------------------------------------
void npGlutEventFuncs (void)
{
	//! register keyboard events with GLUT
	glutKeyboardFunc (npGlutKeyDown);
	glutKeyboardUpFunc (npGlutKeyUp);
	glutSpecialFunc (npGlutKeyDownSpecial);
	glutSpecialUpFunc (npGlutKeyUpSpecial);
	
	//! register mouse events with GLUT
	//glutEntryFunc( npMouseEntry );  //zz only works when clicking
	glutMouseFunc (npMouseEvent);
	glutMotionFunc (npMouseMotion);
	glutPassiveMotionFunc( npMousePosition );

	/// Apple GLUT does not support the mouse wheel
#ifndef NP_OSX_														//zz-osx debug lde
	glutMouseWheelFunc (npMouseWheel);
#endif
	//! register display functions with GLUT
	glutDisplayFunc (npGlutDrawGLScene);
	glutIdleFunc (npGlutDrawGLSceneIdle);
	glutReshapeFunc (npGLResizeScene);
	
}

//------------------------------------------------------------------------------
void npCloseGlut (void* dataRef)
{
	npCloseGL (dataRef);

	return;
}

void npInitAppGlut( void)
{
	int test = 0;
	glutInit( &test, 0);
}
/*!
* @param dataRef is the command line argument count.
*
* @return 0 if exited normally, otherwise returns an error number which is 
*		generated by the application framework.
*
* Starts the main app loop using defined (OS specific) app framework.
* Currently using freeglut for MSW and Linux. OSX build uses Apple GLUT.      
*/
//------------------------------------------------------------------------------
void npAppLoopGlut (void* dataRef)
{
	npInitGlut (0, 0, dataRef);
	glutMainLoop();
}

//------------------------------------------------------------------------------
void npGlutDrawGLScene(void) 
{
	double deltaTime = 0.0;
	double currentTime = 0.0;
	double targePeriod = 0.0;

	pData data = (pData) npGetDataRef();


	//update data, positions, physics, user input
	npUpdateCtrl (data);

	//screenshot
    if( data->io.gl.screenGrab ) // Screenshot F4
		npScreenShot (data);
	else					//init time on first run
		npGLDrawScene (data);

//	glFlush();		//zzhp							//vsync, not yet functional, debug zz
//	glFinish();		//zzhp

	//calculate the delta time using previously stored time
	currentTime = nposGetTime();
	deltaTime = currentTime - data->io.time;	//add clock roll-over handling, debug zz

	//if extra time left over then sleep for remainder of cycle
	if (deltaTime < data->io.cyclePeriod)
		nposSleep (data->io.cyclePeriod - deltaTime);

	glutPostRedisplay();		//supposed to help with window freezing,	debug zz
								//issue with mouse hitting edge of screen
	glutSwapBuffers();

//	glFinish();					//vsync

	if( !data->io.gl.fullscreen )		//S3D
	{
		data->io.gl.position.x = glutGet(GLUT_WINDOW_X);
		data->io.gl.position.y = glutGet(GLUT_WINDOW_Y);
	}

	//update the locally stored time, used when calculating the delta time
	nposUpdateTime (data);
}

//------------------------------------------------------------------------------

void npGlutDrawGLSceneIdle(void)
{
//	npGlutDrawGLScene();

	return;
}

//fullscreen GameMode creates a new GL context
//currently requires re-registering event callbacks and re-loading texture maps
//------------------------------------------------------------------------------
void npglFullscreen (void* dataRef)
{
	int deltaX = 0, deltaY = 0;
	int result = 0;		//fullscreen window used only if gamemode fails

	pData data = (pData) dataRef;
	pNPgl gl = &data->io.gl;

	if (gl->fullscreen)
	{
		if (glutGameModeGet(GLUT_GAME_MODE_ACTIVE) != 0)		//stereo 3D
		{
			glutLeaveGameMode();
			glutSetWindow (gl->windowID);
            glutShowWindow ();

			//! register keyboard, mouse and display events with GLUT
			npGlutEventFuncs();
			
			npInitGL (data);
		}
	
		//exit fullscreen and restore previous window position
		gl->fullscreen = false;

		glutReshapeWindow (gl->windowSize.x, gl->windowSize.y);
		glutPositionWindow (gl->position.x, gl->position.y);

		//correct for window border offset, glut workaround
		deltaX = gl->position.x - glutGet((GLenum)GLUT_WINDOW_X);
		deltaY = gl->position.y - glutGet((GLenum)GLUT_WINDOW_Y);
		if (deltaX != 0 || deltaY != 0)
			glutPositionWindow (gl->position.x + deltaX,
								gl->position.y + deltaY);
	
		npPostMsg("Exit FullScreen", kNPmsgGL, data);
	}
	else
	{
/*		glutSetWindow (	gl->windowID);
		glutHideWindow ();

		//Game Mode with stereo 3D
		if (gl->stereo3D)
			glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STEREO); // stereo display mode for glut
		else
			glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);

		//stereo 3D and GameMode
		printf("Attempting Game Mode, please ignore GLUT warnings\n");
		glutGameModeString("1920x1080:32@121");
		if ( glutEnterGameMode() == 0 )
		{
			glutGameModeString("1920x1080:32@120");
			if ( glutEnterGameMode() == 0 )
			{
				glutGameModeString("1920x1080:32@119");
				if ( glutEnterGameMode() == 0 )
				{
					glutGameModeString("1920x1080:32@60"); //does not specify refresh
					result = ( glutEnterGameMode() );
		}}}
*/
		gl->position.x = glutGet((GLenum)GLUT_WINDOW_X);
		gl->position.y = glutGet((GLenum)GLUT_WINDOW_Y);

		gl->windowSize.x = gl->width;
		gl->windowSize.y = gl->height;

		if (result == 0)	//fullscreen window used only if gamemode fails
		{
			printf("FullScreen Window\n");
			glutShowWindow ();
			glutFullScreen ();
		}
		else
		{	//GameMode may be different then what we requested, so get the modes
			glutSetWindowTitle("ANTz - GameMode");

			gl->width = glutGameModeGet( GLUT_GAME_MODE_WIDTH );
			gl->height = glutGameModeGet( GLUT_GAME_MODE_HEIGHT );
			gl->pixelDepth = glutGameModeGet( GLUT_GAME_MODE_PIXEL_DEPTH );
			gl->refreshRate = (float)glutGameModeGet( GLUT_GAME_MODE_REFRESH_RATE );
			printf("FullScreen Game Mode: %dx%d:%d@%d\n", gl->width, gl->height,
							gl->pixelDepth, (int)gl->refreshRate);
		}

		gl->fullscreen = true;
	}
}


//------------------------------------------------------------------------------
void npGlutKeyDown (unsigned char key, int x, int y) 
{
	// printf("1 key: %d \n", key);
	int special = 0;
	special = glutGetModifiers();
	//printf("\nSpecial : %d", special);
	//printf("\nKey : %c", key);
//	npKeyGlut (key, x, y, kGlutKeyDown, 0);		// glutGetModifiers()); //zz debug, not a problem here, just no longer needed
	npKeyGlut (key, x, y, kGlutKeyDown, special);		// glutGetModifiers()); //zz debug, not a problem here, just no longer needed
}

//------------------------------------------------------------------------------
void npGlutKeyUp (unsigned char key, int x, int y) 
{
	// printf("2 key: %d \n", key);
	npKeyGlut (key, x, y, kGlutKeyUp, 0);		// glutGetModifiers()); //zz debug
}

//------------------------------------------------------------------------------
void npGlutKeyDownSpecial (int key, int x, int y) 
{
	int special = 0;
	special = glutGetModifiers();  // temp, lde @todo
	// printf("3 key: %d \n", key);
	npKeyGlut (key, x, y, kGlutKeyDownSpecial, special);	// glutGetModifiers());	
	
	//zz debug, if glutGetModifiers() called from a ...Special callback 
	//then err - "freeglut glutGetModifiers() called outside an input callback"
}

//------------------------------------------------------------------------------
void npGlutKeyUpSpecial (int key, int x, int y) 
{
	// printf("4 key: %d \n", key);
	npKeyGlut (key, x, y, kGlutKeyUpSpecial, 0);	// glutGetModifiers());	
}


//------------------------------------------------------------------------------
void npGLSolidSphere (GLdouble radius, GLint slices, GLint stacks)
{
	glutSolidSphere (radius, slices, stacks);
}

//------------------------------------------------------------------------------
void npGLWireSphere (GLdouble radius, GLint slices, GLint stacks)
{
	glutWireSphere (radius, slices, stacks);
}

//------------------------------------------------------------------------------
void npGLSolidCone (GLdouble base, GLdouble height, GLint slices, GLint stacks)
{
	glutSolidCone (base, height, slices, stacks);
}

//------------------------------------------------------------------------------
void npGLWireCone (GLdouble base, GLdouble height, GLint slices, GLint stacks)
{
	glutWireCone (base, height, slices, stacks);
}

//------------------------------------------------------------------------------
void npGLSolidTorus (GLdouble innerRadius, GLdouble outerRadius, 
					GLint slices, GLint stacks)
{
	glutSolidTorus (innerRadius, outerRadius, slices, stacks);
}

//------------------------------------------------------------------------------
void npGLWireTorus (GLdouble innerRadius, GLdouble outerRadius, 
					GLint slices, GLint stacks)
{
	glutWireTorus (innerRadius, outerRadius, slices, stacks);
}

//------------------------------------------------------------------------------
void npGlutPrimitive (int primitive)
{
	switch (primitive)
	{
	case kNPgeoCubeWire :
		glScalef (0.6f, 0.6f, 0.6f); 
		glutWireCube(2.0f);
		glScalef (1.666667f, 1.666667f, 1.666667f);
		break;
	case kNPgeoCube :
		glScalef (0.6f, 0.6f, 0.6f);
		glutSolidCube(2.0f);
		glScalef (1.666667f, 1.666667f, 1.666667f);
		break;
	case kNPgeoSphereWire : glutWireSphere( 1.0f, 24, 12); break;//15, 15 ); break;
	case kNPgeoSphere : glutSolidSphere( 1.0f, 24, 12 ); break;

	case kNPgeoConeWire : glutWireCone( 1.0f, 2.0f, 24, 1 ); break;
	case kNPgeoCone : glutSolidCone( 1.0f, 2.0f, 24, 1 ); break;

	case kNPgeoTorusWire : glutWireTorus(kNPtorusRadius * 0.1f, kNPtorusRadius, 7, 16); break;
	case kNPgeoTorus : glutSolidTorus(kNPtorusRadius * 0.1f, kNPtorusRadius, 7, 16); break;

	case kNPgeoDodecahedronWire :
		glScalef (0.6f, 0.6f, 0.6f);
		glutWireDodecahedron();
		glScalef (1.666667f, 1.666667f, 1.666667f);
		break;
	case kNPgeoDodecahedron :
		glScalef (0.6f, 0.6f, 0.6f);
		glutSolidDodecahedron();
		glScalef (1.666667f, 1.666667f, 1.666667f);
		break;
	case kNPgeoOctahedronWire : glutWireOctahedron(); break;
	case kNPgeoOctahedron : glutSolidOctahedron(); break;
	case kNPgeoTetrahedronWire : glutWireTetrahedron(); break;
	case kNPgeoTetrahedron : glutSolidTetrahedron(); break;
	case kNPgeoIcosahedronWire : glutWireIcosahedron(); break;
	case kNPgeoIcosahedron : glutSolidIcosahedron(); break;

//	case kNPglutWireTeapot : glutWireTeapot( 2.0f ); break;
//	case kNPglutSolidTeapot : glutSolidTeapot( 2.0f ); break;

	default : glutWireTetrahedron(); break;
	}
}

