// Copyright (c) 2006-2008 Fernando Luis Cacciola Carballal. All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//

// $URL: https://github.com/CGAL/cgal/blob/v6.1.1/Straight_skeleton_2/include/CGAL/Straight_skeleton_builder_2.h $
// $Id: include/CGAL/Straight_skeleton_builder_2.h 08b27d3db14 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Fernando Cacciola <fernando_cacciola@ciudad.com.ar>
//
#ifndef CGAL_STRAIGHT_SKELETON_BUILDER_2_H
#define CGAL_STRAIGHT_SKELETON_BUILDER_2_H 1

#include <CGAL/license/Straight_skeleton_2.h>

#include <CGAL/algorithm.h>
#include <CGAL/Straight_skeleton_2/Straight_skeleton_aux.h>
#include <CGAL/Straight_skeleton_2/Straight_skeleton_builder_events_2.h>
#include <CGAL/Straight_skeleton_2.h>
#include <CGAL/Straight_skeleton_builder_traits_2.h>
#include <CGAL/HalfedgeDS_const_decorator.h>
#include <CGAL/enum.h>

#include <boost/mpl/bool.hpp>

#include <algorithm>
#include <exception>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <queue>
#include <string>
#include <sstream>
#include <utility>

namespace CGAL {

template<class SSkel_>
struct Dummy_straight_skeleton_builder_2_visitor
{
  typedef SSkel_ SSkel ;

  typedef typename SSkel::Halfedge_const_handle Halfedge_const_handle ;
  typedef typename SSkel::Vertex_const_handle   Vertex_const_handle ;

  void on_contour_edge_entered ( Halfedge_const_handle const&  ) const {}

  void on_initialization_started( std::size_t /* size_of_vertices */ ) const {}

  void on_initial_events_collected( Vertex_const_handle const& , bool /* is_reflex */, bool /*is_degenerate*/ )  const  {}

  void on_edge_event_created( Vertex_const_handle const& /* lnode */
                            , Vertex_const_handle const& /* rnode */
                            )  const {}

  void on_split_event_created( Vertex_const_handle const&  )  const {}

  void on_pseudo_split_event_created( Vertex_const_handle const& /* lnode */
                                    , Vertex_const_handle const& /* rnode */
                                    )  const {}

  void on_initialization_finished() const {}

  void on_propagation_started() const {}

  void on_anihiliation_event_processed ( Vertex_const_handle const& /* node0 */
                                       , Vertex_const_handle const& /* node1 */
                                       )  const  {}


  void on_edge_event_processed( Vertex_const_handle const& /* lseed */
                              , Vertex_const_handle const& /* rseed */
                              , Vertex_const_handle const& /* node */
                              )  const {}

  void on_split_event_processed( Vertex_const_handle const& /* seed */
                               , Vertex_const_handle const& /* node0 */
                               , Vertex_const_handle const& /* node1 */
                               )  const {}

  void on_pseudo_split_event_processed( Vertex_const_handle const& /* lseed */
                                      , Vertex_const_handle const& /* rseed */
                                      , Vertex_const_handle const& /* node0 */
                                      , Vertex_const_handle const& /* node1 */
                                      )  const {}

  void on_vertex_processed( Vertex_const_handle const& )  const {}

  void on_propagation_finished() const {}

  void on_cleanup_started() const {}

  void on_cleanup_finished() const {}

  void on_algorithm_finished ( bool /* finished_ok */ ) const {}

  void on_error( char const* ) const {}
} ;

template<class Traits_, class SSkel_, class Visitor_ = Dummy_straight_skeleton_builder_2_visitor<SSkel_> >
class Straight_skeleton_builder_2
{
public:

  typedef Traits_  Traits ;
  typedef SSkel_   SSkel ;
  typedef Visitor_ Visitor ;

  typedef std::shared_ptr<SSkel> SSkelPtr ;

private :

  typedef typename Traits::Kernel K ;

  typedef typename Traits::FT               FT ;
  typedef typename Traits::Point_2          Point_2 ;
  typedef typename Traits::Segment_2        Segment_2 ;
  typedef typename Traits::Vector_2         Vector_2 ;
  typedef typename Traits::Direction_2      Direction_2 ;

  typedef typename Traits::Trisegment_2     Trisegment_2 ;
  typedef typename Traits::Trisegment_2_ptr Trisegment_2_ptr ;

  typedef typename SSkel::Vertex   Vertex ;
  typedef typename SSkel::Halfedge Halfedge ;
  typedef typename SSkel::Face     Face ;

  typedef typename SSkel::Vertex_const_handle   Vertex_const_handle ;
  typedef typename SSkel::Halfedge_const_handle Halfedge_const_handle ;
  typedef typename SSkel::Face_const_handle     Face_const_handle ;

  typedef typename SSkel::Vertex_const_iterator   Vertex_const_iterator ;
  typedef typename SSkel::Halfedge_const_iterator Halfedge_const_iterator ;
  typedef typename SSkel::Face_const_iterator     Face_const_iterator ;

  typedef typename SSkel::Vertex_handle   Vertex_handle ;
  typedef typename SSkel::Halfedge_handle Halfedge_handle ;
  typedef typename SSkel::Face_handle     Face_handle ;

  typedef typename SSkel::Vertex_iterator   Vertex_iterator ;
  typedef typename SSkel::Halfedge_iterator Halfedge_iterator ;
  typedef typename SSkel::Face_iterator     Face_iterator ;

  typedef typename Vertex::Halfedge_around_vertex_circulator Halfedge_around_vertex_circulator ;

  typedef typename SSkel::size_type size_type ;

  typedef CGAL_SS_i::Triedge<Halfedge_handle> Triedge ;

  typedef CGAL_SS_i::Event_2             <SSkel,Traits> Event ;
  typedef CGAL_SS_i::Edge_event_2        <SSkel,Traits> EdgeEvent ;
  typedef CGAL_SS_i::Split_event_2       <SSkel,Traits> SplitEvent ;
  typedef CGAL_SS_i::Pseudo_split_event_2<SSkel,Traits> PseudoSplitEvent ;
  typedef CGAL_SS_i::Artificial_event_2  <SSkel,Traits> ArtificialEvent ;

  typedef std::shared_ptr<Event> EventPtr ;

  typedef std::pair<Vertex_handle,Vertex_handle> Vertex_handle_pair ;

  typedef std::vector<EventPtr>           EventPtr_Vector ;
  typedef std::vector<Halfedge_handle>    Halfedge_handle_vector ;
  typedef std::vector<Vertex_handle>      Vertex_handle_vector ;
  typedef std::vector<Vertex_handle_pair> Vertex_handle_pair_vector ;

  typedef typename Halfedge_handle_vector   ::iterator Halfedge_handle_vector_iterator ;
  typedef typename Vertex_handle_vector     ::iterator Vertex_handle_vector_iterator ;
  typedef typename Vertex_handle_pair_vector::iterator Vertex_handle_pair_vector_iterator ;

  typedef typename EventPtr_Vector::const_iterator event_const_iterator ;

  typedef Straight_skeleton_builder_2<Traits,SSkel,Visitor> Self ;

  typedef typename Halfedge::Base_base HBase_base ;
  typedef typename Halfedge::Base      HBase ;
  typedef typename Vertex::Base        VBase ;
  typedef typename Face::Base          FBase ;

  enum Site { AT_SOURCE = -1 , INSIDE = 0, AT_TARGET = +1 } ;

  struct Multinode
  {
    Multinode ( Halfedge_handle b, Halfedge_handle e )
      :
       begin(b)
      ,end  (e)
      ,v    (b->vertex())
      ,size (0)
    {}

