/*
 *  Copyright (c) 2000-2022 Inria
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *  * Neither the name of the ALICE Project-Team nor the names of its
 *  contributors may be used to endorse or promote products derived from this
 *  software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 *  Contact: Bruno Levy
 *
 *     https://www.inria.fr/fr/bruno-levy
 *
 *     Inria,
 *     Domaine de Voluceau,
 *     78150 Le Chesnay - Rocquencourt
 *     FRANCE
 *
 */

#include <geogram/voronoi/RVD_callback.h>
#include <geogram/voronoi/RVD_mesh_builder.h>
#include <geogram/voronoi/generic_RVD_cell.h>
#include <geogram/mesh/mesh_geometry.h>
#include <geogram/mesh/mesh_io.h>
#include <geogram/basic/argused.h>

namespace {
    using namespace GEO;

    /**
     * \brief Gets the maximum region index in the mesh, not counting
     *   index_t(-1) triangles.
     * \return The maximum region index, or index_t(-1) if all region
     *   indices are index_t(-1)
     */
    index_t max_region(Mesh& mesh, Attribute<index_t>& facet_region) {
        index_t result = index_t(-1);
        for(index_t f=0; f<mesh.facets.nb(); ++f) {
            if(facet_region[f] != index_t(-1)) {
                if(result == index_t(-1)) {
                    result = facet_region[f];
                } else {
                    result = std::max(result, facet_region[f]);
                }
            }
        }
        return result;
    }

    /**
     * \brief Splits the regions along hard edges.
     * \param[in,out] mesh a reference to the surface mesh.
     * \param[in,out] facet_region the attribute that defines the regions.
     *  The regions with facet_region == index_t(-1) will be split.
     * \param[in] threshold (in degrees). Edges with two adjacent facets
     *  with normals that form an  angle larger than \p threshold degrees are
     *  considered as hard edges.
     */
    void split_regions_along_hard_edges(
        Mesh& mesh, Attribute<index_t>& facet_region, double threshold
    ) {

        threshold *= (M_PI / 180.0);

        index_t R = max_region(mesh, facet_region);
        if(R == index_t(-1)) {
            R = 0;
        } else {
            ++R;
        }
        vector<bool> is_crease(mesh.facet_corners.nb(), false);
        for(index_t f=0; f<mesh.facets.nb(); ++f) {
            vec3 n = Geom::mesh_facet_normal(mesh,f);
            for(
                index_t c=mesh.facets.corners_begin(f);
                c<mesh.facets.corners_end(f); ++c
            ) {
                index_t f2 = mesh.facet_corners.adjacent_facet(c);
                if(f2 != index_t(-1)) {
                    vec3 n2 = Geom::mesh_facet_normal(mesh,f2);
                    double alpha = Geom::angle(n,n2);
                    if(alpha > threshold) {
                        is_crease[c] = true;
                    }
                }
            }
        }
        for(index_t f=0; f<mesh.facets.nb(); ++f) {
            if(facet_region[f] == index_t(-1)) {
                std::stack<index_t> S;
                facet_region[f] = R;
                S.push(f);
                while(!S.empty()) {
                    index_t f2 = S.top();
                    S.pop();
                    for(
                        index_t c=mesh.facets.corners_begin(f2);
                        c<mesh.facets.corners_end(f2); ++c
                    ) {
                        if(is_crease[c]) {
                            continue;
                        }
                        index_t f3 = mesh.facet_corners.adjacent_facet(c);
                        if(
                            f3 != index_t(-1) &&
                            facet_region[f3] == index_t(-1)
                        ) {
                            facet_region[f3] = R;
                            S.push(f3);
                        }
                    }
                }
                ++R;
            }
        }
    }

