// Copyright (c) 2013,2014,2015 GeometryFactory (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.1/Polygon_mesh_processing/include/CGAL/Polygon_mesh_slicer.h $
// $Id: include/CGAL/Polygon_mesh_slicer.h 08b27d3db14 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
//
// Author(s)     : Ilker O. Yaz and Sebastien Loriot

#ifndef CGAL_POLYGON_MESH_SLICER_H
#define CGAL_POLYGON_MESH_SLICER_H

#include <CGAL/license/Polygon_mesh_processing/miscellaneous.h>


#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits_3.h>
#include <CGAL/AABB_halfedge_graph_segment_primitive.h>
#include <CGAL/tuple.h>

#include <vector>
#include <set>
#include <type_traits>

#include <boost/graph/adjacency_list.hpp>
#include <CGAL/Polygon_mesh_processing/internal/Polygon_mesh_slicer/Traversal_traits.h>
#include <CGAL/Polygon_mesh_processing/internal/Polygon_mesh_slicer/Axis_parallel_plane_traits.h>

#include <variant>

#include <CGAL/boost/graph/split_graph_into_polylines.h>
#include <CGAL/boost/graph/helpers.h>

namespace CGAL {

/// \ingroup PkgPolygonMeshProcessingRef
/// Function object that computes the intersection of a plane with
/// a triangulated surface mesh.
///
/// \tparam TriangleMesh a triangulated surface mesh, model of `FaceGraph` and `HalfedgeListGraph`
/// \tparam Traits a model of `AABBGeomTraits_3`
/// \tparam VertexPointMap a model of `ReadablePropertyMap` with
///         `boost::graph_traits<TriangleMesh>::%vertex_descriptor` as key and
///         `Traits::Point_3` as value type.
///         The default is `typename boost::property_map< TriangleMesh, vertex_point_t>::%type`.
/// \tparam AABBTree must be an instantiation of `CGAL::AABB_tree` able to handle
///         the edges of `TriangleMesh`, having its `edge_descriptor` as primitive id.
///         The default is `CGAL::AABB_tree<CGAL::AABB_traits_3<
///                  Traits, CGAL::AABB_halfedge_graph_segment_primitive<TriangleMesh> > >`
/// \tparam UseParallelPlaneOptimization if `true`, the code will use specific
///         predicates and constructions in case the functor is called with a plane
///         orthogonal to a frame axis, the non-null coefficient being 1 or -1.
///         The default is `true`.
///
/// The implementation of this class depends on the package \ref PkgAABBTree.
/// \todo Shall we document more in details what is required?
///       `Traits` must provide:
///        - `Plane_3`
///        - `Point_3`
///        - `Segment_3`
///        - `Oriented_side_3` with `Oriented_side operator()(Plane_3, Point_3)`
///        - `Do_intersect_3` with `std::optional<variant<Point_3,Segment_3> operator()(Plane_3,Segment_3)`
///        - `Do_intersect_3` with `bool operator()(Plane_3, Bbox_3)`
///
/// \todo If we keep the traits for plane orthogonal to a frame axis, `Traits` must also provide:
///       - `FT`
///       - `Construct_cartesian_const_iterator_3` with `Iterator operator()(Point_3)` `Iterator` being a random access iterator with `FT` as value type
///       - `Construct_point_3` with `Point_3 operator()(FT,FT,FT)`; `Construct_source_3` with  `const Point_3& operator()(Segment_3)`
///       - `Construct_target_3` with `const Point_3& operator()(Segment_3)`
///
/// \todo `_object()` functions must also be provided
template<class TriangleMesh,
  class Traits,
  class VertexPointMap = typename boost::property_map< TriangleMesh, vertex_point_t>::type,
  class AABBTree = AABB_tree<
                       AABB_traits_3<Traits,
                         AABB_halfedge_graph_segment_primitive<TriangleMesh,
                                                                std::conditional_t<
                                                                  std::is_same_v<
                                                                    VertexPointMap,
                                                                    typename boost::property_map< TriangleMesh, vertex_point_t>::type >,
                                                                  Default,
                                                                  VertexPointMap>>>>,
  bool UseParallelPlaneOptimization=true>
class Polygon_mesh_slicer
{
/// Polygon_mesh typedefs
  typedef typename boost::graph_traits<TriangleMesh>               graph_traits;
  typedef typename graph_traits::vertex_descriptor            vertex_descriptor;
  typedef typename graph_traits::edge_descriptor                edge_descriptor;
  typedef typename graph_traits::halfedge_descriptor        halfedge_descriptor;
  typedef typename graph_traits::face_descriptor                face_descriptor;

/// Geometric typedefs
  typedef typename Traits::Plane_3                                      Plane_3;
  typedef typename Traits::Segment_3                                  Segment_3;
  typedef typename Traits::Point_3                                      Point_3;
  typedef typename Traits::FT                                                FT;

/// typedefs for internal graph to get connectivity of the polylines
  typedef std::variant<vertex_descriptor, edge_descriptor>     AL_vertex_info;
  typedef boost::adjacency_list <
                              boost::vecS,
                              boost::vecS,
                              boost::undirectedS,
                              AL_vertex_info >                         AL_graph;
  typedef typename AL_graph::vertex_descriptor             AL_vertex_descriptor;
  typedef std::pair<AL_vertex_descriptor, AL_vertex_descriptor>  AL_vertex_pair;
  typedef std::map<vertex_descriptor, AL_vertex_descriptor>        Vertices_map;
  typedef std::pair<const vertex_descriptor,AL_vertex_descriptor>   Vertex_pair;
/// Traversal traits
  typedef Polygon_mesh_slicer_::Traversal_traits<
    AL_graph,
    TriangleMesh,
    VertexPointMap,
    typename AABBTree::AABB_traits,
    Traits >                                           General_traversal_traits;

