// Copyright (c) 2025 GeometryFactory SARL (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/Segment_set/Segment_length_sorting.h $
// $Id: include/CGAL/Shape_detection/Region_growing/Segment_set/Segment_length_sorting.h cac3e9d75e2 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Mael Rouxel-Labbé
//

#ifndef CGAL_SHAPE_DETECTION_REGION_GROWING_SEGMENT_SET_SEGMENT_LENGTH_SORTING_H
#define CGAL_SHAPE_DETECTION_REGION_GROWING_SEGMENT_SET_SEGMENT_LENGTH_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/region_growing_traits.h>

namespace CGAL {
namespace Shape_detection {
namespace Segment_set {

/*!
  \ingroup PkgShapeDetectionRGOnSegments

  \brief Sorting of segments with respect to their length.

  `Items` of segments are sorted in decreasing length.

  \tparam GeomTraits
  a model of `Kernel`

  \tparam Item_
  a descriptor representing a given segment. Must be a model of `Hashable`.

  \tparam SegmentMap
  a model of `ReadablePropertyMap` whose key type is the value type of the input
  range and value type is `GeomTraits::Segment_2` or `GeomTraits::Segment_3`
*/
template<typename GeomTraits,
         typename Item_,
         typename SegmentMap>
class Segment_length_sorting
{
public:
  /// \name Types
  /// @{
  using Segment_map = SegmentMap;
  using Segment_type = typename boost::property_traits<Segment_map>::value_type;

  /// Item type.
  using Item = Item_;

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

private:
  using FT = typename GeomTraits::FT;
  using Segment_set_traits = typename std::conditional<
    std::is_same<typename GeomTraits::Segment_2, Segment_type>::value,
    internal::Region_growing_traits_2<GeomTraits>,
    internal::Region_growing_traits_3<GeomTraits> >::type;

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

  /*!
    \brief initializes all internal data structures.

    \tparam InputRange
    a model of `ConstRange` whose iterator type is `InputIterator`

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

    \param input_range
    an instance of `InputRange` with 2D or 3D segments

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

    \cgalNamedParamsBegin
      \cgalParamNBegin{segment_map}
        \cgalParamDescription{an instance of `SegmentMap` that maps the `Item` of a segment
        to `GeomTraits::Segment_2` or `GeomTraits::Segment_3`}
        \cgalParamDefault{`SegmentMap()`}
      \cgalParamNEnd
      \cgalParamNBegin{item_map}
        \cgalParamDescription{an instance of a model of `ReadablePropertyMap` with `InputRange::const_iterator`
                              as key type and `Item` as value type.}
        \cgalParamDefault{A default is provided when `Item` is `InputRange::const_iterator` or its value type.}
      \cgalParamNEnd
      \cgalParamNBegin{geom_traits}
        \cgalParamDescription{an instance of `GeomTraits`}
        \cgalParamDefault{`GeomTraits()`}
      \cgalParamNEnd
    \cgalNamedParamsEnd

    \pre `input_range.size() > 0`
  */
  template<typename InputRange,
           typename NamedParameters = parameters::Default_named_parameters>
  Segment_length_sorting(const InputRange& input_range,
                         const NamedParameters& np = parameters::default_values())
  : m_segment_map(parameters::choose_parameter<SegmentMap>(parameters::get_parameter(
      np, internal_np::segment_map))),
      m_traits(parameters::choose_parameter<GeomTraits>(parameters::get_parameter(np, internal_np::geom_traits))),
      m_segment_set_traits(m_traits)
  {
    CGAL_precondition(input_range.size() > 0);

    using NP_helper = internal::Default_property_map_helper<NamedParameters, Item, typename InputRange::const_iterator, internal_np::item_map_t>;
    using Item_map = typename NP_helper::type;
    Item_map item_map = NP_helper::get(np);

    m_ordered.resize(input_range.size());
    std::size_t index = 0;
    for (auto it = input_range.begin(); it != input_range.end(); it++)
      m_ordered[index++] = get(item_map, it);
    m_scores.resize(input_range.size(), 0.);
  }

