/* Universidade Federal Fluminense
 * Programa de Pós-Graduação em Computação
 *
 * Tópicos Avançados em Computação Visual e Interfaces II (Técnicas Avançadas de Rendering)
 * Professor: Leandro Augusto Frata Fernandes
 *
 * Version: 2012.05.16-001
 */

// Include the Standard Template Library (STL).
#include <cstdlib>
#include <iostream>

// Include the OpenGL Extension Wrangler Library (GLEW).
//TODO Include the GLEW's header file here.
#include <GL/glew.h>
// Include the Open-Source OpenGL Utility Toolkit (FreeGLUT).
#include <GL/freeglut.h>

// Include some custom definitions.
#include "../utils/basic.h"
#include "../utils/basic_io.h"
#include "../utils/camera.h"
#include "../utils/geometry.h"
#include "../utils/light.h"
#include "../utils/object.h"


// --- DRAWABLE OBJECTS DEFINITIONS ------------------------------------------------------------------------------------

// A drawable geometry class that uses deprecated stuff.
class BoringDrawableGeometry : public DrawableGeometry {
public:

	BoringDrawableGeometry(const Geometry* geometry) :
		DrawableGeometry(geometry) {
	}

public:

	virtual void initSpecial() {
	}
	
	virtual void draw() const {
		const std::vector<unsigned short>& indices = getIndices();
		const std::vector<VertexData>& vertices = getVertices();

		glPushAttrib(GL_CURRENT_BIT);
		glBegin(GL_TRIANGLES);
		
		for (size_t i=0; i<indices.size(); ++i) {
			const VertexData& current = vertices[indices[i]];

			glColor3f(0.5f * (current.normal.x + 1.0f), 0.5f * (current.normal.y + 1.0f), 0.5f * (current.normal.z + 1.0f));
			glTexCoord2fv(current.texCoord0.data());
			glNormal3fv(current.normal.data());
			glVertex4fv(current.vertex.data());
		}

		glEnd();
		glPopAttrib();
	}

	virtual void freeSpecial() {
	}
};

// A drawable geometry class that uses new stuff.
class CoolDrawableGeometry : public DrawableGeometry {
public:

	CoolDrawableGeometry(const Geometry* geometry) :
		DrawableGeometry(geometry) {
	}

public:

	virtual void initSpecial() {
		const std::vector<unsigned short>& indices = getIndices();
		const std::vector<VertexData>& vertices = getVertices();

		//TODO Make it work using Vertex Buffer Object.
		///*
		glGenBuffers(1, &m_vertexArrayVBO);
		glGenBuffers(1, &m_indexArrayVBO);

		glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW);

		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexArrayVBO);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices, GL_STATIC_DRAW);
		//*/
	}

	virtual void draw() const {
		//TODO Make it work using Vertex Buffer Object.
		///*
		glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

		//Set custom attributes
		glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayVBO);
		//Vertex
		glEnableVertexAttribArray(0);													//Offset
		glVertexAttribPointer(0, sizeof(coord4f), GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)0);
		//Normal
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(1, sizeof(coord3f), GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)(0 + sizeof(coord4f)));

		//Draw Elements
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexArrayVBO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (void*)0);

		glPopClientAttrib();
		//*/
	}

	virtual void freeSpecial() {
		//TODO Make it work using Vertex Buffer Object.
		glDeleteBuffers(1, &m_vertexArrayVBO);
		glDeleteBuffers(1, &m_indexArrayVBO);
	}

private: 
	GLuint m_vertexArrayVBO;
	GLuint m_indexArrayVBO;
};


// --- SOME USEFUL GLOBAL VARIABLES ------------------------------------------------------------------------------------

// The main camera.
PinholeCamera camera;

// The light source.
SpotLight light;

// Some geometry.
BoringDrawableGeometry drawableQuadGeometry = BoringDrawableGeometry(new QuadGeometry());           //TODO Replace the BoringDrawableGeometry class by the CoolDrawableGeometry class.
BoringDrawableGeometry drawableSphereGeometry = BoringDrawableGeometry(new SphereGeometry(90, 90)); //TODO Replace the BoringDrawableGeometry class by the CoolDrawableGeometry class.