  typedef Polygon_mesh_slicer_::Traversal_traits<
    AL_graph,
    TriangleMesh,
    VertexPointMap,
    typename AABBTree::AABB_traits,
    Polygon_mesh_slicer_::Axis_parallel_plane_traits<Traits>
  >                                              Axis_parallel_traversal_traits;
/// Auxiliary classes
  // compare the faces using the halfedge descriptors
  struct Compare_face{
    TriangleMesh& m_tmesh;
    Compare_face(TriangleMesh& tmesh)
      :m_tmesh(tmesh)
    {}

    bool operator()(halfedge_descriptor hd1, halfedge_descriptor hd2) const
    {
      return face(hd1,m_tmesh) < face(hd2,m_tmesh);
    }
  };

  typedef std::map< halfedge_descriptor, AL_vertex_pair, Compare_face > AL_edge_map;

  template <class OutputIterator, class Traits_>
  struct Polyline_visitor{
    AL_graph& al_graph;
    TriangleMesh& m_tmesh;
    const Plane_3& m_plane;
    VertexPointMap m_vpmap;
    typename Traits_::Intersect_3 intersect_3;
    OutputIterator out;
    std::pair<AL_vertex_descriptor, AL_vertex_descriptor> nodes_for_orient;

    Polyline_visitor( TriangleMesh& tmesh,
                      AL_graph& al_graph,
                      const Plane_3& plane,
                      VertexPointMap vpmap,
                      const Traits_& traits,
                      OutputIterator out)
      : al_graph(al_graph)
      , m_tmesh(tmesh)
      , m_plane(plane)
      , m_vpmap(vpmap)
      , intersect_3( traits.intersect_3_object() )
      , out(out)
    {}

    std::vector< Point_3 > current_poly;