  /*!
    \brief initializes all internal data structures.
    Three-parameter constructor with a dummy parameter provided for compatibility with other sorting types.

    \tparam InputRange
    a model of `ConstRange` whose iterator type is `InputIterator`

    \tparam Dummy
    Dummy parameter that matches NeighborQuery in other sorting classes. Is not used.

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

    \param input_range
    an instance of `InputRange` with 2D or 3D segments

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

    \cgalNamedParamsBegin
      \cgalParamNBegin{segment_map}
        \cgalParamDescription{an instance of `SegmentMap` that maps the `Item` of a segment
        to `GeomTraits::Segment_2` or `GeomTraits::Segment_3`}
        \cgalParamDefault{`SegmentMap()`}
      \cgalParamNEnd
      \cgalParamNBegin{item_map}
        \cgalParamDescription{an instance of a model of `ReadablePropertyMap` with `InputRange::const_iterator`
                              as key type and `Item` as value type.}
        \cgalParamDefault{A default is provided when `Item` is `InputRange::const_iterator` or its value type.}
      \cgalParamNEnd
      \cgalParamNBegin{geom_traits}
        \cgalParamDescription{an instance of `GeomTraits`}
        \cgalParamDefault{`GeomTraits()`}
      \cgalParamNEnd
    \cgalNamedParamsEnd

    \pre `input_range.size() > 0`
  */
  template<typename InputRange, typename Dummy,
    typename NamedParameters = parameters::Default_named_parameters,
    std::enable_if_t<std::is_same<Item, typename Dummy::Item>::value, bool> = true>
  Segment_length_sorting(const InputRange& input_range,
    const Dummy&,
    const NamedParameters& np = parameters::default_values())
    : m_segment_map(parameters::choose_parameter<SegmentMap>(parameters::get_parameter(
      np, internal_np::segment_map))),
    m_traits(parameters::choose_parameter<GeomTraits>(parameters::get_parameter(np, internal_np::geom_traits))),
    m_segment_set_traits(m_traits)
  {
    CGAL_precondition(input_range.size() > 0);

    using NP_helper = internal::Default_property_map_helper<NamedParameters, Item, typename InputRange::const_iterator, internal_np::item_map_t>;
    using Item_map = typename NP_helper::type;
    Item_map item_map = NP_helper::get(np);

    m_ordered.resize(input_range.size());
    std::size_t index = 0;
    for (auto it = input_range.begin(); it != input_range.end(); it++)
      m_ordered[index++] = get(item_map, it);
    m_scores.resize(input_range.size(), 0.);
  }
  /// @}

  /// \name Sorting
  /// @{

  /*!
    \brief sorts `Items` of input segments.
  */
  void sort() {
    compute_scores();
    CGAL_precondition(m_scores.size() > 0);
    auto cmp = [this](const std::size_t i, const std::size_t j) {
      CGAL_precondition(i < m_scores.size());
      CGAL_precondition(j < m_scores.size());
      return m_scores[i] > m_scores[j];
    };
    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 segments.
  */
  const Seed_range &ordered() {
    return m_ordered;
  }
  /// @}

private:
  const Segment_map m_segment_map;
  GeomTraits m_traits;
  Segment_set_traits m_segment_set_traits;
  Seed_range m_ordered;
  std::vector<FT> m_scores;

  void compute_scores() {
    auto segment_length = m_segment_set_traits.compute_squared_length_object();
    std::size_t idx = 0;
    for (const Item& item : m_ordered) {
      const auto& segment = get(m_segment_map, item);
      FT len = segment_length(segment);
      m_scores[idx++] = len;
    }
  }
};

} // namespace Segment_set
} // namespace Shape_detection
} // namespace CGAL

#endif // CGAL_SHAPE_DETECTION_REGION_GROWING_SEGMENT_SET_SEGMENT_LENGTH_SORTING_H
