/* class demo01
   mostly for testing individual things
*/

#include "demo01.hpp"
#include "scenenode.h"
#include "tga.h"

#include <glm/gtx/transform.hpp>
// glm::perspective
#include <glm/gtc/matrix_projection.hpp>
// glm::translate, glm::rotate, glm::scale
#include <glm/gtc/matrix_transform.hpp>
// glm::value_ptr
#include <glm/gtc/type_ptr.hpp> 

#include "transformation_node.h"

demo01::demo01(void)
{	
	load_config();
	near_far.x = 0.5;
	near_far.y = 60;
	perspective_matrix = glm::perspective(45.0f, static_cast<GLfloat>(width) / static_cast<GLfloat>(height), near_far.x, near_far.y);
	view_frustrum.set_perspective(45.0f, static_cast<GLfloat>(width) / static_cast<GLfloat>(height), near_far.x, near_far.y - 10);
	pp_border = 0.5;
	
	number_culled = 0;
}

demo01::~demo01(void)
{
}

bool demo01::init()
{
	transformation_node t;

	if (!init_window())
	{
		return false;
	}

	//load textures
	if (!load_tga_texture(&texture[BG], "../../textures/tobacco_key.tga", GL_SRGB8_ALPHA8, true) 
		)
	{
		std::cout << "failed to load textures!\n";
		return false;
	}
	if (!load_tga_texture(&texture[TREE], "../../textures/bark11_2.tga", GL_SRGB8_ALPHA8))
	{
		std::cout << "failed to load textures!\n";
		return false;
	}
		if (!load_tga_texture(&texture[CHIMNEY], "../../textures/chimney.tga", GL_SRGB8_ALPHA8))
	{
		std::cout << "failed to load textures!\n";
		return false;
	}
	if (!load_tga_texture(&texture[BRICK], "../../textures/brick_red.tga", GL_SRGB8_ALPHA8, true))
	{
		std::cout << "failed to load textures!\n";
		return false;
	}

	//cam
	cam_eye = glm::vec3(0.0f, 0.0f, 10.f);
	cam_center = glm::vec3(0.0f, 0.0f, 0.0f);
	cam_up = glm::vec3(0.0f, 1.0f, 0.0f);
	view_frustrum.set_camera(cam_eye, cam_center, cam_up);

	//light
	light_0 = glm::vec3(5.0f, 5.0f, 10.0f);

	//do opengl initialization here
	glClearColor(0.2f, 0.2f, 0.2f, 0.5f);
	glEnable(GL_DEPTH_TEST);
	glClearDepth(1.0f);
	glDepthFunc(GL_LEQUAL);

	// Load and compile Shader files

	minimal = std::auto_ptr<Shader>( new Shader ("../../shader/minimal") );
	if (!(*minimal)) {
		cerr << "Could not compile minimal shader program." << endl;
		return 1;
	}

	minimal->bind_frag_data_location("out_color");
	
	sandbox = std::auto_ptr<Shader>( new Shader ("../../shader/sandbox") );
	if (!(*sandbox)) {
		cerr << "Could not compile sandbox shader program." << endl;
		return 1;
	}

	sandbox->bind_frag_data_location("out_color");

		
	texture_shader = std::auto_ptr<Shader>( new Shader ("../../shader/texture") );
	if (!(*texture_shader)) {
		cerr << "Could not compile sandbox shader program." << endl;
		return 1;
	}

	texture_shader->bind_frag_data_location("out_color");
			
	phong = std::auto_ptr<Shader>( new Shader ("../../shader/phong") );
	if (!(*phong)) {
		cerr << "Could not compile phong shader program." << endl;
		return 1;
	}
	
	phong->bind_frag_data_location("out_color");
				
	smooth = std::auto_ptr<Shader>( new Shader ("../../shader/smooth") );
	if (!(*smooth)) {
		cerr << "Could not compile smooth shader program." << endl;
		return 1;
	}

	smooth->bind_frag_data_location("out_color");

	toon = std::auto_ptr<Shader>( new Shader ("../../shader/toon") );
	if (!(*toon)) {
		cerr << "Could not compile toon shader program." << endl;
		return 1;
	}

	toon->bind_frag_data_location("out_color");
	 
	texture_cm = std::auto_ptr<Shader>( new Shader ("../../shader/texture_cm") );
	if (!(*texture_cm)) {
		cerr << "Could not compile texture_cm shader program." << endl;
		return 1;
	}

	texture_cm->bind_frag_data_location("out_color");

	gauss_vertical = std::auto_ptr<Shader>( new Shader ("../../shader/gauss_vertical") );
	if (!(*gauss_vertical)) {
		cerr << "Could not compile gauss_vertical shader program." << endl;
		return 1;
	}

	gauss_vertical->bind_frag_data_location("out_color");

	gauss_horizontal = std::auto_ptr<Shader>( new Shader ("../../shader/gauss_horizontal") );
	if (!(*gauss_horizontal)) {
		cerr << "Could not compile gauss_horizontal shader program." << endl;
		return 1;
	}

	gauss_horizontal->bind_frag_data_location("out_color");

		
	// Load and compile Shader files	
	shadowmap_gen = std::auto_ptr<Shader>( new Shader ("../../shader/depthmap_gen") );
	if (!(*shadowmap_gen)) {
		cerr << "Could not compile sandbox shader program." << endl;
		return false;
	}

	shadowmap_gen->bind_frag_data_location("out_color");

		// Load and compile Shader files	
	pp_dof = std::auto_ptr<Shader>( new Shader ("../../shader/pp_dof") );
	if (!(*pp_dof)) {
		cerr << "Could not compile pp_dof shader program." << endl;
		return false;
	}

	pp_dof->bind_frag_data_location("out_color");

	std::cout << "init objects:\n";

	//wavefront object model setup
	if (object_model.loadMod("../../models/Tree3.obj"))
	{
		cout << "object model loading failed\n";
		//cube.obj, cauldron_Scene
		return false;
	}	
	if (chimney_model.loadMod("../../models/fireplace.obj"))
	{
		cout << "object model loading failed\n";
		//cube.obj, cauldron_Scene
		return false;
	}	

	if (teapot_model.loadMod("../../models/cube2.obj"))
	{
		cout << "object model loading failed\n";
		//cube.obj, cauldron_Scene
		return false;
	}

	init_postprocess();
	
	//fbo init
	if (!init_fbo()) {
		std::cout << "error creating fbo" << std::endl;
		return false;
	}

	init_vbo_vao(*minimal, vbo_id, &vao_id);
	init_bg(*texture_shader, bg_vbo_id, &bg_vao_id);
	int atoff_elements1 = init_cube(*phong, cube_vbo_id, &cube_vao_id);
	int atoff_elements2 = init_cube(*toon, atoff_vbo_id, &atoff_vao_id);
	init_model(*texture_shader);
	chimney_model.buildVBOs(*texture_shader);
	teapot_model.buildVBOs(*texture_shader);

	glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSPROC)glfwGetProcAddress("glDeleteVertexArrays");

	//scenegraph setup
	std::cout << "cube dim = \n" << teapot_model.getMesh()->getDimVec().x << "\n" << teapot_model.getMesh()->getDimVec().y << "\n" << teapot_model.getMesh()->getDimVec().z << std::endl;
	atoff = object_node(glm::translate(5.0f, 0.0f, 0.0f) * glm::scale(1.f, 1.f, 1.f), teapot_model );
	atoff.set_texture_id(texture[BRICK]);
	atoff2 = geometry_node(glm::scale(0.5f, 0.5f, 0.5f) *  glm::translate(-5.0f, 0.0f, 0.0f),
		atoff_vao_id, atoff_elements2, SH_TOON);
	bg = object_node(  glm::translate(0.0f, 0.0f, -40.0f) * glm::scale(10.0f, 10.0f, 10.0f), bg_vao_id, 6, SH_TEXTURE);
	bg.set_texture_id(texture[BG]);
	model = object_node(glm::translate(0.0f, 5.0f, 0.0f), object_model);
	model.set_texture_id(texture[TREE]);
	chimney = object_node(glm::translate(-2.0f, -5.0f, 0.0f)  * glm::scale(0.3f, 0.3f, 0.3f), chimney_model.getVAO(), chimney_model.getMesh()->getIndicesCount() , SH_TEXTURE);
	chimney.set_texture_id(texture[CHIMNEY]);
	triangle_pos.transformation_matrix = glm::translate(0.0f, -3.0f, 0.0f);
	chimney_pos.transformation_matrix = glm::translate(-5.0f, 0.0f, 0.0f) * glm::scale(-1.0f, 1.0f, 1.0f);

	scene_quad = object_node(  glm::translate(0.0f, 0.0f, -7.0f) * glm::scale(4.0f, 3.0f, 1.0f), bg_vao_id, 6, SH_TEXTURE);
	scene_quad.set_texture_id(scene_tex_id[0]);

	root.add_child(bg);
	//root.add_child(triangle);
	root.add_child(atoff);
	root.add_child(atoff2);
	//atoff2.add_child(chimney);
	//atoff2.add_child(chimney_pos);
	//chimney_pos.add_child(chimney);
	//triangle.add_child(triangle_pos);
	//triangle.add_child(model);
	//triangle_pos.add_child(triangle2);#

	//arrange a grid of equal cubes
	float spread = 5.0f;
	for (int x=0; x < num_cubes; x++) {
		for (int y=0; y < num_cubes; y++) {
			int position = num_cubes*x + y;
			cube_node[position] = transformation_node(glm::translate(x*spread - 10.0f, 0.0f, -y*spread));
			cube_node[position].add_child(atoff);
			root.add_child(cube_node[position]);
		}
	}

	//skybox init
	init_skybox();

	return true; //everything went fine
}

