/*
 *  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
 *
 */

#ifndef PERIODIC_DELAUNAY_TRIANGULATION_3D
#define PERIODIC_DELAUNAY_TRIANGULATION_3D

#include <geogram/basic/common.h>
#include <geogram/delaunay/delaunay.h>
#include <geogram/delaunay/periodic.h>
#include <geogram/voronoi/convex_cell.h>
#include <geogram/basic/process.h>
#include <geogram/basic/geometry.h>
#include <stack>

#include <geogram/delaunay/delaunay_sync.h>

namespace GEO {

    class PeriodicDelaunay3dThread;


    /**
     * \brief Multithreaded implementation of Delaunay in 3d with
     *  optional periodic boundary conditions.
     * \details Periodicity is taken into account by detecting the
     *   vertices with Voronoi cells that straddle the boundary and
     *   duplicating them as need be (with an additional propagation to
     *   have the correct neighborhoods).
     * \see Delaunay3d, ParallelDelaunay3d
     */
    class GEOGRAM_API PeriodicDelaunay3d : public Delaunay, public Periodic {
    public:

        /**
         * \brief Gathers some structures used by some algorithms, makes
         *  multithreading more efficient by avoiding dynamic reallocations.
         * \details It is used to compute the set of tetrahedra incident to
         *  a vertex. It gathers a stack and the vector of incident tets
         *  obtained so far.
         */
        struct IncidentTetrahedra {
            std::stack<index_t> S;
            vector<index_t> incident_tets_set;

            /**
             * \brief Clears the set of incident tets.
             */
            void clear_incident_tets() {
                incident_tets_set.resize(0);
            }

            /**
             * \brief Inserts a tet into the set of incident tets.
             * \param[in] t the tet to be inserted.
             */
            void add_incident_tet(index_t t) {
                incident_tets_set.push_back(t);
            }

            /**
             * \brief Tests whether a tet belongs to the set of incident
             *  tets.
             * \param[in] t the tet to be tested
             * \retval true if the tet belongs to the set of incident tets
             * \retval false otherwise
             */
            bool has_incident_tet(index_t t) const {
                for(index_t i=0; i<incident_tets_set.size(); ++i) {
                    if(incident_tets_set[i] == t) {
                        return true;
                    }
                }
                return false;
            }

            vector<index_t>::const_iterator begin() const {
                return incident_tets_set.begin();
            }

            vector<index_t>::const_iterator end() const {
                return incident_tets_set.end();
            }
        };

        /**
         * \brief Constructs a new PeriodicDelaunay3d.
         * \param[in] periodic if true, constructs a periodic triangulation.
         * \param[in] period the edge length of the periodic domain.
         */
        PeriodicDelaunay3d(bool periodic, double period=1.0);

        /**
         * \brief Constructs a new PeriodicDelaunay3d.
         * \param[in] period the edge lengths along x,y,z in the periodic domain
         */
        PeriodicDelaunay3d(const vec3& period);

        /**
         * \copydoc Delaunay::set_vertices()
         * \note compute() needs to be called after.
         */
        void set_vertices(
            index_t nb_vertices, const double* vertices
        ) override;

        /**
         * \brief Sets the weights.
         * \param[in] weights pointer to the array of
         *  weights. Size is the number of real vertices,
         *  i.e., the parameter nb_vertices passed to
         *  set_vertices().
         * \note compute() needs to be called after.
         */
        void set_weights(const double* weights);

        /**
         * \brief Computes the Delaunay triangulation.
         */
        void compute();

        /**
         * \brief Use exact predicates in convex cell computations.
         * \details Convex cell computations are used in periodic
         *  mode for determining the cells that straddle the domain
         *  boundary.
         * \param[in] x true if exact predicates should be used
         *  (default), false otherwise.
         */
        void use_exact_predicates_for_convex_cell(bool x) {
            convex_cell_exact_predicates_ = x;
        }

        /**
         * \brief Gets a vertex by index.
         * \param[in] v a vertex index. Can be a virtual
         *  vertex index when in periodic mode.
         * \return the 3d point associated with the vertex.
         *  In periodic mode, if \p v is a virtual vertex,
         *  then the translation is applied to the real vertex.
         */
        vec3 vertex(index_t v) const {
            if(!periodic_) {
                geo_debug_assert(v < nb_vertices());
                return vec3(vertices_ + 3*v);
            }
            index_t instance = v/nb_vertices_non_periodic_;
            v = v%nb_vertices_non_periodic_;
            vec3 result(vertices_ + 3*v);
            result.x += double(translation[instance][0]) * period_.x;
            result.y += double(translation[instance][1]) * period_.y;
            result.z += double(translation[instance][2]) * period_.z;
            return result;
        }

        /**
         * \brief Gets a weight by index.
         * \param[in] v a vertex index. Can be a virtual
         *  vertex index in periodic mode.
         * \return the weight associated with the vertex.
         */
        double weight(index_t v) const {
            if(weights_ == nullptr) {
                return 0.0;
            }
            return periodic_ ? weights_[periodic_vertex_real(v)] : weights_[v] ;
        }

