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

#include <math.h>
#include <assert.h>
#include <stdio.h>

#include "linmath.h"
#include "global.h"
#include "resources.h"
#include "planet.h"
#include "skybox.h"

GLuint skyboxShaderPgm;
GLuint skybox_VAO;

GLuint s_uniform_loc_view_matrix;
GLuint s_uniform_loc_cam_rot;
GLuint s_uniform_locs_radius[5];
GLuint s_uniform_locs_pos[5];
GLuint s_uniform_locs_rot[5];
GLuint s_uniform_loc_light_direction_A;
GLuint s_uniform_loc_light_direction_B;

static float skybox_vertices[] = {
	// front
	-1.0, -1.0,  1.0,
	1.0, -1.0,  1.0,
	1.0,  1.0,  1.0,
	-1.0,  1.0,  1.0,
	// back
	-1.0, -1.0, -1.0,
	1.0, -1.0, -1.0,
	1.0,  1.0, -1.0,
	-1.0,  1.0, -1.0
};

static const unsigned int skybox_indices[] = {
	0, 1, 2,
	2, 3, 0,
	1, 5, 6,
	6, 2, 1,
	7, 6, 5,
	5, 4, 7,
	4, 0, 3,
	3, 7, 4,
	4, 5, 1,
	1, 0, 4,
	3, 2, 6,
	6, 7, 3
};

texture valaya_tex;
texture valaya_norm;
texture mov_tex;
texture mov_norm;
texture rinala_tex;
texture rinala_norm;
texture ralu_tex;
texture ralu_norm;
texture nevu_tex;
texture nevu_norm;

void skybox_setup(mat4x4 pjm) {
	skyboxShaderPgm = create_shader_program("skybox_vertex.glsl", "skybox_fragment.glsl");
	assert(skyboxShaderPgm != 0);
	glUseProgram(skyboxShaderPgm);
	glUniformMatrix4fv(glGetUniformLocation(skyboxShaderPgm, "projection_matrix"), 1, GL_FALSE, &pjm[0][0]);
	s_uniform_loc_view_matrix = glGetUniformLocation(skyboxShaderPgm, "view_matrix");
	s_uniform_loc_cam_rot = glGetUniformLocation(skyboxShaderPgm, "cam_rot");
	s_uniform_loc_light_direction_A = glGetUniformLocation(skyboxShaderPgm, "light_direction_A");
	s_uniform_loc_light_direction_B = glGetUniformLocation(skyboxShaderPgm, "light_direction_B");
	glUniform1i(glGetUniformLocation(skyboxShaderPgm, "samples"), 20);
	glUniform1i(glGetUniformLocation(skyboxShaderPgm, "samples_light"), 10);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "light_strength"), 100);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "planet_radius_L"), 600);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "planet_radius_L"), 600);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "atmo_height_L"), 25);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "height_offset_L"), 0.0096f);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "hr_L"), 8);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "hm_L"), 1.7f);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "mie_g_L"), 0.8f);
	glUniform3f(glGetUniformLocation(skyboxShaderPgm, "beta_R_L"), 0.008f, 0.012f, 0.03f);
	glUniform3f(glGetUniformLocation(skyboxShaderPgm, "beta_M_L"), 0.01f * 0.222f, 0.01f * 0.222f, 0.04f * 0.222f);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "atmo_height_1"), 483);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "hr_1"), 14);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "hm_1"), 2);
	glUniform1f(glGetUniformLocation(skyboxShaderPgm, "mie_g_1"), 0.85f);
	glUniform3f(glGetUniformLocation(skyboxShaderPgm, "beta_R_1"), 0.0025f, 0.0051f, 0.018f);
	glUniform3f(glGetUniformLocation(skyboxShaderPgm, "beta_M_1"), 0.004f, 0.004f, 0.004f);
	
	char radius_str[] = "radius_x";
	char pos_str[] = "pos_x";
	char rot_str[] = "rot_x";
	for(int i = 0; i < 5; i++) {
		radius_str[7] = '1'+i;
		pos_str[4]    = radius_str[7];
		rot_str[4]    = radius_str[7];
		s_uniform_locs_radius[i] = glGetUniformLocation(skyboxShaderPgm, radius_str);
		s_uniform_locs_pos[i]    = glGetUniformLocation(skyboxShaderPgm, pos_str);
		s_uniform_locs_rot[i]    = glGetUniformLocation(skyboxShaderPgm, rot_str);
	}
	
	/*
	 * Setup skybox model
	 */
	{
		for(int i = 0; i < sizeof(skybox_vertices) / sizeof(float); i++) {
			skybox_vertices[i] *= 300;
		}
		GLuint vertex_VBO = create_vbo();
		assert(vertex_VBO != 0);
		GLuint EBO = create_vbo();
		assert(EBO != 0);
		GLuint VAO = create_vao();
		assert(VAO != 0);
		GLuint uv_VBO = create_vbo();
		assert(uv_VBO != 0);
		glBindVertexArray(VAO);
		//Fill vertices buffer
		glBindBuffer(GL_ARRAY_BUFFER, vertex_VBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(skybox_vertices), skybox_vertices, GL_STATIC_DRAW);
		//Fill indices buffer
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(skybox_indices), skybox_indices, GL_STATIC_DRAW);
		glVertexAttribPointer(glGetAttribLocation(skyboxShaderPgm, "aPos"), 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), NULL);
		//Fill UVs buffer
		//glBindBuffer(GL_ARRAY_BUFFER, uv_VBO);
		//glBufferData(GL_ARRAY_BUFFER, sizeof(UVs), UVs, GL_STATIC_DRAW);
		//glVertexAttribPointer(glGetAttribLocation(shaderPgm, "uv"), 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), NULL);
		skybox_VAO = VAO;
		glBindVertexArray(0);
	}
	
	/*
	 * Load skybox textures
	 */
	{
		valaya_tex = texture_from_file("textures/Valaya_colors_2.png", 0);
		assert(valaya_tex.id != 0);
		mov_tex = texture_from_file("textures/Mov_colors.png", 0);
		assert(mov_tex.id != 0);
		mov_norm = texture_from_file("textures/Mov_height.png_converted.png", 0);
		assert(mov_norm.id != 0);
		rinala_tex = texture_from_file("textures/Rinala_colors.png", 0);
		assert(rinala_tex.id != 0);
		rinala_norm = texture_from_file("textures/Rinala_height.png_converted.png", 0);
		assert(rinala_norm.id != 0);
		ralu_tex = texture_from_file("textures/Ralu_colors.png", 0);
		assert(ralu_tex.id != 0);
		ralu_norm = texture_from_file("textures/Ralu_height.png_converted.png", 0);
		assert(ralu_norm.id != 0);
		nevu_tex = texture_from_file("textures/Nevu_colors.png", 0);
		assert(nevu_tex.id != 0);
		nevu_norm = texture_from_file("textures/Nevu_height.png_converted.png", 0);
		assert(nevu_norm.id != 0);
		valaya_norm = texture_from_file("textures/very_flat.png", 0);
		assert(valaya_norm.id != 0);
		
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "tex_1"), 0);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "tex_2"), 1);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "norm_2"), 2);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "tex_3"), 3);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "norm_3"), 4);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "tex_4"), 5);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "norm_4"), 6);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "tex_5"), 7);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "norm_5"), 8);
		glUniform1i(glGetUniformLocation(skyboxShaderPgm, "norm_1"), 9);
	}
}

