/**
 * A very simple basic modern OpenGL application.
 * There is no error handling code in here. We just assume that we have
 * OpenGL and everything works.
 */

// Settings
#define WINDOW_TITLE "Infinite patpat works"
#define WINDOW_WIDTH 1366
#define WINDOW_HEIGHT 768
#define FULLSCREEN

// Include files with neat things.
#include "glhelpers.h"
#include "Vector.h"

// Globally shared things. Not exactly good style. Don't do it if what you are
// writing is actually important.

// Shaders
GLuint vertexBuffer;
GLuint elementBuffer[4];
GLuint shaderProgram;

// These are variables passed into shaders
GLuint vertexPosition;
GLuint vertexTexcoords;
GLuint vertexNormal;

GLuint modelviewMatrix;
GLuint normalviewMatrix;
GLuint projectionMatrix;

// Textures
GLuint defaultTextureData;
GLuint shobonTextureData;
GLuint defaultTexture;

// Status variable
float angle;
float angle2;

// Forward declaration, blah blah
void draw();
void update();
void handleKeypress( unsigned char k, int x, int y );

// Vertices, for use later
typedef struct vertex_t {
	Vector p;
	float w;
	Vector n;
	float s, t;
} vertex;

// These are vertices and normals for a cube.
// They're interleaved [array-of-struct], which isn't ideal for processing
// on the CPU, but nice to read.
vertex vertices[] = {
	{ {  2.0,  2.0,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 0.0 },
	{ { -2.0,  2.0,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 0.0 },
	{ { -2.0, -2.0,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 1.0 },
	{ {  2.0, -2.0,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 1.0 },
	{ {  1.7,  1.7,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 0.0 },
	{ { -1.7,  1.7,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 0.0 },
	{ { -1.7, -1.7,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 1.0 },
	{ {  1.7, -1.7,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 1.0 },
	{ {  1.4,  1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 0.0 },
	{ { -1.4,  1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 0.0 },
	{ { -1.4, -1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 1.0 },
	{ {  1.4, -1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 1.0 },
	{ {  1.4,  1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 1.0 },
	{ { -1.4,  1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 1.0 },
	{ { -1.4, -1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 1.0, 0.0 },
	{ {  1.4, -1.4,  0.0 }, 1.0, {  0.0,  0.0,  1.0 }, 0.0, 0.0 },
};

// Make a window for doing OpenGL
void makeWindow( int argc, char** argv ) {
	glutInit( &argc, argv );
	glutInitDisplayMode( GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );

	// Open window / full screen
	#ifdef FULLSCREEN
		char modeString[255] = "";
		sprintf( modeString, "%dx%d:24", WINDOW_WIDTH, WINDOW_HEIGHT );
		glutGameModeString( modeString );
		glutEnterGameMode();
	#else
		glutInitWindowSize( WINDOW_WIDTH, WINDOW_HEIGHT );
		glutCreateWindow( WINDOW_TITLE );
	#endif

	// Use GLEW
	#ifndef __APPLE__
	glewInit();
	#endif
	
	// Set up OpenGL features
// 	glEnable( GL_DEPTH_TEST );
	glClearDepth( 1.0f );
	glDepthFunc( GL_LEQUAL );

	glClearColor( 1.0f, 1.0f, 1.0f, 1.0f );

// 	glEnable( GL_CULL_FACE );
// 	glCullFace( GL_BACK );
	glEnable (GL_BLEND);
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

// Functions that glut calls for us
void setupGlutCallbacks() {
	glutDisplayFunc( draw );
	glutTimerFunc( 15, update, 0 );
}

// Initialize buffers, textures, etc.
void initObjects() {
	// Make one vertex buffer object with vertex data.
	vertexBuffer = makeBO(
		GL_ARRAY_BUFFER,
		vertices,
		sizeof( vertices ),
		GL_STATIC_DRAW
	);

	// Element data - just draw all vertices in order
	GLushort elementData[] = {
		0,  1,  2,  3,
	};
	elementBuffer[0] = makeBO(
		GL_ELEMENT_ARRAY_BUFFER,
		elementData,
		sizeof( elementData ),
		GL_STATIC_DRAW
	);
	GLushort elementData2[] = {
		4,  5,  6,  7,
	};
	elementBuffer[1] = makeBO(
		GL_ELEMENT_ARRAY_BUFFER,
		elementData2,
		sizeof( elementData2 ),
		GL_STATIC_DRAW
	);
	GLushort elementData3[] = {
		8,  9,  10,  11,
	};
	elementBuffer[2] = makeBO(
		GL_ELEMENT_ARRAY_BUFFER,
		elementData3,
		sizeof( elementData3 ),
		GL_STATIC_DRAW
	);
	GLushort elementData4[] = {
		12,  13,  14,  15,
	};
	elementBuffer[3] = makeBO(
		GL_ELEMENT_ARRAY_BUFFER,
		elementData4,
		sizeof( elementData4 ),
		GL_STATIC_DRAW
	);

	// Load textures.
	defaultTextureData = loadTexture( "texture.tga" );
	shobonTextureData = loadTexture( "texture2.tga" );
}

// Initialize shaders.
void initShaders() {
	// Load a simple Vertex/Fragment shader
	GLuint vertexShader = loadShader( GL_VERTEX_SHADER, "simple.vert" );
	GLuint fragmentShader = loadShader( GL_FRAGMENT_SHADER, "simple.frag" );
	shaderProgram = makeShaderProgram( vertexShader, fragmentShader );

	// Get locations of attributes and uniforms used inside.
	vertexPosition = glGetAttribLocation( shaderProgram, "vertex" );
	vertexNormal = glGetAttribLocation( shaderProgram, "vnormal" );
	vertexTexcoords = glGetAttribLocation( shaderProgram, "texcoords" );
	modelviewMatrix = glGetUniformLocation( shaderProgram, "modelview" );
	normalviewMatrix = glGetUniformLocation( shaderProgram, "normalview" );
	projectionMatrix = glGetUniformLocation( shaderProgram, "projection" );
	defaultTexture = glGetUniformLocation( shaderProgram, "texture" );
}

// Update status variables.
// Called every 15ms, unless the PC is too slow.
void update() {
	// Update things here.
	angle += 0.02;
	angle2 += 0.035;
	
	// Redraw screen now, please, and call again in 15ms.
	glutPostRedisplay();
	glutTimerFunc( 15, update, 0 );
	glutKeyboardFunc( handleKeypress );
}

// Draw the scene to the screen
void draw() {
	// Clear everything first thing.
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	
	// Activate shader.
	glUseProgram( shaderProgram );

	// Set projection - you don't have to recalculate this each frame, but
	// for the sake of simplicity that's what is done here.
	MatrixAsUniform(
		projectionMatrix,
		PerspectiveMatrix(
			45.0,
			(float)WINDOW_WIDTH/(float)WINDOW_HEIGHT,
			1.0,
			-1.0
		)
	);
	
	// Vertices
	glBindBuffer( GL_ARRAY_BUFFER, vertexBuffer );
	glVertexAttribPointer(
		vertexPosition,
		4,
		GL_FLOAT,
		GL_FALSE,
		sizeof(GLfloat) * 9,
		(void*)0
	);
	glEnableVertexAttribArray( vertexPosition );

	// Normals
	glVertexAttribPointer(
		vertexNormal,
		3,
		GL_FLOAT,
		GL_FALSE,
		sizeof(GLfloat) * 9,
		(void*)(sizeof(GLfloat) * 4)
	);
	glEnableVertexAttribArray( vertexNormal );
	
	// Texture coordinates
	glVertexAttribPointer(
		vertexTexcoords,
		2,
		GL_FLOAT,
		GL_FALSE,
		sizeof(GLfloat) * 9,
		(void*)(sizeof(GLfloat) * 7)
	);
	glEnableVertexAttribArray( vertexTexcoords );

	// Textures
	glActiveTexture( GL_TEXTURE0 );
	glBindTexture( GL_TEXTURE_2D, defaultTextureData );
	glUniform1i( defaultTexture, 0 );

	for( int i = 0; i < 3; i++ ) {
		// Send element buffer to GPU and draw.
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, elementBuffer[i] );

		// Set modelview - the objects rotation and translation.
		Matrix rot = XAxisRotationMatrix( angle * -i * (((i%2)==0)?1:-1) );
		rot = MatrixMul( rot, ZAxisRotationMatrix( -angle * (1.0+i/5.0) ) );
		rot = MatrixMul( rot, YAxisRotationMatrix( -angle2 ) );
		Matrix trans = TranslationMatrix( 0.0, 0.0, -5.0 );
		Matrix modelview = MatrixMul( trans, rot );
		MatrixAsUniform( modelviewMatrix, modelview );

		// Normal view matrix - inverse transpose of modelview.
		Matrix normalview = MatrixTranspose( FastMatrixInverse( modelview ) );
		MatrixAsUniform( normalviewMatrix, normalview );

		glDrawElements(
			GL_QUADS,
			4,
			GL_UNSIGNED_SHORT,
			(void*)0
		);
	}

	glBindTexture( GL_TEXTURE_2D, shobonTextureData );
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, elementBuffer[3] );

	Matrix rot = YAxisRotationMatrix( angle );
	Matrix trans = TranslationMatrix( 0.0, 0.0, -5.0 );
	Matrix modelview = MatrixMul( trans, rot );
	MatrixAsUniform( modelviewMatrix, modelview );

	// Normal view matrix - inverse transpose of modelview.
	Matrix normalview = MatrixTranspose( FastMatrixInverse( modelview ) );
	MatrixAsUniform( normalviewMatrix, normalview );

	glDrawElements(
		GL_QUADS,
		4,
		GL_UNSIGNED_SHORT,
		(void*)0
	);
	
	// Clean up
	glDisableVertexAttribArray( vertexPosition );
	glDisableVertexAttribArray( vertexTexcoords );

	// Switch drawing area and displayed area.
	glutSwapBuffers();
}

// Key press handler
// Mostly here to allow us to break on escape
void handleKeypress( unsigned char k, int x, int y ) {
	switch(k) {
		case 27: // Escape -> die.
			exit( 0 );
		break;
		case 'p': // Neat for debugging. Wait a second on 'p'.
			sleep( 1 );
		break;
	}
}

// Just:
//	1) Set up OpenGL
//	2) Set up GLUT
//	3) Run GLUT
int main( int argc, char** argv ) {
	makeWindow( argc, argv );
	initObjects();
	initShaders();
	setupGlutCallbacks();
	glutMainLoop();
}
