#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define GL_GLEXT_PROTOTYPES 1
#ifdef USE_OSM
#include <GL/glu.h>
#else
#include <GL/glu.h>
#include <GL/gl.h>
#include <EGL/egl.h>
#endif

#include "linmath.h"
#include "global.h"
#include "resources.h"
#include "terrain.h"
#include "stb_image.h"

GLuint terrainVAO;
GLuint terrainShaderPgm;
GLuint t_uniform_loc_view_matrix;
GLuint t_uniform_loc_transformation_matrix;
GLuint t_uniform_loc_light_dir;
float t_scale;
int t_indices_count;
void terrain_build_mesh(float scale, int polyCount, mat4x4 pjm, float maxHeight) {
	t_scale = scale;
	int polyCountM1 = polyCount - 1;
	int vertexArrSize = polyCount * polyCount * 3 * sizeof(float);
	t_indices_count = polyCountM1 * polyCountM1 * 6;
	int indicesArrSize = t_indices_count * sizeof(int);
	float* vertices = (float*)malloc(vertexArrSize);
	float* normals = (float*)malloc(vertexArrSize);
	assert(normals);
	int* indices = (int*)malloc(indicesArrSize);
	assert(vertices);
	assert(indices);
	
	int imgWidth, imgHeight, nrChannels;
	unsigned char *hmdata = stbi_load("hm_gen/output.png", &imgWidth, &imgHeight, &nrChannels, 0);
	assert(hmdata);
	
	int* indices_write = indices;
	int baseIdx;
	for(int y = 0; y < polyCount; y++) {
		for(int x = 0; x < polyCount; x++) {
			float ax = (float)x / (float)polyCount;
			float ay = (float)y / (float)polyCount;
			int pxX = (int)(ax * (float)imgWidth);
			int pxY = (int)(ay * (float)imgHeight);
			
			int idx = (pxY * imgWidth + pxX) * nrChannels + 2;
			int pixval = hmdata[idx] + (hmdata[idx - 1] << 8);
			
			baseIdx = (y * polyCount + x) * 3;
			vertices[baseIdx + 0] = (ax - 0.5f) * scale;
			vertices[baseIdx + 2] = (ay - 0.5f) * scale;
			vertices[baseIdx + 1] = (float)pixval / 65535.0f * maxHeight;
			
			idx = (pxY * imgWidth + (pxX == imgWidth - 1 ? pxX : pxX + 1)) * nrChannels + 2;
			float h1 = (hmdata[idx] + (hmdata[idx - 1] << 8)) / 65535.0f;
			idx = ((pxY == imgHeight - 1 ? pxY : pxY + 1) * imgWidth + pxX) * nrChannels + 2;
			float h2 = (hmdata[idx] + (hmdata[idx - 1] << 8)) / 65535.0f;
			idx = (pxY * imgWidth + (pxX == 0 ? pxX : pxX - 1)) * nrChannels + 2;
			float h3 = (hmdata[idx] + (hmdata[idx - 1] << 8)) / 65535.0f;
			idx = ((pxY == 0 ? pxY : pxY - 1) * imgWidth + pxX) * nrChannels + 2;
			float h4 = (hmdata[idx] + (hmdata[idx - 1] << 8)) / 65535.0f;
			
			normals[baseIdx + 0] = -(h1 - h3);
			normals[baseIdx + 1] = 15.0f / (float)imgWidth;
			normals[baseIdx + 2] = h2 - h4;
			vec3_norm(normals + baseIdx, normals + baseIdx);
			
			if(x != 0 && y != 0) {
				int currRow = y * polyCount + x;
				int prevRow = (y - 1) * polyCount + x;
				//CCW Winding order
				*indices_write = prevRow;
				indices_write++;
				*indices_write = prevRow - 1;
				indices_write++;
				*indices_write = currRow - 1;
				indices_write++;
				*indices_write = currRow;
				indices_write++;
				*indices_write = prevRow;
				indices_write++;
				*indices_write = currRow - 1;
				indices_write++;
			}
		}
	}
	
	terrainShaderPgm = create_shader_program("terrain_vertex.glsl", "terrain_fragment.glsl");
	assert(terrainShaderPgm != 0);
	glUseProgram(terrainShaderPgm);
	glUniformMatrix4fv(glGetUniformLocation(terrainShaderPgm, "projection_matrix"), 1, GL_FALSE, &pjm[0][0]);
	t_uniform_loc_view_matrix = glGetUniformLocation(terrainShaderPgm, "view_matrix");
	t_uniform_loc_transformation_matrix = glGetUniformLocation(terrainShaderPgm, "transformation_matrix");
	glUniform1i(glGetUniformLocation(terrainShaderPgm, "steep_tex"), 0);
	glUniform1i(glGetUniformLocation(terrainShaderPgm, "grass_tex"), 1);
	glUniform1f(glGetUniformLocation(terrainShaderPgm, "height_scale"), maxHeight);
	t_uniform_loc_light_dir = glGetUniformLocation(terrainShaderPgm, "light_dir");
	
	GLuint vertex_VBO = create_vbo();
	assert(vertex_VBO != 0);
	GLuint normals_VBO = create_vbo();
	assert(normals_VBO != 0);
	GLuint EBO = create_vbo();
	assert(EBO != 0);
	terrainVAO = create_vao();
	assert(terrainVAO != 0);
	glBindVertexArray(terrainVAO);
	//Fill vertices buffer
	glBindBuffer(GL_ARRAY_BUFFER, vertex_VBO);
	glBufferData(GL_ARRAY_BUFFER, vertexArrSize, vertices, GL_STATIC_DRAW);
	//Fill indices buffer
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesArrSize, indices, GL_STATIC_DRAW);
	glVertexAttribPointer(glGetAttribLocation(terrainShaderPgm, "aPos"), 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), NULL);
	//Fill normals buffer
	glBindBuffer(GL_ARRAY_BUFFER, normals_VBO);
	glBufferData(GL_ARRAY_BUFFER, vertexArrSize, normals, GL_STATIC_DRAW);
	glVertexAttribPointer(glGetAttribLocation(terrainShaderPgm, "aNormal"), 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), NULL);
	glBindVertexArray(0);
	int a = glGetError();
	if(a) {
		const unsigned char *errStr;
		errStr = gluErrorString(a);
		printf("%s\r\n", errStr);
		exit(1);
	}
	
	free(normals);
	free(vertices);
	free(indices);
}

