// Copyright (c) 2005-2009  INRIA Sophia-Antipolis (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1/CGAL_ipelets/include/CGAL/CGAL_Ipelet_base_v6.h $
// $Id: include/CGAL/CGAL_Ipelet_base_v6.h b26b07a1242 $
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Sebastien Loriot, Sylvain Pion


#ifndef CGAL_IPELET_BASE_H
#define CGAL_IPELET_BASE_H

// Ipe headers use uint which is not standard.
#ifdef __APPLE__
typedef unsigned int uint;
#endif

#include <ipelib.h>
#include <CGAL/Polygon_2.h>
#include <CGAL/iterator.h>
#include <CGAL/Triangulation_2.h>
#include <CGAL/grabbers.h>
#include <CGAL/iterator.h>
#include <CGAL/tuple.h>
#include<CGAL/Exact_circular_kernel_2.h>
#include <CGAL/Cartesian_converter.h>
#include <CGAL/assertions.h>

#include <boost/utility.hpp>

namespace CGAL{

  template <class Kernel,int nbf>
  class Ipelet_base : public Ipelet {
  private:
    const std::string* SubLab;
    const std::string* HMsg;
    std::string Name;
    IpePage* _page;
    IpeletHelper* _helper;

  public:

    //typedefs
    typedef typename Kernel::FT                                               FT;
    typedef typename CGAL::Point_2<Kernel>                                    Point_2;
    typedef typename CGAL::Weighted_point_2<Kernel>                           Weighted_point_2;
    typedef typename Kernel::Segment_2                                        Segment_2;
    typedef typename Kernel::Ray_2                                            Ray_2;
    typedef typename Kernel::Line_2                                           Line_2;
    typedef typename Kernel::Iso_rectangle_2                                  Iso_rectangle_2;
    typedef typename Kernel::Triangle_2                                       Triangle_2;
    //~ typedef typename CGAL::Polygon_2<Kernel,std::list<Point_2> >              Polygon_2;
    typedef typename CGAL::Polygon_2<Kernel>              Polygon_2;

    typedef typename Kernel::Circle_2                                         Circle_2;
    typedef std::tuple<Circle_2,Point_2,Point_2,CGAL::Sign>           Circular_arc_2;


    Ipelet_base(const std::string NameS,const std::string SubLabS[],const std::string HMsgS[])
      :SubLab(&SubLabS[0]),HMsg(&HMsgS[0]),Name(NameS),_page(nullptr),_helper(nullptr){};


    IpePage* get_IpePage() const {return _page;}
    IpeletHelper* get_IpeletHelper() const {return _helper;}
    int IpelibVersion() const { return IPELIB_VERSION; }
    int NumFunctions() const { return nbf; }
    virtual const char *Label() const{ return &Name[0]; }
    const char *About() const {return "https://www.cgal.org";};
    virtual const char *SubLabel(int function) const {return &SubLab[function][0];};
    virtual const char *HelpMsg(int function) const{return &HMsg[function][0];};
    void Run (int i, IpePage *page, IpeletHelper *helper) {
      _page=page;
      _helper=helper;
      try{
        protected_run(i);
      }
      catch(...){
        helper->MessageBox("Error : Save your page in a file and submit it to \n https://www.cgal.org/bug_report.html","OK",nullptr,nullptr);
      }
    };

    virtual void protected_run(int)=0;

    void group_selected_objects_() const {
      get_IpePage()->Group(get_IpeletHelper()->CurrentLayer());
    }

    void transform_selected_objects_(const IpeMatrix& tfm) const {
      for (IpePage::iterator it = get_IpePage() -> begin();it!=get_IpePage() -> end(); ++it)
        if (it->Select()) it->Transform(tfm);
    }

    void show_help(bool gen=true) const{
      std::string hmsg;
      hmsg="<qt><h1>"+Name+"</h1><ul>";
      if (gen)
        for(int i=0;i<nbf-1;++i)
          hmsg=hmsg+"<li><i>"+SubLab[i]+"</i>: "+HMsg[i]+"</li>";
      else
        hmsg=hmsg+"<li>"+HMsg[0]+"</li>";
      _helper->MessageBox(&hmsg[0],"OK",nullptr,nullptr);
      return;
    }


    //grabbers

    template <class output_iterator>
    struct Point_grabber:public internal::Point_grabber<Kernel,output_iterator>{
      Point_grabber(output_iterator it):internal::Point_grabber<Kernel,output_iterator>(it){}
    };

    template<class output_iterator>
    boost::function_output_iterator<Point_grabber<output_iterator> >
    point_grabber(output_iterator it){
      return boost::make_function_output_iterator(Point_grabber<output_iterator>(it));
    }


    template <class output_iterator>
    struct Segment_grabber:public internal::Segment_grabber<Kernel,output_iterator>{
      Segment_grabber(output_iterator it):internal::Segment_grabber<Kernel,output_iterator>(it){}
    };

    template<class output_iterator>
    boost::function_output_iterator<Segment_grabber<output_iterator> >
    segment_grabber(output_iterator it){
      return boost::make_function_output_iterator(Segment_grabber<output_iterator>(it));
    }

    template <class output_iterator>
    struct Wpoint_grabber:public internal::Wpoint_grabber<Kernel,output_iterator>{
      Wpoint_grabber(output_iterator it):internal::Wpoint_grabber<Kernel,output_iterator>(it){}
    };

