#include "ofAppGlutWindow.h"
#include "ofBaseApp.h"
#include "ofMain.h"

// glut works with static callbacks UGH, so we need static variables here:

int				windowMode;
bool			bNewScreenMode;
float			timeNow, timeThen, fps;
int				nFramesForFPS;
int				nFrameCount;
int				buttonInUse;
bool			bEnableSetupScreen;

bool			bFrameRateSet;
int 			millisForFrame;
int 			prevMillis;
int 			diffMillis;

float 			frameRate;

int				requestedWidth;
int				requestedHeight;
int 			nonFullScreenX;
int 			nonFullScreenY;
int				mouseX, mouseY;
ofBaseApp *		ofAppPtr;


//----------------------------------------------------------
ofAppGlutWindow::ofAppGlutWindow(){
	timeNow				= 0;
	timeThen			= 0;
	fps					= 60; //give a realistic starting value - win32 issues
	windowMode			= OF_WINDOW;
	bNewScreenMode		= true;
	nFramesForFPS		= 0;
	nFrameCount			= 0;
	buttonInUse			= 0;
	bEnableSetupScreen	= true;
	bFrameRateSet		= false;
	millisForFrame		= 0;
	prevMillis			= 0;
	diffMillis			= 0;
	frameRate			= 0;
	requestedWidth		= 0;
	requestedHeight		= 0;
	nonFullScreenX		= -1;
	nonFullScreenY		= -1;
	mouseX				= 0;
	mouseY				= 0;

}

//------------------------------------------------------------
void ofAppGlutWindow::setupOpenGL(int w, int h, int screenMode){

	int argc = 1;
	char *argv = "openframeworks";
	char **vptr = &argv;
	glutInit(&argc, vptr);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_ALPHA );

	windowMode = screenMode;
	bNewScreenMode = true;

	if (windowMode != OF_GAME_MODE){
		glutInitWindowSize(w, h);
		glutCreateWindow("");

		//Default colors etc are now in ofGraphics - ofSetupGraphicDefaults
		ofSetupGraphicDefaults();

		/*
		ofBackground(200,200,200);		// default bg color
		ofSetColor(0xFFFFFF); 			// default draw color
		// used to be black, but
		// black + texture = black
		// so maybe grey bg
		// and "white" fg color
		// as default works the best...
		*/

		requestedWidth  = glutGet(GLUT_WINDOW_WIDTH);
		requestedHeight = glutGet(GLUT_WINDOW_HEIGHT);
	} else {
		glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_ALPHA );
    	// w x h, 32bit pixel depth, 60Hz refresh rate
		char gameStr[64];
		sprintf( gameStr, "%dx%d:%d@%d", w, h, 32, 60 );

    	glutGameModeString(gameStr);

    	if (!glutGameModeGet(GLUT_GAME_MODE_POSSIBLE)){
    		ofLog(OF_LOG_ERROR,"game mode error: selected format (%s) not available \n", gameStr);
    	}
    	// start fullscreen game mode
    	glutEnterGameMode();
	}
}

//------------------------------------------------------------
void ofAppGlutWindow::initializeWindow(){


	 //----------------------
	 // setup the callbacks

	 glutMouseFunc(mouse_cb);
	 glutMotionFunc(motion_cb);
	 glutPassiveMotionFunc(passive_motion_cb);
	 glutIdleFunc(idle_cb);
	 glutDisplayFunc(display);

	 glutKeyboardFunc(keyboard_cb);
	 glutKeyboardUpFunc(keyboard_up_cb);
	 glutSpecialFunc(special_key_cb);
	 glutSpecialUpFunc(special_key_up_cb);

	 glutReshapeFunc(resize_cb);

}

//------------------------------------------------------------
void ofAppGlutWindow::runAppViaInfiniteLoop(ofBaseApp * appPtr){
	static ofEventArgs voidEventArgs;

	ofAppPtr = appPtr;

	if(ofAppPtr){
		ofAppPtr->setup();
		ofAppPtr->update();
	}

	#ifdef OF_USING_POCO
		ofNotifyEvent( ofEvents.setup, voidEventArgs );
		ofNotifyEvent( ofEvents.update, voidEventArgs );
	#endif


	glutMainLoop();
}

