#include "phongentity.h"
#include "shader.h"
#include <glm/glm.hpp>
#include "phongmaterial.h"
#include "mesh.h"
#include <glm/ext.hpp>
#include "glassert.h"
#include "sparrowrenderer.h"

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

PhongEntity::PhongEntity(Mesh* myMesh) :
    mesh(myMesh),
    vao(0),
    nb_buffers(0),
    vbo(NULL),
    displayList(0),
    modelMatrix()
{}

void PhongEntity::draw(const glm::mat4 &viewMatrix, const glm::mat4 &projectionMatrix, Lights::Light* dirLight, Lights* pointLights)
{
    if(SparrowRenderer::isModernOpenGLAvailable())
        modernDraw(viewMatrix, projectionMatrix, dirLight, pointLights);
    else
        crappyDraw(viewMatrix, projectionMatrix, dirLight, pointLights);
}

void PhongEntity::modernDraw(const glm::mat4 &viewMatrix, const glm::mat4 &projectionMatrix, Lights::Light* dirLight, Lights* pointLights)
{
    glm::mat4 modelViewMatrix = viewMatrix * modelMatrix;
    glm::mat4 mvp = projectionMatrix * modelViewMatrix;
    glm::mat4 normalMatrix = glm::transpose(glm::inverse(modelViewMatrix));
	for(int i=0; i<mesh->indiceGroups.size(); ++i)
	{
		Material* mat = mesh->indiceGroups[i].material;
		mat->bindAttributes();
		Shader* shader = mat->getShader();
		shader->bindMatrix(shader->getLocation("viewMatrix"), viewMatrix);
		shader->bindMatrix(shader->getLocation("modelViewMatrix"), modelViewMatrix);
		shader->bindMatrix(shader->getLocation("normalMatrix"), normalMatrix);
		shader->bindMatrix(shader->getLocation("MVP"), mvp);
        shader->bindVec3Array(shader->getLocation("dirLight"), (glm::vec3*)dirLight, 2);
        pointLights->bind(shader->getLocation("pointLights"), shader->getLocation("nbPointLights"), shader);
		drawGroup(i);
	}
}

void PhongEntity::crappyDraw(const glm::mat4 &viewMatrix, const glm::mat4 &projectionMatrix, Lights::Light* dirLight, Lights* pointLights)
{
    glMatrixMode(GL_MODELVIEW);
    glm::mat4 modelViewMatrix = viewMatrix * modelMatrix;
    glLoadMatrixf(glm::value_ptr(modelViewMatrix));
    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(glm::value_ptr(projectionMatrix));
    glCallList(displayList);
}

void PhongEntity::modernInit(bool isDynamic)
{
    GLenum buffer_type = isDynamic ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW;

    // create VAO
    glAssert(glGenVertexArrays(1, &vao));
    glAssert(glBindVertexArray(vao));

    nb_buffers = 1; // positions buffer
    if(mesh->hasNormals())
        ++nb_buffers;
    if(mesh->hasTexCoords())
        ++nb_buffers;
    if(mesh->hasTangents())
        ++nb_buffers;

    nb_buffers += mesh->indiceGroups.size();

    // create VBOs
    vbo = new GLuint[nb_buffers]();
    glAssert(glGenBuffers(nb_buffers, vbo));

    for(const Mesh::Group &g : mesh->indiceGroups)
    {
        // init indices vbos
        glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[INDICES_BUFFER]));
        glAssert(glBufferData(GL_ARRAY_BUFFER, g.indices.size() * sizeof(GLuint), g.indices.data(), buffer_type));
    }

    // init positions vbo
    glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[POSITION_BUFFER]));
    glAssert(glBufferData(GL_ARRAY_BUFFER, mesh->positions.size() * sizeof(glm::vec3), mesh->positions.data(), buffer_type));

    if(mesh->hasNormals())
    {
        // init normals vbo
        glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[NORMAL_BUFFER]));
        glAssert(glBufferData(GL_ARRAY_BUFFER, mesh->normals.size() * sizeof(glm::vec3), mesh->normals.data(), buffer_type));
    }

    if(mesh->hasNormals())
    {
        // init texCoords vbo
        glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[TEXCOORD_BUFFER]));
        glAssert(glBufferData(GL_ARRAY_BUFFER, mesh->texCoords.size() * sizeof(glm::vec2), mesh->texCoords.data(), buffer_type));
    }

    if(mesh->hasTangents())
    {
        // init tangents vbo
        glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[TANGENT_BUFFER]));
        glAssert(glBufferData(GL_ARRAY_BUFFER, mesh->tangents.size() * sizeof(glm::vec3)*2, mesh->tangents.data(), buffer_type));
    }

    // unbind vao
    glAssert(glBindVertexArray(0));
}