    /**
     * \brief Simplifies the facets of a surface mesh based on an attribute.
     * \details Groups of connected facets with the same attribute value are
     *  replaced with a single facet.
     * \param[in,out] mesh a reference to the surface mesh to be simplified
     * \param[in] facet_region a reference to the attribute with the facet
     *  region
     * \param[in] angle_threshold (in degrees). In the outer region
     *  (i.e. facet_region == index_t(-1)), an edge shared by two adjacent
     *  facets is suppressed if the angle between the
     *  two facet normals is smaller than \p angle_threshold.
     */
    bool simplify(
        Mesh& mesh,
        Attribute<index_t>& facet_region,
        double angle_threshold
    ) {

        bool keep_outer_region = (angle_threshold == 0.0);

        index_t max_r = 0;

        if(!keep_outer_region) {
            max_r = max_region(mesh, facet_region);
            split_regions_along_hard_edges(mesh, facet_region, angle_threshold);
        }

        vector<bool> is_corner(mesh.vertices.nb(),false);

        {
            vector<index_t> rgn1(mesh.vertices.nb(), index_t(-2));
            vector<index_t> rgn2(mesh.vertices.nb(), index_t(-2));

            // Keep all the vertices adjacent to 3 regions or more
            for(index_t f=0; f<mesh.facets.nb(); ++f) {
                index_t r = facet_region[f];
                for(index_t lv=0; lv<mesh.facets.nb_vertices(f); ++lv) {
                    index_t v = mesh.facets.vertex(f,lv);
                    if(rgn1[v] == r || rgn2[v] == r) {
                        continue;
                    }
                    if(rgn1[v] == index_t(-2)) {
                        rgn1[v] = r;
                    } else if(rgn2[v] == index_t(-2)) {
                        rgn2[v] = r;
                    } else {
                        is_corner[v] = true;
                    }
                }
            }

            // Keep also the vertices adjacent to region -1 and
            // to another region

            if(keep_outer_region) {
                for(index_t v=0; v<mesh.vertices.nb(); ++v) {
                    if(
                        (rgn1[v] == index_t(-1) && rgn2[v] != index_t(-1)) ||
                        (rgn2[v] == index_t(-1) && rgn1[v] != index_t(-1))
                    ) {
                        is_corner[v] = true;
                    }
                }
            }
        }

        // -1: not visited, 0: keep, 1: delete
        vector<index_t> facet_status(mesh.facets.nb(),index_t(-1));

        // Needs to be backed-up, we are modifying the mesh !!
        index_t nf = mesh.facets.nb();

        for(index_t f=0; f<nf; ++f) {
            if(facet_status[f] == index_t(-1)) {
                index_t r = facet_region[f];
                if(keep_outer_region && (r == index_t(-1))) {
                    facet_status[f] = 0;
                    continue;
                } else {
                    std::stack<index_t> S;
                    std::map<index_t, index_t> border_next;
                    S.push(f);
                    facet_status[f] = 1;
                    while(!S.empty()) {
                        index_t f2 = S.top();
                        S.pop();
                        for(index_t c1=mesh.facets.corners_begin(f2);
                            c1<mesh.facets.corners_end(f2); ++c1) {
                            index_t f3 = mesh.facet_corners.adjacent_facet(c1);
                            if(f3 == index_t(-1) || facet_region[f3] != r) {
                                index_t c2 =
                                    mesh.facets.next_corner_around_facet(f2,c1);
                                index_t v1 = mesh.facet_corners.vertex(c1);
                                index_t v2 = mesh.facet_corners.vertex(c2);
                                if(border_next.find(v1) != border_next.end()) {
                                    Logger::warn("Simplify")
                                        << "Region has non-manifold border"
                                        << std::endl;
                                    // Yes, goto!, why not, what's wrong with goto ?
                                    // Why would it be ok to throw() and not goto ?
                                    goto rollback;
                                }
                                border_next[v1] = v2;
                            } else {
                                if(facet_status[f3] == index_t(-1)) {
                                    facet_status[f3] = 1;
                                    S.push(f3);
                                }
                            }
                        }
                    }
                    index_t nb_border_visited = 0;
                    vector<index_t> new_facet;
                    index_t v = border_next.begin()->first;
                    do {
                        if(is_corner[v]) {
                            new_facet.push_back(v);
                        }
                        ++nb_border_visited;
                        v = border_next[v];
                        if(nb_border_visited > mesh.vertices.nb()) {
                            Logger::warn("Simplify")
                                << "Region has singular border topology"
                                << std::endl;
                            goto rollback;
                        }
                    } while(v != border_next.begin()->first);

                    if(nb_border_visited != border_next.size()) {
                        Logger::warn("Simplify")
                            << "Region has multiple borders"
                            << std::endl;
                        goto rollback;
                    }

                    if(new_facet.size() < 3) {
                        Logger::warn("Simplify")
                            << "Region has border with less than 3 corners"
                            << std::endl;
                        goto rollback;
                    }

                    index_t new_f = mesh.facets.nb();
                    mesh.facets.create_polygon(new_facet.size());
                    for(index_t i=0; i<new_facet.size(); ++i) {
                        mesh.facets.set_vertex(new_f,i,new_facet[i]);
                    }
                    facet_region[new_f] = r;
                }
            }
        }

        facet_status.resize(mesh.facets.nb(), 0);
        mesh.facets.delete_elements(facet_status);
        if(!keep_outer_region) {
            for(index_t f=0; f<mesh.facets.nb(); ++f) {
                if(facet_region[f] > max_r) {
                    facet_region[f] = index_t(-1);
                }
            }
        }
        return true;

    rollback:
        Logger::out("Simplify") << "...Rolling back." << std::endl;
        // delete all facets...
        facet_status.resize(mesh.facets.nb(), 1);
        // ... except the initial ones !
        for(index_t f=0; f<nf; ++f) {
            facet_status[f] = 0;
        }
        mesh.facets.delete_elements(facet_status);
        if(!keep_outer_region) {
            for(index_t f=0; f<mesh.facets.nb(); ++f) {
                if(facet_region[f] > max_r) {
                    facet_region[f] = index_t(-1);
                }
            }
        }
        return false;
    }