DrawableObject ground = DrawableObject(&drawableQuadGeometry);
DrawableObject bronzeBall = DrawableObject(&drawableSphereGeometry);
DrawableObject chromeBall = DrawableObject(&drawableSphereGeometry);
DrawableObject goldenBall = DrawableObject(&drawableSphereGeometry);

// The objects related to the shader and programs.
//TODO Declare some handles for shader objects and program objects.

GLuint applyMapVertex;
GLuint applyMapFrag;
GLuint applyMapProgram;

GLuint computeMapVertex;
GLuint computeFrag;
GLuint computeProgram;

GLint modelViewProjectionMatrixLocation;
GLint modelViewMatrixLocation;
GLint modelLightViewMatrixLocation;


// The objects related to the custom render target.
//TODO Declare some handles for a framebuffer object, a color texture object, and a depth renderbuffer object.
GLuint framebuffer;
GLuint colortexture;

// --- SCENE RELATED FUNCTIONS -----------------------------------------------------------------------------------------

// Use this procedure for making the scene.
void initScene() {
	camera.setPosition(coord3f(0.0f, 8.0f, 30.0f), coord3f(0.0f, 0.0f, -1.0f), coord3f(0.0f, 1.0f, 0.0f));

	light.setPosition(coord3f(2.9739254f, 26.712812f, 17.259989f), coord3f(-0.094171725f, -0.84104335f, -0.53270084f), coord3f(-0.092878990f, 0.54016888f, -0.83641320f));
	light.setProjection(degreesToRadians(90.0f), 1.0f, 0.5f, 100.0f);

	ground.initSpecial();
	ground.setPosition(coord3f(0.0f, 0.0f, 0.0f), coord3f(0.0f, -1.0f, 0.0f), coord3f(0.0f, 0.0f, 1.0f));
	ground.scale(coord3f(100.0f, 100.0f, 1.0f));

	bronzeBall.initSpecial();
	bronzeBall.setPosition(coord3f(1.5f, 4.0f, 5.5f), coord3f(0.0f, 0.0f, 1.0f), coord3f(0.0f, 1.0f, 0.0f));
	bronzeBall.scale(coord3f(2.0f));

	chromeBall.initSpecial();
	chromeBall.setPosition(coord3f(-4.0f, 8.0f, 2.0f), coord3f(0.0f, 0.0f, 1.0f), coord3f(0.0f, 1.0f, 0.0f));
	chromeBall.scale(coord3f(5.0f));

	goldenBall.initSpecial();
	goldenBall.setPosition(coord3f(4.0f, 10.0f, 9.0f), coord3f(0.0f, 0.0f, 1.0f), coord3f(0.0f, 1.0f, 0.0f));
	goldenBall.scale(coord3f(4.0f));
}