bool demo01::init_fbo()
{
	glGenTextures(3, depth_tex_id);
	glGenTextures(3, scene_tex_id);
	glGenFramebuffers(3, fbo_id);

	for (int i=0; i < 3; i++) {

	//depth texture
	glBindTexture(GL_TEXTURE_2D, depth_tex_id[i]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
		GL_COMPARE_REF_TO_TEXTURE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width,
	height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);

	//color texture
	glBindTexture(GL_TEXTURE_2D, scene_tex_id[i]);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
		GL_COMPARE_REF_TO_TEXTURE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width,
	height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

	glBindFramebuffer(GL_FRAMEBUFFER, fbo_id[i]);
	glBindTexture(GL_TEXTURE_2D, scene_tex_id[i]);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 ,
	GL_TEXTURE_2D, scene_tex_id[i], 0);

	// create a depth-buffer in case depth texture doesnt work
	//glGenRenderbuffers(1, depth_buffer[i]);
	//// bind our depth-buffer
	//glBindRenderbuffer(GL_RENDERBUFFER, depth_buffer[i]);
	//// create storage for our renderbuffer
	//glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT,
	//width, height);
	//// attach renderbuffer to FBO
	//glFramebufferRenderbuffer(GL_FRAMEBUFFER,
	//GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buffer[i]);

	glBindTexture(GL_TEXTURE_2D, depth_tex_id[i]);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
	GL_TEXTURE_2D, depth_tex_id[i], 0);
	

	std::cout << "framebuffer completeness check:\n";
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
		// ToDo:
		printf("GL_FRAMEBUFFER_COMPLETE error 0x%x\n", glCheckFramebufferStatus(GL_FRAMEBUFFER));
	}
	std::cout << "framebuffer completeness check done\n";

	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	//---------------------
	//init shadow/depth map
	//---------------------
		// create the texture we'll use for the shadowmap
	glGenTextures(1, &shadow_tex_id);
	glBindTexture(GL_TEXTURE_2D, shadow_tex_id);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
	//	GL_COMPARE_REF_TO_TEXTURE);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width,
	height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
	glGenFramebuffers(1, &shadow_fbo);
	glBindFramebuffer(GL_FRAMEBUFFER, shadow_fbo);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
	GL_TEXTURE_2D, shadow_tex_id, 0);
	glDrawBuffer(GL_NONE); // essential for depth-only FBOs!!!
	glReadBuffer(GL_NONE); // essential for depth-only FBOs!!!
	

	std::cout << "framebuffer completeness check:\n";
	if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	{
		// ToDo:
		printf("GL_FRAMEBUFFER_COMPLETE error 0x%x", glCheckFramebufferStatus(GL_FRAMEBUFFER));
	}
	std::cout << "framebuffer completeness check done\n";

	glBindFramebuffer(GL_FRAMEBUFFER, 0);


	return true;
}