    /**
     * \brief Gets a 2d polygon that represents a mesh facet.
     * \param[in] mesh a const reference to the mesh
     * \param[in] f the facet
     * \param[in] N the normal vector to the facet
     * \param[out] P the vertices of the polygon
     * \param[out] P_ind the global indices of the vertices in \p mesh
     */
    void get_mesh_polygon2d(
        const Mesh& mesh,
        index_t f,
        const vec3& N,
        vector<vec2>& P,
        vector<index_t>& P_ind
    ) {
        P.resize(0);
        P_ind.resize(0);
        vec3 Z = normalize(N);
        vec3 X = Geom::perpendicular(Z);
        vec3 Y = cross(Z,X);
        vec3 C = Geom::mesh_facet_center(mesh,f);
        index_t n = mesh.facets.nb_vertices(f);
        FOR(lv,n) {
            index_t v = mesh.facets.vertex(f,lv);
            vec3 W = mesh.vertices.point(v)-C;
            P_ind.push_back(v);
            P.push_back(vec2(dot(W,X), dot(W,Y)));
        }
        // TODO: normalize vertices order so that two
        // opposite facets will have the same tessellation.
    }


    /**
     * \brief Evaluates the score of a triangle in a closed polygon.
     * \param[in] pts the closed polygon
     * \param[in] i , j , k the three vertices of the triangle
     * \retval 1024 if a concave angle was encountered or if the proposed
     *    triangle contains one of the points.
     * \retval the maximum angle of the proposed triangle otherwise.
     */
    double triangle_cost(
        const vector<vec2>& pts, index_t i, index_t j, index_t k
    ) {
        vec2 C[3] = { pts[i], pts[j], pts[k] };
        double m = 0;
        FOR(v, 3) {
            // note that angle is not the angle inside the triangle,
            // but its complement
            // angle variable has the "direction" information, thus it
            // is negative for concave angles (right turn) and positive
            // for convex angles (left turn)
            double angle = atan2(
                det(
                    C[(v + 1) % 3] - C[(v + 0) % 3],
                    C[(v + 2) % 3] - C[(v + 1) % 3]
                ),
                dot(
                    C[(v + 1) % 3] - C[(v + 0) % 3],
                    C[(v + 2) % 3] - C[(v + 1) % 3]
                )
            );
            if (angle <= 0) return 1024.;
            m = std::max(m, M_PI - angle);
        }

        FOR(other, pts.size()) {
            // TODO: check also whether triangle is inversed ?
            // To be checked: I think it is already done in
            // angle computations above.
            if (other == i || other == j || other == k) {
                continue;
            }
            const vec2& P = pts[other];
            bool inside = true;
            FOR(l, 3) {
                inside = inside && (det(C[(l + 1) % 3] - C[l], P - C[l]) > 0);
            }
            if (inside) {
                return 1024.0;
            }
        }
        return m;
    }