mat4x4 sky_VIM;
void skybox_render(camera cam) {
	/*
	 * Render Skybox
	 */
	glBindVertexArray(skybox_VAO);
	glEnableVertexAttribArray(0);
	//glEnableVertexAttribArray(1);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, valaya_tex.id);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, mov_tex.id);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, mov_norm.id);
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, rinala_tex.id);
	glActiveTexture(GL_TEXTURE4);
	glBindTexture(GL_TEXTURE_2D, rinala_norm.id);
	glActiveTexture(GL_TEXTURE5);
	glBindTexture(GL_TEXTURE_2D, ralu_tex.id);
	glActiveTexture(GL_TEXTURE6);
	glBindTexture(GL_TEXTURE_2D, ralu_norm.id);
	glActiveTexture(GL_TEXTURE7);
	glBindTexture(GL_TEXTURE_2D, nevu_tex.id);
	glActiveTexture(GL_TEXTURE8);
	glBindTexture(GL_TEXTURE_2D, nevu_norm.id);
	glActiveTexture(GL_TEXTURE9);
	glBindTexture(GL_TEXTURE_2D, valaya_norm.id);
	
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	glUseProgram(skyboxShaderPgm);
	mat4x4_identity(sky_VIM);
	mat4x4_rotate_Z(sky_VIM, sky_VIM, cam.roll * DEG2RAD);
	mat4x4_rotate_X(sky_VIM, sky_VIM, cam.pitch * DEG2RAD);
	mat4x4_rotate_Y(sky_VIM, sky_VIM, cam.yaw * DEG2RAD);
	glUniformMatrix4fv(s_uniform_loc_view_matrix, 1, GL_FALSE, &sky_VIM[0][0]);
	
	glDrawElements(GL_TRIANGLES, sizeof(skybox_indices) / sizeof(const unsigned int), GL_UNSIGNED_INT, 0);
	glDisableVertexAttribArray(0);
	glClear(GL_DEPTH_BUFFER_BIT);
	glBindVertexArray(0);
}

