#define MAIN_CPP
#include <NxPhysics.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stddef.h>
#include <sys/types.h>
#include <windows.h>
#include "../headers/PhysXWrapper.h"
#include "../headers/oglheaders.h"
#include "../headers/Settings.h"
#include "../headers/GeSortedBranch.h"
#include "../headers/GeActor.h"
#include "../headers/GeWindow.h"
#include "../headers/GeLight.h"
#include "../headers/GeCamera.h"
#include "../headers/GeHud.h"
#include "../headers/GeTransform.h"
#include "../headers/Input.h"
#include "../headers/main.h"
#include "../headers/Character.h"

#define ITEM_COUNT 6

/*
 * fbx object naming code:
 * TC...##
 * T = Object Type
 *		C = Character, I = Item, W = Wall, S = Shadow Casting Wall, T = Trigger (action name in the actionmap begins on character 2)
 * C = Collision Type
 *		B = Bounding Box, S = Sphere, M = Mesh, C = Convex Mesh, H = Hierarchy(not valid for Trigger), N = None
 * ## = id
 *
 * Convex Mesh:
 * physx is able to do better optimizations if it knows that a mesh is convex, also the triangle mesh has some problems.
 *
 * Hierarchy:
 * only get shapes from the hierarchy
 *
 *
 *
 * Subnodes:
 * TCAA...
 * T = Subnode Type (M = Mesh, S = Collision Shape only)
 * C = Collision Type (N = None, B, S, M, C, H as above)
 * AA = Animation Type (NA = No Animation, RX = Rotation along the X-Axis, RY and RZ like RX)
 * SN... will result in the node being ignored
 *
 *
 * Marker:
 * MC...
 * M = Marker
 * C = Character Position
 *
 * MLBSM...##
 * M = Marker
 * L = Mesh Loader (loaded mesh depends on the object name beginning at character 5)
 * B = Blendable (B = Blendable, N = Not Blendable)
 * S = Static (S = Static, D = Dynamic)
 * M = Mass ( A = low...Z = high, any other character = no collision ) 
 * ## = id
 */

/*
 * @TODO:
 *  timo: shadow volumes
 *  georg: mesh map, trigger
 *  
 *  open, ordered by importance:
 *  steer angle collision
 *  object profiles (store density etc. for each object profile in
 *    configuration and add a letter for each profile to the naming
 *    code at the 3rd digit), maybe store model path => use this to
 *    prevent, that a model is loaded multiple times.
 *    maybe use radius of the dummy-object as scaling factor of
 *    the instance)
 *  get text width and height to be able to create an horzontal and vertical alignment
 *  different fonts or bitmap fonts
 *  multitexturing, normal mapping
 *  sound
 */

bool running = true;
GeBranch rootNode;
GeHud hud;
GeHudText *hudTime, *hudItemCount;
GeWindow* window;
//GeBranch objects;
GeBranch* level;
LARGE_INTEGER fpsFrequency;
LARGE_INTEGER fpsTimeStart, fpsTimeStop, updateStartTime, timerCur, timerStart;
int fps, frameCounter;
char fpsText[12];
char timerText[100];
char itemCountText[100];
bool timerRun;
int finishedItems;

double stopTimer(void) {
	timerRun = false;
	QueryPerformanceCounter(&timerCur);
	double time = (double)(timerCur.QuadPart-timerStart.QuadPart)/(double)fpsFrequency.QuadPart;
	sprintf(timerText, "%02d:%02d.%02d", (int)(time/60), ((int)time)%60, ((int)(time*100))%100);
	return time;
}

void restartTimer(void) {
	QueryPerformanceCounter(&timerStart);
	timerRun = true;
}

int updateFpsRate() {
	frameCounter++;
    QueryPerformanceCounter(&fpsTimeStop);

	double diff = (double)(fpsTimeStop.QuadPart-fpsTimeStart.QuadPart);
	if(diff/(double)fpsFrequency.QuadPart > 0.25) {
		fps = (int)(fpsFrequency.QuadPart * frameCounter / diff + 0.5);
		QueryPerformanceCounter(&fpsTimeStart);
		frameCounter = 0;
	}
	return fps;
}