    /**
     * \brief Triangulates a (possibly non-convex) polygon.
     * \note The algorithm is in O(n^4) (bad but good enough for now).
     * \param[in] pts the polygon
     * \param[out] triangles the indices of the triangles vertices
     * \retval true on success
     * \retval false otherwise
     */
    bool triangulate_polygon(
        const vector<vec2>& pts, vector<index_t>& triangles
    ) {
        triangles.resize(0);
        index_t n = pts.size();
        geo_assert(n >= 3);

        if (n == 3) {
            FOR(v, 3) {
                triangles.push_back(v);
            }
            return true;
        }

        // we store in this table results of subproblems
        // table[i*n + j] stores the triangulation cost for points from i to j
        // the entry table[0*n + n-1] has the final result.
        vector<double> table(n*n, 0.);

        // this table stores triangle indices:
        //  for each subproblem (i,j) we have table[i*n + j]==k,
        //    i.e. the triangle is (i,k,j)
        vector<index_t> tri(n*n, index_t(-1));

        // note that the table is filled in diagonals;
        // elements below main diagonal are not used at all
        for (index_t pbsize = 2; pbsize < n; pbsize++) {
            for (index_t i = 0, j = pbsize; j < n; i++, j++) {
                // recall that we are testing triangle (i,k,j)
                // which splits the problem (i,j) into
                // two smaller subproblems (i,k) and (k,j)

                double minv = 1e20;

                index_t mink = index_t(-1);

                for (index_t k = i + 1; k < j; k++) {

                    double val =
                        table[i*n + k] + table[k*n + j] +
                        triangle_cost(pts, i, k, j);

                    if (minv <= val) {
                        continue;
                    }
                    minv = val;
                    mink = k;
                }
                geo_assert(mink!=index_t(-1));
                table[i*n + j] = minv;
                tri[i*n + j] = mink;
            }
        }

        vector<index_t> Q(1, n - 1);
        FOR(t, Q.size()) {
            index_t idx = Q[t];

            index_t i = idx / n;
            index_t k = tri[idx];
            index_t j = idx % n;

            geo_assert(i!=index_t(-1) && k != index_t(-1) && j!=index_t(-1));

            triangles.push_back(i);
            triangles.push_back(k);
            triangles.push_back(j);

            if (k + 2 <= j) {
                Q.push_back(k*n + j);
            }
            if (i + 2 <= k) {
                Q.push_back(i*n + k);
            }
        }
        return table[n-1] < 1024.;
    }

    /**
     * \brief Tests whether a 2d polygon is convex.
     * \param[in] P a const reference to the polygon.
     * \retval true if the polygon \p P is convex.
     * \retval false otherwise.
     */
    bool polygon_is_convex(const vector<vec2>& P) {
        Sign s = ZERO;
        FOR(i, P.size()) {
            index_t j = (i+1)%P.size();
            index_t k = (j+1)%P.size();
            Sign cur_s = PCK::orient_2d(P[i], P[j], P[k]);
            if(int(cur_s) * int(s) == -1) {
                return false;
            }
            if(s == ZERO) {
                s = cur_s;
            }
        }
        return true;
    }