    template<class output_iterator>
    boost::function_output_iterator<Wpoint_grabber<output_iterator> >
    wpoint_grabber(output_iterator it){
      return boost::make_function_output_iterator(Wpoint_grabber<output_iterator>(it));
    }

    //Interaction functions
    //------------------------------

    void
    print_error_message(const char* s) const
    {
      _helper->Message(s);
    }

    template <class T>
    std::pair<int,T>
    request_value_from_user(std::string msg) const
    {
      IpeString str;
      std::pair<int,T> ret_val=std::make_pair(-1,T());
      if (_helper-> GetString(msg.c_str(),str)){
        if (!str.empty()){
          IpeLex lex(str);
          lex >> ret_val.second;
          ret_val.first=1;
        }
        else
          ret_val.first=0;
      }
      return ret_val;
    }

    //Conversion functions
    //------------------------------
    Point_2
    segment_endpoint(const IpePathSegment& segment,IpePath* obj_ipe,int i) const
    {
      CGAL_precondition(i<2);
      IpeVector pt_ipe = obj_ipe -> Matrix() * segment.CP(i);
      return Point_2((double)(pt_ipe.iX),(double)(pt_ipe.iY));//conversion into CGAL point
    }

    Point_2
    to_point_2(IpeObject*  object) const
    {
      IpeVector pt_ipe = object-> Matrix() * object-> AsMark() -> Position();
      return Point_2((double)(pt_ipe.iX),(double)(pt_ipe.iY));//conversion into CGAL point
    }

    Circle_2
    to_circle_2(IpePath* obj_ipe,int subpath=0) const
    {
      const IpeEllipse* ell_ipe = obj_ipe -> SubPath(subpath) -> AsEllipse();
      IpeMatrix mat_ipe = obj_ipe -> Matrix() * ell_ipe -> Matrix();
      FT radius = (mat_ipe*IpeVector(1,0)-mat_ipe.Translation()).Len();
      IpeVector pt_ipe = mat_ipe.Translation();
      return Circle_2(Point_2(pt_ipe.iX,pt_ipe.iY),radius*radius);
    }


    //Picking functions
    //------------------------------

    bool
    is_only_rotated_or_scaled(const IpeMatrix& m) const
    {
      return (m.iA[0]==m.iA[3] && m.iA[1]==-m.iA[2]);
    }

    bool
    is_IPE_circle(IpeObject* object,int subpath=0) const
    {
      return ( object -> AsPath() && object -> AsPath() -> SubPath(subpath) -> AsEllipse()
        && is_only_rotated_or_scaled(object ->AsPath()->Matrix()));
    }


private:
    //declaration
    template< class multi_output_iterator >
    bool read_one_active_object( IpeObject* object,
      multi_output_iterator it_out) const;

public:

    template< class V,class O>
    Iso_rectangle_2
    read_active_objects (
      CGAL::Dispatch_or_drop_output_iterator<V,O> it_out,
      bool deselect_all=true,
      bool delete_selected_objects=false) const
    {
      IpeRect bbox_ipe;

      if (!_page->HasSelection()) {
        return Iso_rectangle_2();
      }

      for(IpePage::iterator it = get_IpePage() -> begin(); it!=get_IpePage() -> end(); ++it){
        if ( !it->Select() )
          continue;

        bbox_ipe.AddRect(it->BBox());

        //Test one function for segments, circles, circle arcs and polygons
        bool desel_it=read_one_active_object(it->Object(),it_out);
        if ( delete_selected_objects && desel_it  )
          it->SetSelect(IpePgObject::ENone);
      }

      if (delete_selected_objects)
        _page -> Delete();

      if (deselect_all)
        _page->DeselectAll();

      Iso_rectangle_2 bbox_cgal(
        static_cast<double>((bbox_ipe.Min)().iX),static_cast<double>((bbox_ipe.Min)().iY),
        static_cast<double>((bbox_ipe.Max)().iX),static_cast<double>((bbox_ipe.Max)().iY)
      );

        return bbox_cgal;
    }

    //drawing functions
    //------------------------------

    void
    copy_attributes(IpeAllAttributes& AAttr,IpeObject* obj_ipe) const
    {
      AAttr.iStroke = obj_ipe -> Stroke();

      if (obj_ipe->AsFillable()){
        AAttr.iFill = obj_ipe->AsFillable() -> Fill();
        AAttr.iLineWidth = obj_ipe->AsFillable() -> LineWidth();
        AAttr.iDashStyle = obj_ipe->AsFillable() -> DashStyle();
      }
      if (obj_ipe->AsPath()){
        AAttr.iForwardArrow = obj_ipe->AsPath() -> ForwardArrow();
        AAttr.iBackwardArrow = obj_ipe->AsPath() -> BackwardArrow();
      }
    }


    void
    create_polygon_with_holes(bool delete_underlying_polygons=false) const
    {
      std::list<IpeSubPath*> SSPqu;
      for(IpePage::iterator it=get_IpePage()->begin();it!=get_IpePage()->end();++it){
        if(it->Select() && it->Object()->AsPath()->SubPath(0)->Closed()){
          IpeSubPath* ssp=it->Object()->AsPath()->SubPath(0)->Transform(it->Object()->AsPath()->Matrix());
          SSPqu.push_back(ssp);
        }
      }
      if (!delete_underlying_polygons)
        get_IpePage() -> DeselectAll();
      IpePath* obj_ipe = new IpePath(get_IpeletHelper() -> Attributes());// create new objects with current attributes
      for (std::list<IpeSubPath*>::iterator it=SSPqu.begin();it!=SSPqu.end();++it)
        obj_ipe->AddSubPath(*it);
      if (delete_underlying_polygons)
        get_IpePage()->Delete();
      get_IpePage()->push_back(IpePgObject(IpePgObject::ESecondary,get_IpeletHelper()->CurrentLayer(),obj_ipe));
    }