    Halfedge_handle        begin ;
    Halfedge_handle        end ;
    Vertex_handle          v ;
    std::size_t            size ;
    Halfedge_handle_vector bisectors_to_relink ;
    Halfedge_handle_vector bisectors_to_remove ;
    Vertex_handle_vector   nodes_to_remove ;
  } ;

  typedef std::shared_ptr<Multinode> MultinodePtr ;

  struct MultinodeComparer
  {
    bool operator() ( MultinodePtr const& x, MultinodePtr const& y ) { return x->size > y->size ; }
  } ;

  typedef std::vector<MultinodePtr> MultinodeVector ;

  struct Halfedge_ID_compare : CGAL::cpp98::binary_function<bool,Halfedge_handle,Halfedge_handle>
  {
    bool operator() ( Halfedge_handle const& aA, Halfedge_handle const& aB ) const
    {
      return aA->id() < aB->id() ;
    }
  } ;

public:

  Straight_skeleton_builder_2 ( std::optional<FT> aMaxTime = std::nullopt, Traits const& = Traits(), Visitor const& aVisitor = Visitor() ) ;

  SSkelPtr construct_skeleton(  bool aNull_if_failed = true ) ;

private :
  // Only used to debug
  double ComputeApproximateAngle ( Vector_2 const& u, Vector_2 const& v) const
  {
    typename K::Compute_scalar_product_2 sp = K().compute_scalar_product_2_object();

    double product = CGAL::sqrt(to_double(sp(u,u)) * to_double(sp(v,v)));

    if(product == FT(0))
      return 0;

    // cosine
    double dot = to_double(sp(u,v));
    double cosine = dot / product;

    if(cosine > 1.)
      cosine = 1.;
    if(cosine < -1.)
      cosine = -1.;

    return std::acos(cosine) * 180. / CGAL_PI;
  }
// Only used to debug
  double ComputeSupportsAngleSplit ( EventPtr const& aEvent )
  {
    SplitEvent& lEvent = dynamic_cast<SplitEvent&>(*aEvent) ;

    Vector_2 lV1 ( aEvent->point(), lEvent.seed0()->point() );
    Vector_2 lV2 ( aEvent->point(), aEvent->point() + CreateVector(aEvent->triedge().e2()) ) ;

    return ComputeApproximateAngle(lV1, lV2) ;
  }
// Only used to debug
  double ComputeSupportsAnglePseudoSplit ( EventPtr const& aEvent )
  {
    PseudoSplitEvent& lEvent = dynamic_cast<PseudoSplitEvent&>(*aEvent) ;

    Vector_2 lV1 ;
    Vector_2 lV2 ;

    if(lEvent.is_at_source_vertex())
    {
      // is_at_source_vertex <=> opposite node is seed0
      lV1 = Vector_2( aEvent->point(), lEvent.seed1()->point() );
      lV2 = Vector_2( aEvent->point(), aEvent->point() + CreateVector(aEvent->triedge().e2()) ) ;
    }
    else
    {
      lV1 = Vector_2( aEvent->point(), lEvent.seed0()->point() );
      lV2 = Vector_2( aEvent->point(), aEvent->point() - CreateVector(aEvent->triedge().e2()) ) ;
    }

    return ComputeApproximateAngle(lV1, lV2) ;
  }
// Only used to debug
  double ComputeSupportsAngle ( EventPtr const& aEvent )
  {
    if ( aEvent->type() == Event::cSplitEvent )
    {
      Halfedge_handle lOppEdge = aEvent->triedge().e2() ;
      Site lSite;
      Vertex_handle_pair lOpp = LookupOnSLAV(lOppEdge,aEvent,lSite);
      if ( handle_assigned(lOpp.first) )
      {
        EventPtr lPseudoSplitEvent = IsPseudoSplitEvent(aEvent,lOpp,lSite);
        if ( lPseudoSplitEvent )
          return ComputeSupportsAnglePseudoSplit ( lPseudoSplitEvent ) ;
        else
          return ComputeSupportsAngleSplit ( aEvent ) ;
      }
      else
      {
        return 2 * CGAL_PI; // event isn't valid anymore
      }
    }
    else
    {
      CGAL_assertion ( aEvent->type() == Event::cPseudoSplitEvent );
      return ComputeSupportsAngleSplit ( aEvent ) ;
    }
  }

  // Real stuff

  Comparison_result CompareEventsSupportAnglesSplitSplit ( EventPtr const& aA, EventPtr const& aB )
  {
    CGAL_precondition ( aA->triedge().e0() == aB->triedge().e0() && aA->triedge().e1() == aB->triedge().e1() ) ;
    return mTraits.compare_ss_event_angles_2_object()( CreateVector(aA->triedge().e0()),
                                                       CreateVector(aA->triedge().e1()),
                                                       CreateVector(aA->triedge().e2()),
                                                       CreateVector(aB->triedge().e2()) );
  }

  Comparison_result CompareEventsSupportAnglesSplitPseudoSplit ( EventPtr const& aA, EventPtr const& aB )
  {
    CGAL_precondition ( aA->triedge().e0() == aB->triedge().e0() && aA->triedge().e1() == aB->triedge().e1() ) ;

    PseudoSplitEvent& lPSEvent = dynamic_cast<PseudoSplitEvent&>(*aB) ;
    if(lPSEvent.is_at_source_vertex())
    {
      return mTraits.compare_ss_event_angles_2_object()( CreateVector(aA->triedge().e0()),
                                                         CreateVector(aA->triedge().e1()),
                                                         CreateVector(aA->triedge().e2()),
                                                         CreateVector(aB->triedge().e2()) );
    }
    else
    {
      return mTraits.compare_ss_event_angles_2_object()( CreateVector(aA->triedge().e0()),
                                                         CreateVector(aA->triedge().e1()),
                                                         CreateVector(aA->triedge().e2()),
                                                         K().construct_opposite_vector_2_object()( CreateVector(aB->triedge().e2())) );
    }
  }

  Comparison_result CompareEventsSupportAnglesPseudoSplitPseudoSplit ( EventPtr const& aA, EventPtr const& aB )
  {
    CGAL_precondition ( aA->triedge().e0() == aB->triedge().e0() && aA->triedge().e1() == aB->triedge().e1() ) ;

    PseudoSplitEvent& lPSEventA = dynamic_cast<PseudoSplitEvent&>(*aA) ;
    PseudoSplitEvent& lPSEventB = dynamic_cast<PseudoSplitEvent&>(*aB) ;

    if(lPSEventA.is_at_source_vertex())
    {
      if(lPSEventB.is_at_source_vertex())
      {
        return mTraits.compare_ss_event_angles_2_object()( CreateVector(aA->triedge().e0()),
                                                           CreateVector(aA->triedge().e1()),
                                                           CreateVector(aA->triedge().e2()),
                                                           CreateVector(aB->triedge().e2()) );
      }
      else
      {
        return mTraits.compare_ss_event_angles_2_object()( CreateVector(aA->triedge().e0()),
                                                           CreateVector(aA->triedge().e1()),
                                                           CreateVector(aA->triedge().e2()),
                                                           K().construct_opposite_vector_2_object()( CreateVector(aB->triedge().e2())) );
      }
    }
    else // aA is a Pseudo-split Event at the target
    {
      if(lPSEventB.is_at_source_vertex())
      {
        return mTraits.compare_ss_event_angles_2_object()( CreateVector(aA->triedge().e0()),
                                                           CreateVector(aA->triedge().e1()),
                                                           K().construct_opposite_vector_2_object()( CreateVector(aA->triedge().e2()) ),
                                                           CreateVector(aB->triedge().e2()) );
      }
      else
      {
        return mTraits.compare_ss_event_angles_2_object()( CreateVector(aA->triedge().e0()),
                                                           CreateVector(aA->triedge().e1()),
                                                           K().construct_opposite_vector_2_object()( CreateVector(aA->triedge().e2())),
                                                           K().construct_opposite_vector_2_object()( CreateVector(aB->triedge().e2())) );
      }
    }
  }