    /**
     * \brief Tesselates the non-convex facets of a mesh.
     * \param[in,out] mesh a pointer to the mesh.
     */
    void tessellate_non_convex_facets(
        Mesh* mesh
    ) {
        // TODO: use facet_seed_ attribute and replace normal vector
        // with (seed-facet seed) vector.
        vector<index_t> to_delete;
        vector<vec2> P;
        vector<index_t> P_ind;
        vector<index_t> P_tri;
        index_t nf = mesh->facets.nb();
        FOR(f, nf) {
            vec3 N = Geom::mesh_facet_normal(*mesh, f);
            get_mesh_polygon2d(*mesh, f, N, P, P_ind);
            if(!polygon_is_convex(P)) {
                if(triangulate_polygon(P, P_tri)) {
                    to_delete.resize(mesh->facets.nb(),0);
                    to_delete[f] = 1;
                    FOR(t, P_tri.size()/3) {
                        index_t newf = mesh->facets.create_triangle(
                            P_ind[P_tri[3*t  ]],
                            P_ind[P_tri[3*t+1]],
                            P_ind[P_tri[3*t+2]]
                        );
                        mesh->facets.attributes().copy_item(newf,f);
                    }
                } else {
                    Logger::warn("RVD")
                        << "Could not triangulate non-convex facet"
                        << std::endl;
                }
            }
        }
        if(to_delete.size() != 0) {
            to_delete.resize(mesh->facets.nb(), 0);
            mesh->facets.delete_elements(to_delete);
        }
    }
}


namespace GEO {

    RVDCallback::RVDCallback() :
        seed_(index_t(-1)),
        simplex_(index_t(-1)),
        spinlocks_(nullptr) {
    }

    RVDCallback::~RVDCallback() {
    }

    void RVDCallback::begin() {
        seed_ = index_t(-1);
        simplex_ = index_t(-1);
    }

    void RVDCallback::end() {
    }

    /*********************************************************************/

    RVDPolygonCallback::RVDPolygonCallback() {
    }

    RVDPolygonCallback::~RVDPolygonCallback() {
    }

    void RVDPolygonCallback::operator() (
        index_t v,
        index_t t,
        const GEOGen::Polygon& C
    ) const {
        const_cast<RVDPolygonCallback*>(this)->seed_ = v;
        const_cast<RVDPolygonCallback*>(this)->simplex_ = t;
        geo_argused(C);
    }

    void RVDPolygonCallback::begin() {
    }

    void RVDPolygonCallback::end() {
    }

    /*********************************************************************/

    RVDPolyhedronCallback::RVDPolyhedronCallback() :
        facet_seed_(index_t(-1)),
        facet_tet_(index_t(-1)),
        last_seed_(index_t(-1)),
        simplify_internal_tet_facets_(false),
        simplify_voronoi_facets_(false),
        simplify_boundary_facets_(false),
        simplify_boundary_facets_angle_threshold_(0.0),
        tessellate_non_convex_facets_(false),
        use_mesh_(false),
        facet_is_skipped_(false),
        vertex_map_(nullptr)
    {
    }

    RVDPolyhedronCallback::~RVDPolyhedronCallback() {
        if(mesh_vertex_sym_.is_bound()) {
            mesh_vertex_sym_.unbind();
        }
        if(mesh_facet_seed_.is_bound()) {
            mesh_facet_seed_.unbind();
        }
        if(mesh_facet_tet_.is_bound()) {
            mesh_facet_tet_.unbind();
        }
    }

    void RVDPolyhedronCallback::set_use_mesh(bool x) {
        use_mesh_ = x;
        if(!mesh_vertex_sym_.is_bound()) {
            mesh_vertex_sym_.bind(mesh_.vertices.attributes(), "sym");
        }
        if(!mesh_facet_seed_.is_bound()) {
            mesh_facet_seed_.bind(mesh_.facets.attributes(),"seed");
        }
        if(!mesh_facet_tet_.is_bound()) {
            mesh_facet_tet_.bind(mesh_.facets.attributes(),"tet");
        }
    }