void demo01::draw()
{
	load_identity(modelview_matrix);

	//set camera position
	look_at(cam_eye, cam_center, cam_up);

	//position light
	glm::vec4 temp =  modelview_matrix * glm::vec4(light_0, 0.0f);
	transformed_light_0 = glm::vec3(temp.x, temp.y, temp.z);

	glBindTexture(GL_TEXTURE_2D, texture[TREE]);

	//spin triangle
	triangle.transformation_matrix = glm::rotate(static_cast<GLfloat>(10.f * glfwGetTime()), 0.0f, 1.f, 0.f);

	//-----------------------
	//generate the depth map (from camera position)
	//-----------------------

	glBindFramebuffer(GL_FRAMEBUFFER, shadow_fbo);
	glViewport(0,0,width,height);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	render_shadowmap(root);
	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	//set shadow texture uniform here
	//glActiveTexture(GL_TEXTURE1);
	//glBindTexture(GL_TEXTURE_2D, shadow_tex_id);
	//gauss_vertical->bind();
	//gauss_vertical->set_uniform("in_depth_texture", 1);
	//gauss_horizontal->bind();
	//gauss_horizontal->set_uniform("in_depth_texture", 1);

	//glActiveTexture(GL_TEXTURE0);


	glUseProgram(0);
	//-----------------------
	//actual scene rendering
	//-----------------------	
	glBindFramebuffer(GL_FRAMEBUFFER, fbo_id[0]);
	//glEnable(GL_FRAMEBUFFER_SRGB);
	glViewport(0,0,width,height);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	load_identity(modelview_matrix);

	//set camera position
	look_at(view_matrix, cam_eye, cam_center, cam_up);

	//glDepthMask(GL_FALSE);
	////draw skybox
	//draw_skybox();
	//glDepthMask(GL_TRUE);

	//draw scenegraph
	render_node(root);
	
	glUseProgram(0);
	//glDisable(GL_FRAMEBUFFER_SRGB);
	glBindFramebuffer(GL_FRAMEBUFFER, 0); 

	//----------------
	// post-processing
	//----------------
	glBindFramebuffer(GL_FRAMEBUFFER, fbo_id[1]);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glViewport(0,0,width,height);

	draw_postprocess(*gauss_horizontal, scene_tex_id[0], shadow_tex_id);

	glBindFramebuffer(GL_FRAMEBUFFER, fbo_id[2]); 
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glViewport(0,0,width,height);

	draw_postprocess(*gauss_vertical, scene_tex_id[1], shadow_tex_id);

	glBindFramebuffer(GL_FRAMEBUFFER, 0); 
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_FRAMEBUFFER_SRGB);
	//do the mix of scene_tex_id 0 and 2 here (based on depth texture)
	//set blur texture
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, scene_tex_id[2]);
	//set depth texture
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, shadow_tex_id);

	draw_postprocess(*pp_dof, scene_tex_id[0], shadow_tex_id);
	glDisable(GL_FRAMEBUFFER_SRGB);
	
	//culling statistics
	std::ostringstream out;
	out << number_culled;
	std::string str(out.str());
	str = "demo01  -  number of culled objects: " + str + "  ";
	set_title(str);
	number_culled = 0;
}