        /**
         * \copydoc Delaunay::nearest_vertex()
         */
        index_t nearest_vertex(const double* p) const override;

        /**
         * \copydoc Delaunay::set_BRIO_levels()
         */
        void set_BRIO_levels(const vector<index_t>& levels) override;

        /**
         * \brief computes the set of tetrahedra that are incident to
         *  a vertex.
         * \param[in] v the index of the vertex.
         * \param[in,out] W a reference to a
         *  PeriodicDelaunay3d::IncidentTetrahedra.
         *  On exit it contains the list of incident tets.
         */
        void get_incident_tets(index_t v, IncidentTetrahedra& W) const;

        /**
         * \brief Copies a Laguerre cell from the triangulation.
         * \details Delaunay neigbhors are stored in ConvexCell vertex global
         *  indices.
         * \param[in] i the index of the vertex of which the Laguerre cell
         *  should be computed.
         * \param[out] C the Laguerre cell.
         * \param[in,out] W a reference to a
         *  PeriodicDelaunay3d::IncidentTetrahedra
         */
        void copy_Laguerre_cell_from_Delaunay(
            GEO::index_t i,
            ConvexCell& C,
            IncidentTetrahedra& W
        ) const;

        /**
         * \brief Copies a Laguerre cell from the triangulation.
         * \details Delaunay neigbhors are stored in ConvexCell vertex global
         *  indices.
         * \param[in] i the index of the vertex of which the Laguerre cell
         *  should be computed.
         * \param[out] C the Laguerre cell.
         */
        void copy_Laguerre_cell_from_Delaunay(
            GEO::index_t i,
            ConvexCell& C
        ) const {
            IncidentTetrahedra W;
            copy_Laguerre_cell_from_Delaunay(i,C,W);
        }

        /**
         * \brief Tests whether the Laguerre diagram has empty cells.
         * \details If the Laguerre diagram has empty cells, then
         *  computation is stopped, and all the queries on the Laguerre
         *  diagram will not work (including the non-empty cells).
         * \retval true if the Laguerre diagram has empty cells.
         * \retval false otherwise.
         */
        bool has_empty_cells() const {
            return has_empty_cells_;
        }

        /**
         * \brief Saves the cells in an Alias-Wavefront file.
         * \param[in] basename the basename of the file. Filename
         *   is basename_callid.obj, where callid is the number of
         *   times the function was invoked.
         * \param[in] clipped if true, clip the cells by the domain
         *   without saving, else keep the cells as is.
         */
        void save_cells(const std::string& basename, bool clipped);

    protected:

        /**
         * \brief Copies a Laguerre cell facet from the triangulation.
         * \param[in] i the index of the vertex of which the Laguerre cell
         *  should be computed.
         * \param[in] Pi the coordinates of vertex \p i
         * \param[in] wi the weight associated to vertex \p i
         * \param[in] Pi_len2 the squared length of vertex \p i (considered as
         *  a vector).
         * \param[in] t a tetrahedron of the Delaunay triangulation,
         *  incident to vertex i
         * \param[out] C the Laguerre cell.
         * \param[in,out] W a reference to a
         *  PeriodicDelaunay3d::IncidentTetrahedra
         * \return the local index of vertex \p i within tetrahedron \p t
         */
        GEO::index_t copy_Laguerre_cell_facet_from_Delaunay(
            GEO::index_t i,
            const GEO::vec3& Pi,
            double wi,
            double Pi_len2,
            GEO::index_t t,
            ConvexCell& C,
            IncidentTetrahedra& W
        ) const;


        /**
         * \brief Removes unused tetrahedra.
         * \return the final number of tetrahedra.
         * \param[in] shrink if true, then array space is shrunk
         *  to fit the new number of tetrahedra.
         */
        index_t compress(bool shrink=true);

        /**
         * \copydoc Delaunay::update_v_to_cell()
         * \details if update_periodic_v_to_cell_ is set to true,
         *  also updates the map periodic_v_to_cell_ that maps
         *  each virtual vertex to a tet incident to it.
         */
        void update_v_to_cell() override;

        /**
         * \copydoc Delaunay::update_cicl()
         */
        void update_cicl() override;

        /**
         * \brief Duplicates the points with Voronoi cells
         *  that cross the boundary.
         */
        void handle_periodic_boundaries();


	/**
	 * \brief Phase I of periodic boundaries handling
	 * \details For each cell that traverses the boundary, generates
	 *  the periodic vertices instances corresponding to the crossed
	 *  faces of the domain, and inserts them in the list of vertices
	 *  to be inserted (the reorder_ vector).
	 */
	void handle_periodic_boundaries_phase_I();