    /********************************************************************/

    void RVDPolyhedronCallback::begin_polyhedron(
        index_t seed, index_t tetrahedron
    ) {
        geo_argused(seed);
        geo_argused(tetrahedron);
    }

    void RVDPolyhedronCallback::begin_facet(
        index_t facet_seed, index_t facet_tet
    ) {
        geo_argused(facet_seed);
        geo_argused(facet_tet);
    }

    void RVDPolyhedronCallback::vertex(
        const double* geometry, const GEOGen::SymbolicVertex& symb
    ) {
        geo_argused(geometry);
        geo_argused(symb);
    }

    void RVDPolyhedronCallback::end_facet() {
    }

    void RVDPolyhedronCallback::end_polyhedron() {
    }

    /********************************************************************/

    void RVDPolyhedronCallback::begin_polyhedron_internal(
        index_t seed, index_t tetrahedron
    ) {
        last_seed_ = seed;
        seed_ = seed;
        simplex_ = tetrahedron;
        if(use_mesh_) {
            vertex_map_ = new RVDVertexMap;
        } else {
            begin_polyhedron(seed, tetrahedron);
        }
    }

    void RVDPolyhedronCallback::begin_facet_internal(
        index_t facet_seed, index_t facet_tet
    ) {
        facet_seed_ = facet_seed;
        facet_tet_ = facet_tet;
        facet_is_skipped_ = (
            simplify_internal_tet_facets_ && (facet_tet != index_t(-1))
        );
        if(!facet_is_skipped_) {
            if(use_mesh_) {
            } else {
                begin_facet(facet_seed, facet_tet);
            }
        }
    }

    void RVDPolyhedronCallback::vertex_internal(
        const double* geometry, const GEOGen::SymbolicVertex& symb
    ) {
        if(!facet_is_skipped_) {
            if(use_mesh_) {
                index_t v = vertex_map_->find_or_create_vertex(seed(),symb);
                if(v >= mesh_.vertices.nb()) {
                    mesh_.vertices.create_vertex(geometry);
                    mesh_vertex_sym_[v] = symb;
                }
                base_current_facet_.push_back(v);
            } else {
                vertex(geometry, symb);
            }
        }
    }

    void RVDPolyhedronCallback::end_facet_internal() {
        if(!facet_is_skipped_) {
            if(use_mesh_) {
                index_t f = mesh_.facets.nb();
                mesh_.facets.create_polygon(base_current_facet_.size());
                for(index_t i=0; i<base_current_facet_.size(); ++i) {
                    mesh_.facets.set_vertex(f, i, base_current_facet_[i]);
                }
                mesh_facet_seed_[f] = facet_seed();
                mesh_facet_tet_[f] = facet_tet();
                base_current_facet_.resize(0);
            } else {
                end_facet();
            }
        }
        facet_seed_ = index_t(-1);
        facet_tet_ = index_t(-1);
    }

    void RVDPolyhedronCallback::end_polyhedron_internal() {
        if(use_mesh_) {
            mesh_.facets.connect();
            process_polyhedron_mesh();
            mesh_.clear(true,true);
            delete vertex_map_;
            vertex_map_ = nullptr;
        } else {
            end_polyhedron();
        }
        seed_ = index_t(-1);
        simplex_ = index_t(-1);
    }

    /********************************************************************/

