#include <Rcpp.h>
// Copyright (c) 2004-2005  INRIA Sophia-Antipolis (France).
// Copyright (c) 2010       GeometryFactory Sarl (France)
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.0.1/Mesh_2/include/CGAL/Mesh_2/Clusters.h $
// $Id: include/CGAL/Mesh_2/Clusters.h 50cfbde3b84 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Laurent Rineau

#ifndef CGAL_MESH_2_CLUSTERS_H
#define CGAL_MESH_2_CLUSTERS_H

#include <CGAL/license/Mesh_2.h>


#include <CGAL/Filter_circulator.h>
#include <CGAL/Unique_hash_map.h>

#include <utility>
#include <CGAL/boost/iterator/transform_iterator.hpp>

namespace CGAL {

namespace Mesh_2
{

  namespace details
  {
    template <class Tr>
    class Is_edge_constrained {
      const Tr* tr_;
    public:
      typedef Is_edge_constrained<Tr> Self;
      typedef typename Tr::Edge_circulator Edge_circulator;

      Is_edge_constrained(const Tr& tr) : tr_(&tr)
      {}

      bool operator()(const Edge_circulator& ec) const
      {
        return tr_->is_constrained(*ec);
      }
    };
  } // end namespace details

template <class Tr>
class Clusters
{
  typedef typename Tr::Vertex_handle          Vertex_handle;
  typedef typename Tr::Point                  Point;
  typedef typename Tr::Geom_traits Geom_traits;
  typedef typename Geom_traits::FT FT;
  typedef FT      Squared_length; /**<This typedef is used to remind that
                                     the length is squared. */
  typedef typename Tr::Edge_circulator Edge_circulator;

  /**
   *  Special type: filtered circulator that returns only constrained
   *  edges.
   */
  typedef Filter_circulator<Edge_circulator,
                            details::Is_edge_constrained<Tr> >
    Constrained_edge_circulator;

public:
  /** \name Clusters public types */

  /**
   * `Cluster` register information about clusters.
   * A cluster is a set of vertices v_i incident to one vertice
   * v_0, so that angles between segments [v_0, v_i] is less than 60
   * degrees.
   */
  struct Cluster {
    bool reduced ; /**< Is the cluster reduced? */

    /**
     * Smallest_angle gives the two vertices defining the
     * smallest angle in the cluster.
     */
    std::pair<Vertex_handle, Vertex_handle> smallest_angle;

    FT rmin; // @fixme: rmin has no meaning if reduced=false!!!
    Squared_length minimum_squared_length;

    /**
     * The following map tells what vertices are in the cluster and if
     * the corresponding segment has been split once.
     */
    typedef std::map<Vertex_handle, bool> Vertices_map;
    Vertices_map vertices;

    bool is_reduced() const {
      return reduced;
    }

    bool is_reduced(const Vertex_handle v) {
      return vertices[v];
    }
  };
private:
  /** \name Clusters associated types */

  typedef std::multimap<Vertex_handle, Cluster> Cluster_map;
  typedef typename Cluster_map::value_type Cluster_map_value_type;

  template <class Pair>
  struct Pair_get_first
    : public CGAL::cpp98::unary_function<Pair, typename Pair::first_type>
  {
    typedef typename Pair::first_type result;
    const result& operator()(const Pair& p) const
    {
      return p.first;
    }
  };

  typedef typename Cluster::Vertices_map Cluster_vertices_map;

private:
  /* --- protected data --- */

  Tr& tr; /**< The triangulation itself. */

  /**
   * Multimap `Vertex_handle -> Cluster`
   * Each vertex can have several clusters.
   */
  Cluster_map cluster_map;

public:
  typedef typename Cluster_map::const_iterator const_iterator;
  typedef typename Cluster_map::iterator iterator;

  Clusters(Tr& tr_) : tr(tr_)
  {
  }

  /** For all vertices, calls create_clusters_of_vertex(). */
  void create_clusters() {
    create_clusters(typename Tr::Constraint_hierarchy_tag());
  }

  // function that depends of Tr::Constraint_hierarchy_tag
  template <typename Constraint_hierarchy_tag>
  void create_clusters(Constraint_hierarchy_tag) {
    cluster_map.clear();
    for(typename Tr::Finite_vertices_iterator vit = tr.finite_vertices_begin();
        vit != tr.finite_vertices_end();
        vit++)
    {
      create_clusters_of_vertex(vit);
    }
  }