	/**
	 * \brief Tests the position of a Laguerre vertex w.r.t. a plane
	 * \details The positive side of the plane equation corresponds to
	 *  what is kept. In other words, the normal vector P.x, P.y, P.z
	 *  points towards the interior of this ConvexCell.
	 * \param[in] t a tetrahedron index. The considered Laguerre vertex
	 *  is the dual of this tetrahedron.
	 * \param[in] P the plane equation.
	 * \retval true if the Laguerre vertex would be clipped by plane P
	 * \retval false otherwise
	 */
	bool Laguerre_vertex_is_in_conflict_with_plane(index_t t, vec4 P) const;

	/**
	 * \brief Phase II of periodic boundaries handling
	 * \details Adds the newly discovered neighbors of
	 *  the vertices inserted during phase I, back-translated
	 *  to their original domain instances in the list of
	 *  vertices to be inserted (in the reorder_ member).
	 */
	void handle_periodic_boundaries_phase_II();

        /**
         * \brief Inserts vertices from reorder_[b] to reorder_[e-1] using
	 *   multithreaded Delaunay. Called by insert_vertices() if there
	 *   are many vertices to insert.
         * \details If an empty cells is detected, has_empty_cells_ is
         *  set and the function exits.
         */
	void insert_vertices(const char* phase, index_t b, index_t e);

	/**
	 * \brief Inserts vertices as indicated by a reordering vector
	 *  and a vector of BRIO levels, as obtained using
	 *  compute_BRIO_order_periodic() (internal function, in the .cpp)
	 * \details used by insert_vertices() (and maybe also compute(), we shall
	 *  see if we can). Returns immediatly if an empty cell is encountered,
	 *  then it sets has_empty_cells_.
	 * \param[in] levels a const reference to the vector of BRIO levels, as
	 *  offsets in the member reordering vector reorder_.
	 */
	void insert_vertices_with_BRIO(
	    const char* phase, const vector<index_t>& levels
	);

        /**
         * \brief Checks the volume of Laguerre cells.
         */
        void check_volume();

        /**
         * \brief Gets a thread by index.
         * \param[in] t the index of the thread, in 0..nb_threads()-1
         * \return a pointer to the t-th thread.
         */
        PeriodicDelaunay3dThread* thread(index_t t) {
            geo_debug_assert(t < threads_.size());
            return reinterpret_cast<PeriodicDelaunay3dThread*>(
                threads_[t].get()
            );
        }

        /**
         * \brief Gets the number of threads.
         * \return the number of threads.
         */
        index_t nb_threads() const {
            return index_t(threads_.size());
        }

	void check_max_t();

    private:
        friend class PeriodicDelaunay3dThread;

        bool periodic_;
        vec3 period_;

        const double* weights_;
        vector<index_t> cell_to_v_store_;
        vector<index_t> cell_to_cell_store_;
        vector<index_t> cell_next_;

        CellStatusArray cell_status_;

        ThreadGroup threads_;
        vector<index_t> reorder_;
        vector<index_t> levels_;

        /**
         * Performs additional checks (costly !)
         */
        bool debug_mode_;

        /**
         * Displays the result of the additional checks.
         */
        bool verbose_debug_mode_;

        /**
         * Displays a synthetic summary of timings at the end of the algorithm
         */
        bool benchmark_mode_;

        /**
         * Displays the detailed timing of all the phases of the algorithm
         */
        bool detailed_benchmark_mode_;

        /**
         * \brief Bitmask that indicates for each real vertex
         *  the virtual vertices that were created.
         */
        vector<Numeric::uint32> vertex_instances_;

        bool update_periodic_v_to_cell_;
        vector<index_t> periodic_v_to_cell_rowptr_;
        vector<index_t> periodic_v_to_cell_data_;

        /**
         * \brief Early detection of empty cells.
         */
        bool has_empty_cells_;

        /**
         * \brief Number of reallocations when inserting vertices
         *  in sequential mode.
         */
        index_t nb_reallocations_;

        /**
         * \brief Use exact predicates in convex cell.
         */
        bool convex_cell_exact_predicates_;

	struct Stats {

	    Stats();

	    void reset();

	    std::string to_string() {
		return raw_ ? to_string_raw() : to_string_pretty();
	    }

	    std::string to_string_raw() const;
	    std::string to_string_pretty() const;

            /**
	     * If set, displays numbers without any formatting.
	     * Set by constructor if command line argument dbg:raw_logs is set.
	     */
	    bool raw_;

	    double  total_t_;

	    double  phase_0_t_;

	    double  phase_I_t_;
	    double  phase_I_classify_t_;
	    index_t phase_I_nb_inside_;
	    index_t phase_I_nb_cross_;
	    index_t phase_I_nb_outside_;
	    double  phase_I_insert_t_;
	    index_t phase_I_insert_nb_;

	    double  phase_II_t_;
	    double  phase_II_classify_t_;
	    double  phase_II_insert_t_;
	    index_t phase_II_insert_nb_;
	} stats_;

	friend class LaguerreDiagramOmegaSimple3d;
    };


}

#endif