void demo01::cleanup()
{
	release_vbo_vao(vbo_id, &vao_id,3);
	release_vbo_vao(bg_vbo_id, &bg_vao_id, 4);
	release_vbo_vao(cube_vbo_id, &cube_vao_id, 3);
	release_vbo_vao(skybox_vbo, &skybox_vao, 3);
	release_vbo_vao(pp_vbo, &pp_vao_id, 2);
	glDeleteFramebuffers(2, fbo_id);
	glDeleteRenderbuffers(2, depth_buffer);

}

void demo01::release_vbo_vao(GLuint *vbo_id, GLuint *vao_id,  int numbuffers)
{
	glDeleteVertexArrays(1, vao_id);

	glDeleteBuffers(numbuffers, vbo_id);

	*vao_id = 0;
	for(int i=0; i<numbuffers; i++)
	{
		vbo_id[i] = 0;
	}
}


void demo01::init_vbo_vao(const Shader &shader, GLuint *vbo_id, GLuint *vao_id)
{
	// Our triangle data
	static GLfloat triangle_positions[] = {	 0.0f,		  1.0f, 0.0f, 1.0f,
		0.8660254f, -0.5f, 0.0f, 1.0f,
		-0.8660254f, -0.5f, 0.0f, 1.0f	};

	static GLfloat triangle_colors[] = {	1, 0, 0,
		0, 1, 0,
		0, 0, 1	};

	static GLuint triangle_indices[] = {0,1,2};

	// Allocate and assign a Vertex Array Object to our handle

	glGenVertexArrays(1, vao_id);

	// Bind our Vertex Array Object as the current used object

	glBindVertexArray(*vao_id);

	// Allocate and assign two Vertex Buffer Objects (VBOs) to our handle
	glGenBuffers(3, vbo_id);

	// Bind our first VBO as being the active buffer and storing vertex attributes (coordinates)	
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[0]);
	glBufferData(GL_ARRAY_BUFFER, 3 * 4 * sizeof(GLfloat), triangle_positions, GL_STATIC_DRAW);

	GLint vertex_location = shader.get_attrib_location("in_position");
	glEnableVertexAttribArray(vertex_location);
	glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, 
		GL_FALSE, 0, NULL);

	// Repeat for second VBO storing vertex colors
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[1]);
	glBufferData(GL_ARRAY_BUFFER, 3 * 3 * sizeof(GLfloat), triangle_colors, GL_STATIC_DRAW);

	GLint color_location = shader.get_attrib_location("in_color");
	glEnableVertexAttribArray(color_location);
	glVertexAttribPointer(	color_location, 3, GL_FLOAT, 
		GL_FALSE, 0, NULL);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_id[2]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3*sizeof(GLuint), triangle_indices, GL_STATIC_DRAW);

	triangle = geometry_node(glm::mat4(1.0f), *vao_id, 3, SH_MINIMAL);
	triangle2 = geometry_node(glm::mat4(1.0f), *vao_id, 3, SH_MINIMAL);

	// unbind VAO
	glBindVertexArray(0);

	// Unbind VBOs
	glBindBuffer(GL_ARRAY_BUFFER, 0);


}

//init background quad
void demo01::init_bg(const Shader &shader, GLuint *vbo_id, GLuint *vao_id)
{
		// Our triangle data
	GLfloat bg_positions[] = {	 
		-1.0f, -1.0f, 0.0f, 1.0f,
		1.0f, -1.0f, 0.0f, 1.0f,
		1.0f, 1.0f, 0.0f, 1.0f,
		-1.0f, 1.0f, 0.0f, 1.0f	};

	GLfloat bg_normals[] = {	
		0, 0, 1,
		0, 0, 1,
		0, 0, 1,
		0, 0, 1	};

	GLfloat bg_texcoords[] = {	 
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		0.0f, 1.0f	};

	GLuint bg_indices[] = {
		0,1,2,3,0,2
	};


	// Allocate and assign a Vertex Array Object to our handle

	glGenVertexArrays(1, vao_id);

	// Bind our Vertex Array Object as the current used object

	glBindVertexArray(*vao_id);

	// Allocate and assign two Vertex Buffer Objects (VBOs) to our handle
	glGenBuffers(4, vbo_id);

	// Bind our first VBO as being the active buffer and storing vertex attributes (coordinates)	
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[0]);
	glBufferData(GL_ARRAY_BUFFER, 4 * 4 * sizeof(GLfloat), bg_positions, GL_STATIC_DRAW);

	shader.set_attrib_location("in_position", 4);
	//GLint vertex_location = shader.get_attrib_location("in_position");
	//std::cout << "vertex_location of bg: " << vertex_location << std::endl;
	//glEnableVertexAttribArray(vertex_location);
	//glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, 
	//	GL_FALSE, 0, NULL);
	get_errors();

	// Repeat for second VBO storing vertex colors
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[1]);
	glBufferData(GL_ARRAY_BUFFER, 4 * 3 * sizeof(GLfloat), bg_normals, GL_STATIC_DRAW);

	GLint normal_location = shader.get_attrib_location("in_normal");
	std::cout << "normal_location of bg: " << normal_location << std::endl;
	glEnableVertexAttribArray(normal_location);
	glVertexAttribPointer(	normal_location, 3, GL_FLOAT, 
		GL_FALSE, 0, NULL);
	get_errors();

	//vbo for texcoords
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[2]);
	glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), bg_texcoords, GL_STATIC_DRAW);
	
	GLint texcoord_location = shader.get_attrib_location("in_texcoord");
	glEnableVertexAttribArray(texcoord_location);
	glVertexAttribPointer(	texcoord_location, 2, GL_FLOAT, GL_FALSE, 0, NULL);

	//vbo for texcoords
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_id[3]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(bg_indices), bg_indices, GL_STATIC_DRAW);

	//glBindTexture(GL_TEXTURE_2D, texture[0]);
	get_errors();

	// unbind VAO
	glBindVertexArray(0);

	// Unbind VBOs
	glBindBuffer(GL_ARRAY_BUFFER, 0);


}