//------------------------------------------------------------
void ofAppGlutWindow::exitApp(){

//  -- This already exists in ofExitCallback

//	static ofEventArgs voidEventArgs;
//
//	if(ofAppPtr)ofAppPtr->exit();
//
//	#ifdef OF_USING_POCO
//		ofNotifyEvent( ofEvents.exit, voidEventArgs );
//	#endif

	ofLog(OF_LOG_VERBOSE,"GLUT OF app is being terminated!");

	OF_EXIT_APP(0);
}

//------------------------------------------------------------
float ofAppGlutWindow::getFrameRate(){
	return frameRate;
}

//------------------------------------------------------------
int ofAppGlutWindow::getFrameNum(){
	return nFrameCount;
}

//------------------------------------------------------------
void ofAppGlutWindow::setWindowTitle(string title){
	glutSetWindowTitle(title.c_str());
}

//------------------------------------------------------------
ofPoint ofAppGlutWindow::getWindowSize(){
	int width = glutGet(GLUT_WINDOW_WIDTH);
	int height = glutGet(GLUT_WINDOW_HEIGHT);
	return ofPoint(width, height,0);
}

//------------------------------------------------------------
ofPoint ofAppGlutWindow::getWindowPosition(){
	int x = glutGet(GLUT_WINDOW_X);
	int y = glutGet(GLUT_WINDOW_Y);
	return ofPoint(x,y,0);
}

//------------------------------------------------------------
ofPoint ofAppGlutWindow::getScreenSize(){
	int width = glutGet(GLUT_SCREEN_WIDTH);
	int height = glutGet(GLUT_SCREEN_HEIGHT);
	return ofPoint(width, height,0);
}

//------------------------------------------------------------
void ofAppGlutWindow::setWindowPosition(int x, int y){
	glutPositionWindow(x,y);
}

//------------------------------------------------------------
void ofAppGlutWindow::setWindowShape(int w, int h){
	glutReshapeWindow(w, h);
	// this is useful, esp if we are in the first frame (setup):
	requestedWidth  = w;
	requestedHeight = h;
}

//------------------------------------------------------------
void ofAppGlutWindow::hideCursor(){
	glutSetCursor(GLUT_CURSOR_NONE);
}

//------------------------------------------------------------
void ofAppGlutWindow::showCursor(){
	glutSetCursor(GLUT_CURSOR_LEFT_ARROW);
}

//------------------------------------------------------------
void ofAppGlutWindow::setFrameRate(float targetRate){
	// given this FPS, what is the amount of millis per frame
	// that should elapse?

	// --- > f / s

	if (targetRate == 0){
		bFrameRateSet = false;
		return;
	}

	bFrameRateSet 			= true;
	float durationOfFrame 	= 1.0f / (float)targetRate;
	millisForFrame 			= (int)(1000.0f * durationOfFrame);

	frameRate				= targetRate;

}

//------------------------------------------------------------
int ofAppGlutWindow::getWindowMode(){
	return windowMode;
}

//------------------------------------------------------------
void ofAppGlutWindow::toggleFullscreen(){
	if( windowMode == OF_GAME_MODE)return;

	if( windowMode == OF_WINDOW ){
		windowMode = OF_FULLSCREEN;
	}else{
		windowMode = OF_WINDOW;
	}

	bNewScreenMode = true;
}

//------------------------------------------------------------
void ofAppGlutWindow::setFullscreen(bool fullscreen){
    if( windowMode == OF_GAME_MODE)return;

    if(fullscreen && windowMode != OF_FULLSCREEN){
        bNewScreenMode  = true;
        windowMode      = OF_FULLSCREEN;
    }else if(!fullscreen && windowMode != OF_WINDOW) {
        bNewScreenMode  = true;
        windowMode      = OF_WINDOW;
    }
}

//------------------------------------------------------------
void ofAppGlutWindow::enableSetupScreen(){
	bEnableSetupScreen = true;
}

//------------------------------------------------------------
void ofAppGlutWindow::disableSetupScreen(){
	bEnableSetupScreen = false;
}

