#include "CMesh.hpp"

void CMesh::bindMaterial(glm::mat4 model, glm::mat4 view, glm::mat4 projection, glm::vec3 lightPos, float intensity) {
	// Test the material.
	if (m_material == nullptr) {
		throw CLogicException("This mesh is not linked to a material.");
	}

	// Active the shader.
	m_material->m_shader->use();

	// Give the matrix to the shader.
	m_material->m_shader->setMat4("model", model);
	m_material->m_shader->setMat4("view", view);
	m_material->m_shader->setMat4("projection", projection);
	m_material->m_shader->setVec3("lightPos", lightPos);
	m_material->m_shader->setFloat("intensity", intensity);

	// Counter for each Texture Type :
	unsigned int countTex[TEXTURE_TYPE_NUMBER];
	for (unsigned int i = 0; i < TEXTURE_TYPE_NUMBER; i++) {
		countTex[i] = 0;
	}

	// For each texture in the material :
	for (unsigned int i = 0; i < m_material->m_textures.size(); i++) {

		// Active the shader unit.
		glActiveTexture(GL_TEXTURE0 + i);

		// Retrieve texture name.
		std::string name;

		switch (m_material->m_textures[i]->getType()) {
		case TEXTURE_DIFFUSE:
			name = "textureDiffuse" + std::to_string(countTex[0]);
			countTex[0]++;
			break;
		case TEXTURE_SPECULAR:
			name = "textureSpecular" + std::to_string(countTex[1]);
			countTex[1]++;
			break;
		case TEXTURE_NORMAL:
			name = "textureNormal" + std::to_string(countTex[2]);
			countTex[2]++;
			break;
		case TEXTURE_HEIGHT:
			name = "textureHeight" + std::to_string(countTex[3]);
			countTex[3]++;
			break;
		default:
			break;
		}

		// Get the uniform storage space of the shader.
		glUniform1i(glGetUniformLocation(m_material->m_shader->getId(), name.c_str()), i);

		// And finally bind the texture.
		glBindTexture(GL_TEXTURE_2D, m_material->m_textures[i]->getId());
	}

	// Always good practice to set everything back to defaults once configured.
	glActiveTexture(GL_TEXTURE0);
	


	// Counter for each color Type :
	unsigned int countCol[COLOR_TYPE_NUMBER];
	for (unsigned int i = 0; i < COLOR_TYPE_NUMBER; i++) {
		countCol[i] = 0;
	}

	// For each color in the material :
	for (unsigned int i = 0; i < m_material->m_colors.size(); i++) {

		// Retrieve texture name.
		std::string name;

		switch (m_material->m_colors[i].m_type) {
		case COLOR_DIFFUSE:
			name = "colorDiffuse" + std::to_string(countCol[0]);
			countCol[0]++;
			break;
		case COLOR_SPECULAR:
			name = "colorSpecular" + std::to_string(countCol[1]);
			countCol[1]++;
			break;
		default:
			break;
		}

		// Give the color as a vector 4.
		m_material->m_shader->setVec4(name, m_material->m_colors[i].m_vector);
	}

	// Active the shader.
	//m_material->m_shader->use();
}

CMesh::CMesh(void) :
	m_vertexes(),
	m_indexes(),
	m_material(nullptr),
	m_vertexBufferObject(0),
	m_elementBufferObject(0),
	m_vertexArrayObject(0) {
}

CMesh::~CMesh(void) {
	// Delete the VAO.
	if (m_vertexArrayObject != 0) {
		glDeleteVertexArrays(1, &m_vertexArrayObject);
	}

	if (m_material != nullptr)
	{
		delete m_material;
	}
}

std::vector<SVertex>& CMesh::getVertexes(void) {
	return m_vertexes;
}

std::vector<unsigned int>& CMesh::getIndexes(void) {
	return m_indexes;
}

SMaterial* CMesh::getMaterial(void) {
	return m_material;
}

void CMesh::setMaterial(SMaterial* material) {
	m_material = material;
}

void CMesh::setVertices(std::vector<SVertex> vertices)
{
	m_vertexes = vertices;
}

void CMesh::setIndexes(std::vector<unsigned int> indexes)
{
	m_indexes = indexes;
}

void CMesh::load(void) {
	if (m_vertexes.empty() || m_indexes.empty()) {
		throw CLogicException("This mesh do no contain vertex data.");
	}

	// Create the VAO.
	glGenVertexArrays(1, &m_vertexArrayObject);

	// Create the VBO.
	glGenBuffers(1, &m_vertexBufferObject);

	// Create the EBO.
	glGenBuffers(1, &m_elementBufferObject);


	// Open the VAO.
	glBindVertexArray(m_vertexArrayObject);

	// Open the VBO, and link it to the VAO.
	glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferObject);

	// Give the block of memory of vertexes vector (so, it works like a c list), and the struct is transparent du to their conception).
	glBufferData(GL_ARRAY_BUFFER, m_vertexes.size() * sizeof(SVertex), &m_vertexes[0], GL_STATIC_DRAW);

	// Open the EBO, and link it to the VAO.
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementBufferObject);

	// Give the block of memory of indexes vector (so, it works like a c list), and the struct is transparent du to their conception).
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexes.size() * sizeof(unsigned int), &m_indexes[0], GL_STATIC_DRAW);


	// Set the vertex attribute pointers.
	// Vertex Positions.
	glEnableVertexAttribArray(0);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)0);
	// Vertex normals.
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, m_normal));
	// Vertex texture coords.
	glEnableVertexAttribArray(2);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, m_texCoords));
	// Vertex tangent.
	glEnableVertexAttribArray(3);
	glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, m_tangent));
	// Vertex bitangent.
	glEnableVertexAttribArray(4);
	glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, m_bitangent));


	// Unbind the VAO.
	glBindVertexArray(0);
}

void CMesh::draw(glm::mat4 model, glm::mat4 view, glm::mat4 projection, glm::vec3 lightPos, float intensity) {
	if (m_vertexArrayObject == 0) {
		throw CLogicException("This mesh is not loaded in GPU, you can not draw it.");
	}

	// Bind the material.
	bindMaterial(model, view, projection, lightPos, intensity);

	// Enable the vertex array object, so the shader use the vertex attribute in this vao.
	glBindVertexArray(m_vertexArrayObject);

	// Render the triangles in the vertex array object.
	glDrawElements(GL_TRIANGLES, (GLsizei)m_indexes.size(), GL_UNSIGNED_INT, 0);

	// Disable the vertex array object.
	glBindVertexArray(0);
}