#include <opengl-playground/mesh.hpp>
#include <opengl-playground/globalcanvassettings.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <iostream>

Mesh::Mesh(std::vector<Vertex> vertices, std::vector<unsigned int> indices,
	   const Material &mat,
	   std::shared_ptr<OpenGlShaderProgram> shader) :
	OpenGlGraphics(shader)
{
	this->vertices = vertices;
	this->indices = indices;
	this->material = mat;
}

void Mesh::realize()
{
	glGenVertexArrays(1, &this->vao);
	glGenBuffers(1, &this->vbo);
	glGenBuffers(1, &this->ebo);

	glBindVertexArray(this->vao);

	glBindBuffer(GL_ARRAY_BUFFER, this->vbo);
	glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->ebo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0);
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, Normal));
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)offsetof(Vertex, TextureCoords));

	glBindVertexArray(0);
	std::cout << "Realizing Mesh" << std::endl;
}

void Mesh::render()
{
	this->render(this->model_matrix, this->normal_matrix);
}

void Mesh::render(const glm::mat4 &model_matrix, const glm::mat4 &normal_matrix)
{
	this->shaderprog->use();
	this->shaderprog->setUniformMat4("model_matrix", model_matrix);
	this->shaderprog->setUniformMat3("normal_matrix", normal_matrix);
	this->shaderprog->setUniformMat4("projection_view_matrix", GlobalCanvasSettings::getPVMatrix());
	this->shaderprog->setUniformVec3("camera_position", GlobalCanvasSettings::getCameraPosition());

	auto diffTex = this->material.getDiffuseTextures();
	auto specTex = this->material.getSpecularTextures();
	auto ambTex = this->material.getAmbientTextures();

	/* Setup the texture samplers */
	int tex_slot = 0;
	for (unsigned int i = 0; i < diffTex.size(); i++, tex_slot++) {
		std::string uniform_name;
		diffTex[i]->use(tex_slot);
		uniform_name = "material.diffuse" + std::to_string(i);
		this->shaderprog->setUniformInt(uniform_name, i);
	}

	for (unsigned int i = 0; i < specTex.size(); i++, tex_slot++) {
		std::string uniform_name;
		specTex[i]->use(tex_slot);
		uniform_name = "material.specular" + std::to_string(i);
		this->shaderprog->setUniformInt(uniform_name, i);
	}

	for (unsigned int i = 0; i < ambTex.size(); i++, tex_slot++) {
		std::string uniform_name;
		specTex[i]->use(tex_slot);
		uniform_name = "material.ambient" + std::to_string(i);
		this->shaderprog->setUniformInt(uniform_name, i);
	}

	if (diffTex.size() == 0) {
		this->shaderprog->setUniformVec3("material.diff_color", this->material.getDiffuseColor());
		this->shaderprog->setUniformBool("material.use_diff_color", true);
	} else {
		this->shaderprog->setUniformBool("material.use_diff_color", false);
	}

	if (specTex.size() == 0) {
		this->shaderprog->setUniformVec3("material.spec_color", this->material.getSpecularColor());
		this->shaderprog->setUniformBool("material.use_spec_color", true);
	} else {
		this->shaderprog->setUniformBool("material.use_spec_color", false);
	}

	if (specTex.size() == 0) {
		this->shaderprog->setUniformVec3("material.spec_color", this->material.getSpecularColor());
		this->shaderprog->setUniformBool("material.use_spec_color", true);
	} else {
		this->shaderprog->setUniformBool("material.use_spec_color", false);
	}

	if (diffTex.size() == 0 && ambTex.size() == 0) {
		this->shaderprog->setUniformVec3("material.amb_color", this->material.getAmbientColor());
		this->shaderprog->setUniformInt("material.use_amb_color", 2);
	} else {
		if (diffTex.size() > 0)
			this->shaderprog->setUniformInt("material.use_amb_color", 1);
		else
			this->shaderprog->setUniformInt("material.use_amb_color", 0);
	}

	glBindVertexArray(this->vao);
	glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
	glBindVertexArray(0);
}