    // returns true iff the polyline is not correctly oriented
    // Using the first edge is oriented such that the normal induced by the face
    // containing the edge, the oriented edge and the plane orthogonal vector defines
    // a direct orthogonal basis
    bool do_reverse_polyline()
    {
      if (current_poly.size() < 2)
        return false;

      AL_vertex_info v1 = al_graph[nodes_for_orient.first];
      AL_vertex_info v2 = al_graph[nodes_for_orient.second];

      if (const vertex_descriptor* vd1_ptr = std::get_if<vertex_descriptor>(&v1) )
      {
        if (const vertex_descriptor* vd2_ptr = std::get_if<vertex_descriptor>(&v2) )
        {
          CGAL_assertion( halfedge(*vd1_ptr, *vd2_ptr, m_tmesh).second );
          halfedge_descriptor h_opp = halfedge(*vd1_ptr, *vd2_ptr, m_tmesh).first;
          if ( !is_border(h_opp, m_tmesh) )
          {
            CGAL_assertion(source(h_opp, m_tmesh) == *vd1_ptr);
            CGAL::Oriented_side os = m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh) ) );
            if (os != CGAL::ON_ORIENTED_BOUNDARY)
              return m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh)) ) == CGAL::ON_NEGATIVE_SIDE;
          }
          h_opp = opposite(h_opp, m_tmesh);
          if ( !is_border(h_opp, m_tmesh) )
          {
            CGAL_assertion(source(h_opp, m_tmesh) == *vd2_ptr);
            CGAL::Oriented_side os = m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh) ) );
            if (os != CGAL::ON_ORIENTED_BOUNDARY)
              return m_plane.oriented_side( get(m_vpmap, target(next(h_opp, m_tmesh), m_tmesh)) ) == CGAL::ON_POSITIVE_SIDE;
          }
          return false; // since coplanar edges are filtered out, we should never end up here.
        }
        else
        {
          // e2 is intersected in its interior
          edge_descriptor e2 = std::get<edge_descriptor>(v2);
          halfedge_descriptor h2 = halfedge(e2, m_tmesh);
          if ( target(next(h2, m_tmesh), m_tmesh) != *vd1_ptr )
            h2=opposite(h2, m_tmesh);
          return m_plane.oriented_side( get(m_vpmap, source(h2, m_tmesh)) ) == CGAL::ON_POSITIVE_SIDE;
        }
      }
      else
      {
        edge_descriptor e1 = std::get<edge_descriptor>(v1);
        halfedge_descriptor h1 = halfedge(e1, m_tmesh);
        if (const vertex_descriptor* vd2_ptr = std::get_if<vertex_descriptor>(&v2) )
        {
          // e1 is intersected in its interior
          if ( target(next(h1, m_tmesh), m_tmesh) != *vd2_ptr )
            h1=opposite(h1, m_tmesh);
          CGAL_assertion( target(next(h1, m_tmesh), m_tmesh) == *vd2_ptr );
        }
        else
        {
          // intersection in the interior of both edges
          edge_descriptor e2 = std::get<edge_descriptor>(v2);
          halfedge_descriptor h2 = halfedge(e2, m_tmesh);
          if ( face(h1, m_tmesh) != face(h2,m_tmesh) )
          {
            halfedge_descriptor opp_h1 = opposite(h1, m_tmesh),
                                opp_h2 = opposite(h2, m_tmesh);
            if ( face(opp_h1, m_tmesh) == face(opp_h2, m_tmesh) )
            {
              h1=opp_h1;
              h2=opp_h2;
            }
            else
              if ( face(opp_h1, m_tmesh)==face(h2,m_tmesh) )
                h1=opp_h1;
              else
                h2=opp_h2;
          }
          CGAL_assertion( face(h1, m_tmesh) == face(h2,m_tmesh) );
        }
        return m_plane.oriented_side( get(m_vpmap, source(h1, m_tmesh)) ) == CGAL::ON_NEGATIVE_SIDE;
      }
    }

    void start_new_polyline()
    {
      current_poly.clear();
    }

    void add_node(AL_vertex_descriptor node_id)
    {
      if (current_poly.empty())
        nodes_for_orient.first=node_id;
      else
        if (current_poly.size()==1)
          nodes_for_orient.second=node_id;

      AL_vertex_info v = al_graph[node_id];
      if (const vertex_descriptor* vd_ptr = std::get_if<vertex_descriptor>(&v) )
      {
        current_poly.push_back( get(m_vpmap, *vd_ptr) );
      }
      else
      {
        edge_descriptor ed = std::get<edge_descriptor>(v);
        Segment_3 s(
          get(m_vpmap, source(ed, m_tmesh)),
          get(m_vpmap,target(ed, m_tmesh))
        );
        const auto inter = intersect_3(m_plane, s);
        CGAL_assertion(inter != std::nullopt);
        const Point_3* pt_ptr = std::get_if<Point_3>(&(*inter));
        current_poly.push_back( *pt_ptr );
      }
    }

    void end_polyline()
    {
      CGAL_assertion(!current_poly.empty());
      if(do_reverse_polyline())
        std::reverse(current_poly.begin(), current_poly.end());
      *out++=current_poly;
    }
  };

/// member variables
  const AABBTree* m_tree_ptr;
  TriangleMesh& m_tmesh;
  VertexPointMap m_vpmap;
  Traits m_traits;
  bool m_own_tree;

