/*************************************************************************
 *                                                                       *
 * Vega FEM Simulation Library Version 4.0                               *
 *                                                                       *
 * "objMesh" library , Copyright (C) 2007 CMU, 2009 MIT, 2018 USC        *
 * All rights reserved.                                                  *
 *                                                                       *
 * Code authors: Jernej Barbic,                                          *
 * http://www.jernejbarbic.com/vega                                      *
 *                                                                       *
 * Research: Jernej Barbic, Hongyi Xu, Yijing Li,                        *
 *           Danyong Zhao, Bohan Wang,                                   *
 *           Fun Shing Sin, Daniel Schroeder,                            *
 *           Doug L. James, Jovan Popovic                                *
 *                                                                       *
 * Funding: National Science Foundation, Link Foundation,                *
 *          Singapore-MIT GAMBIT Game Lab,                               *
 *          Zumberge Research and Innovation Fund at USC,                *
 *          Sloan Foundation, Okawa Foundation,                          *
 *          USC Annenberg Foundation                                     *
 *                                                                       *
 * This library is free software; you can redistribute it and/or         *
 * modify it under the terms of the BSD-style license that is            *
 * included with this library in the file LICENSE.txt                    *
 *                                                                       *
 * This library is distributed in the hope that it will be useful,       *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file     *
 * LICENSE.TXT for more details.                                         *
 *                                                                       *
 *************************************************************************/

#include <map>
#include "objMeshGraph.h"

using namespace std;