// Use this procedure to draw the scene.
void drawScene() {
	//TODO Replace the fixed-function pipeline by the programable pipeline.
	/*
	// Setup projection.
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadMatrixf(camera.getProjectionMatrix().data());

	// Setup view space.
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadMatrixf(camera.getViewMatrix().data());

	// Setup world space and draw the ground.
	glPushMatrix();
	glMultMatrixf(ground.getModelMatrix().data());
	ground.draw();
	glPopMatrix();

	// Setup world space and draw the bronze ball.
	glPushMatrix();
	glMultMatrixf(bronzeBall.getModelMatrix().data());
	bronzeBall.draw();
	glPopMatrix();

	// Setup world space and draw the chrome ball.
	glPushMatrix();
	glMultMatrixf(chromeBall.getModelMatrix().data());
	chromeBall.draw();
	glPopMatrix();

	// Setup world space and draw the golden ball.
	glPushMatrix();
	glMultMatrixf(goldenBall.getModelMatrix().data());
	goldenBall.draw();
	glPopMatrix();

	glPopMatrix();
	glPopMatrix();
	*/
	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		const matrix4x4f& modelMatrix = ground.getModelMatrix(); 
		
		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);

		glUniform1i(computeProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());

		ground.draw();
		glPopAttrib();
	}
	
	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		const matrix4x4f& modelMatrix = bronzeBall.getModelMatrix();

		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);

		glUniform1i(computeProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());

		bronzeBall.draw();
		glPopAttrib();
	}

	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		const matrix4x4f& modelMatrix = chromeBall.getModelMatrix(); 

		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);

		glUniform1i(computeProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());

		chromeBall.draw();
		glPopAttrib();
	}

	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		const matrix4x4f& modelMatrix = goldenBall.getModelMatrix(); 

		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);

		glUniform1i(computeProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());

		goldenBall.draw();
		glPopAttrib();
	}
}
void drawScenePass2() {
	//TODO Replace the fixed-function pipeline by the programable pipeline.
	/*
	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		
		const matrix4x4f& modelMatrix = ground.getModelMatrix(); 
		
		//ModelViewProjectionMatrix
		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		//ModelViewMatrix
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);
		//ModelLightViewMatrix
		const matrix4x4f& modelLightViewMatrix = multiply(light.getViewMatrix(), modelMatrix);


		glUniform1i(applyMapProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());
		glUniformMatrix4fv(modelLightViewMatrixLocation, 1, GL_FALSE, modelLightViewMatrix.data());

		glPushAttrib(GL_TEXTURE_BIT);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, colortexture);

		ground.draw();
		glPopAttrib();
	}
	
	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		
		const matrix4x4f& modelMatrix = bronzeBall.getModelMatrix(); 
		
		//ModelViewProjectionMatrix
		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		//ModelViewMatrix
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);
		//ModelLightViewMatrix
		const matrix4x4f& modelLightViewMatrix = multiply(light.getViewMatrix(), modelMatrix);


		glUniform1i(applyMapProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());
		glUniformMatrix4fv(modelLightViewMatrixLocation, 1, GL_FALSE, modelLightViewMatrix.data());

		glPushAttrib(GL_TEXTURE_BIT);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, colortexture);

		bronzeBall.draw();
		glPopAttrib();
	}

	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		
		const matrix4x4f& modelMatrix = chromeBall.getModelMatrix(); 
		
		//ModelViewProjectionMatrix
		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		//ModelViewMatrix
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);
		//ModelLightViewMatrix
		const matrix4x4f& modelLightViewMatrix = multiply(light.getViewMatrix(), modelMatrix);


		glUniform1i(applyMapProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());
		glUniformMatrix4fv(modelLightViewMatrixLocation, 1, GL_FALSE, modelLightViewMatrix.data());

		glPushAttrib(GL_TEXTURE_BIT);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, colortexture);

		chromeBall.draw();
		glPopAttrib();
	}

	{
		const matrix4x4f& viewProjectionMatrix = multiply(camera.getProjectionMatrix(), camera.getViewMatrix());
		
		const matrix4x4f& modelMatrix = goldenBall.getModelMatrix(); 
		
		//ModelViewProjectionMatrix
		const matrix4x4f& modelViewProjectionMatrix = multiply(viewProjectionMatrix, modelMatrix);
		//ModelViewMatrix
		const matrix4x4f& modelViewMatrix = multiply(camera.getViewMatrix(), modelMatrix);
		//ModelLightViewMatrix
		const matrix4x4f& modelLightViewMatrix = multiply(light.getViewMatrix(), modelMatrix);


		glUniform1i(applyMapProgram, 0);
		glUniformMatrix4fv(modelViewProjectionMatrixLocation, 1, GL_FALSE, modelViewProjectionMatrix.data());
		glUniformMatrix4fv(modelViewMatrixLocation, 1, GL_FALSE, modelViewMatrix.data());
		glUniformMatrix4fv(modelLightViewMatrixLocation, 1, GL_FALSE, modelLightViewMatrix.data());

		glPushAttrib(GL_TEXTURE_BIT);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, colortexture);

		goldenBall.draw();
		glPopAttrib();
	}
	*/
}

// Use this procedure for cleaning up some scene data.
void freeScene() {
	ground.freeSpecial();
	bronzeBall.freeSpecial();
	chromeBall.freeSpecial();
	goldenBall.freeSpecial();
}

/*TODO Use this procedure.
// Query and display a shader info log.
void printShaderInfoLog(const char* description, GLuint shader) {
	const int LOG_LENGTH = 1024;
	
	char log[LOG_LENGTH] = {};
	int length = 0;
	glGetShaderInfoLog(shader, LOG_LENGTH, &length, log);

	std::clog << description << std::endl;
	std::clog << log << std::endl;
}
*/

