// Copyright (C) 2019 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef LOCALKMEANSREGRESSION_H
#define LOCALKMEANSREGRESSION_H
#include <vector>
#include <array>
#include <utility>
#include <Eigen/Dense>
#include "StOpt/regression/LocalAdaptCellRegression.h"


/** \file  LocalKMeansRegression.h
 *         K Means clustering algorithm for conditional expectation
 *         Here the algorithm is used dimension by dimension giving a K-D tree
 *         The methodology is very similar to constant local regressions
 *         Only the support of the nodes are calculated differently.
 *         At the opposite of LocalConstRegression object, cells are calculated to have equi-probability of the meshes.
 */
namespace StOpt
{
///  \class LocalKMeansRegression LocalKMeansRegression.h
class LocalKMeansRegression : public LocalAdaptCellRegression
{
private :

    /// \brief from d dimesnonal coordinate to one D
    /// \param p_nbMesh  number of mesh in each direction
    /// \param p_coord   coordinate of the mesh (size p_nbMesh.size())
    int fromCoordTo1D(const Eigen::ArrayXi &p_nbMesh,  const Eigen::ArrayXi &p_coord);

    /// \brief list of all mesh stating with some given coordinate
    /// \param p_nbMesh  number of mesh in each direction
    /// \param p_coord   coordinate of the mesh (size p_nbMesh.size()), only p_dim are taken into account*
    /// \param p_list    list of all cells with some given coordinates for the (p_dim+1) first coordinates
    void listOfAllMesh(const Eigen::ArrayXi &p_nbMesh,  const Eigen::ArrayXi &p_coord, const int &p_idim,  std::vector< int > &p_list);


    /// \brief Create a meshing with K-Means clustering in all dimensions
    /// \param p_idim   dimension of sorting
    /// \param p_particles set of of all particles
    /// \param p_index   index of all particles to sort
    /// \param p_nbMesh  number of mesh in each dimension
    /// \param p_coord   currnt coordinate of the cell
    /// \param p_mesh    Array ( dimension, nb of mesh) : for each mesh and each dimension min and max of mesh coordinates
    /// \param p_simulBelongingToCell  To each cell give the mesh belonging to the cell
    void  meshing(const int &p_idim, const Eigen::ArrayXXd  &p_particles,
                  const std::vector< int > &p_index,
                  const Eigen::ArrayXi &p_nbMesh,  const Eigen::ArrayXi &p_coord,
                  Eigen::Array< std::array< double, 2>, Eigen::Dynamic, Eigen::Dynamic >   &p_mesh,
                  std::vector<  std::shared_ptr< std::vector< int> > >    &p_simulBelongingToCell);


    /// \brief To a particle affect to cell number
    /// \param p_oneParticle  One point
    /// \return cell number
    int  particleToMesh(const Eigen::ArrayXd &p_oneParticle) const;

public :

    /// \brief Default ructor
    LocalKMeansRegression() {}

    /// \brief Constructor
    /// \param  p_nbMesh       discretization in each direction
    /// \param  p_bRotationAndRecale do we use SVD
    LocalKMeansRegression(const Eigen::ArrayXi   &p_nbMesh,  bool p_bRotationAndRecale = false);

    /// \brief Constructor for object constructed at each time step
    /// \param  p_bZeroDate          first date is 0?
    /// \param  p_particles          particles used for the meshes.
    ///                              First dimension  : dimension of the problem,
    ///                              second dimension : the  number of particles
    /// \param  p_nbMesh             discretization in each direction
    /// \param  p_bRotationAndRecale do we use SVD
    LocalKMeansRegression(const bool &p_bZeroDate,
                          const Eigen::ArrayXXd  &p_particles,
                          const Eigen::ArrayXi   &p_nbMesh,
                          bool p_bRotationAndRecale = false);

    /// \brief Second constructor , only to be used in simulation
    LocalKMeansRegression(const   bool &p_bZeroDate,
                          const  Eigen::ArrayXi &p_nbMesh,
                          const   Eigen::Array< std::array< double, 2>, Eigen::Dynamic, Eigen::Dynamic >   &p_mesh,
                          const   Eigen::ArrayXd &p_meanX,
                          const   Eigen::ArrayXd   &p_etypX,
                          const   Eigen::MatrixXd   &p_svdMatrix,
                          const   bool &p_bRotationAndRecale) ;

    /// \brief Copy constructor
    /// \param p_object object to copy
    LocalKMeansRegression(const LocalKMeansRegression   &p_object);

    /// \brief update the particles used in regression
    /// \param  p_bZeroDate    first date is 0?
    /// \param  p_particles    particles used for the meshes.
    ///                        First dimension  : dimension of the problem,
    ///                        second dimension : the  number of particles
    void updateSimulations(const bool &p_bZeroDate, const Eigen::ArrayXXd &p_particles);