  Comparison_result CompareEventsSupportAnglesSplitX ( EventPtr const& aA, EventPtr const& aB )
  {
    if ( aB->type() == Event::cSplitEvent )
    {
      Halfedge_handle lOppEdge = aB->triedge().e2() ;
      Site lSite;
      Vertex_handle_pair lOpp = LookupOnSLAV(lOppEdge,aB,lSite);
      if ( handle_assigned(lOpp.first) )
      {
        EventPtr lPseudoSplitEvent = IsPseudoSplitEvent(aB,lOpp,lSite);
        if ( lPseudoSplitEvent )
          return CompareEventsSupportAnglesSplitPseudoSplit ( aA,lPseudoSplitEvent ) ;
        else
          return CompareEventsSupportAnglesSplitSplit ( aA,aB ) ;
      }
      else
      {
        // Event B does not exist, so give it the lower priority by returning SMALLER
        // (meaning, operator() == false and A has higher priority than B)
        return CGAL::SMALLER;
      }
    }
    else
    {
      CGAL_assertion ( aB->type() == Event::cPseudoSplitEvent );
      return CompareEventsSupportAnglesSplitPseudoSplit ( aA,aB ) ;
    }
  }

  Comparison_result CompareEventsSupportAnglesPseudoSplitX ( EventPtr const& aA, EventPtr const& aB )
  {
    if ( aB->type() == Event::cSplitEvent )
    {
      Halfedge_handle lOppEdge = aB->triedge().e2() ;
      Site lSite;
      Vertex_handle_pair lOpp = LookupOnSLAV(lOppEdge,aB,lSite);
      if ( handle_assigned(lOpp.first) )
      {
        EventPtr lPseudoSplitEvent = IsPseudoSplitEvent(aB,lOpp,lSite);
        if ( lPseudoSplitEvent )
        {
          return CompareEventsSupportAnglesPseudoSplitPseudoSplit ( aA,lPseudoSplitEvent ) ;
        }
        else
        {
          Comparison_result lRes = CompareEventsSupportAnglesSplitPseudoSplit ( aB,aA ) ;
          if ( lRes == LARGER )
            return SMALLER ;
          else if ( lRes == SMALLER )
            return LARGER ;
          else
            return EQUAL ;
        }
      }
      else
      {
        // Event B does not exist, so give it the lower priority by returning SMALLER
        // (meaning, operator() == false and A has higher priority than B)
        return CGAL::SMALLER;
      }
    }
    else
    {
      CGAL_assertion ( aB->type() == Event::cPseudoSplitEvent );
      return CompareEventsSupportAnglesPseudoSplitPseudoSplit ( aA,aB ) ;
    }
  }

  // @todo Should split events always have lower priority than edge events?
  Comparison_result CompareEventsSupportAngles ( EventPtr const& aA, EventPtr const& aB )
  {
    CGAL_precondition ( aA->type() != Event::cEdgeEvent && aB->type() != Event::cEdgeEvent ) ;

    if(aA->triedge() == aB->triedge())
      return EQUAL;

    if ( aA->type() == Event::cSplitEvent )
    {
      Halfedge_handle lOppEdge = aA->triedge().e2() ;
      Site lSite;
      Vertex_handle_pair lOpp = LookupOnSLAV(lOppEdge,aA,lSite);
      if ( handle_assigned(lOpp.first) )
      {
        EventPtr lPseudoSplitEvent = IsPseudoSplitEvent(aA,lOpp,lSite);
        if ( lPseudoSplitEvent )
          return CompareEventsSupportAnglesPseudoSplitX ( lPseudoSplitEvent,aB ) ;
        else
          return CompareEventsSupportAnglesSplitX ( aA,aB ) ;
      }
      else
      {
        // Event A does not exist, so give it the lower priority by returning LARGER
        // (meaning, operator() == true and A has lower priority than B)
        //
        // Exception: event B also is a pseudo split that does not exist...
        if ( aB->type() == Event::cSplitEvent )
        {
          Halfedge_handle lOppEdgeB = aB->triedge().e2() ;
          Site lSiteB;
          Vertex_handle_pair lOppB = LookupOnSLAV(lOppEdgeB,aB,lSiteB);
          if ( !handle_assigned(lOppB.first) )
            return EQUAL;
        }

        return LARGER;
      }
    }
    else
    {
      CGAL_assertion ( aA->type() == Event::cPseudoSplitEvent );
      return CompareEventsSupportAnglesPseudoSplitX ( aA,aB ) ;
    }
  }

public:
  // Event compare for the main queue
  class Event_compare
    : public CGAL::cpp98::binary_function<bool,EventPtr,EventPtr>
  {
  public:
    Event_compare ( Self* aBuilder ) : mBuilder(aBuilder) {}

    bool operator() ( EventPtr const& aA, EventPtr const& aB ) const
    {
      return mBuilder->CompareEvents(aA,aB) == LARGER ;
    }

  private:
    Self* mBuilder ;
  } ;

  // Event compare for the local queues
  //
  // Special ordering for simultaneous split events (i.e. both (pseudo)splits + same time + same point)
  // to prevent impossible-to-untangle knots
  class Split_event_compare
    : public CGAL::cpp98::binary_function<bool,EventPtr,EventPtr>
  {
  public:
    Split_event_compare ( Self* aBuilder, Vertex_handle aV ) : mBuilder(aBuilder), mV(aV) {}

    bool operator() ( EventPtr const& aA, EventPtr const& aB ) const
    {
      CGAL_precondition( aA->type() != Event::cEdgeEvent || aB->type() != Event::cEdgeEvent ) ;

      if ( ! mBuilder->AreEventsSimultaneous(aA,aB) )
      {
        Comparison_result res = mBuilder->CompareEvents(aA,aB);
        if ( res == EQUAL )
          return aA < aB;
        return ( res == LARGER ) ;
      }

      // There are simultaneous events, we will need to refresh the queue before calling top()
      // see PopNextSplitEvent()
      mBuilder->GetVertexData(mV).mHasSimultaneousEvents = true ;

      // Priority queue comparison: `A` has higher priority than `B` if `operator()(A, B)` is `false`.
      // We want to give priority to smaller angles, so we must return `false` if the angle is smaller
      // i.e. `true` if the angle is larger
      Comparison_result res = mBuilder->CompareEventsSupportAngles(aA, aB);
      if ( res == EQUAL )
        return aA < aB;
      return ( res == LARGER ) ;
    }

  private:
    Self* mBuilder ;
    const Vertex_handle mV ;
  } ;

  typedef std::priority_queue<EventPtr,std::vector<EventPtr>,Split_event_compare> SplitPQ ;

  struct Vertex_data
  {
    Vertex_data ( Vertex_handle aVertex, Split_event_compare const& aComparer )
      :
        mVertex(aVertex)
      , mIsReflex(false)
      , mIsDegenerate(false)
      , mIsProcessed(false)
      , mIsExcluded(false)
      , mPrevInLAV(-1)
      , mNextInLAV(-1)
      , mNextSplitEventInMainPQ(false)
      , mHasSimultaneousEvents(false)
      , mSplitEvents(aComparer)
    {}

