/*
 *  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 GEOGRAM_VORONOI_CVT
#define GEOGRAM_VORONOI_CVT

#include <geogram/basic/common.h>
#include <geogram/voronoi/RVD.h>
#include <geogram/voronoi/integration_simplex.h>
#include <geogram/mesh/mesh.h>
#include <geogram/delaunay/delaunay.h>


/**
 * \file geogram/voronoi/CVT.h
 * \brief Main class for computing centroidal Voronoi tesselations.
 */

namespace GEO {

    class RestrictedVoronoiDiagram;
    class ProgressTask;

    /**
     * \brief CentroidalVoronoiTesselation is the main component
     *  of the remeshing algorithm.
     *
     * \details It evenly distributes points over a surface embedded in Rn,
     *  where n can be of arbitrary dimension. The geometrical
     *  computations are done by RestrictedVoronoiDiagram, and
     *  the numerical optimization by Optimizer.
     */
    class GEOGRAM_API CentroidalVoronoiTesselation {

        /** \brief This class type */
        typedef CentroidalVoronoiTesselation thisclass;

    public:
    /**
     * \brief Constructs a new CentroidalVoronoiTesselation.
     * \details This constructor should be used when the
     *  first three coordinates of the mesh are x,y,z.
     * \param[in] mesh a pointer to the input mesh
     * \param[in] dimension If set, uses only the dimension first
     *  coordinates in mesh, else dimension is determined
     *  by mesh->dimension().
     * \param[in] delaunay factory name of the implementation of
     *  Delaunay triangulation. Default uses ANN and radius
     *  of security.
     */
    CentroidalVoronoiTesselation(
        Mesh* mesh,
        coord_index_t dimension = 0,
        const std::string& delaunay = "default"
    );

    /**
     * \brief Constructs a new CentroidalVoronoiTesselation.
     * \details This constructor should be used when the coordinates of
     *  the mesh are not related with R3.
     * \param[in] mesh a pointer to the input mesh
     * \param[in] R3_embedding (dimension = mesh->nb_vertices()):
     *  coordinates of the mesh vertices in R3. Ignored
     *  if size is zero.
     * \param[in] dimension If set, uses only the dimension first
     *  coordinates in mesh, else dimension is determined
     *  by mesh->dimension().
     * \param[in] delaunay factory name of the implementation of
     *  Delaunay triangulation. delaunay="default" uses
     *  ANN and radius of security.
     */
    CentroidalVoronoiTesselation(
        Mesh* mesh,
        const vector<vec3>& R3_embedding, coord_index_t dimension = 0,
        const std::string& delaunay = "default"
    );

    /**
     * \brief Destructor
     */
    virtual ~CentroidalVoronoiTesselation();

    /**
     * \brief Computes a random initial sampling of the surface in nD.
     *
     * \details This initial sampling (of low quality/regularity) needs to
     * be further optimized (using Lloyd_iterations() and
     *  Newton_iterations()).
     *
     * \param[in] nb_samples number of points to generate in the sampling
     * \param[in] verbose if set, display message
     */
    bool compute_initial_sampling(index_t nb_samples, bool verbose=false);

    /**
     * \brief Initializes the points with a user-specified vector.
     *
     * \param[in] nb_points number of points in \p points
     * \param[in] points (size = dimension()*nb_points):
     *  user-defined initialization. It is copied into
     *  an internal vector
     */
    void set_points(index_t nb_points, const double* points);

    /**
     * \brief Changes the number of points.
     * \param[in] nb_points new number of points
     * \details Resizes the internal vector used to store the points
     */
    void resize_points(index_t nb_points);

    /**
     * \brief Relaxes the points with Lloyd's algorithm.
     * \details It is in general less efficient than Newton, but more
     *  resistant to heterogeneous point distribution. Therefore a
     *  small number of Lloyd iterations may be used right after
     *  a call to compute_initial_sampling() to regularize
     *  the point set before calling Newton_iterations().
     * \param[in] nb_iter number of iterations
     */
    virtual void Lloyd_iterations(index_t nb_iter);

    /**
     * \brief Relaxes the points with Newton-Lloyd's algorithm.
     * \param[in] nb_iter number of iterations
     * \param[in] m number of evaluations used for Hessian approximation
     */
    virtual void Newton_iterations(index_t nb_iter, index_t m = 7);

