// Copyright (c) 2018  Liangliang Nan. All rights reserved.
//
// This file is part of CGAL (www.cgal.org)
//
// $URL: https://github.com/CGAL/cgal/blob/v6.1.1/Polygonal_surface_reconstruction/include/CGAL/Polygonal_surface_reconstruction/internal/alpha_shape_mesh.h $
// $Id: include/CGAL/Polygonal_surface_reconstruction/internal/alpha_shape_mesh.h 08b27d3db14 $
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s) : Liangliang Nan

#ifndef CGAL_POLYGONAL_SURFACE_RECONSTRUCTION_ALPHA_SHAPE_MESH_H
#define CGAL_POLYGONAL_SURFACE_RECONSTRUCTION_ALPHA_SHAPE_MESH_H

#include <CGAL/license/Polygonal_surface_reconstruction.h>

#include <CGAL/Alpha_shape_2.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_hierarchy_2.h>
#include <CGAL/Surface_mesh.h>


// warn the user if he/she is using an unsupported version of CGAL
#if CGAL_VERSION_NR < 1041100000
#error CGAL 4.11 or above is required (due to the breaking change in CGAL 4.11). Please update your code.
#endif

/*!
\file alpha_shape_mesh.h
*/

namespace CGAL {

        namespace internal {

                /// \cond SKIP_IN_MANUA

                template <typename Ht>
                class Alpha_shape;

                /// \endcond

                /// \cond SKIP_IN_MANUA

                /* A vertex class with an additional member representing its index */
                template < class Gt, class VB = CGAL::Triangulation_hierarchy_vertex_base_2<Gt> >
                class AS_vertex_base : public VB
                {
                public:
                        typedef VB                                                                Base;
                        typedef typename VB::Vertex_handle      Vertex_handle;
                        typedef typename VB::Face_handle        Face_handle;
                        typedef typename VB::Point              Point;

                        template < typename TDS2 >
                        struct Rebind_TDS {
                                typedef typename VB::template Rebind_TDS<TDS2>::Other        VB2;
                                typedef AS_vertex_base<Gt, VB2>                         Other;
                        };

                public:
                        AS_vertex_base() : Base(), index_(-1) {}
                        AS_vertex_base(const Point & p) : Base(p), index_(-1) {}
                        AS_vertex_base(const Point & p, Face_handle f) : Base(f, p), index_(-1) {}
                        AS_vertex_base(Face_handle f) : Base(f), index_(-1) {}

                        void set_index(int idx) { index_ = idx; }
                        int  index() const { return index_; }

                private:
                        int index_;
                };


                template <typename Ht>
                class Alpha_shape : public Alpha_shape_2<Ht>
                {
                public:
                        typedef Alpha_shape_2<Ht>                                                Parent_class;
                        typedef typename Ht::Point_2                                        Point2;
                        typedef typename Parent_class::Vertex_handle        Vertex_handle;

                public:
                        // constructs alpha shapes from the input points
                        template <typename InputIterator>
                        Alpha_shape(InputIterator first, InputIterator beyond);
                };

                /// \endcond


                /**
                *        An Alpha Shape Mesh approximates the point covered region by a mesh representation.
                */

                template <typename Kernel>
                class Alpha_shape_mesh
                {
                        typedef typename Kernel::FT                                                FT;
                        typedef typename Kernel::Point_2                                Point2;
                        typedef typename Kernel::Point_3                                Point3;
                        typedef typename Kernel::Plane_3                                Plane3;
                        typedef CGAL::Surface_mesh<Point3>                                Mesh3;
                        typedef typename Mesh3::Vertex_index                        Vertex_descriptor;

                        typedef CGAL::Alpha_shape_vertex_base_2<Kernel>                        Avb;
                        typedef AS_vertex_base<Avb>                                                                Av;
                        typedef CGAL::Triangulation_face_base_2<Kernel>                        Tf;
                        typedef CGAL::Alpha_shape_face_base_2<Kernel, Tf>                Af;
                        typedef CGAL::Triangulation_default_data_structure_2<Kernel, Av, Af> Tds;
                        typedef CGAL::Delaunay_triangulation_2<Kernel, Tds>                Dt;
                        typedef CGAL::Triangulation_hierarchy_2<Dt>                                Ht;