void render (void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	if(settings.flags & FLAG_VOLUME_SHADOWS) {
		// ambient pass
		GeLight::setGlobalAmbient(0.05f,0.05f,0.05f);
		branchShader->setLight(-2);
		
		cam->render();
		GeLight::setGlobalAmbient(0.0f, 0.0f, 0.0f);
		// for each light L:
		std::list<GeLight*>::iterator iter = lights.begin();
		for(;iter != lights.end(); iter++)
		{
			// TODO: Optimization: test if light region intersects the view frustum
			// INFO dazu: view frustum ist ein physx trigger, dh man braucht die licht
			// sphere nur als actor hinzufuegen und in cam::trigger behandeln (shape->userData->setActive(false);)
			//  wichtig: userdata auf das lichtobjekt setzen, actorFlags entsprechend setzen (No Collision Response),
			//  evtl in physxwrapper::init die trigger einstellungen anpassen.
 			if(!(*iter)->isActive())
				continue;

			branchShader->setLight((*iter)->getIndex());

			// determine shadows
			glPushAttrib(GL_ALL_ATTRIB_BITS);
			glClear(GL_STENCIL_BUFFER_BIT);
			glEnable(GL_STENCIL_TEST);          // write to stencil buffer
			glStencilMask(0xff);                
			glStencilFunc(GL_ALWAYS, 0, 0xff);  // always pass stencil test
			glDepthMask(0);                     // do not write to z-buffer
			glEnable(GL_CULL_FACE);             // enable face culling
			glColorMask(0, 0, 0, 0);            // do not write to frame buffer
			glDepthFunc(GL_LESS);

			cam->shadowPass(*iter);
			glPopAttrib();
			
			// illuminate
			glPushAttrib(GL_ALL_ATTRIB_BITS);
			glEnable(GL_STENCIL_TEST);          // read from stencil buffer
			glStencilMask(0);                   // do not write to stencil buffer
			glStencilFunc(GL_EQUAL, 0, 0xff);   // set stencil test function
			glDepthMask(0);                     // do not write to z-buffer
			glEnable(GL_CULL_FACE);             // enable face culling
			glCullFace(GL_BACK);
			glColorMask(1,1,1,1);
			glEnable(GL_BLEND);         // add light contribution to frame buffer
			glBlendFunc(GL_ONE, GL_ONE);
			glDepthFunc(GL_LEQUAL);
			
			(*iter)->show();
			(*iter)->render();
			cam->render();
			(*iter)->hide();
			glPopAttrib();
		}
		// final pass
		branchShader->setLight(-1);

		iter = lights.begin();
		for(;iter != lights.end(); iter++)
		{	
			if((*iter)->isActive()) {
				(*iter)->show();
				(*iter)->render();
			}
		}	

		glPushMatrix();
		cam->renderSingle();
		branchShader->renderSingle();
		GeSortedBranch::instance.render();
		branchShader->postRender();
		glPopMatrix();

		
		iter = lights.begin();
		for(;iter != lights.end(); iter++)
		{	
			if((*iter)->isActive()) {
				(*iter)->hide();
			}
		}	

		hud.render();
	} else {
		glPushMatrix();
		cam->renderSingle();

		branchShader->setLight(-1);
		std::list<GeLight*>::iterator iter = lights.begin();
		for(;iter != lights.end(); iter++)
		{	
			if((*iter)->isActive()) {
				(*iter)->show();
				(*iter)->render();
			}
		}			
		//cam->render();

		branchShader->render();

		branchShader->renderSingle();
		GeSortedBranch::instance.render();
		branchShader->postRender();
		glPopMatrix();

		hud.render();
	}

	glfwSwapBuffers();
}

void update_main(double elapsedTime) {
	// Reihenfolge von Physx empfohlen: 
	// 1. GetPhysicsResults();  2. ProcessInputs();  3. StartPhysics();

	physx.fetch();
	GeSortedBranch::flush();
	
	if (timerRun) {
		QueryPerformanceCounter(&timerCur);
		double time = (double)(timerCur.QuadPart-timerStart.QuadPart)/(double)fpsFrequency.QuadPart;
		sprintf(timerText, "%02d:%02d.%02d", (int)(time/60), ((int)time)%60, ((int)(time*100))%100);
	}

	rootNode.update();
	physx.advance();
}

bool triggerActive[100];

void itemtarget(NxActor* actor, Trigger* trigger, NxTriggerFlag flags) {
	GeActor* obj = (GeActor*)(actor->userData);
	//printf("bla: %s - %s\n", obj->id.c_str(), trigger->id.c_str());
	int triggerId = ((trigger->id[0]-'0')*10+(trigger->id[1]-'0'));
	if (triggerActive[triggerId]) {
		if (obj->id[0] == trigger->id[0]) {
			if (flags & NX_TRIGGER_ON_ENTER) {
				finishedItems++;
				triggerActive[triggerId] = false;
				if (finishedItems >= ITEM_COUNT)
					stopTimer();
					sprintf(itemCountText, "YOU WIN!!! CONGRATULATIONS!");
			}
			else {
				finishedItems--;
				triggerActive[triggerId] = true;
			}
			if (finishedItems < ITEM_COUNT)
				sprintf(itemCountText, "Items: %d/%d", finishedItems, ITEM_COUNT);
		}
	}
}