/*TODO Use this procedure.
// Query and display a program info log.
void printProgramInfoLog(const char* description, GLuint program) {
	const int LOG_LENGTH = 1024;
	
	char log[LOG_LENGTH] = {};
	int length = 0;
	glGetProgramInfoLog(program, LOG_LENGTH, &length, log);

	std::clog << description << std::endl;
	std::clog << log << std::endl;
}
*/

// Use this procedure for loading, compiling and linking the shaders and program objects for shadow mapping.
void initShadowMappingPipeline() {
	//TODO Make it work.

	//
	//Compute_Map
	//
	///*
	char *vsSource = loadShaderFromFile("compute_map.vert");
	char *fsSource = loadShaderFromFile("compute_map.frag");

	//Create
	computeMapVertex = glCreateShader(GL_VERTEX_SHADER);
	computeFrag = glCreateShader(GL_FRAGMENT_SHADER);

	//Source
	glShaderSource(computeMapVertex, 1, (const char**)&vsSource, (int*)0);
	glShaderSource(computeFrag, 1, (const char**)&fsSource, (int*)0);

	//Compile
	glCompileShader(computeMapVertex);
	glCompileShader(computeFrag);

	//Program
	computeProgram = glCreateProgram();
	glAttachShader(computeProgram, computeMapVertex);
	glAttachShader(computeProgram, computeFrag);

	//Bind
	glBindAttribLocation(computeProgram, 0, "rm_Vertex");
	glBindAttribLocation(computeProgram, 1, "rm_Normal");

	//Link
	glLinkProgram(computeProgram);

	//Uniform Locations
	modelViewProjectionMatrixLocation = glGetUniformLocation(computeProgram, "ModelViewProjectionMatrix");
	modelViewMatrixLocation = glGetUniformLocation(computeProgram, "ModelViewMatrix");
	//

	//
	//Apply_Map
	//
	vsSource = loadShaderFromFile("apply_map.vert");
	fsSource = loadShaderFromFile("apply_map.frag");

	//Create
	applyMapVertex = glCreateShader(GL_VERTEX_SHADER);
	applyMapFrag = glCreateShader(GL_FRAGMENT_SHADER);

	//Source
	glShaderSource(applyMapVertex, 1, (const char**)&vsSource, (int*)0);
	glShaderSource(applyMapFrag, 1, (const char**)&fsSource, (int*)0);

	//Compile
	glCompileShader(applyMapVertex);
	glCompileShader(applyMapFrag);

	//Program
	applyMapProgram = glCreateProgram();
	glAttachShader(applyMapProgram, applyMapVertex);
	glAttachShader(applyMapProgram, applyMapFrag);

	//Bind
	glBindAttribLocation(applyMapProgram, 0, "rm_Vertex");
	glBindAttribLocation(applyMapProgram, 1, "rm_Normal");

	//Link
	glLinkProgram(applyMapProgram);

	//Uniform Locations
	modelViewProjectionMatrixLocation = glGetUniformLocation(applyMapProgram, "ModelViewProjectionMatrix");
	modelViewMatrixLocation = glGetUniformLocation(applyMapProgram, "ModelViewMatrix");
	modelLightViewMatrixLocation = glGetUniformLocation(applyMapProgram, "ModelLightViewMatrix");


	//Framebuffer

	GLsizei width = 512;
	GLsizei height = 512;


	// Create and initialize a depth renderbuffer object.
	glGenTextures(1, &colortexture);
	glBindTexture(GL_TEXTURE_2D, colortexture);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	// Create and initialize a framebuffer object.
	//TODO Make it work.
	glGenFramebuffers(1, &framebuffer);

	glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colortexture, 0);
	//*/

}

// Use this procedure for deleting shader and program objects for shadow mapping.
void freeShadowMappingPipeline() {
	//TODO Make it work.
	///*
	glDeleteProgram(applyMapProgram);
	glDeleteShader(applyMapVertex);
	glDeleteShader(applyMapFrag);

	glDeleteProgram(computeProgram);
	glDeleteShader(computeMapVertex);
	glDeleteShader(computeFrag);

	glDeleteFramebuffers(1, &framebuffer);
	glDeleteTextures(1, &colortexture);
	//*/
}