void PhongEntity::crappyInit()
{
    displayList = glAssert(glGenLists(1));
    glAssert(glNewList(displayList, GL_COMPILE));
    for(int i=0; i<mesh->indiceGroups.size(); ++i)
    {
        PhongMaterial* mat = (PhongMaterial*)(mesh->indiceGroups[i].material);
        mat->bindAttributes();
        glAssert(glBegin(GL_TRIANGLES));
        for(int j=0; j<mesh->indiceGroups[i].indices.size(); ++j)
        {
            int vid = mesh->indiceGroups[i].indices[j];
            glNormal3fv(glm::value_ptr(mesh->normals[vid]));
            glTexCoord2fv(glm::value_ptr(mesh->texCoords[vid]));
            glVertex3fv(glm::value_ptr(mesh->positions[vid]));
        }
        glAssert(glEnd());
    }
    glAssert(glEndList());
}

void PhongEntity::initGL(bool isDynamic)
{
    if(SparrowRenderer::isModernOpenGLAvailable())
        modernInit(isDynamic);
    else
        crappyInit();
}

void PhongEntity::destroyGL()
{
    if(SparrowRenderer::isModernOpenGLAvailable())
    {
        glAssert(glDeleteVertexArrays(1, &vao));
        glAssert(glDeleteBuffers(nb_buffers, vbo));
        delete[] vbo;
        vbo = NULL;
    }
    else
        glAssert(glDeleteLists(displayList, 1));
}

void PhongEntity::drawGroup(int groupId)
{
    glAssert(glBindVertexArray(vao));
    glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[POSITION_BUFFER]));
    glAssert(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), BUFFER_OFFSET(0)));
    glAssert(glEnableVertexAttribArray(0));
    if(mesh->hasNormals())
    {
        glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[NORMAL_BUFFER]));
        glAssert(glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(glm::vec3), BUFFER_OFFSET(0)));
        glAssert(glEnableVertexAttribArray(1));
    }
    if(mesh->hasTexCoords())
    {
        glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[TEXCOORD_BUFFER]));
        glAssert(glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec2), BUFFER_OFFSET(0)));
        glAssert(glEnableVertexAttribArray(2));
    }
    if(mesh->hasTangents())
    {
        glAssert(glBindBuffer(GL_ARRAY_BUFFER, vbo[TANGENT_BUFFER]));
        glAssert(glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Mesh::Tangents), BUFFER_OFFSET(0)));
        glAssert(glEnableVertexAttribArray(3));
        glAssert(glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Mesh::Tangents), BUFFER_OFFSET(sizeof(glm::vec3))));
        glAssert(glEnableVertexAttribArray(4));
    }
	glAssert(glDrawElements(GL_TRIANGLES, mesh->indiceGroups[groupId].indices.size(), GL_UNSIGNED_INT, mesh->indiceGroups[groupId].indices.data()));
}

PhongEntity* PhongEntity::clone()
{
    PhongEntity* myClone = new PhongEntity(mesh);
    myClone->modelMatrix = modelMatrix;
    myClone->displayList = displayList;
    myClone->nb_buffers = nb_buffers;
    myClone->vao = vao;
    myClone->vbo = vbo;
}