/// Convenience graph functions
  edge_descriptor next_edge(edge_descriptor ed) const
  {
    return edge( next( halfedge(ed, m_tmesh), m_tmesh), m_tmesh );
  }

  edge_descriptor next_of_opposite_edge(edge_descriptor ed) const
  {
    return edge( next( opposite( halfedge(ed, m_tmesh), m_tmesh), m_tmesh), m_tmesh );
  }

  face_descriptor opposite_face(edge_descriptor ed) const
  {
    return face( opposite( halfedge(ed, m_tmesh), m_tmesh), m_tmesh);
  }
/// Other private functions
  /// handle edge insertion in the adjacency_list graph
  /// we add an edge between two edge_descriptor if they
  /// share a common facet
  void update_al_graph_connectivity(
    edge_descriptor ed,
    AL_vertex_descriptor vd,
    AL_edge_map& al_edge_map,
    AL_graph& al_graph) const
  {
      typename AL_edge_map::iterator itm;
      bool new_insertion;

      halfedge_descriptor hd=halfedge(ed, m_tmesh);

      if (face(hd, m_tmesh)!=graph_traits::null_face())
      {
        std::tie(itm, new_insertion) =
          al_edge_map.insert( std::pair< halfedge_descriptor, AL_vertex_pair >
            (hd, AL_vertex_pair(vd, AL_graph::null_vertex())) );
        if (!new_insertion)
        {
          CGAL_assertion(itm->second.second==AL_graph::null_vertex());
          itm->second.second=vd;
          add_edge( itm->second.first,
                    itm->second.second,
                    al_graph);
        }
      }

      hd=opposite(hd, m_tmesh);
      if (face(hd, m_tmesh)!=graph_traits::null_face())
      {
        std::tie(itm, new_insertion) =
          al_edge_map.insert( std::pair< halfedge_descriptor, AL_vertex_pair >
            (hd, AL_vertex_pair(vd, AL_graph::null_vertex())) );
        if (!new_insertion)
        {
          CGAL_assertion(itm->second.second==AL_graph::null_vertex());
          itm->second.second=vd;
          add_edge( itm->second.first,
                    itm->second.second,
                    al_graph);
        }
      }
  }

  std::pair<int, FT>
  axis_parallel_plane_info(const Plane_3& plane) const
  {
    FT a = m_traits.compute_a_3_object()(plane);
    FT b = m_traits.compute_b_3_object()(plane);
    FT c = m_traits.compute_c_3_object()(plane);
    FT d = m_traits.compute_d_3_object()(plane);

    if (a==0)
    {
      if (b==0)
      {
        if (c==1 || c==-1)
          return std::pair<int,FT>(2, -d*c); /// z=-d
      }
      else
      {
        if (c==0 && (b==1 || b==-1))
          return std::pair<int,FT>(1, -d*b); /// y=-d
      }
    }
    else
      if (b==0 && c==0 && ( a==1 || a==-1))
        return std::pair<int,FT>(0, -d*a); /// x=-d
    return std::pair<int,FT>(-1, 0);
  }