/*TODO Use this procedure.
// Use this procedure to show the target texture (this is not the best way to do that).
void drawTexture(GLuint texture) {
	GLubyte* pixels;
	GLfloat x, scale = 0.25f;
	GLint width, height, level;

	glPushAttrib(GL_ALL_ATTRIB_BITS);

	glDisable(GL_DEPTH_TEST);
	
	glViewport(0, 0, glutGet(GLUT_WINDOW_WIDTH), glutGet(GLUT_WINDOW_HEIGHT));

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0.0f, glutGet(GLUT_WINDOW_WIDTH), 0.0f, glutGet(GLUT_WINDOW_HEIGHT));

	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glBindTexture(GL_TEXTURE_2D, texture);
	
	glPixelZoom(scale, scale);

	glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, &level);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
	glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height);

	x = 0.0f;
	pixels = new GLubyte[width * height * 3];

	while ((width > 0) && (height > 0)) {
		glGetTexImage(GL_TEXTURE_2D, level, GL_BGR, GL_UNSIGNED_BYTE, pixels);
	
		glRasterPos2f(x, 0.0f);
		glDrawPixels(width, height, GL_BGR, GL_UNSIGNED_BYTE, pixels);

		glColor3f(1.0f, 1.0f, 1.0f);
		glBegin(GL_LINE_LOOP);
		glVertex2f(x + (width * scale), height * scale);
		glVertex2f(x, height * scale);
		glVertex2f(x, 0.0f);
		glVertex2f(x + (width * scale), 0.0f);
		glEnd();

		level += 1;
		x += (scale * width);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
		glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height);
	}

	delete [] pixels;

	glPopMatrix();
	glPopMatrix();
	glPopAttrib();
}
*/


// --- CUSTOM CALLBACK FUNCTIONS ---------------------------------------------------------------------------------------

// The callback function for handling the display of the current window.
void myDiplayCallback() {
	
	//TODO Replace the fixed-function pipeline by the programable pipeline.
	///*
	// Clear the back buffer.
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	//pass 1
	{
		glUseProgram(computeProgram);
		//bind 1
		glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

		/*
		GLint width, height;

		// Setup projection.
		glMatrixMode(GL_PROJECTION);
		glLoadMatrixf(camera.getProjectionMatrix().data());

		// Setup view space.
		glMatrixMode(GL_MODELVIEW);
		glLoadMatrixf(camera.getViewMatrix().data());

		// Update viewport.
		width = glutGet(GLUT_WINDOW_WIDTH);
		height = glutGet(GLUT_WINDOW_HEIGHT);
		glViewport(0, 0, width, height);


		// Clear the back buffer.
		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		*/
		drawScene();
	}
	//pass 2
	{
		glUseProgram(applyMapProgram);
		//bind 0
		glBindFramebuffer(GL_FRAMEBUFFER, 0);

		//drawScenePass2();
		drawScene();
	}
	//*/
	/*
	// Clear the back buffer.
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Draw the scene.
	drawScene();

	// Display the rendered texture.
	//TODO Call drawTexture(...);
	*/
	// Swap buffers.
	glutSwapBuffers();

}

// The callback function for handling traditional ASCII keyboard input for the current window.
void myKeyboardCallback(unsigned char key, int x, int y) {
	const GLfloat TRANSLATION_STEP = 0.5f;
	const GLfloat ROTATION_STEP_IN_RADIANS = degreesToRadians(1.0f);

	switch (key) {
		// A or a: Step left.
		case 'A':
		case 'a': camera.translateInViewSpace(coord3f(-TRANSLATION_STEP, 0.0f, 0.0f)); break;

		// D or d: Step right.
		case 'D':
		case 'd': camera.translateInViewSpace(coord3f(TRANSLATION_STEP, 0.0f, 0.0f)); break;

		// W or w: Step forward.
		case 'W':
		case 'w': camera.translateInViewSpace(coord3f(0.0f, 0.0f, -TRANSLATION_STEP)); break;

		// S or s: Step backward.
		case 'S':
		case 's': camera.translateInViewSpace(coord3f(0.0f, 0.0f, TRANSLATION_STEP)); break;

		// Q or q: Rotate left.
		case 'Q':
		case 'q': camera.rotateInViewSpace(coord3f(0.0f, 0.0f, -1.0f), -ROTATION_STEP_IN_RADIANS); break;
			
		// E or e: Rotate right.
		case 'E':
		case 'e': camera.rotateInViewSpace(coord3f(0.0f, 0.0f, -1.0f), ROTATION_STEP_IN_RADIANS); break;

		// ESC: Reset camera position.
		case 27: camera.setPosition(coord3f(0.0f, 8.0f, 30.0f), coord3f(0.0f, 0.0f, -1.0f), coord3f(0.0f, 1.0f, 0.0f)); break;
	};

	glutPostRedisplay();
}