    void
    center_selection_in_page() const
    {
      IpeVector paper_size=get_paper_size();
      IpeMatrix tfm (1,0,0,1,paper_size.iX/2.,paper_size.iY/2.);
      for (IpePage::iterator it = get_IpePage()->begin(); it != get_IpePage()->end(); ++it)
        if (it->Select())
          it->Transform(tfm);
    }

    template<class iterator>
    IpeSegmentSubPath*
    create_polyline(const iterator first, const iterator last,bool setclose=false) const
    {
      if (std::next(first)!=last){
        IpeSegmentSubPath* SSP_ipe = new IpeSegmentSubPath();
        IpeVector Prev_pt=IpeVector(CGAL::to_double(first->x()),CGAL::to_double(first->y())) ;
        for (iterator it = std::next(first);it!=last;++it){
          IpeVector Cur_pt=IpeVector(CGAL::to_double(it->x()),CGAL::to_double(it->y()));
          SSP_ipe -> AppendSegment(Prev_pt,Cur_pt);
          Prev_pt=Cur_pt;
        }
        if (setclose)
          SSP_ipe->SetClosed(true);
        return SSP_ipe;
      }
      return nullptr;
    }


    template<class iterator>
    IpePath*
    draw_polyline_in_ipe(const iterator first, const iterator last,
                         bool setclose=false,bool deselect_all=false,
                         bool blackfill=false,
                         std::enable_if_t<
                                    std::is_same<
                                      typename std::iterator_traits<iterator>::value_type,
                                      Point_2
                                    >::value
                                  >* =nullptr) const
    {
      IpeSegmentSubPath* SSP_ipe=create_polyline(first,last,setclose);
      if (SSP_ipe!=nullptr){
        IpePath* obj_ipe = new IpePath(_helper->Attributes());
        obj_ipe->AddSubPath(SSP_ipe);
        if (blackfill)
          obj_ipe->SetFill(IpeAttribute::Black());
        _page->push_back(IpePgObject(IpePgObject::ESecondary,_helper->CurrentLayer(),obj_ipe));
        if (deselect_all) (--_page->end())->SetSelect(IpePgObject::ENone);
        return obj_ipe;
      }
      return nullptr;
    }

    void draw_in_ipe(const Circle_2& C,bool deselect_all=false) const {
      IpeEllipse *ellipse = new IpeEllipse(IpeMatrix(sqrt(CGAL::to_double(C.squared_radius())),0,
                                                     0,sqrt(CGAL::to_double(C.squared_radius())),
                                                     to_double(C.center().x()),to_double(C.center().y())
                                           )
                                );
      IpePath *path = new IpePath(_helper->Attributes());
      path->AddSubPath(ellipse);
      _page->push_back(IpePgObject(IpePgObject::EPrimary,_helper->CurrentLayer(), path));
      if (deselect_all) (--_page->end())->SetSelect(IpePgObject::ENone);
    }

    void
    draw_in_ipe(const Point_2& P,bool deselect_all=false) const
    {
      IpeMark *mark = new IpeMark(_helper->Attributes(), IpeVector(CGAL::to_double(P.x()),CGAL::to_double(P.y())));
      _page->push_back(IpePgObject(IpePgObject::ESecondary,_helper->CurrentLayer(),mark));
      if (deselect_all) (--_page->end())->SetSelect(IpePgObject::ENone);
    }

    void
    draw_in_ipe(const Segment_2& S,bool deselect_all=false) const
    {
      IpeSegment seg_ipe;
      seg_ipe.iP = IpeVector(CGAL::to_double(S.point(0).x()),CGAL::to_double(S.point(0).y()));
      seg_ipe.iQ = IpeVector(CGAL::to_double(S.point(1).x()),CGAL::to_double(S.point(1).y()));
      _page->push_back(IpePgObject(IpePgObject::ESecondary,_helper->CurrentLayer(),new IpePath(_helper->Attributes(),seg_ipe)));
      if (deselect_all) (--_page->end())->SetSelect(IpePgObject::ENone);
    }

    template<class Container>
    void
    draw_in_ipe(const CGAL::Polygon_2<Kernel,Container>& poly,bool deselect_all=false) const
    {
      std::list<Point_2> LP;
      for (typename CGAL::Polygon_2<Kernel,Container>::iterator it=poly.vertices_begin();it!= poly.vertices_end();++it)
        LP.push_back(*it);
      draw_polyline_in_ipe(LP.begin(),LP.end(),true,deselect_all,false);
    }