    /**
     * \brief Computes the surfacic mesh (using the current points).
     * \param[out] mesh the computed surface
     * \param[in] multinerve If set, does topology control (uses
     *  the dual of the connected components of the RVD).
     */
    void compute_surface(Mesh* mesh, bool multinerve = true);

    /**
     * \brief Computes the volumetric mesh (using the current points).
     * \param[out] mesh the computed volumetric mesh
     * \pre volumetric()
     */
    void compute_volume(Mesh* mesh);

    /**
     * \brief Specifies whether a progress bar should be used.
     * \param[in] x If set, shows iterations using a "progress bar".
     */
    void set_show_iterations(bool x) {
        show_iterations_ = x;
    }

    /**
     * \brief Specifies whether centroids of Voronoi cells should be used.
     * \param[in] x If set (default = true), compute_surface() replaces
     *  the vertices with the centroids of the
     *  connected components of the restricted Voronoi cells.
     */
    void set_use_RVC_centroids(bool x) {
        use_RVC_centroids_ = x;
    }

    /**
     * \brief Specifies whether constrained mode should be used.
     * \param[in] x If set (default = false), compute_surface() projects
     * the vertices onto the input surface.
     */
    void set_constrained_cvt(bool x) {
        constrained_cvt_ = x;
    }

    /**
     * Returns the input mesh.
     */
    Mesh* mesh() {
        return mesh_;
    }

    /**
     * Returns the Delaunay triangulation.
     */
    Delaunay* delaunay() {
        return delaunay_;
    }

    /**
     * Returns the RestrictedVoronoiDiagram.
     */
    RestrictedVoronoiDiagram* RVD() {
        return RVD_;
    }

    /**
     * \brief Restricts computation to a part of the input mesh.
     * \details The part of the input mesh should be specified as
     *    a contiguous range of facet indices.
     * \param[in] facets_begin first facet in the range
     * \param[in] facets_end one past last facet in the range
     */
    void set_facets_range(index_t facets_begin, index_t facets_end) {
        RVD_->set_facets_range(facets_begin, facets_end);
    }

    /**
     * \brief Makes this CentroidalVoronoiTesselation the current one.
     * \details The Optimizer uses global variables, therefore there can
     *  be only one CentroidalVoronoiTesselation simultaneously active.
     *  This function can be used to change the currently active
     *  CentroidalVoronoiTesselation.
     * \note Most users will not need to use this function.
     * \pre There is no current CentroidalVoronoiTesselation.
     */
    void make_current() {
        geo_assert(instance_ == nullptr);
        instance_ = this;
    }

    /**
     * \brief Resets the current CentroidalVoronoiTesselation to nullptr.
     * \details The Optimizer uses global variables, therefore there can
     *  be only one CentroidalVoronoiTesselation simultaneously active.
     *  This function can be used to change the currently active
     *  CentroidalVoronoiTesselation.
     * \note Most users will not need to use this function.
     * \pre This CentroidalVoronoiTesselation is the current one.
     */
    void done_current() {
        geo_assert(instance_ == this);
        instance_ = nullptr;
    }

    public:
    /**
     * \brief Callback for the numerical solver.
     * \details Evaluates the objective function and its gradient.
     * \param[in] n number of variables
     * \param[in] x current value of the variables
     * \param[out] f current value of the objective function
     * \param[out] g gradient of the objective function
     */
    static void funcgrad_CB(
        index_t n, double* x, double& f, double* g
    );

    /**
     * \brief Callback for the numerical solver.
     * \details Updates the progress bar.
     * \param[in] n number of variables
     * \param[in] x current value of the variables
     * \param[in] f current value of the objective function
     * \param[in] g gradient of the objective function
     * \param[in] gnorm norm of the gradient of the objective function
     */
    static void newiteration_CB(
        index_t n, const double* x, double f, const double* g, double gnorm
    );

    /**
     * \brief Sets a client for the progress bars.
     * \param[in] progress the ProgressTask.
     */
    void set_progress_logger(ProgressTask* progress) {
        progress_ = progress;
    }

    /**
     * \brief Gets the dimension of the points.
     * \details Can be smaller than the dimension of the mesh.
     */
    coord_index_t dimension() const {
        return dimension_;
    }

    /**
     * \brief Gets the number of points to be optimized.
     */
    index_t nb_points() const {
        return index_t(points_.size() / dimension_);
    }