// The callback function for handling mouse click in the current window.
void myMouseButtonCallback(int button, int state, int x, int y) {
	if (state ==  GLUT_DOWN) {
		glutSetCursor(GLUT_CURSOR_NONE);
		glutWarpPointer(glutGet(GLUT_WINDOW_WIDTH) / 2, glutGet(GLUT_WINDOW_HEIGHT) / 2);
	}
	else if (state ==  GLUT_UP) {
		glutSetCursor(GLUT_CURSOR_INHERIT);
	}
}

// The callback function for handling active mouse motion in the current window.
void myMouseMotionCallback(int x, int y) {
	int xCenter = glutGet(GLUT_WINDOW_WIDTH) / 2;
	int yCenter = glutGet(GLUT_WINDOW_HEIGHT) / 2;

	if ((x != xCenter) || (y != yCenter)) {
		const GLfloat ROTATION_STEP = 0.005f;

		coord3f oldZ(0.0f, 0.0f, 1.0f);
		coord3f& newZ = normalize(coord3f(-ROTATION_STEP * (x - xCenter), ROTATION_STEP * (y - yCenter), 1.0f));

		camera.rotateInViewSpace(cross(oldZ, newZ), acos(dot(oldZ, newZ)));

		glutWarpPointer(xCenter, yCenter);
		glutPostRedisplay();
	}
}

// The callback functor that is triggered when the current window is reshaped.
void myWindowReshapeCallback(int width, int height) {
	// Update viewport.
	glViewport(0, 0, width, height);

	// Update projection.
	camera.setProjection(degreesToRadians(60.0f), static_cast<GLfloat>(width) / height, 0.5f, 200.0f);
}


// --- THE MAIN PROGRAM ------------------------------------------------------------------------------------------------

// The main program.
int main(int argc, char* argv[]) {
	// Initialize FreeGLUT.
	glutInit(&argc, argv);

	// Initialize the properties of windows that will be create in the future.
	const int INIT_WINDOW_POSITION_X = 10;
	const int INIT_WINDOW_POSITION_Y = 10;
	const int INIT_WINDOW_WIDTH = 512;
	const int INIT_WINDOW_HEIGHT = 512;

	glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); // Double buffering, Depth buffer, RGBA color buffer
	glutInitWindowPosition(INIT_WINDOW_POSITION_X, INIT_WINDOW_POSITION_Y);
	glutInitWindowSize(INIT_WINDOW_WIDTH, INIT_WINDOW_HEIGHT);

	// Create one window and set its callback functions.
	glutCreateWindow("GLSL Project: Lecture 4");

	glutDisplayFunc(myDiplayCallback);
	glutKeyboardFunc(myKeyboardCallback);
	glutMotionFunc(myMouseMotionCallback);
	glutMouseFunc(myMouseButtonCallback);
	glutReshapeFunc(myWindowReshapeCallback);

	// Initialize GLEW.
	//TODO Initialize the GLEW library here.
	glewInit();
	// Set some OpenGL states.
	glEnable(GL_DEPTH_TEST); // Depth test is on.
	glEnable(GL_CULL_FACE);  // Back-face culling is on.

	// Setup scene.
	initScene();

	// Setup shadow mapping.
	initShadowMappingPipeline();

	// Start the main loop.
	glutMainLoop();

	// Free shadow mapping data.
	freeShadowMappingPipeline();

	// Free scene data.
	freeScene();

	// Job done!
	return EXIT_SUCCESS;
}
