// Copyright (c) 2018 INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.2/Shape_detection/include/CGAL/Shape_detection/Region_growing/Polygon_mesh/Least_squares_plane_fit_sorting.h $
// $Id: include/CGAL/Shape_detection/Region_growing/Polygon_mesh/Least_squares_plane_fit_sorting.h cac3e9d75e2 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Florent Lafarge, Simon Giraudot, Thien Hoang, Dmitry Anisimov
//

#ifndef CGAL_SHAPE_DETECTION_REGION_GROWING_POLYGON_MESH_LEAST_SQUARES_PLANE_FIT_SORTING_H
#define CGAL_SHAPE_DETECTION_REGION_GROWING_POLYGON_MESH_LEAST_SQUARES_PLANE_FIT_SORTING_H

#include <CGAL/license/Shape_detection.h>

#include <CGAL/Shape_detection/Region_growing/internal/property_map.h>
#include <CGAL/Shape_detection/Region_growing/internal/utils.h>

namespace CGAL {
namespace Shape_detection {
namespace Polygon_mesh {

/*!
  \ingroup PkgShapeDetectionRGOnMesh

  \brief Sorting of polygon mesh faces with respect to the local plane fit quality.

  `Items` of faces in a polygon mesh are sorted with respect to the quality of the
  least squares plane fit applied to the vertices of incident faces of each face.

  \tparam GeomTraits
  a model of `Kernel`

  \tparam PolygonMesh
  a model of `FaceListGraph`

  \tparam NeighborQuery
  a model of `NeighborQuery`

  \tparam VertexToPointMap
  a model of `ReadablePropertyMap` whose key type is the vertex type of a polygon mesh and
  value type is `GeomTraits::Point_3`
*/
template<typename GeomTraits,
         typename PolygonMesh,
         typename NeighborQuery,
         typename VertexToPointMap = typename boost::property_map<PolygonMesh, CGAL::vertex_point_t>::const_type>
class Least_squares_plane_fit_sorting {
public:
  /// \name Types
  /// @{

  /// \cond SKIP_IN_MANUAL
  using Neighbor_query = NeighborQuery;
  using Vertex_to_point_map = VertexToPointMap;
  /// \endcond

  /// Item type.
  using Item = typename boost::graph_traits<PolygonMesh>::face_descriptor;

  /// Seed range.
  using Seed_range = std::vector<Item>;

  /// @}

private:
  using FT = typename GeomTraits::FT;
  using Compare_scores = internal::Compare_scores<FT>;

public:
  /// \name Initialization
  /// @{

  /*!
    \brief initializes all internal data structures.

    \tparam NamedParameters
    a sequence of \ref bgl_namedparameters "Named Parameters"

    \param pmesh
    an instance of `PolygonMesh` that represents a polygon mesh

    \param neighbor_query
    an instance of `NeighborQuery` that is used internally to
    access face's neighbors

    \param np
    a sequence of \ref bgl_namedparameters "Named Parameters"
    among the ones listed below

    \cgalNamedParamsBegin
      \cgalParamNBegin{vertex_point_map}
        \cgalParamDescription{an instance of `VertexToPointMap` that maps a polygon mesh
        vertex (`boost::graph_traits<PolygonMesh>::vertex_descriptor`) to `GeomTraits::Point_3`}
        \cgalParamDefault{`boost::get(CGAL::vertex_point, pmesh)`}
      \cgalParamNEnd
      \cgalParamNBegin{geom_traits}
        \cgalParamDescription{an instance of `GeomTraits`}
        \cgalParamDefault{`GeomTraits()`}
      \cgalParamNEnd
    \cgalNamedParamsEnd

    \pre `faces(pmesh).size() > 0`
  */
  template<typename CGAL_NP_TEMPLATE_PARAMETERS>
  Least_squares_plane_fit_sorting(
    const PolygonMesh& pmesh,
    NeighborQuery& neighbor_query,
    const CGAL_NP_CLASS& np = parameters::default_values()) :
  m_face_graph(pmesh),
  m_neighbor_query(neighbor_query),
  m_vertex_to_point_map(parameters::choose_parameter(parameters::get_parameter(
    np, internal_np::vertex_point), get_const_property_map(CGAL::vertex_point, pmesh))),
  m_traits(parameters::choose_parameter<GeomTraits>(parameters::get_parameter(np, internal_np::geom_traits))) {

    CGAL_precondition(faces(pmesh).size() > 0);

    m_ordered.resize(faces(pmesh).size());

    std::size_t index = 0;
    for (Item item : faces(pmesh))
      m_ordered[index++] = item;
    m_scores.resize(m_ordered.size());
  }

  /// @}

  /// \name Sorting
  /// @{

  /*!
    \brief sorts `Items` of input faces.
  */
  void sort() {
    compute_scores();
    CGAL_precondition(m_scores.size() > 0);
    Compare_scores cmp(m_scores);

    std::vector<std::size_t> order(m_ordered.size());
    std::iota(order.begin(), order.end(), 0);
    std::sort(order.begin(), order.end(), cmp);

    std::vector<Item> tmp(m_ordered.size());
    for (std::size_t i = 0; i < m_ordered.size(); i++)
      tmp[i] = m_ordered[order[i]];

    m_ordered.swap(tmp);
  }
  /// @}

  /// \name Access
  /// @{

  /*!
    \brief returns an instance of `Seed_range` to access the ordered `Items`
    of input faces.
  */
  const Seed_range &ordered() {
    return m_ordered;
  }
  /// @}

private:
  const PolygonMesh& m_face_graph;
  Neighbor_query& m_neighbor_query;
  const Vertex_to_point_map m_vertex_to_point_map;
  GeomTraits m_traits;
  Seed_range m_ordered;
  std::vector<FT> m_scores;

  void compute_scores() {

    std::vector<Item> neighbors;
    std::size_t idx = 0;
    for (Item item : m_ordered) {
      neighbors.clear();
      m_neighbor_query(item, neighbors);
      neighbors.push_back(item);
      m_scores[idx++] = internal::create_plane_from_faces(
        m_face_graph, neighbors, m_vertex_to_point_map, m_traits).second;
    }
  }
};

} // namespace Polygon_mesh
} // namespace Shape_detection
} // namespace CGAL

#endif // CGAL_SHAPE_DETECTION_REGION_GROWING_POLYGON_MESH_LEAST_SQUARES_PLANE_FIT_SORTING_H
