// Copyright (c) 2023 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.2/Tetrahedral_remeshing/include/CGAL/Adaptive_remeshing_sizing_field.h $
// $Id: include/CGAL/Adaptive_remeshing_sizing_field.h 37e2b278910 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Jane Tournois
//
//******************************************************************************
// File Description : Defines a sizing field adapted to a triangulation
//******************************************************************************

#ifndef CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H
#define CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H

#include <CGAL/license/Tetrahedral_remeshing.h>

#include <CGAL/Tetrahedral_remeshing_sizing_field.h>

#include <CGAL/Search_traits_3.h>
#include <CGAL/Search_traits_adapter.h>
#include <CGAL/Orthogonal_k_neighbor_search.h>

#include <CGAL/boost/graph/named_params_helper.h>

#include <CGAL/property_map.h>

#include <CGAL/Tetrahedral_remeshing/internal/tetrahedral_remeshing_helpers.h>
#include <CGAL/Tetrahedral_remeshing/internal/property_maps.h>

#include <vector>
#include <array>

#ifdef CGAL_TETRAHEDRAL_REMESHING_DEBUG
#include <CGAL/property_map.h>
#include <CGAL/IO/write_ply_points.h>
#include <boost/property_map/property_map.hpp>
#endif

namespace CGAL
{

/**
 * @ingroup PkgTetrahedralRemeshingSizing
 *
 * @class Adaptive_remeshing_sizing_field
 * @tparam Tr underlying triangulation type
 *
 * A sizing field for tetrahedral remeshing,
 * that keeps the same mesh density throughout the remeshing process.
 *
 * \cgalModels{RemeshingSizingField_3}
 */
template <typename Tr>
class Adaptive_remeshing_sizing_field
  : public Tetrahedral_remeshing_sizing_field<typename Tr::Geom_traits>
{
  // Types
public:
  typedef typename Tr::Geom_traits              GT;
  typedef typename GT::FT                       FT;
  typedef typename Tr::Geom_traits::Point_3     Point_3; //Bare_point
  typedef typename Tr::Vertex::Index            Index;

private:
  typedef typename Tr::Point                    Tr_point;
  typedef typename Tr::Vertex_handle            Vertex_handle;
  typedef typename Tr::Cell_handle              Cell_handle;

  struct Point_with_info
  {
    Point_3 p;
    FT size;
    int dimension;
  };

private:
  struct Point_property_map
  {
    using Self = Point_property_map;
    using value_type = Point_3;
    using reference = value_type; //TODO : why can't that be value_type& ?
    using key_type = Point_with_info;
    using category = boost::readable_property_map_tag;

    const value_type operator[](const key_type& pwi) const { return pwi.p; }
    friend const value_type get(const Self&, const key_type& pwi) { return pwi.p; }
  };

private:
  using Kd_traits = CGAL::Search_traits_adapter<Point_with_info,
                                                Point_property_map,
                                                CGAL::Search_traits_3<GT> >;
  using Neighbor_search = CGAL::Orthogonal_k_neighbor_search<Kd_traits>;
  using Kd_tree = typename Neighbor_search::Tree;
  using Distance = typename Neighbor_search::Distance;
  using Splitter = typename Neighbor_search::Splitter;

#ifndef DOXYGEN_RUNNING
public:
  template<typename ECMap, typename FCMap, typename CellSelector>
  Adaptive_remeshing_sizing_field(const Tr& tr,
                                  const ECMap& ecmap,
                                  const FCMap& fcmap,
                                  const CellSelector& cell_selector)
    : m_kd_tree_3(points_with_info(tr, 3, ecmap, fcmap, cell_selector),
                  Splitter(),
                  Kd_traits(Point_property_map()))
    , m_kd_tree_2(points_with_info(tr, 2, ecmap, fcmap, cell_selector),
                Splitter(),
                Kd_traits(Point_property_map()))
  {
    m_kd_tree_3.build();
    m_kd_tree_2.build();

#ifdef CGAL_TETRAHEDRAL_REMESHING_DEBUG
    dump_kd_trees();
#endif
  }
#endif

private:

  template<typename ECMap, typename FCMap, typename CellSelector>
  std::vector<Point_with_info> points_with_info(const Tr& tr,
                                                const int dim,
                                                const ECMap& ecmap,
                                                const FCMap& fcmap,
                                                const CellSelector& cell_selector) const
  {
    namespace Tet_remeshing = CGAL::Tetrahedral_remeshing;
    using Facet = typename Tr::Facet;
    using Edge = typename Tr::Edge;

    auto cp = tr.geom_traits().construct_point_3_object();
    auto centroid = tr.geom_traits().construct_centroid_3_object();
    auto midpt = tr.geom_traits().construct_midpoint_3_object();

    std::vector<Point_with_info> points;
    for (const Vertex_handle v : tr.finite_vertex_handles())
    {
      if(  (dim == 3 && dim == v->in_dimension())//inside volume
        || (dim < 3  && v->in_dimension() < 3))  //on surface
      {
        const FT size = average_edge_length_around(v, tr, ecmap, fcmap, cell_selector);
        if (CGAL::is_zero(size))
          continue;
        points.push_back(Point_with_info{ cp(tr.point(v)), size, v->in_dimension() });
      }
    }

    // add internal points
    for (const Cell_handle c : tr.finite_cell_handles())
    {
      if (!get(cell_selector, c))
        continue;

      // inside cells
      if(dim == 3)
      {
        const FT size = average_edge_length_3(c, tr);
        if (CGAL::is_zero(size))
          continue;
        points.push_back(Point_with_info{ centroid(tr.tetrahedron(c)), size, 3 });
      }
      else
      {
        // on surface facets
        for (int i = 0; i < 4; ++i)
        {
          const Cell_handle cn = c->neighbor(i);
          if(  get(cell_selector, c) != get(cell_selector, cn)
            || get(fcmap, Facet(c, i))
            || c->is_facet_on_surface(i) )
          {
            const FT size = average_edge_length_2(c, i, tr);
            if (CGAL::is_zero(size))
              continue;
            points.push_back(Point_with_info{ centroid(tr.triangle(c, i)), size, 2 });
          }
        }
        // on complex edges
        for (const Edge& e : Tet_remeshing::cell_edges(c, tr))
        {
          if(get(ecmap, Tet_remeshing::make_vertex_pair(e)))
          {
            const FT size = Tet_remeshing::approximate_edge_length(e, tr);
            if (CGAL::is_zero(size))
              continue;
            points.push_back(Point_with_info{midpt(tr.segment(e)), size, 1 });
          }
        }
      }
    }
    return points;
  }

  const Kd_tree& kd_tree(const int dim) const
  {
    if (dim == 3)
      return m_kd_tree_3;
    else
      return m_kd_tree_2;
  }

  int nb_neighbors(const int dim) const
  {
    if (dim == 3)      return 30;
    else               return 6;
  }

public:
  /**
  * Returns size at point `p`, assumed to be included in the input
  * subcomplex with dimension `dim` and index `index`.
  */
#ifdef DOXYGEN_RUNNING
  FT operator()(const Point_3& p, const int& dim, const Index& index) const
#else
  FT operator()(const Point_3& p, const int& dim, const Index& ) const
#endif
  {
    Point_property_map pp_map;
    Distance dist(pp_map);

    // Find nearest vertex and local size before remeshing
    Neighbor_search search(kd_tree(dim),
                           p, //query point
                           nb_neighbors(dim), //nb nearest neighbors
                           0, //epsilon
                           true, //search nearest
                           dist);

    FT max_size = 0.;
    std::vector<Point_with_info> neighbors;

    for (const auto& neighbor : search)
    {
      [[maybe_unused]] const auto& [pi, size, dimension] = neighbor.first;

      max_size = (std::max)(max_size, size);

      if(  (dim == 3 && dimension == dim) //volume point
        || (dim < 3  && dimension < 3) )  //surface point
      {
        neighbors.push_back(neighbor.first);
      }
    }

    if (neighbors.empty())
      return max_size;

    return interpolate_on_n_vertices(p, neighbors);
  }

private:
  /**
   * Returns size at point `p`, by interpolation among neighbors
   */
  FT interpolate_on_n_vertices(
    const Point_3& p,
    const std::vector<Point_with_info>& vertices) const;

  template<typename ECMap, typename FCMap, typename CellSelector>
  FT average_edge_length_around(const Vertex_handle v, const Tr& tr,
    const ECMap& ecmap, const FCMap& fcmap, const CellSelector& cell_selector) const;

  FT average_edge_length_3(const Cell_handle c, const Tr& tr) const;
  FT average_edge_length_2(const Cell_handle c, const int i, const Tr& tr) const;

private:
  Kd_tree m_kd_tree_3; //volumes
  Kd_tree m_kd_tree_2; //surfaces

#ifdef CGAL_TETRAHEDRAL_REMESHING_DEBUG
private:
  void dump_kd_trees() const
  {
    std::ofstream ofs("kd_trees.ply");

    using Point_Size = std::pair<Point_3, float>;
    std::vector<Point_Size> points;

    for (auto it = m_kd_tree_3.begin(); it != m_kd_tree_3.end(); ++it)
      points.push_back(std::make_pair(it->p, it->size));

    for (auto it = m_kd_tree_2.begin(); it != m_kd_tree_2.end(); ++it)
      points.push_back(std::make_pair(it->p, it->size));

    using Point_map = CGAL::First_of_pair_property_map<Point_Size>;
    using Size_map = CGAL::Second_of_pair_property_map<Point_Size>;

    CGAL::IO::write_PLY_with_properties(ofs,
      points,
      CGAL::make_ply_point_writer(Point_map()),
      std::make_pair(Size_map(), CGAL::IO::PLY_property<double>("intensity")));
  }
#endif

};//end of class Adaptive_remeshing_sizing_field

/*!
 * @ingroup PkgTetrahedralRemeshingSizing
 *
 * @brief Create an adaptive sizing field for tetrahedral remeshing
 *
 * \relates Adaptive_remeshing_sizing_field
 *
 * This method is a <em>named constructor</em>. It constructs a
 * sizing field of type `Adaptive_remeshing_sizing_field<Tr>`,
 * designed to keep the density unchanged throughout the remeshing
 * process.
 *
 * @returns an `Adaptive_remeshing_sizing_field<Tr>`
 *
 * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
 * \param tr the input triangulation
 * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters"
 *           among the ones listed below. All of them must be the same as the ones
 *           given to `CGAL::tetrahedral_isotropic_remeshing()`.
 *
 *\cgalNamedParamsBegin
 *  \cgalParamNBegin{ edge_is_constrained_map }
 *    \cgalParamDescription{a property map containing the constrained-or-not
 *        status of each edge of `tr`.}
 *    \cgalParamDefault{a default property map where no edge is constrained}
 *  \cgalParamNEnd
 *  \cgalParamNBegin{ facet_is_constrained_map }
 *    \cgalParamDescription{a property map containing the constrained-or-not
 *        status of each facet of `tr`.}
 *   \cgalParamDefault{a default property map where no facet is constrained}
 *  \cgalParamNEnd
 *  \cgalParamNBegin{ cell_is_selected_map }
 *    \cgalParamDescription{a property map containing the selected
 *         - or - not status for each cell of `tr` for remeshing.}
 *    \cgalParamDefault{a default property map where all cells of the domain are selected}
 *  \cgalParamNEnd
 *\cgalNamedParamsEnd
 */
template<typename Tr, typename CGAL_NP_TEMPLATE_PARAMETERS>
Adaptive_remeshing_sizing_field<Tr>
create_adaptive_remeshing_sizing_field(const Tr& tr,
  const CGAL_NP_CLASS& np = parameters::default_values())
{
  using parameters::choose_parameter;
  using parameters::get_parameter;

  using V = typename Tr::Vertex_handle;
  using Edge_vv = std::pair<V, V>;
  using Facet = typename Tr::Facet;

  auto ecmap = choose_parameter(get_parameter(np, internal_np::edge_is_constrained),
    CGAL::Constant_property_map<Edge_vv, bool>(false));
  auto fcmap = choose_parameter(get_parameter(np, internal_np::facet_is_constrained),
    CGAL::Constant_property_map<Facet, bool>(false));
  auto cell_selector = choose_parameter(get_parameter(np, internal_np::cell_selector),
    CGAL::Tetrahedral_remeshing::internal::All_cells_selected<Tr>());

  return Adaptive_remeshing_sizing_field<Tr>(tr, ecmap, fcmap, cell_selector);
}


template <typename Tr>
typename Adaptive_remeshing_sizing_field<Tr>::FT
Adaptive_remeshing_sizing_field<Tr>::
interpolate_on_n_vertices(
  const Point_3& p,
  const std::vector<Point_with_info>& points_with_info) const
{
  // Interpolate value using values at vertices
  const auto sqd = GT().compute_squared_distance_3_object();

  FT sum_weights = 0.;
  FT sum_sizes = 0.;

  for (const auto pwi : points_with_info)
  {
    const FT size = pwi.size;
    const FT sqdist = sqd(pwi.p, p);

    if (is_zero(sqdist))
      return size;

    const FT weight = 1. / CGAL::approximate_sqrt(sqdist);
    sum_weights += weight;
    sum_sizes += weight * size;
  }

  CGAL_assertion(sum_weights > 0);
  CGAL_assertion(sum_sizes > 0);
  return sum_sizes / sum_weights;
}


template<typename Tr>
typename Adaptive_remeshing_sizing_field<Tr>::FT
Adaptive_remeshing_sizing_field<Tr>::
average_edge_length_3(const typename Tr::Cell_handle c, const Tr& tr) const
{
  using namespace CGAL::Tetrahedral_remeshing;

  FT sum = 0.;
  for (const typename Tr::Edge& e : cell_edges(c, tr))
  {
    sum += approximate_edge_length(e, tr);
  }
  return sum / FT(6);
}

template<typename Tr>
typename Adaptive_remeshing_sizing_field<Tr>::FT
Adaptive_remeshing_sizing_field<Tr>::
average_edge_length_2(const typename Tr::Cell_handle c,
                      const int i,
                      const Tr& tr) const
{
  // facet(c,i)
  namespace Tet_Remeshing = CGAL::Tetrahedral_remeshing;

  FT sum = 0.;
  short nb_surface_edges = 0;
  for (const typename Tr::Edge& e : Tet_Remeshing::facet_edges(c, i, tr))
  {
    CGAL_assertion_code(const auto vs = tr.vertices(e));
    CGAL_assertion(vs[0]->in_dimension() < 3);
    CGAL_assertion(vs[1]->in_dimension() < 3);

    sum += Tet_Remeshing::approximate_edge_length(e, tr);
    ++nb_surface_edges;
  }

  return sum / FT(nb_surface_edges);
}

template <typename Tr>
template <typename ECMap, typename FCMap, typename CellSelector>
typename Adaptive_remeshing_sizing_field<Tr>::FT
Adaptive_remeshing_sizing_field<Tr>::
average_edge_length_around(const Vertex_handle v, const Tr& tr,
                           const ECMap& ecmap, const FCMap& fcmap,
                           const CellSelector& cell_selector) const
{
  using Edge = typename Tr::Edge;
  using Facet = typename Tr::Facet;
  using namespace CGAL::Tetrahedral_remeshing;

  std::vector<Edge> tmp_edges;
  tr.incident_edges(v, std::back_inserter(tmp_edges));

  std::vector<Edge> edges;
  switch (v->in_dimension())
  {
  case 3:
    std::copy_if(tmp_edges.begin(), tmp_edges.end(),
      std::back_inserter(edges),
      [&tr, &cell_selector](const Edge& e)
      {
        return is_selected(e, tr, cell_selector);
      });
    break;

  case 2:
    std::copy_if(tmp_edges.begin(), tmp_edges.end(),
      std::back_inserter(edges),
      [&fcmap, &cell_selector, &tr](const Edge& e)
      {
        auto fcirc = tr.incident_facets(e);
        const auto fend = fcirc;
        do
        {
          const Facet& f = *fcirc;
          if ( get(fcmap, f)
            || get(cell_selector, f.first) != get(cell_selector, f.first->neighbor(f.second)))
            return true;
        } while (++fcirc != fend);

        return false;
      });
    break;

  case 1:
  case 0:
    std::copy_if(tmp_edges.begin(), tmp_edges.end(),
      std::back_inserter(edges),
      [&ecmap](const Edge& e)
      {
        const auto evv = make_vertex_pair(e);
        return get(ecmap, evv);
      });
    break;

  default: // could be -1 because of far points
    break;
  }

  if (edges.empty())
    return 0;

  FT sum = 0.;
  for (const Edge& e : edges)
  {
    sum += approximate_edge_length(e, tr);
  }

  return sum / static_cast<FT>(edges.size());
}

} //namespace CGAL

#endif // CGAL_TETRAHEDRAL_REMESHING_ADAPTIVE_SIZING_FIELD_H