                public:
                        /// Given a set of 3D points lying on 'plane', constructs alpha shapes from the
                        /// the projection of the points onto 'plane'
                        template <typename InputIterator>
                        Alpha_shape_mesh(InputIterator first, InputIterator beyond, const Plane3& plane);

                        ~Alpha_shape_mesh() { delete alpha_shape_; }

                        /// Extracts the 3D mesh representation of the alpha shapes
                        bool extract_mesh(FT alpha_value, Mesh3& mesh);

                private:
                        Alpha_shape<Ht>*                        alpha_shape_;
                        std::vector<const Point3*>  original_points_;
                };


                //////////////////////////////////////////////////////////////////////////

                // implementation


                template <typename Traits>
                template <typename InputIterator>
                Alpha_shape<Traits>::Alpha_shape(InputIterator first, InputIterator beyond) {
                        InputIterator it = first;
                        for (int id = 0; it != beyond; ++it, ++id) {
                                const Point2& p = *it;
                                Vertex_handle vh = Traits::insert(p);
                                if (vh->index() == -1)
                                        vh->set_index(id);
                                else {
                                        // p was not inserted (there might be a duplicated point)
                                }
                        }

                        if (Parent_class::dimension() == 2) {
                                // Computes the associated _interval_face_map
                                Parent_class::initialize_interval_face_map();

                                // Computes the associated _interval_edge_map
                                Parent_class::initialize_interval_edge_map();

                                // Computes the associated _interval_vertex_map
                                Parent_class::initialize_interval_vertex_map();

                                // Merges the two maps
                                Parent_class::initialize_alpha_spectrum();
                        }
                }


                template <typename Kernel>
                template <typename InputIterator>
                Alpha_shape_mesh<Kernel>::Alpha_shape_mesh(InputIterator first, InputIterator beyond, const Plane3& plane) {
                        original_points_.clear();

                        std::vector<Point2> pts;
                        for (InputIterator it = first; it != beyond; ++it) {
                                const Point3& p = *it;
                                const Point2& q = plane.to_2d(p);
                                pts.push_back(q);
                                original_points_.push_back(&p);
                        }
                        alpha_shape_ = new Alpha_shape<Ht>(pts.begin(), pts.end());
                }


                template <typename Kernel>
                bool Alpha_shape_mesh<Kernel>::extract_mesh(FT alpha_value, Mesh3& mesh) {
                        alpha_shape_->set_alpha(alpha_value);

                        typedef std::vector<std::size_t> Triangle;
                        std::vector<Triangle>        faces;

                        typedef Alpha_shape<Ht>        Alpha_shape;

                        typename Alpha_shape::Finite_faces_iterator fit = alpha_shape_->finite_faces_begin();
                        for (; fit != alpha_shape_->finite_faces_end(); ++fit) {
                                if (alpha_shape_->classify(fit) == Alpha_shape::INTERIOR) {
                                        Triangle tri;
                                        for (int i = 0; i < 3; ++i) {
                                                typename Alpha_shape::Vertex_handle vh = fit->vertex(i);
                                                int idx = vh->index();
                                                tri.push_back(idx);
                                        }
                                        faces.push_back(tri);
                                }
                        }

                        if (faces.empty())
                                return false;

                        mesh.clear();

                        std::vector<Vertex_descriptor> descriptors(original_points_.size());
                        for (std::size_t i = 0; i < original_points_.size(); ++i) {
                                const Point3* p = original_points_[i];
                                descriptors[i] = mesh.add_vertex(*p);
                        }

                        for (std::size_t i = 0; i < faces.size(); ++i) {
                                std::vector<Vertex_descriptor> face;
                                const Triangle& tri = faces[i];
                                for (std::size_t j = 0; j < tri.size(); ++j) {
                                        std::size_t idx = tri[j];
                                        face.push_back(descriptors[idx]);
                                }
                                mesh.add_face(face);;
                        }

                        return true;
                }

        } //namespace internal

} //namespace CGAL

#endif        // CGAL_POLYGONAL_SURFACE_RECONSTRUCTION_ALPHA_SHAPE_MESH_H
