#include "game.h"
#include "geometric_objects.h"

int flat_panel::init()  {
	// Initialization part
	float vertices[] = {	
		-1.0, 1.0,
		1.0, 1.0,
		1.0, -1.0,
		-1.0, 1.0,
		1.0, -1.0,
		-1.0, -1.0,
	};
	glGenBuffers(1, &vbuf);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, vbuf);
	glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	program = make_program("normal_panel_vertex_shader.glsl",0, 0, 0, "loaded_object_fragment_shader.glsl");
	if (!program)
		return 1;

	tex = load_texture(texturefile);

	glGenBuffers(1, &models_buffer);
	glGenBuffers(1, &scales_buffer);
	glGenBuffers(1, &normals_buffer);
	glGenBuffers(1, &ups_buffer);
	glGenBuffers(1, &object_scales_buffer);

	v_attrib = glGetAttribLocation(program, "in_vertex");
	mvp_uniform = glGetUniformLocation(program, "vp");
	return 0;
}

void flat_panel::addpanel(glm::vec3 location, glm::vec2 object_scale, glm::vec2 texture_scale, glm::vec3 normal, glm::vec3 up){
	locations.push_back(location);
	object_scales.push_back(object_scale);
	texture_scales.push_back(texture_scale);
	normals.push_back(glm::vec4(glm::normalize(normal), 1.0f));
	ups.push_back(glm::vec4(glm::normalize(up), 1.0f));
}

void flat_panel::rotate(size_t index, float angle, glm::vec3 axis){
	
}

void flat_panel::set_normal(size_t index, glm::vec3 normal){
	normals[index] = glm::vec4(glm::normalize(normal), 1.0f);
}

void flat_panel::set_up(size_t index, glm::vec3 up){
	ups[index] = glm::vec4(glm::normalize(up), 1.0f);
}

std::vector<glm::mat4> flat_panel::create_models(){
	std::vector<glm::mat4> models;
	models.reserve(locations.size());
	for(size_t i = 0; i < locations.size(); i++){
		glm::mat4 new_model = glm::mat4(1.0f);
		new_model = glm::translate(new_model, locations[i]);
		models.push_back(new_model);
	}
	return models;
}

void flat_panel::draw(glm::mat4 vp)  {
	glUseProgram(program);
	data_mutex.lock();
	std::vector<glm::mat4> models = create_models();
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, models_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, models.size() * sizeof(glm::mat4), models.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, models_buffer);

	glBindBuffer(GL_SHADER_STORAGE_BUFFER, scales_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, texture_scales.size() * sizeof(glm::vec2), texture_scales.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, scales_buffer);

	glBindBuffer(GL_SHADER_STORAGE_BUFFER, normals_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, normals.size() * sizeof(normals[0]), normals.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, normals_buffer);
	
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, ups_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, ups.size() * sizeof(ups[0]), ups.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, ups_buffer);
	
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, object_scales_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, object_scales.size() * sizeof(object_scales[0]), object_scales.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, object_scales_buffer);
	data_mutex.unlock();

	glEnableVertexAttribArray(v_attrib);
	glBindBuffer(GL_ARRAY_BUFFER, vbuf);
	glVertexAttribPointer(v_attrib, 2, GL_FLOAT, GL_FALSE, 0, 0);

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

	glUniformMatrix4fv(mvp_uniform, 1, 0, glm::value_ptr(vp));

	glDrawArraysInstanced(GL_TRIANGLES, 0, 6, models.size());
}

int sphere::init() {
	/* Note:  These are not really interpreted literally!
 	 * x = sideways
 	 * y = foward
 	 * z = up
 	 * sideways is forward cross up
 	 * color, for now, is the color of each corner, with -1 interpreted as 0
 	 */
	size = glm::vec3(10, 10, 10);
	float 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,
	};
	glGenBuffers(1, &vbuf);
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, vbuf);
	glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	GLushort cube_elements[] = {
		// front
		0, 1, 2,
		2, 3, 0,
		// top
		1, 5, 6,
		6, 2, 1,
		// back
		7, 6, 5,
		5, 4, 7,
		// bottom
		4, 0, 3,
		3, 7, 4,
		// left
		4, 5, 1,
		1, 0, 4,
		// right
		3, 2, 6,
		6, 7, 3,
	};
	glGenBuffers(1, &ebuf);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebuf);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_elements), cube_elements, GL_STATIC_DRAW);

	program = make_program("sphere_vs.glsl", "sphere_tcs.glsl", "sphere_tes.glsl", "sphere_gs.glsl", "sphere_fs.glsl");
	if (!program)
		return 1;


	glGenBuffers(1, &models_buffer);
	glGenBuffers(1, &ups_buffer);
	glGenBuffers(1, &forwards_buffer);
	glGenBuffers(1, &radii_buffer);

	v_attrib = glGetAttribLocation(program, "in_vertex");
	mvp_uniform = glGetUniformLocation(program, "vp");
	return 0;
}