    void
    draw_in_ipe(const Circular_arc_2& arc,bool deselect_all=false) const
    {
      IpeSegmentSubPath* SSP_ipe = new IpeSegmentSubPath;
      IpeVector ipeS=IpeVector( CGAL::to_double(std::get<1>(arc).x()),
                                CGAL::to_double(std::get<1>(arc).y()));//convert to ipe format
      IpeVector ipeT=IpeVector( CGAL::to_double(std::get<2>(arc).x()),
                                CGAL::to_double(std::get<2>(arc).y()));//convert to ipe format
      SSP_ipe->AppendArc(IpeMatrix(sqrt(CGAL::to_double(std::get<0>(arc).squared_radius())),0,
                                   0,(std::get<3>(arc)==CGAL::COUNTERCLOCKWISE?1:-1)*
                                     sqrt(CGAL::to_double(std::get<0>(arc).squared_radius())),
                                   CGAL::to_double(std::get<0>(arc).center().x()),
                                   CGAL::to_double(std::get<0>(arc).center().y())),
                                   ipeS,ipeT);
      IpePath* obj_ipe = new IpePath(_helper->Attributes());
      obj_ipe->AddSubPath(SSP_ipe);
      _page->push_back(IpePgObject(IpePgObject::ESecondary,_helper->CurrentLayer(),obj_ipe));
      if (deselect_all) (--_page->end())->SetSelect(IpePgObject::ENone);
    }


    void
    draw_in_ipe(const Triangle_2& t,bool deselect_all=false) const
    {
      IpeSegmentSubPath* SSP_ipe = new IpeSegmentSubPath();
      IpeVector P0=IpeVector(t[0].x(),t[0].y());
      IpeVector P1=IpeVector(t[1].x(),t[1].y());
      IpeVector P2=IpeVector(t[2].x(),t[2].y());
      SSP_ipe->AppendSegment(P0,P1);
      SSP_ipe->AppendSegment(P1,P2);
      SSP_ipe->AppendSegment(P2,P0);
      SSP_ipe->SetClosed(true);
      IpePath* obj_ipe = new IpePath(_helper->Attributes());
      obj_ipe->AddSubPath(SSP_ipe);
      _page->push_back(IpePgObject(IpePgObject::ESecondary,_helper->CurrentLayer(),obj_ipe));
      if (deselect_all) (--_page->end())->SetSelect(IpePgObject::ENone);
    }

    void
    draw_in_ipe(const Iso_rectangle_2& r,bool deselect_all=false)
    {
      IpeSegmentSubPath* SSP_ipe = new IpeSegmentSubPath();
      IpeVector P0=IpeVector(r[0].x(),r[0].y());
      IpeVector P1=IpeVector(r[1].x(),r[1].y());
      IpeVector P2=IpeVector(r[2].x(),r[2].y());
      IpeVector P3=IpeVector(r[3].x(),r[3].y());
      SSP_ipe->AppendSegment(P0,P1);
      SSP_ipe->AppendSegment(P1,P2);
      SSP_ipe->AppendSegment(P2,P3);
      SSP_ipe->AppendSegment(P3,P0);
      SSP_ipe->SetClosed(true);
      IpePath* obj_ipe = new IpePath(_helper->Attributes());
      obj_ipe->AddSubPath(SSP_ipe);
      _page->push_back(IpePgObject(IpePgObject::ESecondary,_helper->CurrentLayer(),obj_ipe));
      if (deselect_all) (--_page->end())->SetSelect(IpePgObject::ENone);
    }


    //Drawing function with bbox : global version
    template <class T>
    void
    draw_in_ipe(const T& object,const Iso_rectangle_2& bbox,bool deselect_all=false) const
    {
      Segment_2 s;
      bool success=cast_into_seg(object,bbox,&s);
      if (success)
        draw_in_ipe(s,deselect_all);
    }
  private:
    enum Type_circ_arc{SRC,TRG,OSRC,OTRG};
  public:
    void
    draw_in_ipe(const Circular_arc_2& object,const Iso_rectangle_2& bbox,bool deselect_all=false) const
    {
      std::vector<Circular_arc_2> arc_list;
      const Circle_2& circle=std::get<0>(object);
      restrict_circle_to_bbox(circle,bbox,std::back_inserter(arc_list));
      if (arc_list.empty() && bbox.has_on_bounded_side(circle.center()) ){
        draw_in_ipe(object,deselect_all);
        return;
      }

      const Point_2* source=(std::get<3>(object)==CGAL::COUNTERCLOCKWISE)?
                            &std::get<1>(object):&std::get<2>(object);
      const Point_2* target=(std::get<3>(object)==CGAL::COUNTERCLOCKWISE)?
                            &std::get<2>(object):&std::get<1>(object);
      std::multimap<double,std::pair<Type_circ_arc,const Point_2*> > map_theta;
      typedef  typename std::multimap<double,std::pair<Type_circ_arc,const Point_2*> >::iterator Map_theta_iterator;
      Map_theta_iterator s_it=map_theta.insert(
                                std::make_pair(get_theta(*source,circle),std::make_pair(OSRC,source)));
      /*Map_theta_iterator t_it=*/map_theta.insert(
                                std::make_pair(get_theta(*target,circle),std::make_pair(OTRG,target)));

      for (typename std::vector<Circular_arc_2>::iterator it_arc=arc_list.begin();it_arc!=arc_list.end();++it_arc){
        const Point_2* arc_s=(std::get<3>(*it_arc)==CGAL::COUNTERCLOCKWISE)?
                             &std::get<1>(*it_arc):&std::get<2>(*it_arc);
        const Point_2* arc_t=(std::get<3>(*it_arc)==CGAL::COUNTERCLOCKWISE)?
                             &std::get<2>(*it_arc):&std::get<1>(*it_arc);
        map_theta.insert( std::make_pair(get_theta(*arc_s,circle),std::make_pair(SRC,arc_s) ) );
        map_theta.insert( std::make_pair(get_theta(*arc_t,circle),std::make_pair(TRG,arc_t) ) );
      }

      Map_theta_iterator next_s=s_it;
      ++next_s; if (next_s==map_theta.end()) next_s=map_theta.begin();
      switch (next_s->second.first){
        case TRG:
          draw_in_ipe(Circular_arc_2(circle,*source,*(next_s->second.second),CGAL::COUNTERCLOCKWISE));
          break;
        case OSRC:
          CGAL_error();
        case SRC:{
          Map_theta_iterator current=next_s;
          ++next_s; if (next_s==map_theta.end()) next_s=map_theta.begin();
          draw_in_ipe(Circular_arc_2(circle,*(current->second.second),*(next_s->second.second),CGAL::COUNTERCLOCKWISE));
          if(next_s->second.first==OTRG) return;
          break;
        }
        case OTRG:
          ++next_s; if (next_s==map_theta.end()) next_s=map_theta.end();
          if (next_s->second.first==TRG){
            draw_in_ipe(object);
            return;
          }
          else
            return;
      }

      ++next_s; if (next_s==map_theta.end()) next_s=map_theta.begin();
      Map_theta_iterator current=next_s;
      ++next_s; if (next_s==map_theta.end()) next_s=map_theta.begin();
      do{
        if (current->second.first==OTRG) return;
        CGAL_assertion(current->second.first==SRC);
        draw_in_ipe(Circular_arc_2(circle,*(current->second.second),*(next_s->second.second),CGAL::COUNTERCLOCKWISE));
        if (next_s->second.first==OTRG) return;
        CGAL_assertion(next_s->second.first==TRG);
        ++next_s; if (next_s==map_theta.end()) next_s=map_theta.begin();
        current=next_s;
        ++next_s; if (next_s==map_theta.end()) next_s=map_theta.begin();
      }while(true);

    }


