#include "mesh.h"

void Vertex::AddBoneData(uint BoneID, float Weight){
    for (int i = 0 ; i < 4 ; i++) {
        if (weights[i] == 0.0) {
            IDs[i]     = BoneID;
            weights[i] = Weight;
            return;
        }
    }
    qCritical()<<"More tahn 4 Bones at Vertex";
}

Mesh::MeshEntry::MeshEntry()
{
    materialIndex = 0xFFFFFFFF;
    numIndex = 0;
    VB = 0xFFFFFFFF;
    IB = 0xFFFFFFFF;

};

void Mesh::MeshEntry::init(QOpenGLFunctions_4_3_Core *f,QVector<Vertex>& Vertices,
                           QVector<unsigned int>& Indices){
    this->f = f;
    numIndex = Indices.size();

    f->glGenBuffers(1, &VB);
    f->glBindBuffer(GL_ARRAY_BUFFER, VB);
    f->glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * Vertices.size(), &Vertices[0], GL_STATIC_DRAW);

    f->glGenBuffers(1, &IB);
    f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IB);
    f->glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * numIndex, &Indices[0], GL_STATIC_DRAW);
}

Mesh::MeshEntry::~MeshEntry()
{
    f->glDeleteBuffers(1, &VB);
    f->glDeleteBuffers(1, &IB);
}

Mesh::MaterialInfo::MaterialInfo()
{
    Name = QString("Default");
    Diffuse = QVector3D();
    Specular= QVector3D();
    hasTexture = false;
    texture = Texture();
    Shininess = 30;
}

Mesh::Node::Node()
{
    name = QString("DefaultNode");
    transformation.setToIdentity();
    meshes.clear();
    children.clear();
}



Mesh::Mesh(QOpenGLFunctions_4_3_Core *f,QString fileName)
{
    loaded = false;
    this->f =f;
    Assimp::Importer importer;
    this->scene= importer.ReadFile(fileName.toStdString(),
                                             //aiProcess_CalcTangentSpace |
                                             aiProcess_GenSmoothNormals |
                                             //aiProcess_JoinIdenticalVertices |
                                             //aiProcess_SortByPType |
                                             // ? aiProcess_FlipUVs |
                                             aiProcess_Triangulate
                                             );
    qDebug()<<"File Read";
    if( !scene)
    {
        qDebug()<<importer.GetErrorString();
    }else {
        QString dir = QFileInfo(fileName).path();

        //Init Materials
        materials.resize(scene->mNumMaterials);
        if(scene->HasMaterials()){
            //Init Materials
            for (unsigned int i = 0 ; i < scene->mNumMaterials ; i++) {
                const aiMaterial* material = scene->mMaterials[i];
                aiString mname;
                material->Get(AI_MATKEY_NAME, mname);
                qDebug()<<"Loading Material"<<(i+1)<<"/"<<scene->mNumMaterials<<":"<<mname.C_Str();
                initMaterial(dir, i, material);
            }
        }
        qDebug()<<"Materials Read";

        //Init Mesh
        if(scene->HasMeshes())
        {
            entries.resize(scene->mNumMeshes);
            for (int i = 0 ; i < entries.size() ; i++) {
                aiMesh* meshEntry = scene->mMeshes[i];
                initMeshEntry(i, meshEntry);
            }
        } else{
            qWarning()<<"No Mesh found";
            return;
        }

        if(scene->HasLights()){
            qDebug()<<"Ligths found";
        }
        qDebug()<<"Mesh Read";

        //TODO init Nodes
        if(scene->mRootNode != NULL)
        {
            initNode(scene, scene->mRootNode, rootNode);
        } else {
            rootNode.transformation.setToIdentity();
            rootNode.children.resize(0);
            rootNode.meshes.resize(entries.length());

            for(int i = 0; i < entries.length(); ++i)
            {
                rootNode.meshes[i] = i;
            }
            qDebug()<<"No Root Node";
        }
        qDebug()<<"Nodes Read";

        globalInverseTransform = rootNode.transformation.inverted();



        //Todo read skeleton

        loaded = true;
    }
}

void Mesh::initNode(const aiScene *scene, aiNode *node, Node &newNode){
    newNode.name = node->mName.length != 0 ? node->mName.C_Str() : "";
    newNode.transformation = QMatrix4x4(node->mTransformation[0]);
    newNode.meshes.resize(node->mNumMeshes);

    qDebug() << "NodeName" << newNode.name;
    qDebug() << "  NumMeshes" << newNode.meshes.size();

    for(uint i = 0; i < node->mNumMeshes; ++i)
    {
        newNode.meshes[i] = node->mMeshes[i];
        qDebug() << "    MeshName" << entries[newNode.meshes[i]].name;
    }

    qDebug() << "  NumChildren" << node->mNumChildren;
    for(uint i = 0; i < node->mNumChildren; ++i)
    {
        newNode.children.push_back(Node());
        initNode(scene, node->mChildren[i], newNode.children[i]);
    }

}


