#include "SharedCode.h"

#include <windows.h>
#include <stdlib.h>
#include <io.h>
#include <iostream>
#include <stdio.h>
#include <fstream>
#include <string.h>
#include <glew.h>
#include <math.h>
#include <GL/glut.h>

#include <conio.h>

using namespace std;

/**
 * This are the includes needed to use the library
 */
#include "Shader.h"
#include "AnimationShader.h"
#include "Skeleton.h"
#include "Mesh.h"
#include "AnimationCooker.h"
#include "BoneCooker.h"

/**
 * These three are the essential objects needed to use the library.
 * The Shader and AnimationShader perform the animation, the BoneCooker
 * contains the assignment of vertices to bones annd the AnimationCooker
 * contains the bone transforms for all timesteps.
 */
OSkA::Shader** shaders;
OSkA::AnimationShader* animationShader;
OSkA::AnimationCooker** animations;
OSkA::BoneCooker** boneAssignements;

/**
 * These variables will be used to store the geometric data of our model.
 * The variable names are self explaining.
 */
float** vertices;
float** normals;
int** indices;
int numVertices[2], numTriangles[2];

void glutDisplay()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glLoadIdentity();

	// render fps and help text
	renderFPS();
	renderHelpText();
	// set actual camera position
	setCamera();

	// enable lighting
	glEnable(GL_LIGHTING);
	// set the actual light position
	setLightPos();

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glColor3f(0, 0, 1);

	int numNinjas = 11;

	animationShader->addGlobalTime(getTimeSinceLastFrame());

	// we do not render the standard ninjas, hence beginning at 1
	for (int shader=1; shader<3; shader++)
	{
		animationShader->setActualShader(shaders[shader], false);
		animationShader->enable();

		animationShader->setBoneAssignements(boneAssignements[0]);

		animationShader->setAnimationUniform(animations[0], 0);
		glVertexPointer(3, GL_FLOAT, 0, vertices[0]);
		glNormalPointer(GL_FLOAT, 0, normals[0]);
		for(int i=0; i<numNinjas; i++)
		{
			glPushMatrix();
				glTranslatef((i-(numNinjas-1)/2)*10, 0, (shader-1)*30-10);
				glDrawElements(GL_TRIANGLES, 3*numTriangles[0], GL_UNSIGNED_INT, indices[0]);
			glPopMatrix();
		}

		animationShader->setAnimationUniform(animations[0], 1);
		for(int i=0; i<numNinjas; i++)
		{
			glPushMatrix();
				glTranslatef((i-(numNinjas-1)/2)*10, 0, (shader-1)*30);
				glDrawElements(GL_TRIANGLES, 3*numTriangles[0], GL_UNSIGNED_INT, indices[0]);
			glPopMatrix();
		}

		animationShader->setBoneAssignements(boneAssignements[1]);

		animationShader->setAnimationUniform(animations[1], 0);
		glVertexPointer(3, GL_FLOAT, 0, vertices[1]);
		glNormalPointer(GL_FLOAT, 0, normals[1]);
		for(int i=0; i<numNinjas; i++)
		{
			glPushMatrix();
				glTranslatef((i-(numNinjas-1)/2)*10, 0, (shader-1)*30+10);
				glDrawElements(GL_TRIANGLES, 3*numTriangles[1], GL_UNSIGNED_INT, indices[1]);
			glPopMatrix();
		}
	}
	animationShader->disable();

	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);

	// Rendering a ground plane our ninja is standing on
	glPushMatrix();
		glColor3f(0.94, 0.90, 0.60);
		glBegin(GL_QUADS);
			glNormal3f(0, 1, 0);	glVertex3f(55, 0, -45);
			glNormal3f(0, 1, 0);	glVertex3f(-55, 0, -45);
			glNormal3f(0, 1, 0);	glVertex3f(-55, 0, 45);
			glNormal3f(0, 1, 0);	glVertex3f(55, 0, 45);
		glEnd();
	glPopMatrix();

	// disable lighting
	glDisable(GL_LIGHTING);

	glutSwapBuffers();
}

void glutIdle()
{
	newFrame();
	glutDisplay();
}

void keyboardNormal(unsigned char key, int x, int y)
{
	switch(key)
	{
		case 27: exit(0); break; // Exit on ESC
	}
}

void mouseMovePassive(int x, int y)
{
	static int oldX=x, oldY=y;

	rotateCamera(y-oldY, x-oldX);

	int centerX = glutGet( GLUT_WINDOW_WIDTH )/2;
	int centerY = glutGet( GLUT_WINDOW_HEIGHT )/2;
	if( abs(centerX-x) > 250 || abs(centerY-y) > 250 )
	{
		glutWarpPointer(centerX, centerY);
		oldX = centerX;
		oldY = centerY;
	}
	else
	{
		oldX = x;
		oldY = y;
	}
}