  void create_clusters(Tag_true) {
    cluster_map.clear();
    Unique_hash_map<Vertex_handle,bool> created(false);
    for(typename Tr::Subconstraint_iterator it = tr.subconstraints_begin();
        it != tr.subconstraints_end(); ++it) {
      Vertex_handle vh = it->first.first;
      if(!created[vh]){
        created[vh] = true;
        create_clusters_of_vertex(vh);
      }

      vh = it->first.second;
      if(!created[vh]){
        created[vh] = true;
        create_clusters_of_vertex(vh);
      }
    }
  }

private:
  /**
   * Computes clusters of the vertex `v`, using the auxiliary function
   * construct_cluster().
   */
  void create_clusters_of_vertex(const Vertex_handle v);

  /**
   * Adds the sequence `[begin, end]` to the cluster `c` and adds it
   * to the clusters of the vertex `v`.
   */
  void construct_cluster(const Vertex_handle v,
                         const Constrained_edge_circulator& begin,
                         const Constrained_edge_circulator& end,
                         Cluster c = Cluster());

public:
  /** \name Functions to manage clusters during the refinement process. */

  /**
   * Updates the cluster of `[va,vb]`, putting `vm` instead of `vb`.
   * If reduction=false, the edge [va,vm] is not set reduced.
   */
  void update_cluster(Cluster& c, iterator it,
                      const Vertex_handle va, const Vertex_handle vb,
                      const Vertex_handle vm,
                      bool reduction = true);

  /**
   * Returns the cluster of `[va,vb]` in `c` and return true
   * if it is in a cluster. Returns also a const_iterator in `it`.
   */
  bool get_cluster(const Vertex_handle va, const Vertex_handle vb,
                   Cluster& c, iterator& it);

  /** Const version of get_cluster(). */
  bool get_cluster(const Vertex_handle va, const Vertex_handle vb,
                   Cluster& c, const_iterator& it) const;

  /** \name Auxiliary functions that return a boolean. */

  /**
   * Tells if the angle <pleft, pmiddle, pright> is less than 60 degrees.
   * Uses squared_cosine_of_angle_times_4() and used by
   * create_clusters_of_vertex().
   */
  bool is_small_angle(const Point& pleft,
                      const Point& pmiddle,
                      const Point& pright) const;

private:
  /** \name Helping computing functions */

  /** Returns the squared cosine of the angle <pleft, pmiddle, pright>
      times 4. */
  FT squared_cosine_of_angle_times_4(const Point& pleft,
                                     const Point& pmiddle,
                                     const Point& pright) const;

  /** Helper functions to access the two vertices of an Edge
      source is the vertex around which the circulator turns. */
  //@{
  Vertex_handle source(const Edge_circulator& ec) const
  {
    return ec->first->vertex(tr.cw(ec->second));
  }

  Vertex_handle target(const Edge_circulator& ec) const
  {
    return ec->first->vertex(tr.ccw(ec->second));
  }
  //@}

public:
  /** \name CONST ACCESS FUNCTIONS */
  typedef typename boost::transform_iterator<
    Pair_get_first<typename Cluster_map::value_type>,
    typename Cluster_map::const_iterator>
  Cluster_vertices_iterator;

  typedef typename boost::transform_iterator<
    Pair_get_first<typename Cluster_vertices_map::value_type>,
    typename Cluster_vertices_map::const_iterator>
  Vertices_in_cluster_iterator;

  int size() const
  {
    return cluster_map.size();
  }

  Cluster_vertices_iterator clusters_vertices_begin() const
  {
    return Cluster_vertices_iterator(cluster_map.begin());
  }

  Cluster_vertices_iterator clusters_vertices_end() const
  {
    return Cluster_vertices_iterator(cluster_map.end());
  }

  unsigned int number_of_clusters_at_vertex(const Vertex_handle& vh) const
  {
    typedef typename Cluster_map::const_iterator Iterator;
    typedef std::pair<Iterator, Iterator> Range;
    Range range = cluster_map.equal_range(vh);
    return std::distance(range.first, range.second);
  }