    void
    draw_in_ipe(const Circle_2& object,const Iso_rectangle_2& bbox,bool deselect_all=false) const
    {
      std::vector<Circular_arc_2> arc_list;
      restrict_circle_to_bbox(object,bbox,std::back_inserter(arc_list));
      if (arc_list.empty() && bbox.has_on_bounded_side(object.center()) )
        draw_in_ipe(object,deselect_all);
      else
        draw_in_ipe(arc_list.begin(),arc_list.end(),false,deselect_all);
    }


    void
    draw_in_ipe(const Triangle_2& object,const Iso_rectangle_2& bbox,bool deselect_all=false) const
    {
       for (unsigned int i=0;i!=3;++i)
        draw_in_ipe(Segment_2(object.vertex(i),object.vertex(i+1)),bbox,deselect_all);
    }

    void
    draw_in_ipe(const Iso_rectangle_2& object,const Iso_rectangle_2& bbox,bool deselect_all=false) const
    {
      for (unsigned int i=0;i!=4;++i)
        draw_in_ipe(Segment_2(object.vertex(i),object.vertex(i+1)),bbox,deselect_all);
    }

    void
    draw_in_ipe(const Polygon_2& object,const Iso_rectangle_2& bbox,bool deselect_all=false) const
    {
      for (typename Polygon_2::Edge_const_iterator it=object.edges_begin();it!=object.edges_end();++it)
        draw_in_ipe(*it,bbox,deselect_all);
    }



    template<class GT,class TDS>
    void
    draw_in_ipe(const CGAL::Triangulation_2<GT,TDS>& tri,const Iso_rectangle_2& bbox,bool deselect_all=false) const
    {
      typedef CGAL::Triangulation_2<GT,TDS> Triangulation;
      typename Triangulation::Finite_edges_iterator first=tri.finite_edges_begin();
      typename Triangulation::Finite_edges_iterator last=tri.finite_edges_end();
      for(typename Triangulation::Finite_edges_iterator it=first;it!=last;++it)
        draw_in_ipe(tri.segment(*it),bbox);
      if (deselect_all)
        _page->DeselectAll();
    }

    void
    draw_in_ipe(const Line_2& line,bool deselect_all=false) const
    {
      IpeVector paper_size=get_paper_size();
      Iso_rectangle_2 bbox(0,0,paper_size.iX,paper_size.iY);
      draw_in_ipe(line,bbox,deselect_all);
    }

    void
    draw_in_ipe(const Ray_2& ray,bool deselect_all=false)
    {
      IpeVector paper_size=get_paper_size();
      Iso_rectangle_2 bbox(0,0,paper_size.iX,paper_size.iY);
      draw_in_ipe(ray,bbox,deselect_all);
    }

    template<class GT,class TDS>
    void
    draw_in_ipe(const CGAL::Triangulation_2<GT,TDS>& tri,bool deselect_all=false,bool make_grp=true) const
    {
      typedef CGAL::Triangulation_2<GT,TDS> Triangulation;
      typename Triangulation::Finite_edges_iterator first=tri.finite_edges_begin();
      typename Triangulation::Finite_edges_iterator last=tri.finite_edges_end();
      for(typename Triangulation::Finite_edges_iterator it=first;it!=last;++it)
        draw_in_ipe(tri.segment(*it));
      if (make_grp)
        _page->Group(_helper->CurrentLayer());
      if (deselect_all)
        _page->DeselectAll();
    }

    template<class iterator>
    void
    draw_in_ipe(const iterator begin,const iterator end,bool make_grp=true,bool deselect_all=false) const
    {
      for (iterator it=begin;it!=end;++it)
        draw_in_ipe(*it);
      if (make_grp and ++iterator(begin)!=end)
        _page->Group(_helper->CurrentLayer());
      if (deselect_all)
        _page->DeselectAll();
    }