    Vertex_handle     mVertex ;
    bool              mIsReflex ;
    bool              mIsDegenerate ;
    bool              mIsProcessed ;
    bool              mIsExcluded ;
    int               mPrevInLAV ;
    int               mNextInLAV ;
    bool              mNextSplitEventInMainPQ ;
    bool              mHasSimultaneousEvents ;
    SplitPQ           mSplitEvents ;
    Triedge           mTriedge ; // Here, E0,E1 corresponds to the vertex (unlike *event* triedges)
  } ;

  typedef std::shared_ptr<Vertex_data> Vertex_data_ptr ;

  typedef std::priority_queue<EventPtr,std::vector<EventPtr>,Event_compare> PQ ;

private :

  Halfedge_handle validate( Halfedge_handle aH ) const ;
  Vertex_handle   validate( Vertex_handle   aH ) const ;

  std::list<Vertex_handle>& GetHalfedgeLAVList(Halfedge_handle aH)
  {
    return mLAVLists[aH->id()];
  }

  Halfedge_handle SSkelEdgesPushBack(const Halfedge& aH1, const Halfedge& aH2)
  {
    mLAVLists.resize(aH2.id()+1);
    return mSSkel->SSkel::Base::edges_push_back (aH1, aH2);
  }

  void InitVertexData( Vertex_handle aV )
  {
    mVertexData.push_back( Vertex_data_ptr( new Vertex_data(aV, Split_event_compare(this, aV) ) ) ) ;
  }

  Vertex_data const& GetVertexData( Vertex_const_handle aV ) const
  {
    CGAL_precondition( handle_assigned(aV) ) ;
    return *mVertexData[aV->id()];
  }

  Vertex_data&  GetVertexData( Vertex_const_handle aV )
  {
    CGAL_precondition( handle_assigned(aV) ) ;
    return *mVertexData[aV->id()];
  }

  Vertex_handle GetVertex ( int aIdx )
  {
    CGAL_precondition(aIdx>=0);
    return mVertexData[aIdx]->mVertex ;
  }

  // Null if aV is a contour or infinite node
  Trisegment_2_ptr const& GetTrisegment ( Vertex_handle aV ) const
  {
    return aV->trisegment() ;
  }

  void SetTrisegment ( Vertex_handle aV, Trisegment_2_ptr const& aTrisegment )
  {
    aV->set_trisegment(aTrisegment) ;
  }

  // Null if aV is a contour node
  Triedge const& GetVertexTriedge ( Vertex_handle aV ) const
  {
    return GetVertexData(aV).mTriedge ;
  }

  void SetVertexTriedge ( Vertex_handle aV, Triedge const& aTriedge )
  {
    GetVertexData(aV).mTriedge = aTriedge ;
    GetHalfedgeLAVList(aTriedge.e0()).push_back(aV);
  }

  void GLAV_push_back ( Vertex_handle /* aV */ )
  {}

  void GLAV_remove ( Vertex_handle aV )
  {
    GetHalfedgeLAVList(GetVertexData(aV).mTriedge.e0()).remove(aV);
  }

  typename K::Segment_2 CreateRawSegment ( Halfedge_const_handle aH ) const
  {
    const Point_2& s = aH->opposite()->vertex()->point() ;
    const Point_2& t = aH->vertex()->point() ;
    return K().construct_segment_2_object()(s,t);
  }

  template <typename GT> // this is 'Traits', but templating is required for SFINAE
  Segment_2 CreateSegment ( Halfedge_const_handle aH,
                            std::enable_if_t<
                              ! CGAL_SS_i::has_Segment_2_with_ID<GT>::value>* = nullptr ) const
  {
    return Segment_2(CreateRawSegment(aH)) ;
  }

  template <typename GT> // this is 'Traits', but templating is required for SFINAE
  Segment_2 CreateSegment ( Halfedge_const_handle aH,
                            std::enable_if_t<
                              CGAL_SS_i::has_Segment_2_with_ID<GT>::value>* = nullptr ) const
  {
    return Segment_2(CreateRawSegment(aH), aH->id());
  }

  Vector_2 CreateVector ( Halfedge_const_handle aH ) const
  {
    const Point_2& s = aH->opposite()->vertex()->point() ;
    const Point_2& t = aH->vertex()->point() ;
    return K().construct_vector_2_object()(s,t);
  }

  Direction_2 CreateDirection ( Halfedge_const_handle aH ) const
  {
    return K().construct_direction_2_object()( CreateVector(aH) );
  }

  Direction_2 CreatePerpendicularDirection ( Halfedge_const_handle aH ) const
  {
    const Direction_2 lD = CreateDirection(aH);
    return K().construct_perpendicular_direction_2_object()( lD, CGAL::COUNTERCLOCKWISE );
  }

  Trisegment_2_ptr CreateTrisegment ( Triedge const& aTriedge ) const
  {
    CGAL_precondition(aTriedge.is_valid() && aTriedge.is_skeleton());

    Trisegment_2_ptr r = mTraits.construct_ss_trisegment_2_object()( CreateSegment<Traits>(aTriedge.e0()),
                                                                     aTriedge.e0()->weight(),
                                                                     CreateSegment<Traits>(aTriedge.e1()),
                                                                     aTriedge.e1()->weight(),
                                                                     CreateSegment<Traits>(aTriedge.e2()),
                                                                     aTriedge.e2()->weight() );

    CGAL_STSKEL_BUILDER_TRACE(5,"Trisegment for " << aTriedge << ":\n" << r ) ;

    // Consecutive collinear segments must not have the same weight
    CGAL_assertion_code(if(r->collinearity() == TRISEGMENT_COLLINEARITY_01))
    CGAL_assertion_code(if(aTriedge.e0()->weight() != aTriedge.e1()->weight()) {)
    CGAL_STSKEL_BUILDER_TRACE(5, "Collinear check: " << CGAL_SS_i::are_edges_orderly_collinear(r->e0(), r->e1()));
    CGAL_assertion(aTriedge.e1() != aTriedge.e0()->opposite()->prev()->opposite() &&
                   aTriedge.e0() != aTriedge.e1()->opposite()->prev()->opposite());
    CGAL_assertion_code(})

    CGAL_assertion_code(if(r->collinearity() == TRISEGMENT_COLLINEARITY_12))
    CGAL_assertion_code(if(aTriedge.e1()->weight() != aTriedge.e2()->weight()) {)
    CGAL_STSKEL_BUILDER_TRACE(5, "Collinear check: " << CGAL_SS_i::are_edges_orderly_collinear(r->e1(), r->e2()));
    CGAL_assertion(aTriedge.e2() != aTriedge.e1()->opposite()->prev()->opposite() &&
                   aTriedge.e1() != aTriedge.e2()->opposite()->prev()->opposite());
    CGAL_assertion_code(})

    CGAL_assertion_code(if(r->collinearity() == TRISEGMENT_COLLINEARITY_02))
    CGAL_assertion_code(if(aTriedge.e0()->weight() != aTriedge.e2()->weight()) {)
    CGAL_STSKEL_BUILDER_TRACE(5, "Collinear check: " << CGAL_SS_i::are_edges_orderly_collinear(r->e0(), r->e2()));
    CGAL_assertion(aTriedge.e2() != aTriedge.e0()->opposite()->prev()->opposite() &&
                   aTriedge.e0() != aTriedge.e2()->opposite()->prev()->opposite());
    CGAL_assertion_code(})

    CGAL_assertion_code(if(r->collinearity() == TRISEGMENT_COLLINEARITY_ALL) {)
    CGAL_assertion_code(if(aTriedge.e0()->weight() != aTriedge.e1()->weight()) {)
    CGAL_STSKEL_BUILDER_TRACE(5, "Collinear check: " << CGAL_SS_i::are_edges_orderly_collinear(r->e0(), r->e1()));
    CGAL_assertion(aTriedge.e1() != aTriedge.e0()->opposite()->prev()->opposite() &&
                   aTriedge.e0() != aTriedge.e1()->opposite()->prev()->opposite());
    CGAL_assertion_code(})

    CGAL_assertion_code(if(aTriedge.e1()->weight() != aTriedge.e2()->weight()) {)
    CGAL_STSKEL_BUILDER_TRACE(5, "Collinear check: " << CGAL_SS_i::are_edges_orderly_collinear(r->e1(), r->e2()));
    CGAL_assertion(aTriedge.e2() != aTriedge.e1()->opposite()->prev()->opposite() &&
                   aTriedge.e1() != aTriedge.e2()->opposite()->prev()->opposite());
    CGAL_assertion_code(})

    CGAL_assertion_code(if(aTriedge.e0()->weight() != aTriedge.e2()->weight()) {)
    CGAL_STSKEL_BUILDER_TRACE(5, "Collinear check: " << CGAL_SS_i::are_edges_orderly_collinear(r->e0(), r->e2()));
    CGAL_assertion(aTriedge.e2() != aTriedge.e0()->opposite()->prev()->opposite() &&
                   aTriedge.e0() != aTriedge.e2()->opposite()->prev()->opposite());
    CGAL_assertion_code(})
    CGAL_assertion_code(})

    CGAL_postcondition_msg((r != nullptr), "Unable to determine edges collinearity");

    return r ;
  }