namespace vega 
{

ObjMeshGraph::ObjMeshGraph(ObjMesh * objMesh_): objMesh(objMesh_)
{
  if (!(objMesh->isTriangularMesh()))
  {
    printf("Mesh was not triangular. Triangulating..."); fflush(NULL);
    objMesh->triangulate();
    printf(" done\n");
  }

  std::map<std::pair<int, int>, int > meshEdges; // unstructured list of edges, with an integer identifier for each edge

  // create mesh edges
  printf("Building mesh graph edges...\n");
  for(unsigned int i = 0; i < objMesh->getNumGroups(); i++ )
  {
    const ObjMesh::Group * currentGroup = objMesh->getGroupHandle(i);

    std::cout << "  Processing obj group '" << currentGroup->getName() << std::endl;
    std::cout << "  Iterating through group faces..." << std::endl;

    for( unsigned int iFace = 0; iFace < currentGroup->getNumFaces(); ++iFace )
    {
      const ObjMesh::Face face = currentGroup->getFace(iFace); // get face whose number is iFace

      // add to vector of mesh faces
      triple<int,int,int> faceV;
      faceV.first = face.getVertex(0).getPositionIndex();
      faceV.second = face.getVertex(1).getPositionIndex();
      faceV.third = face.getVertex(2).getPositionIndex();
      meshFaceVerticesVector.push_back(faceV);

      // vertexCount() will always be 3
      for ( unsigned int iVertex = 0; iVertex < face.getNumVertices(); iVertex++ )
      {
        int vtxA = face.getVertex(iVertex).getPositionIndex();
        int vtxB = face.getVertex((iVertex + 1) % face.getNumVertices()).getPositionIndex();

        // ensure that vtxA <= vtxB
        if (vtxA >= vtxB)
        {
          int buffer = vtxA;
          vtxA = vtxB;
          vtxB = buffer;
        }

        meshEdges.insert(make_pair(make_pair(vtxA, vtxB), 0));
      }
    }
  }

  // create vector of mesh edges
  for(map<pair<int,int>, int > :: iterator iter = meshEdges.begin();
      iter != meshEdges.end(); iter++)
  {
    meshEdgesVector.push_back(iter->first);
  }

  nObj = objMesh->getNumVertices();
  eObj = meshEdges.size();
  fObj = objMesh->getNumFaces();

  numVertices = nObj + eObj + fObj;

  // add edge indices to the mesh edge datastructure
  int numMeshEdges = 0;
  for(map<pair<int,int>, int> :: iterator iter = meshEdges.begin();
      iter != meshEdges.end(); iter++)
  {
    iter->second = numMeshEdges;
    numMeshEdges++;
  }

  // build graph adjacency
  int faceIndex = 0;
  printf("Building mesh graph adjacency...\n");
  for(unsigned int i = 0; i < objMesh->getNumGroups(); i++ )
  {
    const ObjMesh::Group * currentGroup = objMesh->getGroupHandle(i);

    std::cout << "  Processing obj group '" << currentGroup->getName() << std::endl;
    std::cout << "  Iterating through group faces..." << std::endl;

    for( unsigned int iFace = 0; iFace < currentGroup->getNumFaces(); ++iFace )
    {
      const ObjMesh::Face face = currentGroup->getFace(iFace); // get face whose number is iFace

      // every vtx is adjacent to its face
      for ( unsigned int iVertex = 0; iVertex < face.getNumVertices(); iVertex++ )
      {
        int vtx = face.getVertex(iVertex).getPositionIndex();
        edges.insert(make_pair(GetVertexID(vtx), GetFaceID(faceIndex)));
      }

      // every edge is adjacent to its face
      int edgeIDs[3];
      for ( unsigned int iVertex = 0; iVertex < face.getNumVertices(); iVertex++ )
      {
        int vtxA = face.getVertex(iVertex).getPositionIndex();
        int vtxB = face.getVertex((iVertex + 1) % face.getNumVertices()).getPositionIndex();

        // ensure vtxA <= vtxB
        if (vtxA >= vtxB)
        {
          int buffer = vtxA;
          vtxA = vtxB;
          vtxB = buffer;
        }

        map<pair<int, int>, int > :: iterator iter = meshEdges.find(make_pair(vtxA, vtxB));
        if (iter == meshEdges.end())
        {
          printf("Sanity check error: vtxA=%d vtxB=%d.\n",vtxA,vtxB);
          exit(1);
        }
        int edgeID = iter->second;
        edgeIDs[iVertex] = edgeID;
        edges.insert(make_pair(GetEdgeID(edgeID), GetFaceID(faceIndex)));
      }

      meshFaceEdgesVector.push_back(triple<int,int,int> (edgeIDs[0], edgeIDs[1], edgeIDs[2]));

      faceIndex++;
    }
  }

  // every edge is adjacent to its vertices
  for(map<pair<int,int>, int > :: iterator iter = meshEdges.begin();
      iter != meshEdges.end(); iter++)
  {
    edges.insert(make_pair(GetVertexID(iter->first.first), GetEdgeID(iter->second)));
    edges.insert(make_pair(GetVertexID(iter->first.second), GetEdgeID(iter->second)));
  }

  numEdges = edges.size();

  printf("Graph generated.\n");
  printf("Mesh vertices: %d\n", nObj);
  printf("Mesh edges: %d\n", eObj);
  printf("Mesh faces: %d\n", fObj);

  BuildVertexNeighbors();
}

void ObjMeshGraph::meshID(int graphVertex, int & siteType, int meshVtxData[3])
{
  if (graphVertex < nObj)
  {
    siteType = 0;
    meshVtxData[0] = graphVertex;
  }
  else
  if (graphVertex < nObj + eObj)
  {
    siteType = 1;
    int edgeID = graphVertex - nObj;
    pair<int,int> edge = meshEdgesVector[edgeID];
    meshVtxData[0] = edge.first;
    meshVtxData[1] = edge.second;
  }
  else
  {
    siteType = 2;
    int faceID = graphVertex - nObj - eObj;
    triple<int,int,int> face = meshFaceVerticesVector[faceID];
    meshVtxData[0] = face.first;
    meshVtxData[1] = face.second;
    meshVtxData[2] = face.third;
  }
}

int ObjMeshGraph::graphID(int faceID, int siteIndex)
{
  //  0: vertex0
  //  1: vertex1
  //  2: vertex2
  //  3: edge among 01
  //  4: edge among 12
  //  5: edge among 20
  //  6: the face itself

  int graphVertex = 0;
  switch(siteIndex)
  {
    case 0:
      graphVertex = meshFaceVerticesVector[faceID].first;
    break;

    case 1:
      graphVertex = meshFaceVerticesVector[faceID].second;
    break;

    case 2:
      graphVertex = meshFaceVerticesVector[faceID].third;
    break;

    case 3:
      graphVertex = nObj + meshFaceEdgesVector[faceID].first;
    break;

    case 4:
      graphVertex = nObj + meshFaceEdgesVector[faceID].second;
    break;

    case 5:
      graphVertex = nObj + meshFaceEdgesVector[faceID].third;
    break;

    case 6:
      graphVertex = nObj + eObj + faceID;
    break;
  }

  if (graphVertex > numVertices)
  {
    printf("Error: incorrect graph vertex (%d). faceID=%d site:%d\n",
      graphVertex, faceID, siteIndex);
  }

  return graphVertex;
}

Graph * ObjMeshGraph::GenerateVertexGraph(const ObjMesh * objMesh, int faceClique)
{
  // Generate springs:
  typedef pair<int,int> edge;
  set<edge> edgeSet;
  #define SORTED(i,j) ( (i) <= (j) ? make_pair((i),(j)) : make_pair((j),(i)) )

  for(unsigned int i=0; i < objMesh->getNumGroups(); i++) // over all groups
  {
    const ObjMesh::Group * group = objMesh->getGroupHandle(i);
    for (unsigned int j=0; j < group->getNumFaces(); j++) // over all faces
    {
      const ObjMesh::Face * face = group->getFaceHandle(j);
      if (face->getNumVertices() < 3)
      {
        printf("Warning: encountered a face with fewer than 3 vertices.\n");
        continue;
      }

      unsigned int faceDegree = face->getNumVertices();

      // get the vertices
      vector<ObjMesh::Vertex> vertices;
      for(unsigned int k=0; k<faceDegree; k++)
        vertices.push_back(face->getVertex(k));

      // create edges
      if (faceClique)
      {
        for(unsigned int k=0; k<faceDegree; k++)
          for(unsigned int l=k+1; l<faceDegree; l++)
            edgeSet.insert(SORTED(vertices[k].getPositionIndex(), vertices[l].getPositionIndex()));
      }
      else
        for(unsigned int k=0; k<faceDegree; k++)
          edgeSet.insert(SORTED(vertices[k].getPositionIndex(), vertices[(k+1) % faceDegree].getPositionIndex()));
    }
  }

  int numEdges = edgeSet.size();
  int * edges = (int*) malloc (sizeof(int) * 2 * numEdges);
  int count = 0;
  for(set<edge> :: iterator iter = edgeSet.begin(); iter != edgeSet.end(); iter++)
  {
    edges[2*count+0] = iter->first;
    edges[2*count+1] = iter->second;
    count++;
  }

  Graph * graph  = new Graph(objMesh->getNumVertices(), numEdges, edges);

  free(edges);

  return graph;

  #undef SORTED
}


} // namespace vega
