// Copyright (C) 2017 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#include <Eigen/SVD>
#include <Eigen/Cholesky>
#include "StOpt/regression/LocalGridKernelRegression.h"
#include "StOpt/regression/gridKernelConstruction.h"
#include "StOpt/regression/gridKernelRegression.h"
#include <iostream>

using namespace std ;
using namespace Eigen ;

namespace StOpt
{

LocalGridKernelRegression::LocalGridKernelRegression(const bool &p_bZeroDate,
        const ArrayXXd  &p_particles,
        const double &p_coeffBandWidth,
        const double   &p_coefNbGridPoint,
        const bool &p_bLinear):
    BaseRegression(p_bZeroDate, p_particles, true),
    m_iSort(p_particles.rows(), p_particles.cols()), m_xG(p_particles.rows(), p_particles.cols()),  m_coeffBandWidth(p_coeffBandWidth), m_coefNbGridPoint(p_coefNbGridPoint), m_bLinear(p_bLinear)
{
    if (!p_bZeroDate)
        createGrid();

}


void LocalGridKernelRegression::updateSimulations(const bool &p_bZeroDate, const ArrayXXd &p_particles)
{
    BaseRegression::updateSimulationsBase(p_bZeroDate, p_particles);
    if (!p_bZeroDate)
    {
        m_iSort.resize(p_particles.rows(), p_particles.cols());
        m_xG.resize(p_particles.rows(), p_particles.cols());
        createGrid();
    }
}


void LocalGridKernelRegression::createGrid()
{
    ArrayXi aN, aK;
    int dEff;

    preprocessData(m_particles, m_sing, m_coeffBandWidth, m_coefNbGridPoint, aN, aK, dEff);

    // sort
    ArrayXXd sX(m_particles.rows(), m_particles.cols());
    for (int id = 0 ; id <  m_particles.rows(); ++id)
    {
        vector<pair<double, int> > toSort(m_particles.cols());
        for (int is = 0; is < m_particles.cols(); ++is)
            toSort[is] = make_pair(m_particles(id, is), is);
        sort(toSort.begin(), toSort.end());
        for (int is = 0; is < m_particles.cols(); ++is)
        {
            m_iSort(id, is) = toSort[is].second;
            sX(id, is) = toSort[is].first;
        }
    }
    adaptiveBandwithNd(sX, m_iSort, aN, aK, m_h, m_z, m_zl, m_zr, m_g, m_xG);

}


ArrayXd  LocalGridKernelRegression::getCoordBasisFunction(const ArrayXd &p_fToRegress) const
{
    if (!BaseRegression::m_bZeroDate)
        return gridKernelRegressedValuesOnGrid(m_particles, p_fToRegress,  m_z, m_h,  m_g, m_xG, m_zl, m_zr, m_bLinear) ;
    else
    {
        ArrayXd retAverage(1);
        retAverage(0) = p_fToRegress.mean();
        return retAverage;
    }
}

ArrayXd  LocalGridKernelRegression::getCoordBasisFunctionStable(const ArrayXd &p_fToRegress) const
{

    if (!BaseRegression::m_bZeroDate)
        return gridKernelRegressedValuesOnGridStable(m_particles, p_fToRegress,  m_z, m_h,  m_g, m_xG, m_zl, m_zr, m_bLinear) ;
    else
    {
        ArrayXd retAverage(1);
        retAverage(0) = p_fToRegress.mean();
        return retAverage;
    }
}
ArrayXXd  LocalGridKernelRegression::getCoordBasisFunctionMultiple(const ArrayXXd &p_fToRegress) const
{
    if (!BaseRegression::m_bZeroDate)
    {
        vector< unique_ptr< ArrayXd> >   regressedLocVec;
        for (int ireg = 0; ireg < p_fToRegress.rows(); ++ireg)
        {
            regressedLocVec.push_back(make_unique<ArrayXd>(gridKernelRegressedValuesOnGrid(m_particles, p_fToRegress.row(ireg).transpose(),  m_z, m_h,  m_g, m_xG, m_zl, m_zr, m_bLinear)));
        }

        ArrayXXd regressed(p_fToRegress.rows(), regressedLocVec[0]->size());
        for (int ireg = 0; ireg < p_fToRegress.rows(); ++ireg)
            regressed.row(ireg) = regressedLocVec[ireg]->transpose();
        return regressed;
    }
    else
    {
        ArrayXXd retAverage(p_fToRegress.rows(), 1);
        for (int nsm = 0; nsm <  p_fToRegress.rows(); ++nsm)
            retAverage.row(nsm).setConstant(p_fToRegress.row(nsm).mean());
        return retAverage;
    }
}

ArrayXd  LocalGridKernelRegression::getAllSimulations(const ArrayXd &p_fToRegress) const
{
    if (!BaseRegression::m_bZeroDate)
    {
        return gridKernelRegressedValues(m_particles, p_fToRegress,  m_z, m_h,  m_g, m_xG, m_zl, m_zr, m_iSort, m_bLinear) ;
    }
    else
    {
        return ArrayXd::Constant(p_fToRegress.size(), p_fToRegress.mean());
    }
}

ArrayXXd  LocalGridKernelRegression::getAllSimulationsMultiple(const ArrayXXd &p_fToRegress) const
{
    if (!BaseRegression::m_bZeroDate)
    {
        ArrayXXd regressed(p_fToRegress.rows(), p_fToRegress.cols());
        for (int ireg = 0; ireg < p_fToRegress.rows(); ++ireg)
        {
            ArrayXd regressedLoc = gridKernelRegressedValues(m_particles, p_fToRegress.row(ireg).transpose(),  m_z, m_h,  m_g, m_xG, m_zl, m_zr, m_iSort, m_bLinear) ;
            regressed.row(ireg) = regressedLoc.transpose();
        }
        return regressed;
    }
    else
    {
        ArrayXXd ret(p_fToRegress.rows(), p_fToRegress.cols());
        for (int ism = 0; ism < p_fToRegress.rows(); ++ism)
            ret.row(ism).setConstant(p_fToRegress.row(ism).mean());
        return ret;
    }
}


ArrayXd LocalGridKernelRegression::reconstruction(const ArrayXd   &p_basisCoefficients) const
{
    if (!BaseRegression::m_bZeroDate)
        return fromGridValuesGetRegAllSim(m_particles, p_basisCoefficients, m_z, m_iSort);
    else
    {
        return ArrayXd::Constant(m_particles.cols(), p_basisCoefficients(0));
    }
}


ArrayXXd LocalGridKernelRegression::reconstructionMultiple(const ArrayXXd   &p_basisCoefficients) const
{
    if (!BaseRegression::m_bZeroDate)
    {
        ArrayXXd regressed(p_basisCoefficients.rows(), m_particles.cols());
        for (int ireg = 0; ireg < p_basisCoefficients.rows(); ++ireg)
        {
            ArrayXd regressedLoc = fromGridValuesGetRegAllSim(m_particles, p_basisCoefficients.row(ireg).transpose(), m_z, m_iSort) ;
            regressed.row(ireg) = regressedLoc.transpose();
        }
        return regressed;
    }
    else
    {
        ArrayXXd retValue(p_basisCoefficients.rows(), m_particles.cols());
        for (int nsm = 0; nsm < p_basisCoefficients.rows(); ++nsm)
            retValue.row(nsm).setConstant(p_basisCoefficients(nsm, 0));
        return retValue ;
    }
}

double  LocalGridKernelRegression::reconstructionASim(const int &p_isim, const ArrayXd   &p_basisCoefficients) const
{
    double ret ;
    if (!BaseRegression::m_bZeroDate)
    {
        ret = fromGridValuesGetRegASim(m_particles.col(p_isim), p_basisCoefficients, m_z);
    }
    else
    {
        ret = p_basisCoefficients(0);
    }
    return ret ;
}

double LocalGridKernelRegression::getValue(const ArrayXd   &p_coordinates,
        const ArrayXd   &p_coordBasisFunction)  const
{
    double ret  ;
    if (!BaseRegression::m_bZeroDate)
    {
        VectorXd x = p_coordinates.matrix();
        x = ((x.array() - m_meanX) / m_etypX).matrix();
        x = m_svdMatrix * x;
        ret =  fromGridValuesGetRegASim(x.array(), p_coordBasisFunction, m_z);
    }
    else
        ret =  p_coordBasisFunction(0);
    return ret ;
}


double LocalGridKernelRegression::getAValue(const ArrayXd &p_coordinates,  const ArrayXd &p_ptOfStock,
        const vector< shared_ptr<InterpolatorSpectral> > &p_interpFuncBasis) const
{
    if (!BaseRegression::m_bZeroDate)
    {
        VectorXd x = p_coordinates.matrix();
        x = ((x.array() - m_meanX) / m_etypX).matrix();
        x = m_svdMatrix * x;
        return  fromGridValuesGetRegASimOnBasis(x.array(), m_z, p_ptOfStock, p_interpFuncBasis);
    }
    else
    {
        return p_interpFuncBasis[0]->apply(p_ptOfStock);
    }
}
}