void Mesh::initMaterial(QString dir, unsigned int i, const aiMaterial* material)
{
    aiString mname;
    material->Get(AI_MATKEY_NAME, mname);
    if (mname.length > 0)
        materials[i].Name = QString(mname.C_Str());

    aiColor3D dif(0.f,0.f,0.f);
    aiColor3D amb(0.f,0.f,0.f);
    aiColor3D spec(0.f,0.f,0.f);
    float shine = 0.0;

    material->Get(AI_MATKEY_COLOR_AMBIENT, amb);
    material->Get(AI_MATKEY_COLOR_DIFFUSE, dif);
    material->Get(AI_MATKEY_COLOR_SPECULAR, spec);
    material->Get(AI_MATKEY_SHININESS, shine);

    materials[i].Diffuse = QVector3D(dif.r, dif.g, dif.b);
    materials[i].Specular = QVector3D(spec.r, spec.g, spec.b);
    materials[i].Shininess = shine;

    if (materials[i].Shininess == 0.0)
        materials[i].Shininess = 30;


    if (material->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
        aiString Path;

        if (material->GetTexture(aiTextureType_DIFFUSE, 0, &Path, NULL, NULL, NULL, NULL, NULL) == AI_SUCCESS) {
            std::string FullPath = dir.toStdString() + "/" + Path.data;
            materials[i].texture.Load(GL_TEXTURE_2D, QString(FullPath.c_str()));
            materials[i].hasTexture = true;
        } else{
            qDebug()<<"Warning No Texture";
            materials[i].hasTexture = false;
        }

    }
}

void Mesh::initMeshEntry(int index, aiMesh * entry){

    QVector<Vertex> Vertices;
    QVector<unsigned int> Indices;

    const aiVector3D Zero3D(0.0f, 0.0f, 0.0f);

    for (unsigned int i = 0 ; i < entry->mNumVertices ; i++) {
        const aiVector3D* pPos      = &(entry->mVertices[i]);
        const aiVector3D* pNormal   = &(entry->mNormals[i]);
        const aiVector3D* pTexCoord = entry->HasTextureCoords(0) ? &(entry->mTextureCoords[0][i]) : &Zero3D;

        //possible: nuuv Channels, TAngents and Bitangents

        Vertex v(QVector3D(pPos->x, pPos->y, pPos->z),
                 QVector3D(pNormal->x, pNormal->y, pNormal->z),
                 QVector2D(pTexCoord->x, pTexCoord->y));

        Vertices.push_back(v);
    }

    for (unsigned int i = 0 ; i < entry->mNumFaces ; i++) {
        const aiFace& face = entry->mFaces[i];
        assert(face.mNumIndices == 3);
        Indices.push_back(face.mIndices[0]);
        Indices.push_back(face.mIndices[1]);
        Indices.push_back(face.mIndices[2]);
    }

    entries[index].init(f,Vertices, Indices);
    entries[index].name = entry->mName.length != 0 ? entry->mName.C_Str() : "";

    entries[index].materialIndex = entry->mMaterialIndex;
    qDebug()<<"Loaded Mesh:"<<entries[index].name;
}


void Mesh::render(QOpenGLShaderProgram *shader, QMatrix4x4 V,QMatrix4x4 P){
    if(!loaded)
        return;
    if(f == NULL){
        qDebug()<<"f = null";
        return;
    }

    f->glEnableVertexAttribArray(positionIndex);
    f->glEnableVertexAttribArray(normalIndex);
    f->glEnableVertexAttribArray(uvIndex);
    f->glEnableVertexAttribArray(boneIndex);
    f->glEnableVertexAttribArray(boneweightIndex);

    QMatrix4x4 M;
    M.setToIdentity();

    renderNode(shader,rootNode,V,P,M);


    f->glDisableVertexAttribArray(positionIndex);
    f->glDisableVertexAttribArray(normalIndex);
    f->glDisableVertexAttribArray(uvIndex);
    f->glDisableVertexAttribArray(boneIndex);
    f->glDisableVertexAttribArray(boneweightIndex);



}

void Mesh::renderNode(QOpenGLShaderProgram *shader, Node &node, QMatrix4x4 V,QMatrix4x4 P,QMatrix4x4 M){
    M *= node.transformation;

    QMatrix4x4 MV = V*M;
    QMatrix4x4 MVP = P*MV;
    QMatrix3x3 normalMat = MV.normalMatrix();

    shader->setUniformValue("MVP",MVP);
    shader->setUniformValue("MV",MV);
    shader->setUniformValue("N",normalMat);

    for (int i = 0 ; i < node.meshes.size() ; i++) {
    int index = node.meshes[i];
        //load Material
        renderMesh(shader, index);
    }

    for (int i = 0 ; i < node.children.size() ; i++) {
        renderNode(shader,node.children[i],V,P,M);
    }
}

void Mesh::renderMesh(QOpenGLShaderProgram *shader, int index)
{
    int MaterialIndex = entries[index].materialIndex;
    if (MaterialIndex < materials.size()) {
        shader->setUniformValue("materialInfo.Diffuse",materials[MaterialIndex].Diffuse);
        shader->setUniformValue("materialInfo.Specular",materials[MaterialIndex].Specular);
        shader->setUniformValue("materialInfo.Shininess",materials[MaterialIndex].Shininess);
        shader->setUniformValue("materialInfo.hasTexture",materials[MaterialIndex].hasTexture);

        if(materials[MaterialIndex].hasTexture)
            materials[MaterialIndex].texture.bind(f,GL_TEXTURE0);
    }


    // Draw Vertex Array
    f->glBindBuffer(GL_ARRAY_BUFFER, entries[index].VB);
    f->glVertexAttribPointer(positionIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    f->glVertexAttribPointer(normalIndex, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12); //3*4
    f->glVertexAttribPointer(uvIndex, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)24); //(3+3)*4
    f->glVertexAttribIPointer(boneIndex, 4, GL_INT, sizeof(Vertex), (const GLvoid*)32); //(3+3+2)*4
    f->glVertexAttribPointer(boneweightIndex, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)48); //(3+3+2+4)*4

    f->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, entries[index].IB);
    f->glDrawElements(GL_TRIANGLES, entries[index].numIndex, GL_UNSIGNED_INT, 0);
}


Mesh::~Mesh()
{
    entries.clear();
    materials.clear();
}