void sphere::draw(glm::mat4 vp){
	glUseProgram(program);
	data_mutex.lock();

	std::vector<glm::mat4> models;
	models.reserve(locations.size());
	for(size_t i = 0; i < locations.size(); i++){
		glm::mat4 new_model = glm::mat4(1.0f);
		new_model = glm::translate(new_model, locations[i]);
		models.push_back(new_model);
	}


	glBindBuffer(GL_SHADER_STORAGE_BUFFER, models_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, models.size() * sizeof(glm::mat4), models.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, models_buffer);
	
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, ups_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, ups.size() * sizeof(ups[0]), ups.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ups_buffer);
	
	glBindBuffer(GL_SHADER_STORAGE_BUFFER, forwards_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, forwards.size() * sizeof(forwards[0]), forwards.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, forwards_buffer);

	glBindBuffer(GL_SHADER_STORAGE_BUFFER, radii_buffer);
	glBufferData(GL_SHADER_STORAGE_BUFFER, radii.size() * sizeof(float), radii.data(), GL_STATIC_DRAW);
	glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, radii_buffer);
	
	data_mutex.unlock();

	glEnableVertexAttribArray(v_attrib);
	glBindBuffer(GL_ARRAY_BUFFER, vbuf);
	glVertexAttribPointer(v_attrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
	
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebuf);

	glUniformMatrix4fv(mvp_uniform, 1, 0, glm::value_ptr(vp));

	glPatchParameteri(GL_PATCH_VERTICES, 3);
	glDrawElementsInstanced(GL_PATCHES, 36, GL_UNSIGNED_SHORT, 0, models.size());
}

void sphere::addsphere(glm::vec3 location, float radius, glm::vec3 up, glm::vec3 forward){
	locations.push_back(location);
	radii.push_back(radius);
	ups.push_back(glm::vec4(up, 1.0f));
	forwards.push_back(glm::vec4(forward, 1.0f));
}

void rollsphere::roll_to(size_t index, glm::vec3 new_location, float speed){
	data_mutex.lock();
	speeds[index] = speed;
	new_locations[index] = new_location;
	new_locations[index].y = locations[index].y;
	float angular_velocity = speed / radii[index];
	glm::vec3 axis = glm::cross(new_locations[index] - locations[index], glm::vec3(0, 1, 0));
	rotation_matrices[index] = glm::rotate(glm::mat4(1.0f), -angular_velocity, axis);
	data_mutex.unlock();
}

void rollsphere::move(int elapsed_time){
	data_mutex.lock();
	for(size_t i = 0; i < locations.size(); i++){
		if(speeds[i] > 0.0001){
			glm::vec3 direction = glm::normalize(new_locations[i] - locations[i]);
			locations[i] += speeds[i] * direction;
			ups[i] = rotation_matrices[i] * ups[i];
			forwards[i] = rotation_matrices[i] * forwards[i];
			if(glm::length(new_locations[i] - locations[i]) < speeds[i])
				speeds[i] = 0.0f;
		}
	}
	data_mutex.unlock();
}

void rollsphere::addsphere(glm::vec3 location, float radius, glm::vec3 up, glm::vec3 forward){
	sphere::addsphere(location, radius, up, forward);
	new_locations.push_back(location);
	speeds.push_back(0);
	rotation_matrices.push_back(glm::mat4(1.0));
}

void rollsphere::activate(size_t index){
	roll_to(index, locations[index] + glm::normalize(locations[index] - player_position) * 100.0f, 0.01);
}

