#include "CGFigure.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

//
// FUNCIÓN: CGFigure::~CGFigure()
//
// PROPÓSITO: Destructor de la figura
//
CGFigure::~CGFigure()
{
	if (vertices != NULL) delete[] vertices;
	if (indexes != NULL) delete[] indexes;
	if (normals != NULL) delete[] normals;
	if (textures != NULL) delete[] textures;

	// Delete vertex buffer objects
	glDeleteBuffers(4, VBO);
	glDeleteVertexArrays(1, &VAO);
}

//
// FUNCIÓN: CGFigure::InitBuffers()
//
// PROPÓSITO: Crea el VAO y los VBO y almacena todos los datos
//            en la GPU.
//
void CGFigure::InitBuffers()
{
    ConvertToAdjacency();
    location = glm::mat4(1.0f);

	// Create the Vertex Array Object
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	// Create the Vertex Buffer Objects
	glGenBuffers(4, VBO);

	// Copy data to video memory
	// Vertex data
	int buffsize = sizeof(GLfloat) * numVertices * 3;
	glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
	glBufferData(GL_ARRAY_BUFFER, buffsize, vertices, GL_STATIC_DRAW);

	// Normal data
	glBindBuffer(GL_ARRAY_BUFFER, VBO[NORMAL_DATA]);
	glBufferData(GL_ARRAY_BUFFER, buffsize, normals, GL_STATIC_DRAW);

	// Texture coordinates
	buffsize = sizeof(GLfloat) * numVertices * 2;
	glBindBuffer(GL_ARRAY_BUFFER, VBO[TEXTURE_DATA]);
	glBufferData(GL_ARRAY_BUFFER, buffsize, textures, GL_STATIC_DRAW);

	// Indexes
	buffsize = sizeof(GLushort) * numFaces * 6;
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, VBO[INDEX_DATA]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffsize, indexes, GL_STATIC_DRAW);

	delete[] vertices;
	delete[] indexes;
	delete[] normals;
	delete[] textures;

	vertices = NULL;
	indexes = NULL;
	normals = NULL;
	textures = NULL;

	glEnableVertexAttribArray(0); // Vertex position
	glBindBuffer(GL_ARRAY_BUFFER, VBO[VERTEX_DATA]);
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

	glEnableVertexAttribArray(1); // Vertex normals
	glBindBuffer(GL_ARRAY_BUFFER, VBO[NORMAL_DATA]);
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);

	glEnableVertexAttribArray(2); // Vertex textures
	glBindBuffer(GL_ARRAY_BUFFER, VBO[TEXTURE_DATA]);
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, 0);
}

//
// FUNCIÓN: CGFigure::SetMaterial(CGMaterial* m)
//
// PROPÓSITO: Asigna el material de la figura
//
void CGFigure::SetMaterial(CGMaterial* mat)
{
	material = mat;
}

//
// FUNCIÓN: CGFigure::ResetLocation()
//
// PROPÓSITO: Asigna la posición inicial de la figura 
//
void CGFigure::ResetLocation()
{
	location = glm::mat4(1.0f);
}

//
// FUNCIÓN: CGFigure::Translate(glm::vec3 t)
//
// PROPÓSITO: Añade un desplazamiento a la matriz de posición de la figura 
//
void CGFigure::Translate(glm::vec3 t)
{
	location = glm::translate(location, t);
}

//
// FUNCIÓN: CGFigure::Rotate(GLfloat angle, glm::vec3 axis)
//
// PROPÓSITO: Añade una rotación a la matriz de posición de la figura 
//
void CGFigure::Rotate(GLfloat angle, glm::vec3 axis)
{
	location = glm::rotate(location, glm::radians(angle), axis);
}

//
// FUNCIÓN: CGFigure::Draw(CGShaderProgram * program, glm::mat4 projection, glm::mat4 view)
//
// PROPÓSITO: Dibuja la figura
//
void CGFigure::Draw(CGShaderProgram* program, glm::mat4 projection, glm::mat4 view)
{
	glm::mat4 mvp = projection * view * location;
	program->SetUniformMatrix4("MVP", mvp);
	program->SetUniformMatrix4("ViewMatrix", view);
	program->SetUniformMatrix4("ModelViewMatrix", view * location);
    program->SetUniformMatrix4("ProjMatrix", projection);
	material->SetUniforms(program);

	glBindVertexArray(VAO);
	glDrawElements(GL_TRIANGLES_ADJACENCY, numFaces * 6, GL_UNSIGNED_SHORT, NULL);
}