  Trisegment_2_ptr CreateTrisegment ( Triedge const& aTriedge, Vertex_handle aLSeed ) const
  {
    Trisegment_2_ptr r = CreateTrisegment( aTriedge ) ;
    r->set_child_l( GetTrisegment(aLSeed) ) ;
    return r ;
  }

  Trisegment_2_ptr CreateTrisegment ( Triedge const& aTriedge, Vertex_handle aLSeed, Vertex_handle aRSeed ) const
  {
    Trisegment_2_ptr r = CreateTrisegment( aTriedge ) ;
    r->set_child_l( GetTrisegment(aLSeed) ) ;
    r->set_child_r( GetTrisegment(aRSeed) ) ;
    return r ;
  }

  Vertex_handle GetPrevInLAV ( Vertex_handle aV )
  {
    return GetVertex ( GetVertexData(aV).mPrevInLAV ) ;
  }

  Vertex_handle GetNextInLAV ( Vertex_handle aV )
  {
    return GetVertex ( GetVertexData(aV).mNextInLAV ) ;
  }

  void SetPrevInLAV ( Vertex_handle aV, Vertex_handle aPrev )
  {
    GetVertexData(aV).mPrevInLAV = aPrev->id();
  }

  void SetNextInLAV ( Vertex_handle aV, Vertex_handle aPrev )
  {
    GetVertexData(aV).mNextInLAV = aPrev->id();
  }

  Halfedge_handle GetEdgeEndingAt ( Vertex_handle aV )
  {
    return GetVertexTriedge(aV).e0();
  }

  Halfedge_handle GetEdgeStartingAt ( Vertex_handle aV )
  {
    return GetEdgeEndingAt( GetNextInLAV(aV) ) ;
  }

  void Exclude ( Vertex_handle aV )
  {
    GetVertexData(aV).mIsExcluded = true ;
  }
  bool IsExcluded ( Vertex_const_handle aV ) const
  {
    return GetVertexData(aV).mIsExcluded ;
  }

  void SetIsReflex ( Vertex_handle aV )
  {
    GetVertexData(aV).mIsReflex = true ;
  }

  bool IsReflex ( Vertex_handle aV )
  {
    return GetVertexData(aV).mIsReflex ;
  }

  void SetIsDegenerate ( Vertex_handle aV )
  {
    GetVertexData(aV).mIsDegenerate = true ;
  }

  bool IsDegenerate ( Vertex_handle aV )
  {
    return GetVertexData(aV).mIsDegenerate ;
  }

  void SetIsProcessed ( Vertex_handle aV )
  {
    CGAL_STSKEL_BUILDER_TRACE(2, "Set V" << aV->id() << " Processed");

    GetVertexData(aV).mIsProcessed = true ;

    mVisitor.on_vertex_processed(aV);
  }

  bool IsConvex ( Vertex_handle aV )
  {
    Vertex_data const& lData = GetVertexData(aV) ;
    return !lData.mIsReflex && !lData.mIsDegenerate ;
  }

  bool IsProcessed ( Vertex_handle aV )
  {
    return GetVertexData(aV).mIsProcessed ;
  }

  void AddSplitEvent ( Vertex_handle aV, EventPtr const& aEvent )
  {
    CGAL_STSKEL_BUILDER_TRACE(2, "V" << aV->id() << " PQ: " << *aEvent);
    GetVertexData(aV).mSplitEvents.push(aEvent);
  }

  EventPtr PopNextSplitEvent ( Vertex_handle aV )
  {
    EventPtr rEvent ;
    Vertex_data& lData = GetVertexData(aV) ;
    if ( !lData.mNextSplitEventInMainPQ )
    {
      SplitPQ& lPQ = lData.mSplitEvents ;
      if ( !lPQ.empty() )
      {
        // When there are simultaneous split events, we sort them to handle nearby pseudo split events
        // together as to avoid multiple fronts crossing each other without noticing each other.
        // and creating an invalid SLS.
        //
        // Unfortunately, the way that this sorting is performed requires knowing whether an event
        // is a pseudo split, and requires looking into the LAV tree. More annoyingly, whether an event
        // exists or not (see calls to handle_assigned(lOpp) and such) can change, which will
        // change the position of the item in the split priority queue (as an invalid event is
        // at the bottom of the priority queue).
        //
        // Consequently, when a simultaneous event has been detected, we need to refresh the queue
        // to get the real top.
        //
        // In practice, things are usually fine with a roughly-correct priority queue because an
        // invalid event being popped will just be ignored, but MSVC 2015-Debug is a very zealous
        // compiler that checks that the full queue is sane when it pops() and will detect this kind
        // of unimportant inconsistencies...
        if ( lData.mHasSimultaneousEvents )
        {
          std::make_heap(const_cast<EventPtr*>(&lPQ.top()),
                         const_cast<EventPtr*>(&lPQ.top()) + lPQ.size(),
                         Split_event_compare(this, aV));
        }

        rEvent = lPQ.top();
        lPQ.pop();
        lData.mNextSplitEventInMainPQ = true ;
      }
    }
    return rEvent ;
  }

  void AllowNextSplitEvent ( Vertex_handle aV )
  {
    GetVertexData(aV).mNextSplitEventInMainPQ = false ;
  }

  void InsertEventInPQ( EventPtr aEvent ) ;

  EventPtr PopEventFromPQ() ;

  bool ExistEvent ( Trisegment_2_ptr const& aS )
  {
    return mTraits.do_ss_event_exist_2_object()( aS, mMaxTime ) ;
  }