    template<class iterator>
    void
    draw_in_ipe(const iterator begin,const iterator end,const Iso_rectangle_2& bbox,bool make_grp=true,bool deselect_all=false,
     std::enable_if_t<  std::is_same_v<typename std::iterator_traits<iterator>::value_type,Point_2> ||
                        std::is_same_v<typename std::iterator_traits<iterator>::value_type,Segment_2> ||
                        std::is_same_v<typename std::iterator_traits<iterator>::value_type,Circle_2> ||
                        std::is_same_v<typename std::iterator_traits<iterator>::value_type,Circular_arc_2> ||
                        std::is_same_v<typename std::iterator_traits<iterator>::value_type,Polygon_2>
                    >* = nullptr) const
    {
      for (iterator it=begin;it!=end;++it)
        draw_in_ipe(*it,bbox);
      if (make_grp and ++iterator(begin)!=end)
        _page->Group(_helper->CurrentLayer());
      if (deselect_all)
        _page->DeselectAll();
    }

    private:

    IpeVector
    get_paper_size() const {
      if (IPELIB_VERSION >=  60028)
        return get_IpeletHelper()->StyleSheet()->findLayout().iPaperSize;
      else
        return IpeVector(595,842);
    }

    struct Voronoi_from_tri{  //Class using stream to get the voronoi diagram
      std::list<Ray_2> ray_list;
      std::list<Line_2> line_list;
      std::list<Segment_2> seg_list;

      void operator<<(const Ray_2& p){ray_list.push_back(p);}
      void operator<<(const Line_2& p){line_list.push_back(p);}
      void operator<<(const Segment_2& p){seg_list.push_back(p);}

    };

    template <class T,class output_iterator>
    bool
    cast_into_seg(const T& obj,const Iso_rectangle_2& bbox,output_iterator out_it) const{
      CGAL::Object obj_cgal = CGAL::intersection(obj,bbox);
      Segment_2 s;
      bool ret=CGAL::assign(s, obj_cgal);
      if (ret) *out_it++=s;
      return ret;
    }

    //Convert infinite objects into drawable segments
    template<class iterator,class output_iterator>
    void
    cast_into_seg(const iterator first,const iterator end,
                  const Iso_rectangle_2& bbox, output_iterator out_it) const
    {
      for (iterator it=first;it!=end;++it)
        cast_into_seg(*it,bbox,out_it);
    }



    void
    draw_dual_(Voronoi_from_tri& v_recup,const Iso_rectangle_2& bbox,bool makegrp) const
    {
      std::vector<Segment_2> seg_cont;
      //filter degenerate segments
      for(typename std::list<Segment_2>::iterator iteS = v_recup.seg_list.begin();iteS!=v_recup.seg_list.end();){
        typename std::list<Segment_2>::iterator itc=iteS++;
        if (itc->is_degenerate())
          v_recup.seg_list.erase(itc);
      }

      cast_into_seg(v_recup.ray_list.begin(),v_recup.ray_list.end(),bbox,std::back_inserter(seg_cont));//cast rays into segments in bbox
      cast_into_seg(v_recup.line_list.begin(),v_recup.line_list.end(),bbox,std::back_inserter(seg_cont));//cast lines into segments in bbox
      cast_into_seg(v_recup.seg_list.begin(),v_recup.seg_list.end(),bbox,std::back_inserter(seg_cont));//cast lines into segments in bbox
      draw_in_ipe(seg_cont.begin(), seg_cont.end(),makegrp);
    }

    public:
    template<class Triangulation>
    void
    draw_dual_in_ipe(Triangulation& T,const Iso_rectangle_2& bbox,bool makegrp=true,bool deselect_all=false) const
    {
    //~ template<class GT,class TDS>
    //~ void draw_dual_in_ipe(const CGAL::Triangulation_2<GT,TDS>& T,const Iso_rectangle_2& bbox) const{
      Voronoi_from_tri v_recup;
      T.draw_dual(v_recup);
      draw_dual_(v_recup,bbox,makegrp);
      if (deselect_all) _page->DeselectAll();
    }

    template<class Triangulation>
    void
    draw_skeleton_in_ipe(Triangulation& T,const Iso_rectangle_2& bbox,
                         bool makegrp=true,bool deselect_all=false) const
    {
    //~ template<class GT,class TDS>
    //~ void draw_skeleton_in_ipe(const CGAL::Triangulation_2<GT,TDS>& T,const Iso_rectangle_2& bbox) const{
      Voronoi_from_tri v_recup;
      T.draw_skeleton(v_recup);
      draw_dual_(v_recup,bbox,makegrp);
      if (deselect_all) _page->DeselectAll();
    }

    //Circle restriction
  private:
    inline double get_theta(const Point_2& point, const Circle_2& circle) const {
      return atan2(CGAL::to_double(point.y()-circle.center().y()),CGAL::to_double(point.x()-circle.center().x()));
    }

    //SK objects
    typedef CGAL::Exact_circular_kernel_2 SK;
    typedef SK::Circle_2                  Exact_circle_2;
    typedef SK::Point_2                   Exact_point_2;
    typedef SK::Circular_arc_point_2      Circular_arc_point_2;