//
// FUNCIÓN: CGFigure::ConvertToAdjancency()
//
// PROPÓSITO: Convierte la descripción de triángulos a triángulos con adjacencia
//
void CGFigure::ConvertToAdjacency()
{
    int numIndexesAdj = numFaces * 6;
    GLushort* indexesAdj = new GLushort[numIndexesAdj];
    GLushort MAX = (GLushort) (numVertices + 1);

    // Copy and make room for adjacency info
    for (int i = 0; i < numFaces*3; i += 3)
    {
        indexesAdj[i * 2 + 0] = indexes[i];
        indexesAdj[i * 2 + 1] = MAX;
        indexesAdj[i * 2 + 2] = indexes[i + 1];
        indexesAdj[i * 2 + 3] = MAX;
        indexesAdj[i * 2 + 4] = indexes[i + 2];
        indexesAdj[i * 2 + 5] = MAX;
    }

    // Find matching edges
     for (int i = 0; i < numIndexesAdj; i += 6)
    {
        // A triangle
        GLuint ai1 = indexesAdj[i];
        GLuint bi1 = indexesAdj[i + 2];
        GLuint ci1 = indexesAdj[i + 4];

        glm::vec3 a1 = glm::vec3(vertices[ai1 * 3], vertices[ai1 * 3 + 1], vertices[ai1 * 3 + 2]);
        glm::vec3 b1 = glm::vec3(vertices[bi1 * 3], vertices[bi1 * 3 + 1], vertices[bi1 * 3 + 2]);
        glm::vec3 c1 = glm::vec3(vertices[ci1 * 3], vertices[ci1 * 3 + 1], vertices[ci1 * 3 + 2]);

        // Scan subsequent triangles
        for (int j = i + 6; j < numIndexesAdj; j += 6)
        {
            GLuint ai2 = indexesAdj[j];
            GLuint bi2 = indexesAdj[j + 2];
            GLuint ci2 = indexesAdj[j + 4];

            glm::vec3 a2 = glm::vec3(vertices[ai2 * 3], vertices[ai2 * 3 + 1], vertices[ai2 * 3 + 2]);
            glm::vec3 b2 = glm::vec3(vertices[bi2 * 3], vertices[bi2 * 3 + 1], vertices[bi2 * 3 + 2]);
            glm::vec3 c2 = glm::vec3(vertices[ci2 * 3], vertices[ci2 * 3 + 1], vertices[ci2 * 3 + 2]);

            // Edge 1 == Edge 1
            if ((a1 == a2 && b1 == b2) || (a1 == b2 && b1 == a2))
            {
                indexesAdj[i + 1] = ci2;
                indexesAdj[j + 1] = ci1;
            }
            // Edge 1 == Edge 2
            if ((a1 == b2 && b1 == c2) || (a1 == c2 && b1 == b2))
            {
                indexesAdj[i + 1] = ai2;
                indexesAdj[j + 3] = ci1;
            }
            // Edge 1 == Edge 3
            if ((a1 == c2 && b1 == a2) || (a1 == a2 && b1 == c2))
            {
                indexesAdj[i + 1] = bi2;
                indexesAdj[j + 5] = ci1;
            }
            // Edge 2 == Edge 1
            if ((b1 == a2 && c1 == b2) || (b1 == b2 && c1 == a2))
            {
                indexesAdj[i + 3] = ci2;
                indexesAdj[j + 1] = ai1;
            }
            // Edge 2 == Edge 2
            if ((b1 == b2 && c1 == c2) || (b1 == c2 && c1 == b2))
            {
                indexesAdj[i + 3] = ai2;
                indexesAdj[j + 3] = ai1;
            }
            // Edge 2 == Edge 3
            if ((b1 == c2 && c1 == a2) || (b1 == a2 && c1 == c2))
            {
                indexesAdj[i + 3] = bi2;
                indexesAdj[j + 5] = ai1;
            }
            // Edge 3 == Edge 1
            if ((c1 == a2 && a1 == b2) || (c1 == b2 && a1 == a2))
            {
                indexesAdj[i + 5] = ci2;
                indexesAdj[j + 1] = bi1;
            }
            // Edge 3 == Edge 2
            if ((c1 == b2 && a1 == c2) || (c1 == c2 && a1 == b2))
            {
                indexesAdj[i + 5] = ai2;
                indexesAdj[j + 3] = bi1;
            }
            // Edge 3 == Edge 3
            if ((c1 == c2 && a1 == a2) || (c1 == a2 && a1 == c2))
            {
                indexesAdj[i + 5] = bi2;
                indexesAdj[j + 5] = bi1;
            }
        }
    }

    // Look for any outside edges
    for (int i = 0; i < numIndexesAdj; i += 6)
    {
        if (indexesAdj[i + 1] == MAX) indexesAdj[i + 1] = indexesAdj[i + 4];
        if (indexesAdj[i + 3] == MAX) indexesAdj[i + 3] = indexesAdj[i];
        if (indexesAdj[i + 5] == MAX) indexesAdj[i + 5] = indexesAdj[i + 2];
    }

    // Copy all data back into indexes
    delete[] indexes;
    indexes = indexesAdj;
}