  bool IsOppositeEdgeFacingTheSplitSeed( Vertex_handle aSeed, Halfedge_handle aOpposite ) const
  {
    if ( aSeed->is_skeleton() )
      return mTraits.is_edge_facing_ss_node_2_object()( GetTrisegment(aSeed),
                                                        CreateSegment<Traits>(aOpposite) ) ;
    else
      return mTraits.is_edge_facing_ss_node_2_object()( aSeed->point(),
                                                        CreateSegment<Traits>(aOpposite) ) ;
  }

  Oriented_side EventPointOrientedSide( Event const&          aEvent
                                      , Halfedge_const_handle aE0
                                      , Halfedge_const_handle aE1
                                      , Vertex_handle         aV01
                                      , bool                  aE0isPrimary
                                      ) const
  {
    return mTraits.oriented_side_of_event_point_wrt_bisector_2_object()( aEvent.trisegment(),
                                                                         CreateSegment<Traits>(aE0),
                                                                         aE0->weight(),
                                                                         CreateSegment<Traits>(aE1),
                                                                         aE1->weight(),
                                                                         GetTrisegment(aV01), // Can be null
                                                                         aE0isPrimary ) ;
  }

  Comparison_result CompareEvents ( Trisegment_2_ptr const& aA, Trisegment_2_ptr const& aB ) const
  {
    return mTraits.compare_ss_event_times_2_object()( aA, aB ) ;
  }

  Comparison_result CompareEvents ( EventPtr const& aA, EventPtr const& aB ) const
  {
    Comparison_result rResult = aA->triedge() != aB->triedge() ? CompareEvents( aA->trisegment(), aB->trisegment() )
                                                               : EQUAL;

    CGAL_STSKEL_BUILDER_TRACE(3, "Compare events " << aA->triedge() << " and " << aB->triedge() << " -> " << rResult);
    return rResult;
  }

  Comparison_result CompareEvents( Trisegment_2_ptr const& aTrisegment, Vertex_handle aSeedNode ) const
  {
    return aSeedNode->is_skeleton() ? aSeedNode->has_infinite_time() ? SMALLER
                                                                     : CompareEvents( aTrisegment, GetTrisegment(aSeedNode) )
                                    : LARGER  ;
  }

  Comparison_result CompareEvents( Vertex_handle aLNode, Vertex_handle aRNode ) const
  {
    if ( aLNode->is_skeleton() )
    {
      if ( aRNode->is_skeleton() )
      {
        if ( aLNode->has_infinite_time() && aRNode->has_infinite_time() )
          return EQUAL ;
        else if ( aLNode->has_infinite_time() )
          return LARGER ;
        else if ( aRNode->has_infinite_time() )
          return SMALLER ;
        else
          return CompareEvents ( GetTrisegment(aLNode), GetTrisegment(aRNode) ) ;
      }
      else // left skeleton, right not skeleton
      {
        return LARGER ;
      }
    }
    else // left not skeleton
    {
      if ( aRNode->is_skeleton() )
        return SMALLER ;
      else // both not skeleton
        return EQUAL ;
    }
  }

  bool AreEventsSimultaneous( Trisegment_2_ptr const& x, Trisegment_2_ptr const& y ) const
  {
    return mTraits.are_ss_events_simultaneous_2_object()( x, y ) ;
  }

  bool AreEventsSimultaneous( EventPtr const& x, EventPtr const& y ) const
  {
    return AreEventsSimultaneous(x->trisegment(),y->trisegment());
  }

  bool AreContourNodesCoincident( Vertex_handle aX, Vertex_handle aY ) const
  {
    CGAL_precondition( aX->is_contour() );
    CGAL_precondition( aY->is_contour() );

    return CGAL::compare_xy(aX->point(),aY->point()) == EQUAL ;
  }

  bool AreSkeletonNodesCoincident( Vertex_handle aX, Vertex_handle aY ) const
  {
    CGAL_precondition(  aX->is_skeleton() );
    CGAL_precondition(  aY->is_skeleton() );
    CGAL_precondition( !aX->has_infinite_time() );
    CGAL_precondition( !aY->has_infinite_time() );

    return AreEventsSimultaneous( GetTrisegment(aX),  GetTrisegment(aY) ) ;
  }

  void SetBisectorSlope ( Halfedge_handle aBisector, Sign aSlope )
  {
    aBisector->HBase_base::set_slope(aSlope);
  }

  void SetBisectorSlope ( Vertex_handle aA, Vertex_handle aB )
  {
    Halfedge_handle lOBisector = aA->primary_bisector();
    Halfedge_handle lIBisector = lOBisector->opposite();

    CGAL_precondition( !aA->is_contour() || !aB->is_contour() ) ;

    if ( aA->is_contour() )
    {
      SetBisectorSlope(lOBisector,POSITIVE);
      SetBisectorSlope(lIBisector,NEGATIVE);
    }
    else if ( aB->is_contour())
    {
      SetBisectorSlope(lOBisector,NEGATIVE);
      SetBisectorSlope(lIBisector,POSITIVE);
    }
    else
    {
      if ( aA->has_infinite_time() )
      {
        CGAL_precondition( !aB->has_infinite_time());

        SetBisectorSlope(lOBisector,NEGATIVE);
        SetBisectorSlope(lIBisector,POSITIVE);
      }
      else if ( aB->has_infinite_time())
      {
        CGAL_precondition( !aA->has_infinite_time());

        SetBisectorSlope(lOBisector,NEGATIVE);
        SetBisectorSlope(lIBisector,POSITIVE);
      }
      else
      {
        CGAL_precondition( !aA->has_infinite_time());
        CGAL_precondition( !aB->has_infinite_time());

        Sign lSlope = CompareEvents(GetTrisegment(aB),GetTrisegment(aA));
        SetBisectorSlope(lOBisector,lSlope);
        SetBisectorSlope(lIBisector,opposite(lSlope));
      }
    }
  }

  std::tuple<FT,Point_2> ConstructEventTimeAndPoint( Trisegment_2_ptr const& aS ) const
  {
    std::optional< std::tuple<FT,Point_2> > r = mTraits.construct_ss_event_time_and_point_2_object()( aS ) ;
    CGAL_postcondition_msg(!!r, "Unable to compute skeleton node coordinates");
    return *r ;
  }

  void SetEventTimeAndPoint( Event& aE )
  {
    FT      lTime ;
    Point_2 lP ;
    std::tie(lTime,lP) = ConstructEventTimeAndPoint(aE.trisegment());

    aE.SetTimeAndPoint(lTime,lP);
  }


  void EraseBisector( Halfedge_handle aB )
  {
    CGAL_STSKEL_BUILDER_TRACE(1,"Dangling B" << aB->id() << " and B" << aB->opposite()->id() << " removed.");

    mSSkel->SSkel::Base::edges_erase(aB);
  }

#ifdef CGAL_STSKEL_TRACE_ON
  std::string wavefront2str( Vertex_handle v )
  {
    std::ostringstream ss ;

    ss << "N" << GetPrevInLAV(v)->id() << "->N" << v->id() << "->N" << GetNextInLAV(v)->id()
       << "  E" << GetVertexTriedge(v).e0()->id() << "->E" << GetVertexTriedge(v).e1()->id() ;

    return ss.str() ;

  }
#endif

  void Link( Halfedge_handle aH, Face_handle aF )
  {
    aH->HBase_base::set_face(aF);
  }

  void Link( Halfedge_handle aH, Vertex_handle aV )
  {
    aH->HBase_base::set_vertex(aV);
  }

  void Link( Vertex_handle aV, Halfedge_handle aH )
  {
    aV->VBase::set_halfedge(aH);
  }

  void CrossLinkFwd( Halfedge_handle aPrev, Halfedge_handle aNext )
  {
    aPrev->HBase_base::set_next(aNext);
    aNext->HBase_base::set_prev(aPrev);
  }