    void RVDPolyhedronCallback::process_polyhedron_mesh() {
        if(simplify_voronoi_facets_) {
            simplify(
                mesh_,
                mesh_facet_seed_,
                simplify_boundary_facets_angle_threshold_
            );
        }
        if(tessellate_non_convex_facets_) {
            tessellate_non_convex_facets(&mesh_);
        }
        begin_polyhedron(seed(), tet());
        for(index_t f=0; f<mesh_.facets.nb(); ++f) {
            facet_seed_ = mesh_facet_seed_[f];
            facet_tet_ = mesh_facet_tet_[f];
            begin_facet(facet_seed_, facet_tet_);
            for(index_t lv=0; lv<mesh_.facets.nb_vertices(f); ++lv) {
                index_t v = mesh_.facets.vertex(f,lv);
                vertex(mesh_.vertices.point_ptr(v), mesh_vertex_sym_[v]);
            }
            end_facet();
        }
        end_polyhedron();
    }

    /********************************************************************/

    void RVDPolyhedronCallback::begin() {
    }

    void RVDPolyhedronCallback::end() {

        GEO::RVDPolyhedronCallback& callbacks =
            const_cast<GEO::RVDPolyhedronCallback&>(*this);

        if(simplify_internal_tet_facets_ && seed_ != index_t(-1)) {
            callbacks.end_polyhedron_internal();
        }
    }

    void RVDPolyhedronCallback::operator() (
        index_t v,
        index_t t,
        const GEOGen::ConvexCell& C
    ) const {

        GEO::RVDPolyhedronCallback& callbacks =
            const_cast<GEO::RVDPolyhedronCallback&>(*this);

        if(simplify_internal_tet_facets_) {
            if(v != last_seed_) {
                if(last_seed_ != index_t(-1)) {
                    callbacks.end_polyhedron_internal();
                }
                callbacks.begin_polyhedron_internal(v,t);
            }
        } else {
            callbacks.begin_polyhedron_internal(v,t);
        }

        // Remember that ConvexCell is represented in dual form !
        //   - ConvexCell's vertices are facets
        //   - ConvexCell's triangles are vertices

        for(index_t cv = 0; cv < C.max_v(); ++cv) {
            signed_index_t ct = C.vertex_triangle(cv);
            if(ct == -1) {
                continue;
            }
            geo_debug_assert(C.triangle_is_used(index_t(ct)));

            signed_index_t adjacent = C.vertex_id(cv);
            signed_index_t v_adj = -1;
            signed_index_t t_adj = -1;

            if(adjacent < 0) {
                // Negative adjacent indices correspond to
                // tet-tet links
                t_adj = -adjacent - 1;
            } else if(adjacent > 0) {
                // Positive adjacent indices correspond to
                // Voronoi seed - Voronoi seed link
                v_adj = adjacent - 1;
            } // Zero adjacent indices corresponds to
            // tet facet on border.

            callbacks.begin_facet_internal(index_t(v_adj), index_t(t_adj));

            GEOGen::ConvexCell::Corner first(
                index_t(ct), C.find_triangle_vertex(index_t(ct), cv)
            );

            GEOGen::ConvexCell::Corner c = first;
            do {
                const GEOGen::Vertex& vx = C.triangle_dual(c.t);
                callbacks.vertex_internal(vx.point(), vx.sym());
                C.move_to_next_around_vertex(c);
            } while(c != first);
            callbacks.end_facet_internal();
        }

        if(!simplify_internal_tet_facets_) {
            callbacks.end_polyhedron_internal();
        }
    }

    /********************************************************************/

    BuildRVDMesh::BuildRVDMesh(Mesh& output_mesh) :
        output_mesh_(output_mesh), shrink_(0.0) {
        cell_vertex_map_ = nullptr;
        global_vertex_map_ = nullptr;
        current_cell_id_ = 0;
        generate_ids_ = false;
    }

    BuildRVDMesh::~BuildRVDMesh() {
        if(generate_ids_) {
            cell_id_.unbind();
            seed_id_.unbind();
            vertex_id_.unbind();
            facet_seed_id_.unbind();
            delete global_vertex_map_;
            global_vertex_map_ = nullptr;
        }
        delete cell_vertex_map_;
        cell_vertex_map_ = nullptr;
    }