//------------------------------------------------------------
void ofAppGlutWindow::display(void){
	static ofEventArgs voidEventArgs;

	//--------------------------------
	// when I had "glutFullScreen()"
	// in the initOpenGl, I was gettings a "heap" allocation error
	// when debugging via visual studio.  putting it here, changes that.
	// maybe it's voodoo, or I am getting rid of the problem
	// by removing something unrelated, but everything seems
	// to work if I put fullscreen on the first frame of display.

	if (windowMode != OF_GAME_MODE){
		if ( bNewScreenMode ){
			if( windowMode == OF_FULLSCREEN){

				//----------------------------------------------------
				// before we go fullscreen, take a snapshot of where we are:
				nonFullScreenX = glutGet(GLUT_WINDOW_X);
				nonFullScreenY = glutGet(GLUT_WINDOW_Y);
				//----------------------------------------------------

				glutFullScreen();

				#ifdef TARGET_OSX
					SetSystemUIMode(kUIModeAllHidden,NULL);
				#endif

			}else if( windowMode == OF_WINDOW ){

				glutReshapeWindow(requestedWidth, requestedHeight);

				//----------------------------------------------------
				// if we have recorded the screen posion, put it there
				// if not, better to let the system do it (and put it where it wants)
				if (nFrameCount > 0){
					glutPositionWindow(nonFullScreenX,nonFullScreenY);
				}
				//----------------------------------------------------

				#ifdef TARGET_OSX
					SetSystemUIMode(kUIModeNormal,NULL);
				#endif
			}
			bNewScreenMode = false;
		}
	}

	int width, height;

	width  = ofGetWidth();
	height = ofGetHeight();

	height = height > 0 ? height : 1;
	// set viewport, clear the screen
	glViewport( 0, 0, width, height );
	float * bgPtr = ofBgColorPtr();
	bool bClearAuto = ofbClearBg();

	// I don't know why, I need more than one frame at the start in fullscreen mode
	// also, in non-fullscreen mode, windows/intel graphics, this bClearAuto just fails.
	// I seem to have 2 buffers, alot of flickering
	// and don't accumulate the way I expect.
	// with this line:   if ((bClearAuto == true) || nFrameCount < 3){
	// we do nFrameCount < 3, so that the buffers are cleared at the start of the app
	// or else we have video memory garbage to draw on to...

	#ifdef TARGET_WIN32
		//windows doesn't get accumulation in window mode
		if ((bClearAuto == true || windowMode == OF_WINDOW) || nFrameCount < 3){
	#else
		//mac and linux does :)
		if ( bClearAuto == true || nFrameCount < 3){
	#endif
		glClearColor(bgPtr[0],bgPtr[1],bgPtr[2], bgPtr[3]);
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	}

	if( bEnableSetupScreen )ofSetupScreen();

	if(ofAppPtr)
		ofAppPtr->draw();

	#ifdef OF_USING_POCO
		ofNotifyEvent( ofEvents.draw, voidEventArgs );
	#endif

  	glutSwapBuffers();

  	// -------------- fps calculation:
	timeNow = ofGetElapsedTimef();
	if( (timeNow-timeThen) > 0.05f || nFramesForFPS == 0 ) {
 		fps = (double)nFramesForFPS / (timeNow-timeThen);
      	timeThen = timeNow;
		nFramesForFPS = 0;

		//hack for windows - was getting NAN - maybe unitialized vars???
		if( nFrameCount < 5) frameRate = fps;
		else frameRate = 0.9f * frameRate + 0.1f * fps;
  	}
  	nFramesForFPS++;
  	// --------------

	nFrameCount++;		// increase the overall frame count

	//setFrameNum(nFrameCount); // get this info to ofUtils for people to access

}

//------------------------------------------------------------
void ofAppGlutWindow::mouse_cb(int button, int state, int x, int y) {
	static ofMouseEventArgs mouseEventArgs;

	if (nFrameCount > 0){
		if(ofAppPtr){
		ofAppPtr->mouseX = x;
		ofAppPtr->mouseY = y;
		}

		if (state == GLUT_DOWN) {
			if(ofAppPtr)
				ofAppPtr->mousePressed(x,y,button);

			#ifdef OF_USING_POCO
				mouseEventArgs.x = x;
				mouseEventArgs.y = y;
				mouseEventArgs.button = button;
				ofNotifyEvent( ofEvents.mousePressed, mouseEventArgs );
			#endif
		} else if (state == GLUT_UP) {
			if(ofAppPtr){
				ofAppPtr->mouseReleased(x,y,button);
				ofAppPtr->mouseReleased();
			}

			#ifdef OF_USING_POCO
				mouseEventArgs.x = x;
				mouseEventArgs.y = y;
				mouseEventArgs.button = button;
				ofNotifyEvent( ofEvents.mouseReleased, mouseEventArgs );
			#endif
		}
		buttonInUse = button;
	}
}

//------------------------------------------------------------
void ofAppGlutWindow::motion_cb(int x, int y) {
	static ofMouseEventArgs mouseEventArgs;

	if (nFrameCount > 0){
		if(ofAppPtr){
		ofAppPtr->mouseX = x;
		ofAppPtr->mouseY = y;
			ofAppPtr->mouseDragged(x,y,buttonInUse);
		}

		#ifdef OF_USING_POCO
			mouseEventArgs.x = x;
			mouseEventArgs.y = y;
			mouseEventArgs.button = buttonInUse;
			ofNotifyEvent( ofEvents.mouseDragged, mouseEventArgs );
		#endif
	}

}

//------------------------------------------------------------
void ofAppGlutWindow::passive_motion_cb(int x, int y) {
	static ofMouseEventArgs mouseEventArgs;

	if (nFrameCount > 0){
		if(ofAppPtr){
		ofAppPtr->mouseX = x;
		ofAppPtr->mouseY = y;
			ofAppPtr->mouseMoved(x,y);
		}

		#ifdef OF_USING_POCO
			mouseEventArgs.x = x;
			mouseEventArgs.y = y;
			ofNotifyEvent( ofEvents.mouseMoved, mouseEventArgs );
		#endif
	}
}


//------------------------------------------------------------
void ofAppGlutWindow::idle_cb(void) {
	static ofEventArgs voidEventArgs;

	//	thanks to jorge for the fix:
	//	http://www.openframeworks.cc/forum/viewtopic.php?t=515&highlight=frame+rate

	if (nFrameCount != 0 && bFrameRateSet == true){
		diffMillis = ofGetElapsedTimeMillis() - prevMillis;
		if (diffMillis > millisForFrame){
			; // we do nothing, we are already slower than target frame
		} else {
			int waitMillis = millisForFrame - diffMillis;
			#ifdef TARGET_WIN32
				Sleep(waitMillis);         //windows sleep in milliseconds
			#else
				usleep(waitMillis * 1000);   //mac sleep in microseconds - cooler :)
			#endif
		}
	}
	prevMillis = ofGetElapsedTimeMillis(); // you have to measure here

	if(ofAppPtr)
		ofAppPtr->update();

		#ifdef OF_USING_POCO
		ofNotifyEvent( ofEvents.update, voidEventArgs);
		#endif

	glutPostRedisplay();
}


//------------------------------------------------------------
void ofAppGlutWindow::keyboard_cb(unsigned char key, int x, int y) {
	static ofKeyEventArgs keyEventArgs;

	if(ofAppPtr)
		ofAppPtr->keyPressed(key);

	#ifdef OF_USING_POCO
		keyEventArgs.key = key;
		ofNotifyEvent( ofEvents.keyPressed, keyEventArgs );
	#endif

	if (key == OF_KEY_ESC){				// "escape"
		exitApp();
	}
}

//------------------------------------------------------------
void ofAppGlutWindow::keyboard_up_cb(unsigned char key, int x, int y) {
	static ofKeyEventArgs keyEventArgs;

	if(ofAppPtr)
		ofAppPtr->keyReleased(key);

	#ifdef OF_USING_POCO
		keyEventArgs.key = key;
		ofNotifyEvent( ofEvents.keyReleased, keyEventArgs );
	#endif
}

//------------------------------------------------------
void ofAppGlutWindow::special_key_cb(int key, int x, int y) {
	static ofKeyEventArgs keyEventArgs;

	if(ofAppPtr)
		ofAppPtr->keyPressed(key | OF_KEY_MODIFIER);

	#ifdef OF_USING_POCO
		keyEventArgs.key = (key | OF_KEY_MODIFIER);
		ofNotifyEvent( ofEvents.keyPressed, keyEventArgs );
	#endif
}

//------------------------------------------------------------
void ofAppGlutWindow::special_key_up_cb(int key, int x, int y) {
	static ofKeyEventArgs keyEventArgs;

	if(ofAppPtr)
		ofAppPtr->keyReleased(key | OF_KEY_MODIFIER);

	#ifdef OF_USING_POCO
		keyEventArgs.key = (key | OF_KEY_MODIFIER);
		ofNotifyEvent( ofEvents.keyReleased, keyEventArgs );
	#endif
}

//------------------------------------------------------------
void ofAppGlutWindow::resize_cb(int w, int h) {
	static ofResizeEventArgs resizeEventArgs;

	if(ofAppPtr)
		ofAppPtr->windowResized(w,h);

	#ifdef OF_USING_POCO
		resizeEventArgs.width = w;
		resizeEventArgs.height = h;
		ofNotifyEvent( ofEvents.windowResized, resizeEventArgs );
	#endif
}