//init atoff object
//returns number of elements
int demo01::init_cube(const Shader &shader, GLuint *vbo_id, GLuint *vao_id)
{
	std::vector<GLfloat> points, normals;
	std::vector<GLuint> indices;

	std::string file = "../../atoff-3d/teapot-highres.atoff";
	read_atoff_object(file, points, normals, indices);

	// Allocate and assign a Vertex Array Object to our handle

	glGenVertexArrays(1, vao_id);

	// Bind our Vertex Array Object as the current used object

	glBindVertexArray(*vao_id);

	// Allocate and assign two Vertex Buffer Objects (VBOs) to our handle
	glGenBuffers(3, vbo_id);

	// Bind our first VBO as being the active buffer and storing vertex attributes (coordinates)	
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[0]);
	glBufferData(GL_ARRAY_BUFFER, points.size() * sizeof(GLfloat), &points[0], GL_STATIC_DRAW);

	GLint vertex_location = shader.get_attrib_location("in_position");
	std::cout << "vertex_location of cube: " << vertex_location << std::endl;
	glEnableVertexAttribArray(vertex_location);
	glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, 
		GL_FALSE, 0, NULL);
	get_errors();

	// Repeat for second VBO storing vertex colors
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id[1]);
	glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(GLfloat), &normals[0], GL_STATIC_DRAW);

	GLint normal_location = shader.get_attrib_location("in_normal");
	std::cout << "normal_location of cube: " << normal_location << std::endl;
	glEnableVertexAttribArray(normal_location);
	glVertexAttribPointer(	normal_location, 3, GL_FLOAT, 
		GL_FALSE, 0, NULL);
	get_errors();

	//vbo for texcoords
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo_id[2]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), &indices[0], GL_STATIC_DRAW);
	
	//GLint texcoord_location = shader.get_attrib_location("in_texcoord");
	//glEnableVertexAttribArray(texcoord_location);
	//glVertexAttribPointer(	texcoord_location, 2, GL_FLOAT, GL_FALSE, 0, NULL);

	////glBindTexture(GL_TEXTURE_2D, texture[0]);
	//GLint tex_location = texture_shader->get_uniform_location("firstTexture");
	//std::cout << "location of texture_01: " << tex_location << std::endl;
	//glUniform1i(tex_location, 0); //TODO: WHY DONT I NEED TO SET THIS?
	get_errors();

	//scenegraph testing
	// unbind VAO
	glBindVertexArray(0);

	// Unbind VBOs
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	return indices.size();
}

void demo01::init_model(const Shader &shader)
{
	object_model.buildVBOs(shader);
}

// This draws a shaded 2D triangle on the screen.
void demo01::draw_vao (const Shader &shader, GLuint vao_id, int vertices, GLenum draw_type)
{
	shader.bind();


	// Set our matrix uniform
	GLint modelview_location = shader.get_uniform_location("modelview_matrix");
	glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(modelview_matrix));

		// Set our matrix uniform
	GLint perspective_location = shader.get_uniform_location("perspective_matrix");
	glUniformMatrix4fv( perspective_location, 1, GL_FALSE, glm::value_ptr(perspective_matrix));

	glBindVertexArray(vao_id);

	// Draw triangle in VAO
	//glDrawArrays(draw_type, 0, vertices);
	glDrawElements(draw_type, vertices, GL_UNSIGNED_INT, 0);

	glBindVertexArray(0);

	// Unbind VBOs
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	shader.unbind();
}

