// Copyright (c) 2007-2008  INRIA (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
// You can redistribute it and/or modify it under the terms of the GNU
// General Public License as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// Licensees holding a valid commercial license may use this file in
// accordance with the commercial license agreement provided with the software.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
// WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// $URL$
// $Id$
// SPDX-License-Identifier: GPL-3.0+
//
// Author(s)     : Laurent RINEAU, Laurent Saboret

#ifndef CGAL_POISSON_REFINE_TRIANGULATION_H
#define CGAL_POISSON_REFINE_TRIANGULATION_H

#include <CGAL/license/Poisson_surface_reconstruction_3.h>


// CGAL
#include <CGAL/trace.h>
#include <CGAL/Mesher_level.h>
#include <CGAL/Mesh_3/Poisson_refine_cells_3.h>
#include <CGAL/Poisson_mesh_cell_criteria_3.h>
#include <CGAL/surface_reconstruction_points_assertions.h>
#include <CGAL/Surface_mesh_traits_generator_3.h>

namespace CGAL {

/// \cond SKIP_IN_MANUAL

/// Utility class for poisson_refine_triangulation():
/// implements Delaunay refinement in a loose bounding
/// box of point set (break bad tetrahedra, where
/// bad means badly shaped or too big).
///
/// This class must be derived to inherit from Mesher_level.
template <class Tr,
          class Criteria,
          class Surface,
          class Oracle,
          class Container = Meshes::Double_map_container<typename Tr::Cell_handle,
                                                         typename Criteria::Cell_quality>
>
class Poisson_mesher_level_impl_base :
    public Mesh_3::Poisson_refine_tets_with_oracle_base<Tr, Criteria, Surface, Oracle, Container>
{
  typedef Mesh_3::Poisson_refine_tets_with_oracle_base<Tr, Criteria, Surface, Oracle, Container> Base;

public:
  // Inherited methods and fields used below
  using Base::triangulation_ref_impl;
  using Base::oracle;
  using Base::surface;
  using Base::should_be_refined;

  typedef typename Tr::Geom_traits Geom_traits;
  typedef typename Tr::Vertex_handle Vertex_handle;
  typedef typename Tr::Cell_handle Cell_handle;
  typedef typename Tr::Point Point;
  typedef typename Base::Cell_quality Cell_quality;

public:
  /** \name CONSTRUCTORS */

  Poisson_mesher_level_impl_base(Tr& t, Criteria crit, unsigned int max_vert, Surface& surface, Oracle& oracle)
    : Base(t, crit, surface, oracle),
      max_vertices(max_vert) ///< number of vertices bound (ignored if zero)
  {}

protected:
  /* --- protected functions --- */

  bool test_if_cell_is_bad(const Cell_handle c)
  {
    Cell_quality q;
    if( is_in_domain(c) && should_be_refined(c, q) )
    {
      this->add_bad_element(c, q);
      return true;
    }
    return false;
  }

  bool is_in_domain(const Cell_handle c)
  {
    return oracle.is_in_volume(surface, triangulation_ref_impl().dual(c));
  }

public:
  /* Overriden functions of this level: */
  /* we override all methods that call test_if_cell_is_bad() */

  void scan_triangulation_impl()
  {
    for(typename Tr::Finite_cells_iterator cit = triangulation_ref_impl().finite_cells_begin(),
        eit = triangulation_ref_impl().finite_cells_end();
        cit != eit;
        ++cit)
      test_if_cell_is_bad(cit);
  }

  void after_insertion_impl(const Vertex_handle& v)
  {
    update_star(v);
  }

  void update_star(const Vertex_handle& v)
  {
    // scan refiner
    typedef std::vector<Cell_handle> Cells;
    typedef typename Cells::iterator Cell_iterator;
    Cells incident_cells;
    incident_cells.reserve(32);
    triangulation_ref_impl().incident_cells(v, std::back_inserter(incident_cells));

    for(Cell_iterator cit = incident_cells.begin();
        cit != incident_cells.end();
        ++cit)
    {
      if( ! triangulation_ref_impl().is_infinite(*cit) )
        test_if_cell_is_bad(*cit);
    }
  }

  /// Tells if the algorithm is done.
  bool no_longer_element_to_refine_impl() const
  {
    return Base::no_longer_element_to_refine_impl() ||
           (max_vertices > 0 && triangulation_ref_impl().number_of_vertices() >= max_vertices);
  }


private:
  /* --- private datas --- */
  unsigned int max_vertices; ///< number of vertices bound (ignored if zero)

}; // end Poisson_mesher_level_impl_base


/// Utility class for poisson_refine_triangulation():
/// glue class that inherits from both Mesher_level
/// and Poisson_mesher_level_impl_base.
template <typename Tr,
          typename Criteria,
          typename Surface,
          typename Oracle = typename CGAL::Surface_mesh_traits_generator_3<Surface>::type,
          typename PreviousLevel = Null_mesher_level
 >
class Poisson_mesher_level :
  public Poisson_mesher_level_impl_base<Tr, Criteria, Surface, Oracle>,
  public Mesher_level <
    Tr,
    Poisson_mesher_level<Tr, Criteria, Surface, Oracle, PreviousLevel>,
    typename Tr::Cell_handle,
    PreviousLevel,
    Triangulation_mesher_level_traits_3<Tr>
  >
{
  typedef Poisson_mesher_level_impl_base<Tr, Criteria, Surface, Oracle> Base;

public:
  typedef Mesher_level <
    Tr,
    Poisson_mesher_level<Tr, Criteria, Surface, Oracle, PreviousLevel>,
    typename Tr::Cell_handle,
    PreviousLevel,
    Triangulation_mesher_level_traits_3<Tr>
  > Mesher;

  Poisson_mesher_level(Tr& t, Criteria criteria, unsigned int max_vertices, Surface& surface, Oracle& oracle, PreviousLevel& previous_level)
    : Base(t, criteria, max_vertices, surface, oracle),
      Mesher(previous_level)
  {
  }

}; // end class Poisson_mesher_level


/// Delaunay refinement in a loose bounding box
/// of input point set (break bad tetrahedra, where
/// bad means badly shaped or too big).
/// @return the number of vertices inserted.
///
/// @commentheading Preconditions:
/// - Tr must use a geometric traits with robust circumcenter computation.
/// - convergence is guaranteed if radius_edge_ratio_bound >= 1.0.
///
/// @commentheading Template Parameters:
/// @param Tr 3D Delaunay triangulation.
/// @param Surface Sphere_3 or Iso_cuboid_3.
/// @param Sizing_field A sizing field functor type
/// @param Second_sizing_field A sizing field functor type
///
/// @commentheading Sizing fields 
/// - The first sizing field is the real sizing field that is targeted by
/// the refinement process. It may be costly to use.
/// - The second sizing field is supposed to be a sizing field that is less
/// costly to use (such as a constant sizing field). If a cell has a radius
/// that is smaller than the value of the second sizing field at its
/// center, then the cell is considered as small enough and the first
/// sizing field is not evaluated for that cell.
template <typename Tr,
          typename Surface,
          typename Sizing_field,
          typename Second_sizing_field>
unsigned int poisson_refine_triangulation(
  Tr& tr,
  double radius_edge_ratio_bound, ///< radius edge ratio bound (>= 1.0)
  const Sizing_field& sizing_field, ///< sizing field for cell radius bound
  const Second_sizing_field& second_sizing_field, ///< second sizing field for cell radius bound
  unsigned int max_vertices, ///< number of vertices bound (ignored if zero)
  Surface& enlarged_bbox) ///< new bounding sphere or box
{

  // Convergence is guaranteed if radius_edge_ratio_bound >= 1.0
  CGAL_surface_reconstruction_points_precondition(radius_edge_ratio_bound >= 1.0);

  // Mesher_level types
  typedef Poisson_mesh_cell_criteria_3<
    Tr
    , Sizing_field
    , Second_sizing_field
    > Tets_criteria;
  typedef typename CGAL::Surface_mesh_traits_generator_3<Surface>::type Oracle;
  typedef Poisson_mesher_level<Tr, Tets_criteria, Surface, Oracle, Null_mesher_level> Refiner;


  std::size_t nb_vertices = tr.number_of_vertices(); // get former #vertices

  // Delaunay refinement
  Tets_criteria tets_criteria(radius_edge_ratio_bound*radius_edge_ratio_bound, 
                              sizing_field,
                              second_sizing_field);
  Oracle oracle;
  Null_mesher_level null_mesher_level;
  Refiner refiner(tr, tets_criteria, max_vertices, enlarged_bbox, oracle, null_mesher_level);
  refiner.scan_triangulation(); // Push bad cells to the queue
  refiner.refine(Null_mesh_visitor()); // Refine triangulation until queue is empty

  nb_vertices = tr.number_of_vertices() - nb_vertices;


  return static_cast<unsigned int>(nb_vertices);
}

/// \endcond


} //namespace CGAL

#endif // CGAL_POISSON_REFINE_TRIANGULATION_H