texture terrainSteepTexture;
texture terrainGrassTexture;
void terrain_load_textures() {
	terrainSteepTexture = texture_from_file("textures/T_Snow_Ground_Rock_3_A_SM.tga", 1);
	assert(terrainSteepTexture.id != 0);
	terrainGrassTexture = texture_from_file("textures/Grass 1.png", 1);
	assert(terrainGrassTexture.id != 0);
}

mat4x4 terrain_VIM;
mat4x4 terrain_TFM;
void terrain_render(camera cam, vec3 ld, float posX, float posZ) {
	/*
	 * Render terrain
	 */
	glBindVertexArray(terrainVAO);
	glEnableVertexAttribArray(0);
	glEnableVertexAttribArray(1);
	glUseProgram(terrainShaderPgm);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, terrainSteepTexture.id);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, terrainGrassTexture.id);
	mat4x4_identity(terrain_VIM);
	mat4x4_rotate_Z(terrain_VIM, terrain_VIM, cam.roll * DEG2RAD);
	mat4x4_rotate_X(terrain_VIM, terrain_VIM, cam.pitch * DEG2RAD);
	mat4x4_rotate_Y(terrain_VIM, terrain_VIM, cam.yaw * DEG2RAD);
	mat4x4_translate_in_place(terrain_VIM, -cam.x, -cam.y, -cam.z);
	glUniformMatrix4fv(t_uniform_loc_view_matrix, 1, GL_FALSE, &terrain_VIM[0][0]);
	glUniform3f(t_uniform_loc_light_dir, ld[0], ld[1], ld[2]);
	mat4x4_identity(terrain_TFM);
	mat4x4_translate_in_place(terrain_TFM, posX * t_scale, 0, posZ * t_scale);
	glUniformMatrix4fv(t_uniform_loc_transformation_matrix, 1, GL_FALSE, &terrain_TFM[0][0]);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glDrawElements(GL_TRIANGLES, t_indices_count, GL_UNSIGNED_INT, 0);
	glDisableVertexAttribArray(0);
	glDisableVertexAttribArray(1);
	glBindVertexArray(0);
}