bool demo01::loadGLTextures() {
	bool status = false;
	tga::Texture tex;

	if (tga::LoadTGA(&tex, "../../textures/bg1.tga"))
	{
		status = true;
		glGenTextures(1, &texture[0]);					// Create The Texture

		// Typical Texture Generation Using Data From The Bitmap
		glBindTexture(GL_TEXTURE_2D, texture[0]);
		// Generate The Texture
		glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, tex.width, tex.height, 0, tex.type, GL_UNSIGNED_BYTE, tex.imageData);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);	// Linear Filtering
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);	// Linear Filtering
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 
		if (tex.type == GL_RGB)
		{
			std::cout << "RGB\n";
		}
		else if (tex.type == GL_RGBA)
		{
			std::cout << "RGBA\n";
		}
		else
		{
			std::cout << "smth else\n";
		}
	}

	if (tex.imageData)					// If Texture Image Exists
	{
		free(tex.imageData);				// Free The Texture Image Memory
	}

	return status;
}

void demo01::process_keys()
{

	//factor determines the speed of the camera movement
	float factor = static_cast<float>(delta()) * 5;

	if (glfwGetKey('+')) {
		pp_border = min<GLfloat>(1.0, pp_border + factor/5);
	}
	if (glfwGetKey('-')) {
		pp_border = max<GLfloat>(0.0, pp_border - factor/5);
	}
	if (glfwGetKey(GLFW_KEY_ENTER)) {
		view_frustrum.debug();
	}


	update_camera_movement(factor);
	view_frustrum.set_camera(cam_eye, cam_center, cam_up);

}

//recursively render the scenegraph starting at node
void demo01::render_node(scene_node& node)
{
	bool cull = false;

	//save current transformation
	push_modelview_matrix();

	//apply transformation of this node
	modelview_matrix *= node.transformation_matrix;

	//setup shader and render node
	//Shader* current_shader; //point to the shader of the node
	GLint modelview_location, perspective_location, light_location;
	switch (node.shader_type())
	{
	case SH_NONE:
		break;
	case SH_MINIMAL:
		// Set our matrix uniform
		minimal->bind();
		modelview_location = minimal->get_uniform_location("modelview_matrix");
		glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(view_matrix * modelview_matrix));

			// Set our matrix uniform
		perspective_location = minimal->get_uniform_location("perspective_matrix");
		glUniformMatrix4fv( perspective_location, 1, GL_FALSE, glm::value_ptr(perspective_matrix));
		break;
	case SH_SMOOTH:
		phong->bind();
		//set light
		light_location = phong->get_uniform_location("light_position");
		glUniform3f(light_location, transformed_light_0.x, transformed_light_0.y, transformed_light_0.z);

		modelview_location = phong->get_uniform_location("modelview_matrix");
		glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(view_matrix * modelview_matrix));

		// Set our matrix uniform
		perspective_location = phong->get_uniform_location("perspective_matrix");
		glUniformMatrix4fv( perspective_location, 1, GL_FALSE, glm::value_ptr(perspective_matrix));
		break;
	case SH_TOON:
		// Set our matrix uniform
		toon->bind();
		modelview_location = toon->get_uniform_location("modelview_matrix");
		glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(view_matrix * modelview_matrix));

		// Set our matrix uniform
		perspective_location = toon->get_uniform_location("perspective_matrix");
		glUniformMatrix4fv( perspective_location, 1, GL_FALSE, glm::value_ptr(perspective_matrix));
		break;
	case SH_TEXTURE:
		//view frustrum culling
		if (in_frustrum(node)) {
				cull = true;
				number_culled++;
				break;
		}
		// Set our matrix uniform
		texture_shader->bind();

		//do preparations before rendering
		node.pre_render();

		//set light
		texture_shader->set_uniform("light_position", transformed_light_0);

		texture_shader->set_uniform("in_texture", 0);

		texture_shader->set_uniform("modelview_matrix", view_matrix * modelview_matrix);

		// Set our matrix uniform
		texture_shader->set_uniform("perspective_matrix", perspective_matrix);

		break;
	default:
		break;

	}

	if (!cull) {
		node.render();
	}

	//render children
	std::vector<scene_node*>::iterator it;
	for (it = node.children.begin(); it != node.children.end(); it++)
	{
		render_node(**it);
	}

	//restore previous transformation
	pop_modelview_matrix();
}