public:

  /// the AABB-tree type used internally
  typedef AABBTree AABB_tree;

  /**
  * Constructor using `edges(tmesh)` to initialize the
  * internal `AABB_tree`.
  * @param tmesh the triangulated surface mesh to be sliced.
  *              It must be valid and non modified as long
  *              as the functor is used.
  * @param vpmap an instance of the vertex point property map associated to `tmesh`
  * @param traits a traits class instance, can be omitted
  */
  Polygon_mesh_slicer(const TriangleMesh& tmesh,
                      VertexPointMap vpmap,
                      const Traits& traits = Traits())
  : m_tmesh(const_cast<TriangleMesh&>(tmesh))
  , m_vpmap(vpmap)
  , m_traits(traits)
  , m_own_tree(true)
  {
    m_tree_ptr = new AABBTree(edges(m_tmesh).first,
                              edges(m_tmesh).second,
                              m_tmesh,
                              m_vpmap);
  }

  /**
  * Constructor using a pre-built `AABB_tree` of edges provided by the user.
  * @param tmesh the triangulated surface mesh to be sliced.
  *              It must be valid and non modified as long
  *              as the functor is used.
  * @param tree must be initialized with all the edges of `tmesh`
  * @param vpmap an instance of the vertex point property map associated to `tmesh`
  * @param traits a traits class instance, can be omitted
  */
  Polygon_mesh_slicer(const TriangleMesh& tmesh,
                      const AABBTree& tree,
                      VertexPointMap vpmap,
                      const Traits& traits = Traits())
    : m_tree_ptr(&tree)
    , m_tmesh(const_cast<TriangleMesh&>(tmesh))
    , m_vpmap(vpmap)
    , m_traits(traits)
    , m_own_tree(false)
  { }

  /**
  * Constructor using `edges(tmesh)` to initialize the
  * internal `AABB_tree`. The vertex point property map used
  * is `get(CGAL::vertex_point, tmesh)`
  * @param tmesh the triangulated surface mesh to be sliced.
  *              It must be valid and non modified as long
  *              as the functor is used.
  * @param traits a traits class instance, can be omitted
  */
  Polygon_mesh_slicer(const TriangleMesh& tmesh,
                      const Traits& traits = Traits())
  : m_tmesh(const_cast<TriangleMesh&>(tmesh))
  , m_vpmap(get(boost::vertex_point, m_tmesh))
  , m_traits(traits)
  , m_own_tree(true)
  {
    m_tree_ptr = new AABBTree(edges(m_tmesh).first,
                              edges(m_tmesh).second,
                              m_tmesh,
                              m_vpmap);
  }

  /**
  * Constructor using a `AABB_tree` provided by the user.
  * The vertex point property map used is `get(CGAL::vertex_point, tmesh)`
  * @param tmesh the triangulated surface mesh to be sliced.
  *              It must be valid and non modified as long
  *              as the functor is used.
  * @param tree must be initialized with all the edges of `tmesh`
  * @param traits a traits class instance, can be omitted
  */
  Polygon_mesh_slicer(const TriangleMesh& tmesh,
                      const AABBTree& tree,
                      const Traits& traits = Traits())
    : m_tree_ptr(&tree)
    , m_tmesh(const_cast<TriangleMesh&>(tmesh))
    , m_vpmap(get(boost::vertex_point, m_tmesh))
    , m_traits(traits)
    , m_own_tree(false)
  { }

  /**
   * Constructs the intersecting polylines of `plane` with the input triangulated surface mesh.
   *
   * If a polyline is closed, the first and last point of that polyline will be identical.
   *
   * Each resulting polyline `P` is oriented such that for two consecutive points `p` and `q` in `P`,
   * the normal vector of the face(s) containing the segment `pq`, the vector `pq`, and the orthogonal vertor of `plane`
   * is a direct orthogonal basis.
   * The normal vector of each face is chosen  to point on the side of the face
   * where its sequence of vertices is seen counterclockwise.
   *
   * Note that an edge shared by two faces included in `plane` will not be reported. For example,
   * if `plane` passes though one face of a cube, only one closed polyline will be reported (the boundary of the face)
   *
   * @tparam OutputIterator an output iterator accepting polylines.
   *              A polyline is provided as `std::vector<Traits::Point_3>`.
   *              A polyline is closed if its first and last point are identical.
   * @param plane the plane to intersect the triangulated surface mesh with
   * @param out output iterator of polylines
   */
  template <class OutputIterator>
  OutputIterator operator() (const Plane_3& plane,
                             OutputIterator out) const
  {
    CGAL_precondition(!plane.is_degenerate());

    // containers for storing edges wrt their position with the plane
    std::set<edge_descriptor> all_coplanar_edges;
    std::vector<edge_descriptor> iedges;
    Vertices_map vertices;

    // get all edges intersected by the plane and classify them

    std::pair<int, FT> app_info = axis_parallel_plane_info(plane);
    if (!UseParallelPlaneOptimization || app_info.first==-1)
    {
      General_traversal_traits ttraits(
        all_coplanar_edges,
        iedges,
        vertices,
        m_tmesh,
        m_vpmap,
        m_tree_ptr->traits(),
        m_traits);
      m_tree_ptr->traversal(plane, ttraits);
    }
    else
    {
      Polygon_mesh_slicer_::Axis_parallel_plane_traits<Traits>
        traits(app_info.first, app_info.second, m_traits);

      Axis_parallel_traversal_traits ttraits(
        all_coplanar_edges,
        iedges,
        vertices,
        m_tmesh,
        m_vpmap,
        m_tree_ptr->traits(),
        traits);
      m_tree_ptr->traversal(plane, ttraits);
    }

    // init output graph
    AL_graph al_graph;

    // add nodes for each vertex in the plane
    for(Vertex_pair& vdp : vertices)
    {
      vdp.second=add_vertex(al_graph);
      al_graph[vdp.second]=vdp.first;
    }

    Compare_face less_face(m_tmesh);
    AL_edge_map al_edge_map( less_face );

    // Filter coplanar edges: we consider only coplanar edges incident to one non-coplanar facet
    //   for each such edge, add the corresponding nodes in the adjacency-list graph as well as
    //   the edge
    for(const edge_descriptor ed : all_coplanar_edges)
    {
      if (  face(halfedge(ed, m_tmesh), m_tmesh)==graph_traits::null_face() ||
            opposite_face(ed)==graph_traits::null_face()  ||
            !all_coplanar_edges.count( next_edge(ed) ) ||
            !all_coplanar_edges.count( next_of_opposite_edge(ed) ) )
      {
        typename Vertices_map::iterator it_insert1, it_insert2;
        bool is_new;

        // Each coplanar edge is connecting two nodes
        //  handle source
        std::tie(it_insert1, is_new) =
          vertices.insert(
              Vertex_pair(
                source(ed,m_tmesh), AL_graph::null_vertex()
              )
          );
        if (is_new)
        {
          it_insert1->second=add_vertex(al_graph);
          al_graph[it_insert1->second]=it_insert1->first;
        }
        //  handle target
        std::tie(it_insert2, is_new) =
          vertices.insert(
              Vertex_pair(
                target(ed,m_tmesh), AL_graph::null_vertex()
              )
          );
        if (is_new)
        {
          it_insert2->second=add_vertex(al_graph);
          al_graph[it_insert2->second]=it_insert2->first;
        }
        // add the edge into the adjacency-list graph
        CGAL_assertion( it_insert1->second!=AL_graph::null_vertex() );
        CGAL_assertion( it_insert2->second!=AL_graph::null_vertex() );
        add_edge(it_insert1->second, it_insert2->second, al_graph);
      }
    }

    // for each edge intersected in its interior, creates a node in
    // an adjacency-list graph and put an edge between two such nodes
    // when the corresponding edges shares a common face
    for(edge_descriptor ed : iedges)
    {
      AL_vertex_descriptor vd=add_vertex(al_graph);
      al_graph[vd]=ed;
      update_al_graph_connectivity(ed, vd, al_edge_map, al_graph);
    }

    // If one of the node above is not connected in its two incident faces
    // then it must be connected to a vertex (including those in the set
    // of coplanar edges)
    typedef std::pair<halfedge_descriptor, AL_vertex_pair> Halfedge_and_vertices;
    for(Halfedge_and_vertices hnv :al_edge_map)
    {
      if (hnv.second.second==AL_graph::null_vertex())
      {
        //get the edge and test opposite vertices (if the edge is not on the boundary)
        vertex_descriptor vd = target( next(hnv.first, m_tmesh), m_tmesh);
        typename Vertices_map::iterator itv=vertices.find(vd);
        CGAL_assertion( itv!=vertices.end() );
        add_edge(itv->second, hnv.second.first, al_graph);
      }
    }

    CGAL_assertion(num_vertices(al_graph)==iedges.size()+vertices.size());

    // now assemble the edges of al_graph to define polylines,
    // putting them in the output iterator
    if (!UseParallelPlaneOptimization || app_info.first==-1)
    {
      Polyline_visitor<OutputIterator, Traits> visitor(m_tmesh, al_graph, plane, m_vpmap, m_traits, out);
      split_graph_into_polylines(al_graph, visitor);
      return visitor.out;
    }
    else
    {
      typedef Polygon_mesh_slicer_::Axis_parallel_plane_traits<Traits> App_traits;
      App_traits app_traits(app_info.first, app_info.second, m_traits);

      Polyline_visitor<OutputIterator, App_traits> visitor
        (m_tmesh, al_graph, plane, m_vpmap, app_traits, out);
      split_graph_into_polylines(al_graph, visitor);
      return visitor.out;
    }
  }

  ~Polygon_mesh_slicer()
  {
    if (m_own_tree) delete m_tree_ptr;
  }
};

}// end of namespace CGAL
#endif //CGAL_POLYGON_MESH_SLICER_H
