#include <Rcpp.h>
// Copyright (c) 2018-2019 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/smooth_shape.h $
// $Id: include/CGAL/Polygon_mesh_processing/smooth_shape.h b26b07a1242 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Mael Rouxel-Labbé
//                 Konstantinos Katrioplas (konst.katrioplas@gmail.com)

#ifndef CGAL_POLYGON_MESH_PROCESSING_SMOOTH_SHAPE_H
#define CGAL_POLYGON_MESH_PROCESSING_SMOOTH_SHAPE_H

#include <CGAL/license/Polygon_mesh_processing/meshing_hole_filling.h>

#if defined(CGAL_EIGEN3_ENABLED)
#include <CGAL/Eigen_solver_traits.h>
#endif

#include <CGAL/Polygon_mesh_processing/internal/Smoothing/curvature_flow_impl.h>

#include <CGAL/utility.h>
#include <CGAL/property_map.h>

#ifdef CGAL_PMP_SMOOTHING_OUTPUT_INTERMEDIARY_STEPS
#include <fstream>
#include <sstream>
#endif

namespace CGAL {
namespace Polygon_mesh_processing {

/*!
* \ingroup PMP_meshing_grp
*
* @brief smooths the overall shape of the mesh by using the mean curvature flow.
*
* The effect depends on the curvature of each area and on a time step which
* represents the amount by which vertices are allowed to move.
* The result conformally maps the initial surface to a sphere.
*
* @tparam TriangleMesh model of `MutableFaceGraph`.
* @tparam FaceRange range of `boost::graph_traits<TriangleMesh>::%face_descriptor`,
*         model of `Range`. Its iterator type is `ForwardIterator`.
* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
*
* @param tmesh a polygon mesh with triangulated surface patches to be smoothed.
* @param faces the range of triangular faces defining one or several surface patches to be smoothed.
* @param time a time step that corresponds to the speed by which the surface is smoothed.
*        A larger time step results in faster convergence but details may be distorted to a larger extent
*        compared to more iterations with a smaller step. Typical values scale in the interval (1e-6, 1].
* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
*
* \cgalNamedParamsBegin
*   \cgalParamNBegin{number_of_iterations}
*     \cgalParamDescription{the number of iterations for the sequence of the smoothing iterations performed}
*     \cgalParamType{unsigned int}
*     \cgalParamDefault{`1`}
*   \cgalParamNEnd
*
*   \cgalParamNBegin{vertex_is_constrained_map}
*     \cgalParamDescription{a property map containing the constrained-or-not status of each vertex of `tmesh`.}
*     \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits<TriangleMesh>::%vertex_descriptor`
*                    as key type and `bool` as value type. It must be default constructible.}
*     \cgalParamDefault{a default property map where no vertex is constrained}
*     \cgalParamExtra{A constrained vertex cannot be modified at all during smoothing.}
*   \cgalParamNEnd
*
*   \cgalParamNBegin{do_scale}
*     \cgalParamDescription{Whether to apply rescaling after smoothing. This is useful because
*                           the mean curvature flow tends to shrink the surface.}
*     \cgalParamType{Boolean}
*     \cgalParamDefault{`true`}
*     \cgalParamExtra{Scaling can only be applied if the mesh is closed and if there is no more than
*                     a single constrained vertex.}
*     \cgalParamExtra{If a vertex is constrained, it is the fixed point of the scaling, otherwise
*                     the centroid is used.}
*   \cgalParamNEnd
*
*   \cgalParamNBegin{vertex_point_map}
*     \cgalParamDescription{a property map associating points to the vertices of `tmesh`}
*     \cgalParamType{a class model of `ReadWritePropertyMap` with `boost::graph_traits<TriangleMesh>::%vertex_descriptor`
*                    as key type and `%Point_3` as value type}
*     \cgalParamDefault{`boost::get(CGAL::vertex_point, tmesh)`}
*     \cgalParamExtra{If this parameter is omitted, an internal property map for `CGAL::vertex_point_t`
*                     must be available in `TriangleMesh`.}
*   \cgalParamNEnd
*
*   \cgalParamNBegin{geom_traits}
*     \cgalParamDescription{an instance of a geometric traits class}
*     \cgalParamType{a class model of `Kernel`}
*     \cgalParamDefault{a \cgal Kernel deduced from the point type, using `CGAL::Kernel_traits`}
*     \cgalParamExtra{The geometric traits class must be compatible with the vertex point type.}
*   \cgalParamNEnd
*
*   \cgalParamNBegin{sparse_linear_solver}
*     \cgalParamDescription{an instance of the sparse linear solver used for smoothing}
*     \cgalParamType{a class model of `SparseLinearAlgebraWithFactorTraits_d`}
*     \cgalParamDefault{if \ref thirdpartyEigen "Eigen" 3.2 (or greater) is available and
*                       `CGAL_EIGEN3_ENABLED` is defined, then the following overload of `Eigen_solver_traits`
*                       is provided as default value:
*                       `CGAL::Eigen_solver_traits<Eigen::BiCGSTAB<CGAL::Eigen_sparse_matrix<double>::%EigenType, Eigen::IncompleteLUT<double> > >`}
*   \cgalParamNEnd
* \cgalNamedParamsEnd
*
* @warning This function involves linear algebra, that is computed using non-exact, floating-point arithmetic.
*
* @see `angle_and_area_smoothing()`
*/
template<typename TriangleMesh, typename FaceRange,
         typename NamedParameters = parameters::Default_named_parameters>
void smooth_shape(const FaceRange& faces,
                  TriangleMesh& tmesh,
                  const double time,
                  const NamedParameters& np = parameters::default_values())
{
  if(std::begin(faces) == std::end(faces))
    return;

  typedef typename boost::graph_traits<TriangleMesh>::vertex_descriptor      vertex_descriptor;
  typedef typename GetGeomTraits<TriangleMesh, NamedParameters>::type        GeomTraits;
  typedef typename GetVertexPointMap<TriangleMesh, NamedParameters>::type    VertexPointMap;
  typedef typename internal_np::Lookup_named_param_def<
                     internal_np::vertex_is_constrained_t,
                     NamedParameters,
                     Static_boolean_property_map<vertex_descriptor, false> >::type  VCMap;

  using parameters::choose_parameter;
  using parameters::get_parameter;

  GeomTraits gt = choose_parameter<GeomTraits>(get_parameter(np, internal_np::geom_traits));
  VertexPointMap vpmap = choose_parameter(get_parameter(np, internal_np::vertex_point),
                                          get_property_map(CGAL::vertex_point, tmesh));
  VCMap vcmap = choose_parameter(get_parameter(np, internal_np::vertex_is_constrained),
                                 Static_boolean_property_map<vertex_descriptor, false>());
  const unsigned int nb_iterations = choose_parameter(get_parameter(np, internal_np::number_of_iterations), 1);
  const bool scale_after_smoothing = choose_parameter(get_parameter(np, internal_np::do_scale), true);

#if defined(CGAL_EIGEN3_ENABLED)
#if EIGEN_VERSION_AT_LEAST(3,2,0)
  typedef typename Eigen::SparseMatrix<double>                                         Eigen_sparse_matrix;
  typedef typename Eigen::BiCGSTAB<Eigen_sparse_matrix, Eigen::IncompleteLUT<double> > Eigen_solver;
  typedef CGAL::Eigen_solver_traits<Eigen_solver>                                      Default_solver;
#else
  typedef bool Default_solver;//compilation should crash
  //if no solver is provided and Eigen version < 3.2
#endif
#else
  typedef bool Default_solver;//compilation should crash
  // if no solver is provided and Eigen version < 3.2
#endif

#if defined(CGAL_EIGEN3_ENABLED)
  static_assert(
      (!std::is_same<typename GetSolver<NamedParameters, Default_solver>::type, bool>::value) || EIGEN_VERSION_AT_LEAST(3, 2, 0),
      "Eigen3 version 3.2 or later is required.");
#else
  static_assert(
      (!std::is_same<typename GetSolver<NamedParameters, Default_solver>::type, bool>::value),
      "Eigen3 version 3.2 or later is required.");
#endif

  typedef typename GetSolver<NamedParameters, Default_solver>::type               Sparse_solver;
  typedef typename Sparse_solver::Matrix                                          Eigen_matrix;
  typedef typename Sparse_solver::Vector                                          Eigen_vector;

  Sparse_solver solver = choose_parameter<Default_solver>(get_parameter(np, internal_np::sparse_linear_solver));

  std::size_t n = vertices(tmesh).size();
  Eigen_matrix A(n, n);
  Eigen_vector bx(n), by(n), bz(n), Xx(n), Xy(n), Xz(n);
  std::vector<CGAL::Triple<std::size_t, std::size_t, double> > stiffness;

  internal::Shape_smoother<TriangleMesh, VertexPointMap, VCMap, Sparse_solver, GeomTraits> smoother(tmesh, vpmap, vcmap, scale_after_smoothing, gt);

  smoother.init_smoothing(faces);

  // For robustness reasons, the Laplacian coefficients are computed only once (only the mass
  // matrix is updated at every iteration). See Kazdhan et al. "Can Mean-Curvature Flow Be Made Non-Singular?".
  smoother.calculate_stiffness_matrix_elements(stiffness);

  for(unsigned int iter=0; iter<nb_iterations; ++iter)
  {
#ifdef CGAL_PMP_SMOOTHING_DEBUG
    Rcpp::Rcout << "iteration #" << iter << std::endl;
#endif

    smoother.setup_system(A, bx, by, bz, stiffness, time);

    if(smoother.solve_system(A, Xx, Xy, Xz, bx, by, bz, solver))
    {
      smoother.update_mesh(Xx, Xy, Xz);
    }
    else
    {
#ifdef CGAL_PMP_SMOOTHING_DEBUG
      Rcpp::Rcerr << "Failed to solve system!" << std::endl;
#endif
      break;
    }

#ifdef CGAL_PMP_SMOOTHING_OUTPUT_INTERMEDIARY_STEPS
    std::stringstream oss;
    oss << "smoothed_mesh_step_" << iter << ".off" << std::ends;
    std::ofstream out(oss.str().c_str());
    out.precision(17);
    out << tmesh;
    out.close();
#endif
  }
}

/// \cond SKIP_IN_MANUAL
template <typename TriangleMesh, typename CGAL_NP_TEMPLATE_PARAMETERS>
void smooth_shape(TriangleMesh& tmesh,
                  const double time,
                  const CGAL_NP_CLASS& np = parameters::default_values())
{
  smooth_shape(faces(tmesh), tmesh, time, np);
}
/// \endcond

} // Polygon_mesh_processing
} // CGAL

#endif // CGAL_POLYGON_MESH_PROCESSING_SMOOTH_SHAPE_H