    /// \brief conditional expectation basis function coefficient calculation
    /// \param  p_fToRegress  function to regress associated to each simulation used in optimization
    /// \return regression coordinates on the basis  (size : number of meshes multiplied by the dimension plus one)
    /// @{
    virtual Eigen::ArrayXd getCoordBasisFunction(const Eigen::ArrayXd &p_fToRegress) const ;
    ///@}
    /// \brief conditional expectation basis function coefficient calculation for multiple functions to regress
    /// \param  p_fToRegress  function to regress associated to each simulation used in optimization (size : number of functions to regress \times the number of Monte Carlo simulations)
    /// \return regression coordinates on the basis  (size :  number of function to regress  \times number of meshes multiplied by the dimension plus one)
    /// @{
    virtual Eigen::ArrayXXd getCoordBasisFunctionMultiple(const Eigen::ArrayXXd &p_fToRegress) const ;
    ///@}

    /// \brief conditional expectation calculation
    /// \param  p_fToRegress  simulations  to regress used in optimization
    /// \return regressed value function
    /// @{
    virtual Eigen::ArrayXd getAllSimulations(const Eigen::ArrayXd &p_fToRegress) const ;
    virtual Eigen::ArrayXXd getAllSimulationsMultiple(const Eigen::ArrayXXd &p_fToRegress) const ;
    ///@}

    /// \brief Use basis functions to reconstruct the solution
    /// \param p_basisCoefficients basis coefficients
    ///@{
    virtual Eigen::ArrayXd reconstruction(const Eigen::ArrayXd   &p_basisCoefficients) const;
    virtual Eigen::ArrayXXd reconstructionMultiple(const Eigen::ArrayXXd   &p_basisCoefficients) const ;
    /// @}

    /// \brief use basis function to reconstruct a given simulation
    /// \param p_isim               simulation number
    /// \param p_basisCoefficients  basis coefficients to reconstruct a given conditional expectation
    virtual double reconstructionASim(const int &p_isim, const Eigen::ArrayXd   &p_basisCoefficients) const ;

    /// \brief conditional expectation reconstruction
    /// \param  p_coordinates        coordinates to interpolate (uncertainty sample)
    /// \param  p_coordBasisFunction regression coordinates on the basis  (size: number of meshes multiplied by the dimension plus one)
    /// \return regressed value function reconstructed for each simulation
    virtual double getValue(const Eigen::ArrayXd   &p_coordinates,
                            const Eigen::ArrayXd   &p_coordBasisFunction) const ;


    /// \brief permits to reconstruct a function with basis functions coefficients values given on a grid
    /// \param  p_coordinates          coordinates  (uncertainty sample)
    /// \param  p_ptOfStock            grid point
    /// \param  p_interpFuncBasis      spectral interpolator to interpolate the basis functions  coefficients used in regression on the grid (given for each basis function)
    virtual double getAValue(const Eigen::ArrayXd &p_coordinates,  const Eigen::ArrayXd &p_ptOfStock,
                             const std::vector< std::shared_ptr<InterpolatorSpectral> > &p_interpFuncBasis) const ;

    /// \brief get the number of basis functions
    inline int getNumberOfFunction() const
    {
        if (m_bZeroDate)
            return 1;
        else
            return m_nbMesh.prod() ;
    }

    /// \brief Given a particle and the coordinates of the mesh it belongs to, get back the conditional expectation
    /// \param p_foncBasisCoef  function basis on the current cell (the row is the number of reconstruction to achieve, the columns the number of function basis)
    /// \return send back the array of conditional expectations
    Eigen::ArrayXd getValuesOneCell(const Eigen::ArrayXd &, const int &, const Eigen::ArrayXXd   &p_foncBasisCoef) const;

    /// \brief conditional expectation basis function coefficient calculation for a special cell
    /// \param  p_iCell     cell number
    /// \param  p_fToRegress  function to regress associated to each simulation used in optimization and corresponding to the cell
    /// \return regression coordinates on the basis  (size : the dimension of the problem plus one)
    /// @{
    Eigen::ArrayXd getCoordBasisFunctionOneCell(const int &p_iCell, const Eigen::ArrayXd &p_fToRegress) const ;
    Eigen::ArrayXXd getCoordBasisFunctionMultipleOneCell(const int &p_iCell, const Eigen::ArrayXXd &p_fToRegress) const ;
    ///@}


    /// \brief Clone the regressor
    std::shared_ptr<BaseRegression> clone() const
    {
        return std::static_pointer_cast<BaseRegression>(std::make_shared<LocalKMeansRegression>(*this));
    }
};
}

#endif