  void CrossLink( Halfedge_handle aH, Vertex_handle aV )
  {
    Link(aH,aV);
    Link(aV,aH);
  }

  Triedge GetCommonTriedge( Vertex_handle aA, Vertex_handle aB ) ;

  bool AreBisectorsCoincident ( Halfedge_const_handle aA, Halfedge_const_handle aB ) const ;

  EventPtr IsPseudoSplitEvent( EventPtr const& aEvent, Vertex_handle_pair aOpp, Site const& aSite ) ;

  void CollectSplitEvent( Vertex_handle aNode, Triedge const& aTriedge ) ;

  void CollectSplitEvents( Vertex_handle aNode, Triedge const& aPrevEventTriedge ) ;

  EventPtr FindEdgeEvent( Vertex_handle aLNode, Vertex_handle aRNode, Triedge const& aPrevEventTriedge  ) ;

  void HandleSimultaneousEdgeEvent( Vertex_handle aA, Vertex_handle aB ) ;

  void CollectNewEvents( Vertex_handle aNode, Triedge const& aPrevEventTriedge ) ;
  void UpdatePQ( Vertex_handle aV, Triedge const& aPrevEventTriedge ) ;
  void CreateInitialEvents();
  void CreateContourBisectors();
  void HarmonizeSpeeds(boost::mpl::bool_<false>) { }
  void HarmonizeSpeeds(boost::mpl::bool_<true>);

  // @fixme This function can create an inconsistency between the skeleton and the offset
  // when caching is used: this function will harmonize values in the cache used by the skeleton
  // builder, but not the values in the cache used in the offset builder (actually, no_cache is used
  // for the offset builder, at the moment). It is difficult to harmonize between caches as one might
  // wish to build the skeleton and the offset in completely different moments and independent functions...
  void HarmonizeSpeeds() {
    // Harmonize speed only if we have a segment type with id and a not exact square root
    return HarmonizeSpeeds(
      boost::mpl::bool_< CGAL_SS_i::has_Segment_2_with_ID<Traits>::value &&
                         ( !is_same_or_derived<Field_with_sqrt_tag,
                                               typename Algebraic_structure_traits<FT>::Algebraic_category
                          >::value || std::is_floating_point<FT>::value) >() );
  }
  void InitPhase();

  void SetupNewNode( Vertex_handle aNode );

  Vertex_handle_pair LookupOnSLAV ( Halfedge_handle aOBorder, EventPtr const& aEvent, Site& rSite ) ;

  Vertex_handle      ConstructEdgeEventNode         ( EdgeEvent&   aEvent ) ;
  Vertex_handle_pair ConstructSplitEventNodes       ( SplitEvent&  aEvent, Vertex_handle aOppR ) ;
  Vertex_handle_pair ConstructPseudoSplitEventNodes ( PseudoSplitEvent& aEvent ) ;

  bool IsValidEvent            ( EventPtr                aEvent ) ;
  bool IsValidEdgeEvent        ( EdgeEvent        const& aEvent ) ;
  bool IsValidSplitEvent       ( SplitEvent       const& aEvent ) ;
  bool IsValidPseudoSplitEvent ( PseudoSplitEvent const& aEvent ) ;

  void HandleEdgeEvent               ( EventPtr aEvent ) ;
  void HandleSplitEvent              ( EventPtr aEvent, Vertex_handle_pair aOpp ) ;
  void HandlePseudoSplitEvent        ( EventPtr aEvent ) ;
  void HandleSplitOrPseudoSplitEvent ( EventPtr aEvent ) ;

  void InsertNextSplitEventInPQ( Vertex_handle v ) ;
  void InsertNextSplitEventsInPQ() ;

  bool IsProcessed( EventPtr aEvent ) ;

  void Propagate();

  void EraseNode ( Vertex_handle aNode ) ;

  void MergeSplitNodes ( Vertex_handle_pair aSplitNodes ) ;

  void RelinkBisectorsAroundMultinode( Vertex_handle const& v0, Halfedge_handle_vector& aLinks ) ;

  void PreprocessMultinode( Multinode& aMN ) ;

  void ProcessMultinode( Multinode& aMN, Halfedge_handle_vector& rHalfedgesToRemove , Vertex_handle_vector& rNodesToRemove ) ;

  MultinodePtr CreateMultinode( Halfedge_handle begin, Halfedge_handle end ) ;

  // returns 'true' if something was merged
  bool MergeCoincidentNodes() ;

  void EnforceSimpleConnectedness() ;

  bool FinishUp();

  bool Run();

private:

  //Input
  Traits  mTraits ;

  Visitor const& mVisitor ;

  std::vector<Vertex_data_ptr> mVertexData ;

  std::vector<std::list<Vertex_handle>> mLAVLists;

  Vertex_handle_vector   mReflexVertices ;
  Halfedge_handle_vector mContourHalfedges ;

  Vertex_handle_pair_vector mSplitNodes ;

  Event_compare mEventCompare ;

  int mVertexID ;
  int mEdgeID   ;
  int mFaceID   ;
  int mEventID  ;
  int mStepID   ;

  std::optional<FT> mMaxTime ;

  PQ mPQ ;

  //Output
  SSkelPtr mSSkel ;

private :