//taken from nopper tutorials
bool demo01::init_skybox()
{
	//texture setup
	glGenTextures(1, &skybox_texture);
	get_errors();

	glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_texture);

	tga::Texture tex;

	string filepaths[] = {
		"../../textures/alpine_right.tga",
		"../../textures/alpine_left.tga",
		"../../textures/alpine_top.tga",
		"../../textures/alpine_bottom.tga",
		"../../textures/alpine_front.tga",
		"../../textures/alpine_back.tga",
	};

	for(int side = 0; side < 6; side++) {

		if (tga::LoadTGA(&tex, filepaths[side].c_str()))
		{
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + side, 0, GL_SRGB8_ALPHA8, tex.width, tex.height, 0, tex.type, GL_UNSIGNED_BYTE, tex.imageData);

		
			if (tex.imageData)					// If Texture Image Exists
			{
				free(tex.imageData);				// Free The Texture Image Memory
			}
		}
	}

	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER,GL_LINEAR);	// Linear Filtering
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,GL_LINEAR);	// Linear Filtering
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	//vao/vbo setup
	// Points of a cube.
	GLfloat points[] = {  -0.5f, -0.5f, -0.5f, 1.0f,
						  -0.5f, -0.5f, +0.5f, 1.0f,
						  +0.5f, -0.5f, +0.5f, 1.0f,
						  +0.5f, -0.5f, -0.5f, 1.0f,
						  -0.5f, +0.5f, -0.5f, 1.0f,
						  -0.5f, +0.5f, +0.5f, 1.0f,
						  +0.5f, +0.5f, +0.5f, 1.0f,
						  +0.5f, +0.5f, -0.5f, 1.0f,
						  -0.5f, -0.5f, -0.5f, 1.0f,
						  -0.5f, +0.5f, -0.5f, 1.0f,
						  +0.5f, +0.5f, -0.5f, 1.0f,
						  +0.5f, -0.5f, -0.5f, 1.0f,
						  -0.5f, -0.5f, +0.5f, 1.0f,
						  -0.5f, +0.5f, +0.5f, 1.0f,
						  +0.5f, +0.5f, +0.5f, 1.0f,
						  +0.5f, -0.5f, +0.5f, 1.0f,
						  -0.5f, -0.5f, -0.5f, 1.0f,
						  -0.5f, -0.5f, +0.5f, 1.0f,
						  -0.5f, +0.5f, +0.5f, 1.0f,
						  -0.5f, +0.5f, -0.5f, 1.0f,
						  +0.5f, -0.5f, -0.5f, 1.0f,
						  +0.5f, -0.5f, +0.5f, 1.0f,
						  +0.5f, +0.5f, +0.5f, 1.0f,
						  +0.5f, +0.5f, -0.5f, 1.0f
						};

	// Normals of a cube.
	GLfloat normals[] = { +0.0f, -1.0f, +0.0f,
						  +0.0f, -1.0f, +0.0f,
						  +0.0f, -1.0f, +0.0f,
						  +0.0f, -1.0f, +0.0f,
						  +0.0f, +1.0f, +0.0f,
						  +0.0f, +1.0f, +0.0f,
						  +0.0f, +1.0f, +0.0f,
						  +0.0f, +1.0f, +0.0f,
						  +0.0f, +0.0f, -1.0f,
						  +0.0f, +0.0f, -1.0f,
						  +0.0f, +0.0f, -1.0f,
						  +0.0f, +0.0f, -1.0f,
						  +0.0f, +0.0f, +1.0f,
						  +0.0f, +0.0f, +1.0f,
						  +0.0f, +0.0f, +1.0f,
						  +0.0f, +0.0f, +1.0f,
						  -1.0f, +0.0f, +0.0f,
						  -1.0f, +0.0f, +0.0f,
						  -1.0f, +0.0f, +0.0f,
						  -1.0f, +0.0f, +0.0f,
						  +1.0f, +0.0f, +0.0f,
						  +1.0f, +0.0f, +0.0f,
						  +1.0f, +0.0f, +0.0f,
						  +1.0f, +0.0f, +0.0f
						};

	// The associated indices.
	GLuint indices[] = {		0, 2, 1,
								0, 3, 2, 
								4, 5, 6,
								4, 6, 7,
								8, 9, 10,
								8, 10, 11, 
								12, 15, 14,
								12, 14, 13, 
								16, 17, 18,
								16, 18, 19, 
								20, 23, 22,
								20, 22, 21
						};

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

	// Allocate and assign two Vertex Buffer Objects (VBOs) to our handle
	glGenBuffers(3, skybox_vbo);

	// Bind our first VBO as being the active buffer and storing vertex attributes (coordinates)	
	glBindBuffer(GL_ARRAY_BUFFER, skybox_vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
	get_errors();

	GLint vertex_location = texture_cm->get_attrib_location("in_position");
	std::cout << "vertex_location of skybox: " << vertex_location << std::endl;
	glEnableVertexAttribArray(vertex_location);
	glVertexAttribPointer(	vertex_location, 4, GL_FLOAT, 
		GL_FALSE, 0, NULL);
	get_errors();



	//vbo for texcoords
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, skybox_vbo[1]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	return true;
}

void demo01::draw_skybox(){
		texture_cm->bind();

		glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_texture);

		//set light
		GLint light_location = texture_cm->get_uniform_location("light_position");
		glUniform3f(light_location, transformed_light_0.x, transformed_light_0.y, transformed_light_0.z);
		
		GLint tex_location = texture_cm->get_uniform_location("in_texture");
		glUniform1i(tex_location, 0);

		GLint modelview_location = texture_cm->get_uniform_location("modelview_matrix");
		GLfloat size = 1.0f;
		glm::mat4 sizemat = glm::scale(size, size, size);
		glUniformMatrix4fv( modelview_location, 1, GL_FALSE, glm::value_ptr(modelview_matrix * sizemat));

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

		
		glBindVertexArray(skybox_vao);
		// Draw triangle in VAO
		glDrawElements(GL_TRIANGLES, 6*2*3, GL_UNSIGNED_INT, 0);

		glBindVertexArray(0);

		// Unbind VBOs

		texture_cm->unbind();

}