bool FileExists(const char* fileName)
{
	DWORD attributes = ::GetFileAttributesA(fileName);
	const bool success = ((attributes != INVALID_FILE_ATTRIBUTES) && (attributes != FILE_ATTRIBUTE_DIRECTORY));
	if (!success)
	{
		cerr << "File not found: '" << fileName << "'" << endl;
		cerr << "Please make sure that the application is started in the correct working directory.\n";
		cerr << "There should be a 'Data' folder containing shaders, skeleton and mesh data." << endl << endl;
		cerr << "Press any key to abort program." << endl;
		_getch();
	}
	return success;
}


int main(int argc, char* argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_ALPHA );
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(windowWidth, windowHeight);
	glutCreateWindow("Animation");

	GLenum err = glewInit();
	if (err != GLEW_OK)
	{
		cerr << "Error initializing GLEW: " << glewGetErrorString(err) << endl;
		return EXIT_FAILURE;
	}

	cout << "Initiating Shaders ..." << endl;

	if (!FileExists("Data/shader_uniform.vert"))
		exit(EXIT_FAILURE);

	if (!FileExists("Data/shader.frag"))
		exit(EXIT_FAILURE);

	shaders = new OSkA::Shader*[3];
	shaders[0] = new OSkA::Shader();
	shaders[1] = new OSkA::Shader();
	shaders[2] = new OSkA::Shader();

	shaders[0]->initDefaultShaderUniform();
	shaders[1]->initNewShaderFromFile(
		"Data/shader_uniform.vert",
		"Data/shader.frag");
	shaders[2]->initNewShader(
		loadTextFile("Data/shader_uniform.vert"),
		loadTextFile("Data/shader.frag"));

	animationShader = new OSkA::AnimationShader () ;

	cout << "Loading & Cooking Skeleton Data ..." << endl;

	if (!FileExists("Data/ninja.skeleton.xml"))
		exit(EXIT_FAILURE);

	if (!FileExists("Data/sword_ninja.skeleton.xml"))
		exit(EXIT_FAILURE);

	animations = new OSkA::AnimationCooker*[2];
	animations[0] = new OSkA::AnimationCooker();
	animations[1] = new OSkA::AnimationCooker();

	OSkA::Skeleton* skeleton = new OSkA::Skeleton();
	skeleton->loadFile("Data/ninja.skeleton.xml");
		animations[0]->cook(skeleton, 50, false);
		animations[0]->setRepeatAfterEnd(true);
		animations[0]->setRepeatBeforeBegin(true);
		animations[0]->setTimeOffset(0);
		animations[0]->setTimeScale(3);
	skeleton->loadFile("Data/sword_ninja.skeleton.xml");
		animations[1]->cook(skeleton, 50, false);
		animations[1]->setRepeatAfterEnd(true);
		animations[1]->setRepeatBeforeBegin(true);
		animations[1]->setTimeOffset(0);
		animations[1]->setTimeScale(3);
	delete skeleton;

	cout << "Loading & Cooking Mesh Data ... " << endl;

	if (!FileExists("Data/ninja.mesh.xml"))
		exit(EXIT_FAILURE);

	if (!FileExists("Data/sword_ninja.mesh.xml"))
		exit(EXIT_FAILURE);

	boneAssignements = new OSkA::BoneCooker*[2];
	boneAssignements[0] = new OSkA::BoneCooker();
	boneAssignements[1] = new OSkA::BoneCooker();
	vertices = new float*[2];
	normals = new float*[2];
	indices = new int*[2];

	OSkA::Mesh* mesh = new OSkA::Mesh();
	mesh->loadFile("Data/ninja.mesh.xml");
		normals[0]		= mesh->getNormalArray();
		vertices[0]		= mesh->getVertexArray();
		indices[0]		= mesh->getTriangleArray();
		numTriangles[0]	= mesh->getTrianglesCount();
		numVertices[0]	= mesh->getVerticesCount();
		boneAssignements[0]->cook(mesh);
	mesh->loadFile("Data/sword_ninja.mesh.xml");
		normals[1]		= mesh->getNormalArray();
		vertices[1]		= mesh->getVertexArray();
		indices[1]		= mesh->getTriangleArray();
		numTriangles[1]	= mesh->getTrianglesCount();
		numVertices[1]	= mesh->getVerticesCount();
		boneAssignements[1]->cook(mesh);
	delete mesh;

	init(argc, argv);
	glutDisplayFunc (glutDisplay);
	glutIdleFunc (glutIdle);
	glutKeyboardFunc (keyboardNormal);
	glutPassiveMotionFunc (mouseMovePassive);
	glutMainLoop();
}
