/// Inlcudes
#define MAIN
#include "MicroGlut.h"
#include "GL_utilities.h"
#include "VectorUtils4.h"
#include "LittleOBJLoader.h"
#include "LoadTGA.h"


/// Globals
GLuint   program;
mat4     projection;
Model  * terrain;
GLuint   tex;


/// Generate terrain
Model * GenerateTerrain(const char * filename)
{
	TextureData textureData;
	if (!LoadTGATextureData(filename, &textureData))
		return nullptr;

	int vertexCount   =  textureData.width    *  textureData.height;
	int triangleCount = (textureData.width-1) * (textureData.height-1) * 2;

	vec3   * position = (vec3 *)  malloc(sizeof(GLfloat) * 3 * vertexCount);
	vec3   * normal   = (vec3 *)  malloc(sizeof(GLfloat) * 3 * vertexCount);
	vec2   * texCoord = (vec2 *)  malloc(sizeof(GLfloat) * 2 * vertexCount);
	GLuint * indices  = (GLuint *)malloc(sizeof(GLuint)  * 3 * triangleCount);

	int bytesPerPixel = textureData.bpp / 8;
	for (unsigned int x = 0; x < textureData.width;  ++x)
	for (unsigned int z = 0; z < textureData.height; ++z)
	{
		int indexBase = (x + z * textureData.width);
		// Vertex array. You need to scale this properly.
		GLubyte data = textureData.imageData[indexBase * bytesPerPixel];
		position[indexBase].x = x    / 1.0;
		position[indexBase].y = data / 100.0;
		position[indexBase].z = z    / 1.0;
		// Normal vectors. You need to calculate these.
		normal[indexBase].x = 0.0;
		normal[indexBase].y = 1.0;
		normal[indexBase].z = 0.0;
		// Texture coordinates. You may want to scale them.
		texCoord[indexBase].x = x; // (float)x / textureData.width;
		texCoord[indexBase].y = z; // (float)z / textureData.height;
	}
	for (unsigned int x = 0; x < textureData.width-1;  ++x)
	for (unsigned int z = 0; z < textureData.height-1; ++z)
	{
		int indexBase = (x + z * (textureData.width-1))*6;
		// Triangle 1
		indices[indexBase + 0] = (x+0) + (z+0) * textureData.width;
		indices[indexBase + 1] = (x+0) + (z+1) * textureData.width;
		indices[indexBase + 2] = (x+1) + (z+0) * textureData.width;
		// Triangle 2
		indices[indexBase + 3] = (x+1) + (z+0) * textureData.width;
		indices[indexBase + 4] = (x+0) + (z+1) * textureData.width;
		indices[indexBase + 5] = (x+1) + (z+1) * textureData.width;
	}

	// Create Model and upload to GPU:
	Model * model = LoadDataToModel(
		position,
		normal,
		texCoord,
		nullptr,
		indices,
		vertexCount,
		triangleCount * 3
	);

	free(textureData.imageData);
	return model;
}


/// Init
void init(void)
{
	// GL inits.
	glClearColor(0.2, 0.2, 0.5, 0.0);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);
	printError("init GL");

	// Load and compile shader.
	program = loadShaders("terrain.vert", "terrain.frag");
	glUseProgram(program);
	printError("init shader");

	// Matrices.
	projection = frustum(-0.1, 0.1, -0.1, 0.1, 0.2, 50.0);
	glUniformMatrix4fv(glGetUniformLocation(program, "projection"), 1, GL_TRUE, projection.m);
	printError("init matrices");

	// Textures.
	LoadTGATextureSimple("../assets/terrain/dandelion.tga", &tex);
	glBindTexture(GL_TEXTURE_2D, tex);
	glUniform1i(glGetUniformLocation(program, "tex"), 0);
	printError("init textures");

	// Generate terrain.
	terrain = GenerateTerrain("../assets/terrain/simple-4.tga");
	printError("init terrain");
}


/// Display
void display(void)
{
	// Clear the screen.
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Uniforms.
	mat4 view = lookAt(
		vec3(0, 5, 8), // Position.
		vec3(2, 0, 2), // Target.
		vec3(0, 1, 0)  // Up.
	);
	mat4 model = IdentityMatrix();
	mat4 modelView = view * model;
	glUniformMatrix4fv(glGetUniformLocation(program, "modelView"), 1, GL_TRUE, modelView.m);
	printError("display uniforms");

	// Draw.
	DrawModel(terrain, program, "inPosition", nullptr, "inTexCoord");
	printError("display draw");

	// Show on the screen.
	glutSwapBuffers();
}


/// Mouse
void mouse(int x, int y)
{
	// This function is included in case you want some hints about using
	// passive mouse movement. Uncomment to see mouse coordinates:
	// printf("%d %d\n", x, y);
}


/// Main
int main(int argc, char * argv[])
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH);
	glutInitContextVersion(3, 2);
	glutInitWindowSize(600, 600);
	glutCreateWindow("TSBK07");

	glutRepeatingTimer(20);
	glutDisplayFunc(display);
	glutPassiveMotionFunc(mouse);

	dumpInfo();
	init();
	glutMainLoop();
}