// ---------------------------------------------------------
// Mesh Class
// Author : Tamy Boubekeur (boubek@gmail.com).
// Copyright (C) 2008 Tamy Boubekeur.
// All rights reserved.
// ---------------------------------------------------------

#include "Mesh.h"
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sstream>
#include <GL/glew.h>
#include "Random.h"
#include "Scene.h"

using namespace std;

void Mesh::updateBoundingBox()
{
    if(vertices.empty())
        bbox = BoundingBox();
    else
    {
        bbox = BoundingBox(vertices[0].getPos());
        for(unsigned int i = 1; i < vertices.size(); i++)
            bbox.extendTo(vertices[i].getPos());
    }
}

void Mesh::clear () {
    clearTopology ();
    clearGeometry ();
}

void Mesh::clearGeometry () {
    vertices.clear ();
    tree.clear();
    bbox = BoundingBox();
}

void Mesh::clearTopology () {
    triangles.clear ();
    tree.clear();
    neighbours.clear();
}

void Mesh::unmarkAllVertices () {
    for (unsigned int i = 0; i < vertices.size (); i++)
        vertices[i].unmark ();
}

void Mesh::computeTriangleNormals (vector<Vec3Df> & triangleNormals)
{
    for (vector<Triangle>::const_iterator it = triangles.begin ();
         it != triangles.end ();
         it++) {
        Vec3Df e01 (vertices[it->getVertex (1)].getPos () - vertices[it->getVertex (0)].getPos ());
        Vec3Df e02 (vertices[it->getVertex (2)].getPos () - vertices[it->getVertex (0)].getPos ());
        Vec3Df n (Vec3Df::crossProduct (e01, e02));
        n.normalize ();
        triangleNormals.push_back (n);
    }
}

void Mesh::recomputeSmoothVertexNormals (unsigned int normWeight)
{
    vector<Vec3Df> triangleNormals;
    computeTriangleNormals (triangleNormals);
    for (std::vector<Vertex>::iterator it = vertices.begin (); it != vertices.end (); it++)
        it->setNormal (Vec3Df (0.0, 0.0, 0.0));
    vector<Vec3Df>::const_iterator itNormal = triangleNormals.begin ();
    vector<Triangle>::const_iterator it = triangles.begin ();
    for ( ; it != triangles.end (); it++, itNormal++)
        for (unsigned int  j = 0; j < 3; j++) {
            Vertex & vj = vertices[it->getVertex (j)];
            float w = 1.0; // uniform weights
            Vec3Df e0 = vertices[it->getVertex ((j+1)%3)].getPos () - vj.getPos ();
            Vec3Df e1 = vertices[it->getVertex ((j+2)%3)].getPos () - vj.getPos ();
            if (normWeight == 1) { // area weight
                w = Vec3Df::crossProduct (e0, e1).getLength () / 2.0f;
            } else if (normWeight == 2) { // angle weight
                e0.normalize ();
                e1.normalize ();
                w = (2.0f - (Vec3Df::dotProduct (e0, e1) + 1.0f)) / 2.0f;
            }
            if (w <= 0.0f)
                continue;
            vj.setNormal (vj.getNormal () + (*itNormal) * w);
        }
    Vertex::normalizeNormals (vertices);
}

void Mesh::collectOneRing (vector<vector<unsigned int> > & oneRing) const
{
    oneRing.resize (vertices.size ());
    for (unsigned int i = 0; i < triangles.size (); i++) {
        const Triangle & ti = triangles[i];
        for (unsigned int j = 0; j < 3; j++) {
            unsigned int vj = ti.getVertex (j);
            for (unsigned int k = 1; k < 3; k++) {
                unsigned int vk = ti.getVertex ((j+k)%3);
                if (find (oneRing[vj].begin (), oneRing[vj].end (), vk) == oneRing[vj].end ())
                    oneRing[vj].push_back (vk);
            }
        }
    }
}

void Mesh::collectOrderedOneRing (vector<vector<unsigned int> > & oneRing) const
{
    oneRing.resize (vertices.size ());
    for (unsigned int t = 0; t < triangles.size (); t++) {
        const Triangle & ti = triangles[t];
        for (unsigned int i = 0; i < 3; i++) {
            unsigned int vi = ti.getVertex (i);
            unsigned int vj = ti.getVertex ((i+1)%3);
            unsigned int vk = ti.getVertex ((i+2)%3);
            vector<unsigned int> & oneRingVi = oneRing[vi];
            vector<unsigned int>::iterator begin = oneRingVi.begin ();
            vector<unsigned int>::iterator end = oneRingVi.end ();
            vector<unsigned int>::iterator nj = find (begin, end, vj);
            vector<unsigned int>::iterator nk = find (begin, end, vk);
            if (nj != end && nk == end) {
                if (nj == begin)
                    nj = end;
                nj--;
                oneRingVi.insert (nj, vk);
            } else if (nj == end && nk != end)
                oneRingVi.insert (nk, vj);
            else if (nj == end && nk == end) {
                oneRingVi.push_back (vk);
                oneRingVi.push_back (vj);
            }
        }
    }
}