  template<class InputPointIterator, class Converter>
  void enter_valid_contour ( InputPointIterator aBegin, InputPointIterator aEnd, Converter const& cvt )
  {
    CGAL_STSKEL_BUILDER_TRACE(0,"Inserting Connected Component of the Boundary....");

    Halfedge_handle lFirstCCWBorder ;
    Halfedge_handle lPrevCCWBorder ;
    Halfedge_handle lNextCWBorder ;
    Vertex_handle   lFirstVertex ;
    Vertex_handle   lPrevVertex ;

    InputPointIterator lCurr = aBegin ;
    // InputPointIterator lPrev = aBegin ;

    CGAL_precondition_code(int c = 0 ;)

    while ( lCurr != aEnd )
    {
      Halfedge_handle lCCWBorder = SSkelEdgesPushBack( Halfedge(mEdgeID),Halfedge(mEdgeID+1)  );
      Halfedge_handle lCWBorder = lCCWBorder->opposite();
      mEdgeID += 2 ;

      mContourHalfedges.push_back(lCCWBorder);

      Vertex_handle lVertex = mSSkel->SSkel::Base::vertices_push_back( Vertex(mVertexID++,cvt(*lCurr)) ) ;
      CGAL_STSKEL_BUILDER_TRACE(1,"Vertex: V" << lVertex->id() << " at " << lVertex->point() );
      InitVertexData(lVertex);

      Face_handle lFace = mSSkel->SSkel::Base::faces_push_back( Face(mFaceID++) ) ;

      CGAL_precondition_code(++ c ;)

      lCCWBorder->HBase_base::set_face(lFace);
      lFace     ->FBase     ::set_halfedge(lCCWBorder);

      lVertex   ->VBase     ::set_halfedge(lCCWBorder);
      lCCWBorder->HBase_base::set_vertex(lVertex);

      if ( lCurr == aBegin )
      {
        lFirstVertex    = lVertex ;
        lFirstCCWBorder = lCCWBorder ;
      }
      else
      {
        SetPrevInLAV(lVertex    ,lPrevVertex);
        SetNextInLAV(lPrevVertex,lVertex    );

        SetVertexTriedge(lPrevVertex, Triedge(lPrevCCWBorder,lCCWBorder) ) ;

        lCWBorder->HBase_base::set_vertex(lPrevVertex);

        lCCWBorder    ->HBase_base::set_prev(lPrevCCWBorder);
        lPrevCCWBorder->HBase_base::set_next(lCCWBorder);

        lNextCWBorder->HBase_base::set_prev(lCWBorder);
        lCWBorder    ->HBase_base::set_next(lNextCWBorder);

        CGAL_STSKEL_BUILDER_TRACE(1,"CCW Border: E" << lCCWBorder->id() << ' ' << lPrevVertex->point() << " -> " << lVertex    ->point());
        CGAL_STSKEL_BUILDER_TRACE(1,"CW  Border: E" << lCWBorder ->id() << ' ' << lVertex    ->point() << " -> " << lPrevVertex->point() );

        mVisitor.on_contour_edge_entered(lCCWBorder);

      }

      // lPrev = lCurr ;

      ++ lCurr ;

      lPrevVertex    = lVertex ;
      lPrevCCWBorder = lCCWBorder ;
      lNextCWBorder  = lCWBorder ;
    }

    SetPrevInLAV(lFirstVertex,lPrevVertex );
    SetNextInLAV(lPrevVertex ,lFirstVertex);

    SetVertexTriedge( lPrevVertex, Triedge(lPrevCCWBorder,lFirstCCWBorder) ) ;

    lFirstCCWBorder->opposite()->HBase_base::set_vertex(lPrevVertex);

    lFirstCCWBorder->HBase_base::set_prev(lPrevCCWBorder);
    lPrevCCWBorder ->HBase_base::set_next(lFirstCCWBorder);

    lPrevCCWBorder ->opposite()->HBase_base::set_prev(lFirstCCWBorder->opposite());
    lFirstCCWBorder->opposite()->HBase_base::set_next(lPrevCCWBorder ->opposite());

    CGAL_precondition_msg(c >=3, "The contour must have at least 3 _distinct_ vertices" ) ;

    CGAL_STSKEL_BUILDER_TRACE(1
                             , "CCW Border: E" << lFirstCCWBorder->id()
                             << ' ' << lPrevVertex ->point() << " -> " << lFirstVertex->point() << '\n'
                             << "CW  Border: E" << lFirstCCWBorder->opposite()->id()
                             << ' ' << lFirstVertex->point() << " -> " << lPrevVertex ->point()
                             );

    mVisitor.on_contour_edge_entered(lFirstCCWBorder);
  }

// internal function to filter split event in case the traits is Filtered
  bool CanSafelyIgnoreSplitEventImpl(const EventPtr&, boost::mpl::bool_<false>) const
  {
    return false;
  }

  bool CanSafelyIgnoreSplitEventImpl(const EventPtr& lEvent, boost::mpl::bool_<true>) const
  {
    return mTraits.CanSafelyIgnoreSplitEvent(lEvent);
  }

  bool CanSafelyIgnoreSplitEvent(const EventPtr& lEvent) const
  {
    return CanSafelyIgnoreSplitEventImpl(lEvent, typename CGAL_SS_i::has_Filters_split_events_tag<Traits>::type());
  }

  void ComputeUpperBoundForValidSplitEventsImpl(Vertex_handle,
                                                Halfedge_handle_vector_iterator,
                                                Halfedge_handle_vector_iterator,
                                                boost::mpl::bool_<false>) const
  {
  }

  void ComputeUpperBoundForValidSplitEventsImpl(Vertex_handle aNode,
                                                Halfedge_handle_vector_iterator contour_halfedges_begin,
                                                Halfedge_handle_vector_iterator contour_halfedges_end,
                                                boost::mpl::bool_<true>) const
  {
    return mTraits.ComputeFilteringBound(aNode, contour_halfedges_begin, contour_halfedges_end);
  }

  void ComputeUpperBoundForValidSplitEvents(Vertex_handle aNode,
                                            Halfedge_handle_vector_iterator contour_halfedges_begin,
                                            Halfedge_handle_vector_iterator contour_halfedges_end) const
  {
    return ComputeUpperBoundForValidSplitEventsImpl(aNode, contour_halfedges_begin, contour_halfedges_end,
                                                    typename CGAL_SS_i::has_Filters_split_events_tag<Traits>::type());
  }

public:

  // This compares INPUT vertices so there is no need to filter it.
  struct AreVerticesEqual
  {
    template<class P>
    bool operator() ( P const&x, P const& y ) const
    {
      return CGAL::compare_xy(x,y) == EQUAL ;
    }
  } ;

  template<class InputPointIterator, class Converter>
  Straight_skeleton_builder_2& enter_contour ( InputPointIterator aBegin
                                             , InputPointIterator aEnd
                                             , Converter const&   cvt
                                             , bool               aCheckValidity = true
                                             )
  {
    if ( aCheckValidity )
    {
      typedef typename std::iterator_traits<InputPointIterator>::value_type Input_point;

      typedef std::vector<Input_point> Input_point_vector ;

      // Remove coincident consecutive vertices
      Input_point_vector lList;
      std::unique_copy(aBegin,aEnd,std::back_inserter(lList),AreVerticesEqual());

      while ( lList.size() > 0 && CGAL::compare_xy(lList.front(),lList.back()) == EQUAL )
        lList.pop_back();

      if ( lList.size() >= 3 )
      {
        enter_valid_contour(lList.begin(),lList.end(),cvt);
      }
      else
      {
        CGAL_STSKEL_BUILDER_TRACE(0,"Degenerate contour (fewer than 3 non-degenerate vertices).");
      }
    }
    else
    {
      enter_valid_contour(aBegin,aEnd,cvt);
    }

    return *this ;
  }

  template<class InputPointIterator>
  Straight_skeleton_builder_2& enter_contour ( InputPointIterator aBegin
                                             , InputPointIterator aEnd
                                             , bool               aCheckValidity = true
                                             )
  {
    return enter_contour(aBegin, aEnd, Cartesian_converter<K,K>(), aCheckValidity);
  }

  template <typename WeightIterator, typename Converter>
  Straight_skeleton_builder_2& enter_contour_weights(WeightIterator aWeightsBegin,
                                                     WeightIterator aWeightsEnd,
                                                     const Converter& cvt)
  {
    const auto lWeightsN = std::distance(aWeightsBegin, aWeightsEnd);
    CGAL_assertion(std::size_t(lWeightsN) <= mSSkel->SSkel::Base::size_of_faces());

    Face_iterator fit = std::next(mSSkel->SSkel::Base::faces_end(), -lWeightsN);

    for(; aWeightsBegin!=aWeightsEnd; ++aWeightsBegin, ++fit)
    {
      CGAL_assertion(fit != mSSkel->SSkel::Base::faces_end());

      Halfedge_handle lBorder = fit->halfedge();
      CGAL_assertion(lBorder->opposite()->is_border());
      FT lWeight = cvt(*aWeightsBegin);
      CGAL_STSKEL_BUILDER_TRACE(4, "Assign " << *aWeightsBegin << " (converted to " << cvt(lWeight) << ") to E" << lBorder->id());
      lBorder->set_weight(lWeight);
    }

    return *this;
  }

  template <typename WeightIterator>
  Straight_skeleton_builder_2& enter_contour_weights(WeightIterator aWeightsBegin,
                                                     WeightIterator aWeightsEnd)
  {
    return enter_contour_weightsg(aWeightsBegin, aWeightsEnd, NT_converter<FT,FT>());
  }
} ;

} // namespace CGAL

#include <CGAL/Straight_skeleton_2/Straight_skeleton_builder_2_impl.h>

#endif // CGAL_STRAIGHT_SKELETON_BUILDER_2_H
