/* 
 * FILE: marinov.cpp
 * Executable for mesh face decimation using the Algorithm of Marinov et al.
 *
 * \author Leblanc Christophe.
 * \date   05/09/2019
 * \email cleblancad@gmail.com

 * \source: "Automatic Generation of Structure Preserving Multiresolution Models",
 *  Marinov, M., Kobbelt, L., Computer Graphics Froum, vol 24, issue 3, pp. 479-486 (2005)
 *  doi: 10.1111/j.1467-8659.2005.00873.x
 *
 */

#include <iostream>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Polygon_mesh_processing/orientation.h>
#include <marinov/msh_read.hpp>
#include <marinov/face_decimation.hpp>
#include <marinov/gmsh_writer.hpp>

// Typedefs.
typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef marinov::Face_decimation<Kernel> Face_decimation;
typedef typename Face_decimation::Mesh Mesh;

void usage()
{
  std::cout << "Usage: marinov [option] [target_number_of_faces] [input_file] [output_file]\n";
  std::cout << " [option] -t re-triangulate faces\n"
            << " [target_number_of_faces] number of desired faces in the output file\n"
            << " [input_file] *.msh file (version 2.2) or *.off file\n"
            << " [output_file] name of the geo output file"
            << "\n";
}

void msh_to_mesh(const marinov::MSHRead &msh_read, Mesh &mesh)
{
  typedef typename Face_decimation::Vertex_index Vertex_index;
  typedef typename Face_decimation::Point Point;

  mesh.clear();
  
  // Vertices.
  std::map<typename marinov::MSHRead::Vertex_index, Vertex_index> vhandles;

  typename marinov::MSHRead::Vertex_index v_it = msh_read.v_begin(), v_ite = msh_read.v_end();
  for(; v_it != v_ite; ++v_it)
  {
    const typename marinov::MSHRead::Point &p = msh_read.point(v_it);
    vhandles[v_it] = mesh.add_vertex(Point(p[0], p[1], p[2]));
  }

  // Faces.
  std::vector<Vertex_index> face_vhandles(3);
  typename marinov::MSHRead::Face_index f_it = msh_read.f_begin(), f_ite = msh_read.f_end();

  for(; f_it != f_ite; ++f_it)
  {
    const typename marinov::MSHRead::Face &f = msh_read.face(f_it);
    face_vhandles[0] = vhandles[f[0]];
    face_vhandles[1] = vhandles[f[1]];
    face_vhandles[2] = vhandles[f[2]];

    mesh.add_face(face_vhandles);
  }
}

bool check_mesh(const Mesh &mesh)
{
  if(!mesh.is_valid())
  {
    std::cerr << "CGAL says input mesh file is invalid\n";
    return false;
  }

  // Check if each face is surrounded by valid faces.
  typename Mesh::Face_range::iterator f_it = mesh.faces().begin(),
    f_ite = mesh.faces().end();
  for(; f_it != f_ite; ++f_it)
  {
    bool valid = true;
    CGAL::Face_around_face_iterator<Mesh> ff_it, ff_ite;
    boost::tie(ff_it, ff_ite) = CGAL::faces_around_face(mesh.halfedge(*f_it), mesh);

    for(; ff_it != ff_ite; ++ff_it)
    {
      if(*ff_it == Mesh::null_face())
      {
        valid = false;
        break;
      }
    }

    if(!valid)
    {
      std::cerr << "Found invalid face " << (*f_it) << " without neighbouring face.\n";
      return false;
    }
  }

  return true;
}


int main(int argc, char **argv)
{
  // Parse command line.
  bool do_retriangulate;
  std::size_t target_number_of_faces, initial_number_of_faces, final_number_of_faces;
  std::size_t initial_number_of_vertices, final_number_of_vertices;
  std::string input_filename, output_filename, extension;
  std::ifstream input_file;

  if((argc != 4) && (argc != 5))
  {
    usage();
    return EXIT_FAILURE;
  }

  if(argc == 4)
  {
    do_retriangulate = false;
    target_number_of_faces = strtoul(argv[1], NULL, 10);
    input_filename = argv[2];
    output_filename = argv[3];
  }
  else
  {
    do_retriangulate = true;
    target_number_of_faces = strtoul(argv[2], NULL, 10);
    input_filename = argv[3];
    output_filename = argv[4];
  }

  // Open file.
  input_file.open(input_filename);
  if(!input_file.is_open())
  {
    std::cerr << "Could not open file \"" << input_filename << "\"\n";
    return EXIT_FAILURE;
  }

  // Determine file extension.
  {
    const std::size_t pos = input_filename.find_last_of('.');
    if(pos == std::string::npos)
    {
      std::cerr << "Not extension found in \"" << input_filename << "\"\n";
      return EXIT_FAILURE;
    }

    extension = input_filename.substr(pos+1);
  }

  // Read file.
  Mesh mesh;
  input_file.precision(50);

  if(strcmp(extension.c_str(), "msh") == 0)
  {
    marinov::MSHRead msh_read(input_file);
    msh_read.read();

    // Convert MSH format to CGAL surface mesh format.
    msh_to_mesh(msh_read, mesh);
  }
  else if(strcmp(extension.c_str(), "off") == 0)
  {
    input_file >> mesh;
  }
  else
  {
    std::cerr << "Invalid extension in \"" << input_filename << "\". Should be \"msh\" or \"off\"\n";
    return EXIT_FAILURE;
  }

  input_file.close();
  CGAL::Polygon_mesh_processing::orient_to_bound_a_volume(mesh);
  mesh.collect_garbage();

  // Check mesh for consistency.
  if(!check_mesh(mesh))
  {
    std::cerr << "Invalid mesh found. Stopping here.\n";
    return EXIT_FAILURE;
  }

  // Compute some statistics.
  {
    initial_number_of_faces = mesh.number_of_faces();
    initial_number_of_vertices = mesh.number_of_vertices();
  }

  // Perform Marinov face decimation.
  typename Face_decimation::Mesh_properties mesh_properties;
  Face_decimation decimater(mesh, std::cout);
  decimater.set_target_number_of_faces(target_number_of_faces);
  decimater.set_triangulate_mesh(do_retriangulate);
  decimater.decimate();

  mesh = decimater.get_compound_mesh();
  mesh_properties = decimater.get_compound_mesh_properties();

  CGAL::Polygon_mesh_processing::orient_to_bound_a_volume(mesh);
  mesh.collect_garbage();

  // Conversion to geo file format (gmsh).
  std::cout << "\nWrite geo file.\n";
  marinov::GmshWriter<Face_decimation> writer(output_filename, mesh, 100.0, 50, std::cout);
  writer.set_properties(mesh_properties);
  writer.Write();

  // Compute some statistics.
  {
    final_number_of_faces = mesh.number_of_faces();
    final_number_of_vertices = mesh.number_of_vertices();
  }

  // Output the statistics.
  {
    std::cout << "Initial number of faces: " << initial_number_of_faces
              << " vs final number of faces: " << final_number_of_faces
              << "\n"
              << "Initial number of vertices: " << initial_number_of_vertices
              << " vs final number of vertices: " << final_number_of_vertices
              << "\n";
  }

  return EXIT_SUCCESS;
}