    //s and t are given such that if center of exact_circle is inside bbox then turn COUNTERCLOCKWISE
    Circular_arc_2
    build_arc(const Exact_circle_2& exact_circle,const SK::Circular_arc_point_2& s,
              const SK::Circular_arc_point_2& t,bool sign_known=false) const
    {
      Point_2 sd=Point_2(CGAL::to_double(s.x()),CGAL::to_double(s.y()));
      Point_2 td=Point_2(CGAL::to_double(t.x()),CGAL::to_double(t.y()));
      Point_2 center(CGAL::to_double(exact_circle.center().x()),CGAL::to_double(exact_circle.center().y()));
      CGAL::Cartesian_converter<SK,Kernel> conv;
      Circle_2 approx_circle=conv(exact_circle);
      if (!sign_known){
        CGAL::Sign sign = (CGAL::orientation(sd,td,center)==CGAL::LEFT_TURN)?CGAL::POSITIVE:CGAL::NEGATIVE;
        return std::make_tuple(approx_circle,sd,td,sign);
      }
      return std::make_tuple(approx_circle,sd,td,CGAL::POSITIVE);
    }

    void
    get_pair_indices(int* array,int* pair) const {
      for (int i=0;i<8;++i)
        if (array[i]!=-1) *pair++=i;
    }

    void
    set_done(int* array,int index) const {
      for (int i =0;i<8;++i)
        if (array[i]==index) array[i]=-1;
    }


    bool
    indices_are_on_opposite_side(int* array) const {
        if (array[0]!=-1 || array[5]!=-1)
          return array[2]!=-1 || array[7]!=-1;
        if (array[1]!=-1 || array[6]!=-1)
          return array[3]!=-1 || array[4]!=-1;
        return false;
    }

    int
    count_points(int* array) const {
      int ret=0;
      for (int i =0;i<8;++i)
        if (array[i]!=-1) ++ret;
      return ret;
    }

  public:
    //
    // .-----7---------2-------.
    // |                       |
    // 3                       6
    // |     indices           |
    // 4                       1
    // |                       |
    // .-----0---------5-------.
    template <class Output_iterator>
    void
    restrict_circle_to_bbox(const Circle_2& approx_circle,
                            const Iso_rectangle_2& bbox,Output_iterator out) const
    {
      CGAL::Cartesian_converter<Kernel,SK> conv;
      Exact_circle_2 exact_circle=conv(approx_circle);

      SK::Intersect_2 inter=SK().intersect_2_object();
      std::vector< std::pair<Circular_arc_point_2,unsigned> > points;
      points.reserve(8);

      std::vector<CGAL::Object> ints;
      ints.reserve(2);
      std::pair<Circular_arc_point_2,unsigned> tmp_pt;

      int indices[8]={-1,-1,-1,-1,-1,-1,-1,-1};

      for (unsigned i=0;i!=4;++i){
        ints.clear();
        SK::Segment_2 S(conv(bbox[i]),conv(bbox[(i+1)%4]));
        inter(exact_circle,SK::Line_arc_2(S),std::back_inserter(ints));
        unsigned index=0;
        bool ok=true;
        switch (ints.size()){
          case 1:
            ok=CGAL::assign(tmp_pt,ints[0]);
            CGAL_assertion(ok);
            points.push_back(tmp_pt);
            index=points.size()-1;
            indices[i]=index;
            indices[(i+1)%4+4]=index;
            break;
          case 2:
            int right_ind=i<2?0:1;
            ok=CGAL::assign(tmp_pt,ints[right_ind]);
            CGAL_assertion(ok);
            points.push_back(tmp_pt);
            index=points.size()-1;
            indices[i]=index;
            ok=CGAL::assign(tmp_pt,ints[(right_ind+1)%2]);
            CGAL_assertion(ok);
            points.push_back(tmp_pt);
            index=points.size()-1;
            indices[(i+1)%4+4]=index;
            break;
        }
      }

      //corner case
      for (unsigned i=0;i!=4;++i){
        if (indices[i]!=-1 && indices[i+4]!=-1){
          *out++=build_arc(exact_circle,points[ indices[i+4] ].first,points[ indices[i] ].first);
          if (points[ indices[i] ].second==1) set_done(indices,indices[i]);
          else indices[i]=-1;
          if (points[ indices[i+4] ].second==1) set_done(indices,indices[i+4]);
          else indices[i+4]=-1;
        }
      }
      int rem_pt=count_points(indices);
      if (rem_pt==4){
        //double opposite
        if (indices[0]!=-1){
          *out++=build_arc(exact_circle,points[ indices[7] ].first,points[ indices[0] ].first);
          if (indices[7]!=indices[2] && indices[0]!=indices[5])
            *out++=build_arc(exact_circle,points[ indices[5] ].first,points[ indices[2] ].first);
        }
        else{
          *out++=build_arc(exact_circle,points[ indices[6] ].first,points[ indices[3] ].first);
          if (indices[6]!=indices[1] && indices[3]!=indices[4])
            *out++=build_arc(exact_circle,points[ indices[4] ].first,points[ indices[1] ].first);
        }
        return;
      }

      if (rem_pt==2){
        int pair[2];
        get_pair_indices(indices,pair);
        if (!indices_are_on_opposite_side(indices))
          *out++=build_arc(exact_circle,points[ indices[pair[1]] ].first,points[ indices[pair[0]] ].first,true);
        else
          *out++=build_arc(exact_circle,points[ indices[pair[1]] ].first,points[ indices[pair[0]] ].first);
        return;
      }
      CGAL_assertion (rem_pt==0);
    }
  };


