#include "particle_node.h"
// glm::value_ptr
#include <glm/gtc/type_ptr.hpp> 


particle_node::particle_node()
{
	has_forcefield = false;
}

particle_node::particle_node(std::vector<GLfloat>& pts, std::vector<GLfloat>& prtcls, GLuint tex_particle, GLuint tex_particle2, GLuint particle_dimension,
	const std::string& render_path, const std::string& update_path)
{
	_shader_type = SH_PARTICLE;
	points = pts;
	particles = prtcls;
	texture_particle = tex_particle;
	texture_particle2 = tex_particle2;
	
	particle_lookup_width = particle_dimension;
	flip = 0;
	burn_speed = 5.0f;
	delta = 0.005f;
	transformation_matrix = glm::mat4(1.0f);
	modelview_matrix = NULL;
	perspective_matrix =  NULL;
	has_forcefield = false;
	default_framebuffer = 0;
	depth_enabled = false;

	
	render_shader = std::tr1::shared_ptr<Shader>( new Shader (render_path) );
	if (!(*render_shader)) {
		cerr << "Could not compile render shader program." << endl;
	}
	
	render_shader->bind_frag_data_location("out_color");
	
	update_shader = std::tr1::shared_ptr<Shader>( new Shader (update_path) );
	if (!(*update_shader)) {
		cerr << "Could not compile update shader program." << endl;
	}
	update_shader->bind_frag_data_location("frag_particle");
}

particle_node::~particle_node()
{
	_shader_type = SH_PARTICLE;
	flip = 0;
	transformation_matrix = glm::mat4(1.0f);
	modelview_matrix = NULL;
	perspective_matrix =  NULL;

	glDeleteFramebuffers(2, framebuffer_lookup);

	glDeleteTextures(2, texture_lookup);
}

//setup framebuffers and textures
bool particle_node::init()
{


	//set handles for render shader
	texture_location = render_shader->get_uniform_location("firstTexture");
	texture_location2 = render_shader->get_uniform_location("secondTexture");
	lookup_texture_location = render_shader->get_uniform_location("readTexture");
	vertex_location = render_shader->get_attrib_location("vertex");

	//set handles for update shader
	lookup_texture_update_location = update_shader->get_uniform_location("readTexture");
	size_location = update_shader->get_uniform_location("size");
	time_location = update_shader->get_uniform_location("time");
	vertex_update_location = update_shader->get_attrib_location("vertex");

	glGenVertexArrays(1, &vao_id);
	
	glBindVertexArray(vao_id);

	//texel indices vbo
	glGenBuffers(1, &vertex_vbo_id);
	glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo_id);
	glBufferData(GL_ARRAY_BUFFER, points.size() * sizeof(GLfloat), &points[0], GL_STATIC_DRAW);

	//setup lookup texture
	glGenTextures(2, texture_lookup);
	glBindTexture(GL_TEXTURE_2D, texture_lookup[0]);
	// Generate The Texture
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, particle_lookup_width, particle_lookup_width, 0, GL_RGBA, GL_FLOAT, &particles[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);	// needs GL_NEAREST for particle storage to work properly!
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);	// needs GL_NEAREST for particle storage to work properly!
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 

	
	glBindTexture(GL_TEXTURE_2D, texture_lookup[1]);
	// Generate The Texture
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, particle_lookup_width, particle_lookup_width, 0, GL_RGBA, GL_FLOAT, &particles[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);	// needs GL_NEAREST for particle storage to work properly!
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);	// needs GL_NEAREST for particle storage to work properly!
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 

	//generate framebuffers for the textures
	glGenFramebuffers(2, framebuffer_lookup);

	glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_lookup[0]);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_lookup[0], 0);

	glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_lookup[1]);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_lookup[1], 0);

	render_shader->bind();

	glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo_id);
	glVertexAttribPointer(vertex_location, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vertex_location);

	glUniform1i(texture_location, 0);

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, texture_particle2);
	glUniform1i(texture_location2, 2);

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

	render_shader->unbind();

	update_shader->bind();

	glUniform1f(size_location, static_cast<GLfloat>(particle_lookup_width));
	
	glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo_id);
	glVertexAttribPointer(vertex_update_location, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vertex_update_location);

	
	update_shader->unbind();

	return true;
}

//update particles
void particle_node::pre_render()
{
	update_shader->bind();
	
	glBindVertexArray(vao_id);

	glUniform1f(size_location, static_cast<GLfloat>(particle_lookup_width));
	
	glBindBuffer(GL_ARRAY_BUFFER, vertex_vbo_id);
	glVertexAttribPointer(vertex_update_location, 2, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(vertex_update_location);

	//glBindVertexArray(vao_id);
	glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_lookup[1 - flip]);


	//set viewport for framebuffer
	glViewport(0, 0, particle_lookup_width, particle_lookup_width);

	////need some sort of timing here
	glUniform1f(time_location, delta*burn_speed);

	glUniform1i(lookup_texture_update_location, 1);
	//
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, texture_lookup[flip]);

	//if particle system has a force field, set it
	if (has_forcefield) {
		update_shader->set_uniform("force_field", force_field);
	}


	glDrawArrays(GL_POINTS, 0, particle_lookup_width * particle_lookup_width);

	glBindFramebuffer(GL_FRAMEBUFFER, default_framebuffer);
	
}

//render particles
void particle_node::render()
{
	render_shader->bind();

		// Set our matrix uniform
	GLint modelview_location = render_shader->get_uniform_location("modelview_matrix");
	glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(*modelview_matrix));

	// Set our matrix uniform
	GLint perspective_location = render_shader->get_uniform_location("perspective_matrix");
	glUniformMatrix4fv( perspective_location, 1, GL_FALSE, glm::value_ptr(*perspective_matrix));

	glBindVertexArray(vao_id);

	//textures
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, texture_lookup[flip]);
	glUniform1i(lookup_texture_location, 1);

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, texture_particle2);
	glUniform1i(texture_location2, 2);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, texture_particle);
	glUniform1i(texture_location, 0);
	
	//snow stores depth, fire doesnt
	if (!depth_enabled) {
		glDepthMask(0);
	}
	glEnable(GL_BLEND);
	
	glBlendFunc(GL_SRC_ALPHA , GL_ONE);
	
	glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);

	glDrawArrays(GL_POINTS, 0, particle_lookup_width * particle_lookup_width);

	glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);

	glDisable(GL_BLEND);

	if (!depth_enabled) {
		glDepthMask(1);
	}
	//flip framebuffers
	flip = 1 - flip;
}

//update particles
void particle_node::update()
{
}