  // returns the sequence of vertices bellonging to the n-th cluster of vh
  std::pair<Vertices_in_cluster_iterator, Vertices_in_cluster_iterator>
  vertices_in_cluster_sequence(const Vertex_handle& vh,
                               const unsigned int n) const
  {
    typedef typename Cluster_map::const_iterator Iterator;
    typedef std::pair<Iterator, Iterator> Range;

    Range range = cluster_map.equal_range(vh);
    Iterator first = range.first;
    std::advance(first, n);
    const Cluster& c = first->second;

    return
      std::make_pair(Vertices_in_cluster_iterator(c.vertices.begin()),
                     Vertices_in_cluster_iterator(c.vertices.end()));
  }

}; // end class Clusters

template <typename Tr>
void Clusters<Tr>::
update_cluster(Cluster& c, iterator it, Vertex_handle va,
               Vertex_handle vb, Vertex_handle vm, bool reduction)
{
  typename Geom_traits::Compute_squared_distance_2 squared_distance =
    tr.geom_traits().compute_squared_distance_2_object();

  cluster_map.erase(it);

  c.vertices.erase(vb);
  c.vertices[vm] = reduction;

  if(false == reduction) {
    for(typename Cluster::Vertices_map::iterator
          it = c.vertices.begin(),
          end = c.vertices.end();
        it != end; ++it)
    {
      it->second = false;
    }
  }

  if(vb==c.smallest_angle.first)
    c.smallest_angle.first = vm;
  if(vb==c.smallest_angle.second)
    c.smallest_angle.second = vm;

  FT l = squared_distance(va->point(),vm->point());
  if(l<c.minimum_squared_length)
    c.minimum_squared_length = l;

  if(!c.is_reduced())
    {
      typename Cluster::Vertices_map::iterator it = c.vertices.begin();
      while(it!=c.vertices.end() && c.is_reduced(it->first))
        ++it; // @todo: use std::find and an object class
      if(it==c.vertices.end())
        c.reduced = true;
    }

  if(c.is_reduced())
    c.rmin = squared_distance(c.smallest_angle.first->point(),
                              c.smallest_angle.second->point())/FT(4);
  cluster_map.insert(Cluster_map_value_type(va,c));
#ifdef CGAL_MESH_2_DEBUG_CLUSTERS
  Rcpp::Rcerr << "Cluster at " << va->point() << " is updated.  "
            << "\n  vm: " << vm->point()
            << "\n  reduction: " << reduction
            << "\n  min_sq_len: " << c.minimum_squared_length
            << "\n";
#endif // CGAL_MESH_2_DEBUG_CLUSTERS
}

template <typename Tr>
bool Clusters<Tr>::
get_cluster(Vertex_handle va, Vertex_handle vb, Cluster& c,
            const_iterator& it) const
{
  typedef std::pair<const_iterator, const_iterator> Range;

  Range range = cluster_map.equal_range(va);

  for(it = range.first; it != range.second; it++)
    {
      const Cluster &cl = it->second;
      if(cl.vertices.find(vb)!=cl.vertices.end()) {
        c = it->second;
        return true;
      }
    }
  return false;
}

template <typename Tr>
bool Clusters<Tr>::
get_cluster(Vertex_handle va, Vertex_handle vb, Cluster& c,
            iterator& it)
{
  typedef std::pair<iterator, iterator> Range;

  Range range = cluster_map.equal_range(va);

  for(it = range.first; it != range.second; it++)
    {
      const Cluster &cl = it->second;
      if(cl.vertices.find(vb)!=cl.vertices.end()) {
        c = it->second;
        return true;
      }
    }
  return false;
}


template <typename Tr>
void Clusters<Tr>::
create_clusters_of_vertex(const Vertex_handle v)
{
  details::Is_edge_constrained<Tr> test(tr);

  Constrained_edge_circulator begin(tr.incident_edges(v),test);

  // This circulator represents all constrained edges around the
  // vertex v. An edge [v,v'] is represented by the vertex v'.

  if(begin == 0) return; // if there is only one vertex

  Constrained_edge_circulator
    current(begin), next(begin), cluster_begin(begin);
  ++next; // next is always just after current.
  if(current == next) return;

  bool in_a_cluster = false;
  do
    {
      if(is_small_angle(target(current)->point(), v->point(),
                        target(next)->point()))
        {
          if(!in_a_cluster)
            {
              // at this point, current is the beginning of a cluster
              in_a_cluster = true;
              cluster_begin = current;
            }
        }
      else {
        if(in_a_cluster)
          {
            // at this point, current is the end of a cluster and
            // cluster_begin is its beginning
            construct_cluster(v, cluster_begin, current);
            in_a_cluster = false;
          }
      }
      current = next;
      ++next;
    } while( current!=begin );
  if(in_a_cluster)
    {
      Cluster c;
      iterator it;
      if(get_cluster(v, target(begin), c, it))
        {
          // get the cluster and erase it from the clusters map
          cluster_map.erase(it);
          construct_cluster(v, cluster_begin, begin, c);
        }
      else
        construct_cluster(v, cluster_begin, current);
    }
}

template <typename Tr>
void Clusters<Tr>::
construct_cluster(Vertex_handle v,
                  const Constrained_edge_circulator& begin,
                  const Constrained_edge_circulator& end,
                  Cluster c)
{
  typename Geom_traits::Compute_squared_distance_2 squared_distance =
    tr.geom_traits().compute_squared_distance_2_object();

  if(c.vertices.empty())
    {
      c.reduced = false;
      // c.rmin is not initialized because
      // reduced=false!
      c.minimum_squared_length =
        squared_distance(v->point(), target(begin)->point());
      Constrained_edge_circulator second(begin);
      ++second;
      c.smallest_angle.first = target(begin);
      c.smallest_angle.second = target(second);
    }

  const bool all_edges_in_cluster = (begin == end); // tell if all incident edges
                                              // are in the cluster
  const Point& vp = v->point();

  FT greatest_cosine =
    squared_cosine_of_angle_times_4(c.smallest_angle.first->point(),
                                    v->point(),
                                    c.smallest_angle.second->point());

  bool one_full_loop_is_needed = all_edges_in_cluster;

  bool stop = false;
  Constrained_edge_circulator circ(begin);
  Constrained_edge_circulator next(begin);
  while(!stop)
  {
    c.vertices[target(circ)] = false;
    Squared_length l = squared_distance(vp,
                                        target(circ)->point());
    c.minimum_squared_length =
      (std::min)(l,c.minimum_squared_length);

    if(circ!=end || one_full_loop_is_needed)
    {
      FT cosine =
        squared_cosine_of_angle_times_4(target(circ)->point(),
                                        v->point(),
                                        target(next)->point());
      if(cosine>greatest_cosine)
      {
        greatest_cosine = cosine;
        c.smallest_angle.first = target(circ);
        c.smallest_angle.second = target(next);
      }
    }

    if(one_full_loop_is_needed) {
      one_full_loop_is_needed = false;
    } else {
      stop = (circ == end);
    }
    ++circ;
    ++next;
  }

  typedef typename Cluster_map::value_type Value_key_pair;
  cluster_map.insert(Value_key_pair(v,c));
}

template <typename Tr>
bool Clusters<Tr>::
is_small_angle(const Point& pleft,
               const Point& pmiddle,
               const Point& pright) const
{
  typename Geom_traits::Angle_2 angle =
    tr.geom_traits().angle_2_object();
  typename Geom_traits::Orientation_2 orient =
    tr.geom_traits().orientation_2_object();

  if( angle(pleft, pmiddle, pright)==OBTUSE )
    return false;
  if( orient(pmiddle,pleft,pright)==RIGHT_TURN)
    return false;

  FT cos_alpha = squared_cosine_of_angle_times_4(pleft, pmiddle,
                                                 pright);

  if(cos_alpha > 1)
    {
      return true; //the same cluster
    }
  else
    {
      return false; //another cluster
    }
}

template <typename Tr>
typename Clusters<Tr>::FT
Clusters<Tr>::
squared_cosine_of_angle_times_4(const Point& pb, const Point& pa,
                                const Point& pc) const
{
  typename Geom_traits::Compute_squared_distance_2 squared_distance =
    tr.geom_traits().compute_squared_distance_2_object();

  const FT
    a = squared_distance(pb, pc),
    b = squared_distance(pa, pb),
    c = squared_distance(pa, pc);

  const FT num = a-(b+c);

  return (num*num)/(b*c);
}

} // end namespace Mesh_2

} // end namespace CGAL

#endif // CGAL_MESH_2_CLUSTERS_H