  //definition
  template <class Kernel,int nbf>
  template< class multi_output_iterator >
  bool
  Ipelet_base<Kernel,nbf>::read_one_active_object(IpeObject* object,
                                                  multi_output_iterator it_out) const
  {
    if (object->AsGroup()){
      bool deselect_grp=false;
      for (IpeGroup::const_iterator it=object->AsGroup()->begin();
                                    it!=object->AsGroup()->end();++it)
      {
        IpeObject *obj = (*it)->Clone();
        obj->SetMatrix(obj->Matrix()*object->Matrix());
        bool cur=read_one_active_object(obj,it_out);
        deselect_grp=deselect_grp || cur;
      }
      return deselect_grp;
    }

    //detect Points
    if( object->AsMark() ){
      if ( !CGAL::Is_in_tuple<Point_2,typename multi_output_iterator::Value_type_tuple>::value )
        return true;
      it_out++=to_point_2(object);
      return false;
    }
    bool to_deselect=true;
    //Test one function for segments, circles, circle arcs and polygons
    if (object->AsPath()){
      //iterate on each subpath
      to_deselect=false;
      for (int i=0;i<object->AsPath()->NumSubPaths();++i){
        if(object->AsPath()-> SubPath(i)->AsSegs()){
          std::list<Segment_2> seg_list;
          bool is_polygon=object-> AsPath() -> SubPath(i)->Closed();

          const IpeSegmentSubPath* SSP_ipe = object -> AsPath() -> SubPath(i) -> AsSegs();
          for(int j=0; j< SSP_ipe->NumSegments();++j){
            if (SSP_ipe -> Segment(j).Type()==IpePathSegment::ESegment){
              //TODO depending on if current_it  is  a polygon or not do not do the same thing
              seg_list.push_back(Segment_2(segment_endpoint(SSP_ipe -> Segment(j),object -> AsPath(),0),
                                  segment_endpoint(SSP_ipe -> Segment(j),object -> AsPath(),1)));
            }
            else{
              //retrieve circle arcs
              if(SSP_ipe -> Segment(j).Type()==IpePathSegment::EArc &&
                 is_only_rotated_or_scaled(object->AsPath()->Matrix()))
              {//retrieve circle arcs
                if ( !CGAL::Is_in_tuple<Circular_arc_2,typename multi_output_iterator::Value_type_tuple>::value ){
                  to_deselect=true;
                  continue;
                }
                is_polygon=false;
                IpePath* obj_ipe=object->AsPath();
                IpeMatrix mat=obj_ipe->Matrix() * SSP_ipe->Segment(j).Matrix();
                IpeVector ipe_center=IpeVector(mat.iA[4],mat.iA[5]);
                IpeVector ipe_first=obj_ipe->Matrix() * SSP_ipe->Segment(j).CP(0);
                IpeVector ipe_last=obj_ipe->Matrix() * SSP_ipe->Segment(j).Last();
                //TODO Check how object is defined
                Circular_arc_2 arc(
                            Circle_2(Point_2(ipe_center.iX,ipe_center.iY),(ipe_first-ipe_center).Len()*(ipe_first-ipe_center).Len()),
                            Point_2(ipe_first.iX,ipe_first.iY),
                            Point_2(ipe_last.iX,ipe_last.iY),
                            mat.iA[0]*mat.iA[3]-mat.iA[1]*mat.iA[2]<0?CGAL::CLOCKWISE:CGAL::COUNTERCLOCKWISE
                          );
                it_out++=arc;
              }
              else
                to_deselect=true;
            }
          }
          if (object->AsPath() -> SubPath(i)->Closed() &&
              (SSP_ipe -> Segment(0).CP(0)-SSP_ipe -> Segment(SSP_ipe->NumSegments()-1).CP(1)).Len()!=0 ){ //for close polygon, seg
            seg_list.push_back( Segment_2(
                              segment_endpoint(SSP_ipe -> Segment(SSP_ipe->NumSegments()-1),object-> AsPath(),1),
                              segment_endpoint(SSP_ipe -> Segment(0),object-> AsPath(),0)
                              ));
          }
          //~ if (seg_list.empty())
            //~ to_deselect=true;

          if (is_polygon){
            if (  !CGAL::Is_in_tuple<Polygon_2,typename multi_output_iterator::Value_type_tuple>::value  )
              to_deselect=true;
            else{
              Polygon_2 polygon;
              typename std::list<Segment_2>::iterator its=seg_list.begin();
              for (;its!=seg_list.end();++its)
                polygon.push_back(its->source());
              it_out++=polygon;
            }
          }
          else{
            if (  !CGAL::Is_in_tuple<Segment_2,typename multi_output_iterator::Value_type_tuple>::value  )
              to_deselect=true;
            else{
              for (typename std::list<Segment_2>::iterator its=seg_list.begin();its!=seg_list.end();++its)
                it_out++=*its;
            }
          }
        }
        else
          if (is_IPE_circle(object,i)){
            if (  !CGAL::Is_in_tuple<Circle_2,typename multi_output_iterator::Value_type_tuple>::value )
              to_deselect=true;
            else{
              Circle_2 C=to_circle_2(object -> AsPath(),i);
              it_out++=C;
            }
          }
          else
            to_deselect=true; // avoid deleting no handled objects
      }
    }
    return to_deselect;
  }

} //CGAL

#define CGAL_IPELET(T) IPELET_DECLARE Ipelet *NewIpelet(){ return new T; }

#endif //CGAL_IPELET_BASE_H