    void BuildRVDMesh::set_generate_ids(bool x) {
        if(x == generate_ids_) {
            return;
        }
        generate_ids_ = x;
        if(generate_ids_) {
            cell_id_.bind(
                output_mesh_.facets.attributes(), "cell_id"
            );
            seed_id_.bind(
                output_mesh_.facets.attributes(), "seed_id"
            );
            vertex_id_.bind(
                output_mesh_.vertices.attributes(), "vertex_id"
            );
            facet_seed_id_.bind(
                output_mesh_.facets.attributes(), "facet_seed_id"
            );
            global_vertex_map_ = new RVDVertexMap;
        } else {
            cell_id_.unbind();
            seed_id_.unbind();
            vertex_id_.unbind();
            facet_seed_id_.unbind();
            delete global_vertex_map_;
            global_vertex_map_ = nullptr;
        }
    }

    void BuildRVDMesh::set_shrink(double x) {
        shrink_ = x;
        if(shrink_ != 0.0) {
            set_use_mesh(true);
        }
    }

    void BuildRVDMesh::begin() {
        RVDPolyhedronCallback::begin();
        output_mesh_.clear();
        output_mesh_.vertices.set_dimension(3);
    }

    void BuildRVDMesh::end() {
        RVDPolyhedronCallback::end();
        output_mesh_.facets.connect();
    }


    void BuildRVDMesh::begin_polyhedron(index_t seed, index_t tetrahedron) {
        geo_argused(tetrahedron);
        geo_argused(seed);
        delete cell_vertex_map_;
        cell_vertex_map_ = new RVDVertexMap;
        cell_vertex_map_->set_first_vertex_index(
            output_mesh_.vertices.nb()
        );
    }

    void BuildRVDMesh::begin_facet(index_t facet_seed, index_t facet_tet_facet) {
        geo_argused(facet_seed);
        geo_argused(facet_tet_facet);
        current_facet_.resize(0);
    }

    void BuildRVDMesh::vertex(
        const double* geometry, const GEOGen::SymbolicVertex& symb
    ) {
        index_t v = cell_vertex_map_->find_or_create_vertex(seed(), symb);
        if(v >= output_mesh_.vertices.nb()) {
            output_mesh_.vertices.create_vertex(geometry);
            if(generate_ids_) {
                vertex_id_[v] = int(
                    global_vertex_map_->find_or_create_vertex(seed(), symb)
                );
            }
        }
        current_facet_.push_back(v);
    }

    void BuildRVDMesh::end_facet() {
        index_t f = output_mesh_.facets.nb();
        output_mesh_.facets.create_polygon(current_facet_.size());
        for(index_t i=0; i<current_facet_.size(); ++i) {
            output_mesh_.facets.set_vertex(f,i,current_facet_[i]);
        }
        if(generate_ids_) {
            seed_id_[f] = int(seed());
            cell_id_[f] = int(current_cell_id_);
            facet_seed_id_[f] = int(facet_seed());
        }
    }

    void BuildRVDMesh::end_polyhedron() {
        ++current_cell_id_;
    }

    void BuildRVDMesh::process_polyhedron_mesh() {
        if(shrink_ != 0.0 && mesh_.vertices.nb() != 0) {
            vec3 center(0.0, 0.0, 0.0);
            for(index_t v=0; v<mesh_.vertices.nb(); ++v) {
                center += vec3(mesh_.vertices.point_ptr(v));
            }
            center = (1.0 / double(mesh_.vertices.nb())) * center;
            for(index_t v=0; v<mesh_.vertices.nb(); ++v) {
                vec3 p(mesh_.vertices.point_ptr(v));
                p = shrink_ * center + (1.0 - shrink_) * p;
                mesh_.vertices.point_ptr(v)[0] = p.x;
                mesh_.vertices.point_ptr(v)[1] = p.y;
                mesh_.vertices.point_ptr(v)[2] = p.z;
            }
        }
        RVDPolyhedronCallback::process_polyhedron_mesh();
    }

}