void Mesh::computeDualEdgeMap (EdgeMapIndex & dualVMap1, EdgeMapIndex & dualVMap2)
{
    for (vector<Triangle>::iterator it = triangles.begin ();
         it != triangles.end (); it++) {
        for (unsigned int i = 0; i < 3; i++) {
            Edge eij (it->getVertex (i), it->getVertex ((i+1)%3));
            if (dualVMap1.find (eij) == dualVMap1.end ())
                dualVMap1[eij] = it->getVertex ((i+2)%3);
            else
                dualVMap2[eij] = it->getVertex ((i+2)%3);
        }
    }
}

void Mesh::markBorderEdges (EdgeMapIndex & edgeMap)
{
    for (vector<Triangle>::iterator it = triangles.begin ();
         it != triangles.end (); it++) {
        for (unsigned int i = 0; i < 3; i++) {
            unsigned int j = (i+1)%3;
            Edge eij (it->getVertex (i), it->getVertex (j));
            if (edgeMap.find (eij) == edgeMap.end ())
                edgeMap[eij] = 0;
            else
                edgeMap[eij] += 1;
        }
    }
}

inline void glVertexVec3Df (const Vec3Df & v) {
    glVertex3f (v[0], v[1], v[2]);
}

inline void glNormalVec3Df (const Vec3Df & n) {
    glNormal3f (n[0], n[1], n[2]);
}

inline void glDrawPoint (const Vec3Df & pos, const Vec3Df & normal) {
    glNormalVec3Df (normal);
    glVertexVec3Df (pos);
}

inline void glDrawPoint (const Vertex & v) {
    glDrawPoint (v.getPos (), v.getNormal ());
}

void Mesh::renderGL (bool flat) const
{
    glBegin (GL_TRIANGLES);
    for (unsigned int i = 0; i < triangles.size (); i++)
    {
        const Triangle & t = triangles[i];
        Vertex v[3];
        for (unsigned int j = 0; j < 3; j++)
            v[j] = vertices[t.getVertex(j)];
        if (flat) {
            Vec3Df normal = Vec3Df::crossProduct (v[1].getPos () - v[0].getPos (),
                                                  v[2].getPos () - v[0].getPos ());
            normal.normalize ();
            glNormalVec3Df (normal);
        }
        for (unsigned int j = 0; j < 3; j++)
        {
            //glColor3f(v[j].getAO(), v[j].getAO(), v[j].getAO());
            if (!flat)
                glDrawPoint (v[j]);
            else
                glVertexVec3Df (v[j].getPos ());
        }
    }
    glEnd ();
}

void Mesh::rebuildTree()
{
    tree.rebuild();
}

void Mesh::loadOFF (const std::string & filename) {
    clear ();
    ifstream input (filename.c_str ());
    if (!input)
        throw Exception ("Failing opening the file.");
    string magic_word;
    input >> magic_word;
    if (magic_word != "OFF")
        throw Exception ("Not an OFF file.");
    unsigned int numOfVertices, numOfTriangles, numOfWhat;
    input >> numOfVertices >> numOfTriangles >> numOfWhat;

    for (unsigned int i = 0; i < numOfVertices; i++) {
        Vec3Df pos;
        Vec3Df col;
        input >> pos;
        addVertex(Vertex (pos, Vec3Df (1.0, 0.0, 0.0)));
    }

    for (unsigned int i = 0; i < numOfTriangles; i++) {
        unsigned int polygonSize;
        input >> polygonSize;
        vector<unsigned int> index (polygonSize);
        for (unsigned int j = 0; j < polygonSize; j++)
            input >> index[j];
        for (unsigned int j = 1; j < (polygonSize - 1); j++)
            addTriangle(Triangle (index[0], index[j], index[j+1]));
    }

    input.close ();
    updateBoundingBox();
    recomputeSmoothVertexNormals(0);
    rebuildTree();
    //rebuildAmbientOcclusion(25,0.2f);
}

void Mesh::addVertex(const Vertex& v)
{
    vertices.push_back(v);
    neighbours.push_back(std::vector<unsigned int>());
}

void Mesh::addTriangle(const Triangle& t)
{
    triangles.push_back(t);
    for(unsigned int j = 0; j < 3; j++)
        neighbours[t.getVertex(j)].push_back(triangles.size() - 1);
}

void Mesh::rebuildAmbientOcclusion(unsigned int nbRays, float d)
{
    IntersectedTriangle tri;

    for(unsigned int i = 0; i < vertices.size (); i++)
    {
        // Base of vectors
        Vec3Df n = vertices[i].getNormal();
        Vec3Df u = Vec3Df(0.0f, n[2], -n[1]);
        Vec3Df v = Vec3Df::crossProduct(n, u);
        u = Vec3Df::crossProduct(v, n);
        Ray ray;
        unsigned int intersections = 0;

        // generate rays, in the hemisphere of the normal
        for(unsigned int t = 0; t < nbRays; t++)
        {
            float phi = PI * rnd::random(0.0f, 2.0f);
            float theta = PI * rnd::random(0.0f, 0.5f);
            Vec3Df dir = cosf(phi) * u + sinf(phi) * v;
            dir = cosf(theta) * dir + sinf(theta) * n;

            ray.setOrigin(vertices[i].getPos() + dir * 0.000001f);
            ray.setDirection(vertices[i].getPos() + dir);

            if(tree.intersect(ray,tri) && tri.t > 0.000001f && tri.t <= d)
                intersections++;
        }

        // Set vertex color
        vertices[i].setAO(1.0f - (float)intersections / (float)nbRays);
    }
}