int main (int argc, char** argv) {
	argc = 1;
	argv[0] = "Rota";
	finishedItems = 0;
	
	for (int i=0; i < 99; i++) {
		triggerActive[i] = true;
	}


	actionMap["itemTarget"] = &itemtarget;

	glfwInit();

	settings.loadDefaultSettings();
	//settings.saveSettings();
	settings.loadSettings();
	
	QueryPerformanceFrequency(&fpsFrequency);

	window = new GeWindow(&settings);
	meshMan = new MeshManager("models/");

	/* setup glew stuff */
	GLenum err = glewInit();
	if (GLEW_OK != err) {
		fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
	}
	else if(!glewGetExtension("GL_ARB_vertex_buffer_object")) {
		fprintf(stderr, "Error: Extention GL_ARB_vertex_buffer_object not supported.\n");
	}
	else if (physx.init() && fbx.init()) {

		/* setup opengl state */
		glShadeModel(GL_SMOOTH);
		
		if(settings.flags & FLAG_WIREFRAME) {
			glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		} 
		glEnable(GL_LIGHTING);
		glEnable(GL_TEXTURE_2D);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		
		//glEnable(GL_NORMALIZE);
		glEnable(GL_DEPTH_TEST);
		glDepthFunc(GL_LEQUAL);
		glFrontFace(GL_CCW);
		glEnable(GL_CULL_FACE);
		//glEnable(GL_COLOR_MATERIAL);
		
		if ((settings.flags & FLAG_TRANSPARENCY) != 0) {
			glEnable(GL_BLEND);
		}
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		
		/* setup scene graph */
		branchShader = new GeBranchShader("shader/phong_basic.vert", "shader/phong_basic.frag");
		
		GeLight::setGlobalAmbient(0.05f,0.05f,0.05f);
	    
		cam = new GeCamera(35.0f, settings.viewRatio, 6.0f, 600.0f);
		
		rootNode.push_back(&hud);
		rootNode.push_back(cam);

		//cam->push_back(&lights);
		cam->push_back(branchShader);

		branchShader->push_back(&objects);

		GeActor* tmp = meshMan->getActor("tire"); //fbx.importMesh("models/tire.FBX");
		character = new Character(tmp);
		delete(tmp);
		objects.push_back(character);

		//level = fbx.importLevel("levels/test_level.FBX");
		level = fbx.importLevel("levels/level1.FBX");
		objects.push_back(level);
/*
		tmp = meshMan->getActor("item_fan"); //fbx.importMesh("models/item_fan.FBX");
		tmp->actor->setGlobalPosition(character->actor->getGlobalPosition()+NxVec3(-2.0f, 0.0f, 0.3f));
		objects.push_back(tmp);

		tmp = meshMan->getActor("item_fan"); //fbx.importMesh("models/item_fan.FBX");
		tmp->actor->setGlobalPosition(character->actor->getGlobalPosition()+NxVec3(0.0f, 2.0f, 0.3f));
		objects.push_back(tmp);
*/
		gDebugRenderer.visible = false;
		gDebugRenderer.update();
		cam->push_back(&gDebugRenderer);	

		Input::init(cam);
		
		character->actor->wakeUp();

		// init hud
		sprintf(fpsText, "Fps: -      ");
		fps = frameCounter = 0;
		hudFps = new GeHudText(0.02f, 0.83f, fpsText, (settings.flags & FLAG_SHOW_FPS) != 0);
		sprintf(timerText, "00:00.00    ");
		hudTime = new GeHudText(0.02f, 0.97f, timerText, true);
		sprintf(itemCountText, "Items: 0/%d   ", ITEM_COUNT);
		hudItemCount = new GeHudText(0.02f, 0.90f, itemCountText, true);
		hudInfoText = new GeHudText(0.02f, 0.02f, "", false);
		hud.push_back(hudInfoText);
		hud.push_back(hudFps);
		hud.push_back(hudTime);
		hud.push_back(hudItemCount);

		/* run */
		physx.advance();

		restartTimer();
		QueryPerformanceCounter(&fpsTimeStart);
		QueryPerformanceCounter(&updateStartTime);
		double offset = 0;

		while(running) {
			render();
			sprintf(fpsText, "Fps: %d", updateFpsRate());
			
			//double diff = offset + (double)(fpsTimeStop.QuadPart-updateStartTime.QuadPart)/(double)fpsFrequency.QuadPart;
			double diff = offset + (double)(fpsTimeStop.QuadPart-updateStartTime.QuadPart)/(double)fpsFrequency.QuadPart;
			if(diff > UPDATE_INTERVAL) {
				update_main(diff);
				QueryPerformanceCounter(&updateStartTime);
				//offset = diff - UPDATE_INTERVAL;
			}
		}

		/* cleanup */
		Input::release();
		GeTexture::freeTextures();
		delete(hudInfoText);
		delete(hudFps);
		delete(cam);
		delete(meshMan);
		fbx.release();
	}
	physx.release();
	delete(window);

	glfwTerminate();
	return 0;
}

void closeGame() {
	running = false;
}
