// Copyright (C) 2008-2011 Gael Guennebaud <gael.guennebaud@inria.fr>


#include "mesh.h"
#include "opengl.h"

#include <iostream>
#include <fstream>
#include <limits>

#include <ObjFormat.h>

using namespace Eigen;

Mesh::Mesh()
    : mIsInitialized(false)
{

}

Mesh::Mesh(const std::string& filename)
  : mIsInitialized(false)
{
  std::string ext = filename.substr(filename.size()-3,3);
  if(ext=="off" || ext=="OFF")
    loadOFF(filename);
  else if(ext=="obj" || ext=="OBJ")
    loadOBJ(filename);
  else
    std::cerr << "Mesh: extension \'" << ext << "\' not supported." << std::endl;
}

void Mesh::loadOFF(const std::string& filename)
{
  std::ifstream in(filename.c_str(),std::ios::in);
  if(!in)
  {
    std::cerr << "File not found " << filename << std::endl;
    return;
  }

  std::string header;
  in >> header;

  // check the header file
  if(header != "OFF")
  {
      std::cerr << "Wrong header = " << header << std::endl;
      return;
  }

  int nofVertices, nofFaces, inull;
  int nb, id0, id1, id2;
  Vector3f v;

  in >> nofVertices >> nofFaces >> inull;

  for(int i=0 ; i<nofVertices ; ++i)
  {
      in >> v.x() >> v.y() >> v.z();
      mVertices.push_back(Vertex(v));
  }

  for(int i=0 ; i<nofFaces ; ++i)
  {
      in >> nb >> id0 >> id1 >> id2;
      assert(nb==3);
      mFaces.push_back(Vector3i(id0, id1, id2));
  }

  in.close();
  
  initialize();
}

void Mesh::initialize()
{
  // => create the BufferObjects and copy the related data into them.
  glGenBuffers(1,&mVertexBufferId);
  glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferId);
  glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*mVertices.size(),
               mVertices[0].position.data(), GL_DYNAMIC_DRAW);
  
  glGenBuffers(1,&mIndexBufferId);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferId);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Vector3i)*mFaces.size(),
               mFaces[0].data(), GL_DYNAMIC_DRAW);
  
  glGenVertexArrays(1,&mVertexArrayId);
  
  mIsInitialized = true;
}

Mesh::~Mesh()
{
  if(mIsInitialized)
  {
    glDeleteBuffers(1,&mVertexBufferId);
    glDeleteBuffers(1,&mIndexBufferId);
  }
}


void Mesh::drawGeometry(int prg_id)
{
  glBindVertexArray(mVertexArrayId);
  
  glBindBuffer(GL_ARRAY_BUFFER, mVertexBufferId);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferId);
  
  int vertex_loc = glGetAttribLocation(prg_id, "vtx_position");
  int normal_loc = glGetAttribLocation(prg_id, "vtx_normal");

  // specify the vertex data
  glVertexAttribPointer(vertex_loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
  glEnableVertexAttribArray(vertex_loc);

  if(normal_loc>0) {
    glVertexAttribPointer(normal_loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)sizeof(Vector3f));
    glEnableVertexAttribArray(normal_loc);
  }

  // send the geometry
  glDrawElements(GL_TRIANGLES, 3*mFaces.size(), GL_UNSIGNED_INT, 0);

  // at this point the mesh has been drawn and raserized into the framebuffer!
  glDisableVertexAttribArray(vertex_loc);

  if(normal_loc>0) {
    glDisableVertexAttribArray(normal_loc);
  }
}

void Mesh::loadOBJ(const std::string& filename)
{
  ObjMesh* pRawObjMesh = ObjMesh::LoadFromFile(filename);

  if (!pRawObjMesh)
  {
      std::cerr << "Mesh::loadObj: error loading file " << filename << "." << std::endl;
      return;
  }

  // Makes sure we have an indexed face set
  ObjMesh* pObjMesh = pRawObjMesh->createIndexedFaceSet(Obj::Options(Obj::AllAttribs|Obj::Triangulate));
  delete pRawObjMesh;
  pRawObjMesh = 0;

  // copy vertices
  mVertices.resize(pObjMesh->positions.size());

  for (int i=0 ; i<pObjMesh->positions.size() ; ++i)
  {
    mVertices[i] = Vertex(Vector3f(pObjMesh->positions[i].x, pObjMesh->positions[i].y, pObjMesh->positions[i].z));

//     if(!pObjMesh->texcoords.empty())
//       mVertices[i].texcoord = Vector2f(pObjMesh->texcoords[i]);

    if(!pObjMesh->normals.empty())
      mVertices[i].normal = Vector3f(pObjMesh->normals[i]);
  }

  // copy faces
  for (int smi=0 ; smi<pObjMesh->getNofSubMeshes() ; ++smi)
  {
    const ObjSubMesh* pSrcSubMesh = pObjMesh->getSubMesh(smi);

    mFaces.reserve(pSrcSubMesh->getNofFaces());

    for (uint fid = 0 ; fid<pSrcSubMesh->getNofFaces() ; ++fid)
    {
      ObjConstFaceHandle srcFace = pSrcSubMesh->getFace(fid);
      mFaces.push_back(Vector3i(srcFace.vPositionId(0), srcFace.vPositionId(1), srcFace.vPositionId(2)));
    }
  }
  
  initialize();
}