void from_euler(quat q, double x, double y, double z) {
	double cr = cos(z * 0.5 * DEG2RAD);
	double sr = sin(z * 0.5 * DEG2RAD);
	double cp = cos(x * 0.5 * DEG2RAD);
	double sp = sin(x * 0.5 * DEG2RAD);
	double cy = cos(y * 0.5 * DEG2RAD);
	double sy = sin(y * 0.5 * DEG2RAD);

	q[0] = (float)(sr * cp * cy - cr * sp * sy);
	q[1] = (float)(cr * sp * cy + sr * cp * sy);
	q[2] = (float)(cr * cp * sy - sr * sp * cy);
	q[3] = (float)(cr * cp * cy + sr * sp * sy);
}

void from_to_rotation(quat q, vec3 a, vec3 b) {
	vec3 axis;
	vec3_mul_cross(axis, a, b);
	vec3 a_n;
	vec3 b_n;
	vec3_norm(a_n, a);
	vec3_norm(b_n, b);
	float angle = (float)acos(a_n[0] * b_n[0] + a_n[1] * b_n[1] + a_n[2] * b_n[2]);
	quat res;
	quat_identity(res);
	quat_rotate(res, angle, axis);
	q[0] = res[0];
	q[1] = res[1];
	q[2] = res[2];
	q[3] = res[3];
}

vec3 curr_sunlight_dir;
void skybox_load_moon_data(planet vl, planet sl) {
	glUseProgram(skyboxShaderPgm);
	
	planet avalon = ((planet*)vl.children)[3];
	double cX = avalon.absX;
	double cY = avalon.absY;
	double cZ = avalon.absZ;
	glUniform1f(s_uniform_locs_rot[0], 3.0f -(float)(vl.axisRotation / 300.0));
	glUniform1f(s_uniform_locs_radius[0], (float)(vl.radius / 500));
	glUniform3f(s_uniform_locs_pos[0], (float)((vl.absX - cX) / 1000), (float)((vl.absZ - cZ) / 1000), (float)((vl.absY - cY) / 1000));
	
	for(int i = 1; i < 5; i++) {
		int idx = i-1;
		if(i >= 4) idx++;
		planet current = ((planet*)vl.children)[idx];
		glUniform1f(s_uniform_locs_rot[i], 3.0f - (float)(current.axisRotation / 300.0));
		glUniform1f(s_uniform_locs_radius[i], (float)(current.radius / 500));
		glUniform3f(s_uniform_locs_pos[i], (float)((current.absX - cX) / 1000), (float)((current.absZ - cZ) / 1000), (float)((current.absY - cY) / 1000));
	}
	
	vec3 toSunVec;
	vec3 toSunVecNoRot;
	vec3 sunDir;
	vec3 temp;
	{
		double x1 = cX - sl.absX;
		double y1 = cY - sl.absY;
		double z1 = cZ - sl.absZ;
		double len = sqrt(x1 * x1  + y1 * y1 + z1 * z1);
		toSunVec[0] = toSunVecNoRot[0] = (float)(x1 / len);
		toSunVec[1] = toSunVecNoRot[1] = (float)(y1 / len);
		toSunVec[2] = toSunVecNoRot[2] = (float)(z1 / len);
		double alpha = (avalon.axisRotation + 360 - 234.167694406735) * DEG2RAD;
		double sina = sin(alpha), cosa = cos(alpha);
		temp[0] = toSunVec[0] * cosa - toSunVec[1] * sina;
		temp[1] = toSunVec[0] * sina + toSunVec[1] * cosa;
		temp[2] = toSunVec[2];
		toSunVec[0] = temp[0];
		toSunVec[1] = temp[1];
		toSunVec[2] = temp[2];
		vec3_norm(toSunVec, toSunVec);
		curr_sunlight_dir[0] = -toSunVec[0];
		curr_sunlight_dir[1] = toSunVec[1];
		curr_sunlight_dir[2] = -toSunVec[2];
		sunDir[0] = -toSunVec[0];
		sunDir[1] = -toSunVec[1];
		sunDir[2] = -toSunVec[2];
	}
	printf("Sun Dir:\r\n\t%f,%f,%f\r\n", sunDir[2], -sunDir[1], sunDir[0]);
	glUniform1f(s_uniform_loc_cam_rot, avalon.axisRotation);
	
	glUniform3f(s_uniform_loc_light_direction_A, sunDir[0], sunDir[2], sunDir[1]);
	glUniform3f(s_uniform_loc_light_direction_B, toSunVecNoRot[0], toSunVecNoRot[2], toSunVecNoRot[1]);
}