    /**
     * \brief Gets the representation of a point in R3.
     * \param[in] p index of the point
     * \return a const reference to the 3d version of the point
     * \pre p < nb_points()
     */
    const vec3& R3_embedding(index_t p) const {
        return RVD_->R3_embedding(p);
    }

    /**
     * \brief Returns the representation of a point in embedding space.
     * \param[in] p index of the point
     * \return a pointer to the coordinates of the point
     * \pre p < nb_points()
     */
    double* embedding(index_t p) {
        geo_debug_assert(p < nb_points());
        return &(points_[0]) + dimension_ * p;
    }

    /**
     * \brief Tests whether volumetric mode is used.
     */
    bool volumetric() const {
        return RVD_->volumetric();
    }

    /**
     * \brief Sets volumetric mode.
     * \param[in] x if true, volumetric mode is used, otherwise
     *  surfacic mode is used.
     */
    void set_volumetric(bool x) {
        RVD_->set_volumetric(x);
    }

    /**
     * \brief Tests whether a point is locked.
     * \details A locked point is constrained to stay at the same position
     *    during the optimization.
     * \param[in] i index of the point
     * \pre i < nb_points()
     */
    bool point_is_locked(index_t i) const {
        geo_debug_assert(
            point_is_locked_.size() == 0 || i < point_is_locked_.size()
        );
        return point_is_locked_.size() != 0 && point_is_locked_[i];
    }

    /**
     * \brief Locks a point.
     * \details A locked point is constrained to stay at the same position
     *    during the optimization.
     * \param[in] i index of the point
     * \pre i < nb_points()
     */
    void lock_point(index_t i) {
        geo_debug_assert(i < nb_points());
        if(point_is_locked_.size() != nb_points()) {
            point_is_locked_.resize(nb_points(), false);
        }
        point_is_locked_[i] = true;
    }

    /**
     * \brief Unlocks a point.
     * \details A locked point is constrained to stay at the same position
     *    during the optimization.
     * \param[in] i index of the point
     * \pre i < nb_points()
     */
    void unlock_point(index_t i) {
        geo_debug_assert(i < nb_points());
        if(
            point_is_locked_.size() != nb_points()
        ) {
            point_is_locked_.resize(nb_points(), false);
        }
        point_is_locked_[i] = false;
    }

    /**
     * \brief Unlocks all the points.
     * \details A locked point is constrained to stay at the same position
     *    during the optimization.
     */
    void unlock_all_points() {
        point_is_locked_.clear();
    }

    protected:
    /**
     * \brief Callback for the numerical solver.
     * \details Updates the progress bar.
     */
    virtual void newiteration();

    /**
     * \brief Computes the objective function and its gradient.
     * \param[in] n number of variables
     * \param[in] x current value of the variables
     * \param[out] f current value of the objective function
     * \param[out] g gradient of the objective function
     */
    virtual void funcgrad(index_t n, double* x, double& f, double* g);

    /**
     * \brief Constrains the locked points.
     * \details Zeroes the gradient relative to the components
     *  of locked points.
     * \param[in,out] g gradient of the objective function
     */
    void constrain_points(double* g) const;

    /**
     * \brief Computes the 3d representation of the Nd points.
     * \details It projects the points onto the Nd surface, then recovers
     *  the 3d coordinates by barycentric interpolation.
     */
    void compute_R3_embedding();

    static CentroidalVoronoiTesselation* instance_;
    bool show_iterations_;
    coord_index_t dimension_;
    Delaunay_var delaunay_;
    RestrictedVoronoiDiagram_var RVD_;
    Mesh* mesh_;

    vector<double> points_;
    vector<vec3> points_R3_;
    vector<bool> point_is_locked_;

    ProgressTask* progress_;
    index_t cur_iter_;
    index_t nb_iter_;

    bool is_projection_;   /**< the Nd -> 3d transform is a projection */
    bool constrained_cvt_;
    bool use_RVC_centroids_;

    IntegrationSimplex_var simplex_func_;
    /**< \brief Integration simplex used by custom codes, e.g. LpCVT */

    private:
    /** \brief Forbids construction by copy. */
    CentroidalVoronoiTesselation(const thisclass& rhs);

    /** \brief Forbids assignment. */
    thisclass& operator= (const thisclass& rhs);
    };
}

#endif
