// This file is part of libigl, a simple c++ geometry processing library.
// 
// Copyright (C) 2016 Yotam Gingold <yotam@yotamgingold.com>
// 
// This Source Code Form is subject to the terms of the Mozilla Public License 
// v. 2.0. If a copy of the MPL was not distributed with this file, You can 
// obtain one at http://mozilla.org/MPL/2.0/.

#include "seam_edges.h"
#include <unordered_map>
#include <unordered_set>
#include <cassert>

// Yotam has verified that this function produces the exact same output as
// `find_seam_fast.py` for `cow_triangled.obj`.
template <
  typename DerivedV, 
  typename DerivedTC,
  typename DerivedF, 
  typename DerivedFTC,
  typename Derivedseams,
  typename Derivedboundaries,
  typename Derivedfoldovers>
IGL_INLINE void igl::seam_edges(
  const Eigen::PlainObjectBase<DerivedV>& V,
  const Eigen::PlainObjectBase<DerivedTC>& TC,
  const Eigen::PlainObjectBase<DerivedF>& F,
  const Eigen::PlainObjectBase<DerivedFTC>& FTC,
  Eigen::PlainObjectBase<Derivedseams>& seams,
  Eigen::PlainObjectBase<Derivedboundaries>& boundaries,
  Eigen::PlainObjectBase<Derivedfoldovers>& foldovers)
{
  // Assume triangles.
  assert( F.cols() == 3 );
  assert( F.cols() == FTC.cols() );
  assert( F.rows() == FTC.rows() );
    
  // Assume 2D texture coordinates (foldovers tests).
  assert( TC.cols() == 2 );
  typedef Eigen::Matrix< typename DerivedTC::Scalar, 2, 1 > Vector2S;
  // Computes the orientation of `c` relative to the line between `a` and `b`.
  // Assumes 2D vector input.
  // Based on: https://www.cs.cmu.edu/~quake/robust.html
  const auto& Orientation = []( 
    const Vector2S& a, 
    const Vector2S& b, 
    const Vector2S& c ) -> typename DerivedTC::Scalar
  {
      const Vector2S row0 = a - c;
      const Vector2S row1 = b - c;
      return row0(0)*row1(1) - row1(0)*row0(1);
  };
    
  seams     .setZero( 3*F.rows(), 4 );
  boundaries.setZero( 3*F.rows(), 2 );
  foldovers .setZero( 3*F.rows(), 4 );
    
  int num_seams = 0;
  int num_boundaries = 0;
  int num_foldovers = 0;
    
  // A map from a pair of vertex indices to the index (face and endpoints)
  // into face_position_indices.
  // The following should be true for every key, value pair:
  //    key == face_position_indices[ value ]
  // This gives us a "reverse map" so that we can look up other face
  // attributes based on position edges.
  // The value are written in the format returned by numpy.where(),
  // which stores multi-dimensional indices such as array[a0,b0], array[a1,b1]
  // as ( (a0,a1), (b0,b1) ).
    
  // We need to make a hash function for our directed edges.
  // We'll use i*V.rows() + j.
  typedef std::pair< typename DerivedF::Scalar, typename DerivedF::Scalar > 
    directed_edge;
	const int numV = V.rows();
	const int numF = F.rows();
	const auto& edge_hasher = 
    [numV]( directed_edge const& e ) { return e.first*numV + e.second; };
  // When we pass a hash function object, we also need to specify the number of
  // buckets. The Euler characteristic says that the number of undirected edges
  // is numV + numF -2*genus.
	std::unordered_map<directed_edge,std::pair<int,int>,decltype(edge_hasher) > 
    directed_position_edge2face_position_index(2*( numV + numF ), edge_hasher);
  for( int fi = 0; fi < F.rows(); ++fi ) 
  {
    for( int i = 0; i < 3; ++i )
    {
      const int j = ( i+1 ) % 3;
      directed_position_edge2face_position_index[ 
        std::make_pair( F(fi,i), F(fi,j) ) ] = std::make_pair( fi, i );
    }
  }
    
  // First find all undirected position edges (collect a canonical orientation
  // of the directed edges).
  std::unordered_set< directed_edge, decltype( edge_hasher ) > 
    undirected_position_edges( numV + numF, edge_hasher );
  for( const auto& el : directed_position_edge2face_position_index ) 
  {
    // The canonical orientation is the one where the smaller of
    // the two vertex indices is first.
    undirected_position_edges.insert( std::make_pair( 
      std::min( el.first.first, el.first.second ), 
      std::max( el.first.first, el.first.second ) ) );
  }
    
  // Now we will iterate over all position edges.
  // Seam edges are the edges whose two opposite directed edges have different
  // texcoord indices (or one doesn't exist at all in the case of a mesh
  // boundary).
  for( const auto& vp_edge : undirected_position_edges ) 
  {
    // We should only see canonical edges,
    // where the first vertex index is smaller.
    assert( vp_edge.first < vp_edge.second );
        
    const auto vp_edge_reverse = std::make_pair(vp_edge.second, vp_edge.first);
    // If it and its opposite exist as directed edges, check if their
    // texture coordinate indices match.
    if( directed_position_edge2face_position_index.count( vp_edge ) &&
      directed_position_edge2face_position_index.count( vp_edge_reverse ) ) 
    {
      const auto forwards = 
        directed_position_edge2face_position_index[ vp_edge ];
      const auto backwards = 
        directed_position_edge2face_position_index[ vp_edge_reverse ];
            
      // NOTE: They should never be equal.
      assert( forwards != backwards );
            
      // If the texcoord indices match (are similarly flipped),
      // this edge is not a seam. It could be a foldover.
      if( 
        std::make_pair( 
          FTC( forwards.first, forwards.second ), 
          FTC( forwards.first, ( forwards.second+1 ) % 3 ) )
        ==
        std::make_pair( 
          FTC( backwards.first, ( backwards.second+1 ) % 3 ), 
          FTC( backwards.first, backwards.second ) )) 
      {
        // Check for foldovers in UV space.
        // Get the edge (a,b) and the two opposite vertices's texture
        // coordinates.
        const Vector2S a = TC.row( FTC( forwards.first,  forwards.second ) );
        const Vector2S b = 
          TC.row( FTC( forwards.first, (forwards.second+1) % 3 ) );
        const Vector2S c_forwards  = 
          TC.row( FTC( forwards .first, (forwards .second+2) % 3 ) );
        const Vector2S c_backwards = 
          TC.row( FTC( backwards.first, (backwards.second+2) % 3 ) );
        // If the opposite vertices' texture coordinates fall on the same side
        // of the edge, we have a UV-space foldover.
        const auto orientation_forwards = Orientation( a, b, c_forwards );
        const auto orientation_backwards = Orientation( a, b, c_backwards );
        if( ( orientation_forwards > 0 && orientation_backwards > 0 ) ||
            ( orientation_forwards < 0 && orientation_backwards < 0 )
            ) {
            foldovers( num_foldovers, 0 ) = forwards.first;
            foldovers( num_foldovers, 1 ) = forwards.second;
            foldovers( num_foldovers, 2 ) = backwards.first;
            foldovers( num_foldovers, 3 ) = backwards.second;
            num_foldovers += 1;
        }
      }
      // Otherwise, we have a non-matching seam edge.
      else 
      {
        seams( num_seams, 0 ) = forwards.first;
        seams( num_seams, 1 ) = forwards.second;
        seams( num_seams, 2 ) = backwards.first;
        seams( num_seams, 3 ) = backwards.second;
        num_seams += 1;
      }
    }
    // Otherwise, the edge and its opposite aren't both in the directed edges.
    // One of them should be.
    else if( directed_position_edge2face_position_index.count( vp_edge ) ) 
    {
      const auto forwards = directed_position_edge2face_position_index[vp_edge];
      boundaries( num_boundaries, 0 ) = forwards.first;
      boundaries( num_boundaries, 1 ) = forwards.second;
      num_boundaries += 1;
    } else if( 
      directed_position_edge2face_position_index.count( vp_edge_reverse ) ) 
    {
      const auto backwards = 
        directed_position_edge2face_position_index[ vp_edge_reverse ];
      boundaries( num_boundaries, 0 ) = backwards.first;
      boundaries( num_boundaries, 1 ) = backwards.second;
      num_boundaries += 1;
    } else {
      // This should never happen! One of these two must have been seen.
      assert(
        directed_position_edge2face_position_index.count( vp_edge ) ||
        directed_position_edge2face_position_index.count( vp_edge_reverse )
      );
    }
  }
    
  seams     .conservativeResize( num_seams,      Eigen::NoChange_t() );
  boundaries.conservativeResize( num_boundaries, Eigen::NoChange_t() );
  foldovers .conservativeResize( num_foldovers,  Eigen::NoChange_t() );
}

#ifdef IGL_STATIC_LIBRARY
// Explicit template instantiation
// generated by autoexplicit.sh
template void igl::seam_edges<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> >&);
#endif