void demo01::init_postprocess()
{
	gauss_vertical->bind();

	// Our triangle data
	GLfloat positions[] = {	 
		-1.0f, -1.0f, 0.0f, 1.0f,
		1.0f, -1.0f, 0.0f, 1.0f,
		1.0f, 1.0f, 0.0f, 1.0f,
		-1.0f, 1.0f, 0.0f, 1.0f	};

	GLfloat texcoords[] = {	 
		0.0f, 0.0f,
		1.0f, 0.0f,
		1.0f, 1.0f,
		0.0f, 1.0f	};

	//build vao, vbo
	glGenVertexArrays(1, &pp_vao_id);
	glBindVertexArray(pp_vao_id);

	glGenBuffers(2, pp_vbo);
	glBindBuffer(GL_ARRAY_BUFFER, pp_vbo[0]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);
	
	gauss_vertical->set_attrib_location("in_position", 4);
	gauss_horizontal->bind();
	gauss_horizontal->set_attrib_location("in_position", 4);
	pp_dof->bind();
	pp_dof->set_attrib_location("in_position", 4);

	glBindBuffer(GL_ARRAY_BUFFER, pp_vbo[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(texcoords), texcoords, GL_STATIC_DRAW);
	
	pp_dof->set_attrib_location("in_texcoord", 2);
	gauss_horizontal->bind();
	gauss_horizontal->set_attrib_location("in_texcoord", 2);
	gauss_vertical->bind();
	gauss_vertical->set_attrib_location("in_texcoord", 2);

	//glBindTexture(GL_TEXTURE_2D, texture[0]);

	//set some uniforms
	gauss_vertical->set_uniform("height", static_cast<GLfloat>(height));
	gauss_vertical->set_uniform("border", static_cast<GLfloat>(pp_border));
	glm::mat4 ortho_matrix = glm::ortho(-1.0f, 1.0f, -1.0f, 1.0f);
	gauss_vertical->set_uniform("perspective_matrix",  ortho_matrix);
	gauss_vertical->set_uniform("in_texture", 0);

	gauss_horizontal->bind();
	gauss_horizontal->set_uniform("width", static_cast<GLfloat>(width));
	gauss_horizontal->set_uniform("border", static_cast<GLfloat>(pp_border));
	gauss_horizontal->set_uniform("perspective_matrix",  ortho_matrix);
	gauss_horizontal->set_uniform("in_texture", 0);

	pp_dof->bind();
	pp_dof->set_uniform("perspective_matrix",  ortho_matrix);
	
	pp_dof->set_uniform("in_texture", 0);
	pp_dof->set_uniform("in_depth_texture", 1);
	pp_dof->set_uniform("in_blurred_texture", 2);
	pp_dof->set_uniform("far_blur", 0.25f);
	pp_dof->set_uniform("near_blur", 0.05f);

	pp_dof->unbind();

	get_errors();

	glBindVertexArray(0);
}

void demo01::draw_postprocess(const Shader& shader, GLuint source_tex_id, GLuint z_tex_id)
{
		shader.bind();
		//shader.set_uniform("in_depth_texture", 1);

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, source_tex_id);
		//shader.set_uniform("in_texture", 0);

		shader.set_uniform("border", pp_border);
	
		glBindVertexArray(pp_vao_id);

		//glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glDrawArrays(GL_TRIANGLE_FAN , 0, 4);

		glBindVertexArray(0);

		// Unbind VBOs
		glBindBuffer(GL_ARRAY_BUFFER, 0);

		shader.unbind();
}

//recursively render the scenegraph starting at node
void demo01::render_shadowmap(scene_node& node)
{
	//save current transformation
	push_modelview_matrix();

	//apply transformation of this node
	modelview_matrix *= node.transformation_matrix;

	//setup shader and render node
	//Shader* current_shader; //point to the shader of the node
	switch (node.shader_type())
	{
	case SH_NONE:
		break;
	default:
		//// Set our matrix uniform
		shadowmap_gen->bind();

		//do preparations before rendering
		node.pre_render();

		shadowmap_gen->set_uniform("near_far", near_far);
		shadowmap_gen->set_uniform("modelview_matrix", modelview_matrix);

		// Set our matrix uniform
		shadowmap_gen->set_uniform("perspective_matrix", perspective_matrix);
		node.render();
		break;

	}


	glUseProgram(0);

	//render children
	std::vector<scene_node*>::iterator it;
	for (it = node.children.begin(); it != node.children.end(); it++)
	{
		render_shadowmap(**it);
	}

	//restore previous transformation
	pop_modelview_matrix();
}

bool demo01::in_frustrum(const scene_node& node) const
{
	//center point of our object
	glm::vec4 ref_point = modelview_matrix * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
	//bounding sphere radius
	glm::vec4 model_dimension = modelview_matrix * glm::vec4(node.get_dimension(), 1.0f);
	GLfloat radius = glm::length( model_dimension - ref_point);

	return !view_frustrum.sphere_in_frustrum(glm::vec3(ref_point.x, ref_point.y, ref_point.z), radius / 2.0f );

}