// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#include <vector>
#include <memory>
#include <iostream>
#include <Eigen/Dense>
#include "StOpt/regression/LocalConstRegression.h"
#include "StOpt/regression/localConstMatrixOperation.h"
#include "StOpt/core/grids/InterpolatorSpectral.h"

using namespace std;
using namespace Eigen;

namespace StOpt
{

LocalConstRegression::LocalConstRegression(const ArrayXi &p_nbMesh, bool  p_bRotationAndRecale): LocalRegression(p_nbMesh, p_bRotationAndRecale) {}

LocalConstRegression::LocalConstRegression(const bool &p_bZeroDate,
        const ArrayXXd  &p_particles,
        const ArrayXi &p_nbMesh,
        bool  p_bRotationAndRecale) : LocalRegression(p_bZeroDate, p_particles, p_nbMesh, p_bRotationAndRecale)
{
    if ((!m_bZeroDate) && (p_nbMesh.size() != 0))
    {
        // regression matrix
        m_matReg = localConstMatrixCalculation(m_simToCell, m_mesh.cols());
    }
}

LocalConstRegression:: LocalConstRegression(const   LocalConstRegression &p_object): LocalRegression(p_object),
    m_matReg(p_object.getMatReg())

{}


void LocalConstRegression::updateSimulations(const bool &p_bZeroDate, const ArrayXXd  &p_particles)
{
    BaseRegression::updateSimulationsBase(p_bZeroDate, p_particles);
    m_simToCell.resize(p_particles.cols());
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        if (p_particles.rows() != m_nbMesh.size())
        {
            cout << " Dimension nd  of particles of size (nd, nbSimu) is " << p_particles.rows();
            cout << " and   should be equal to the size of the array describing the mesh refinement " << m_nbMesh.transpose() << endl ;
            abort();
        }

        meshCalculationLocalRegression(m_particles, m_nbMesh, m_simToCell, m_mesh, m_mesh1D);

        // regression matrix
        m_matReg = localConstMatrixCalculation(m_simToCell, m_mesh.cols());
    }
    else
    {
        m_simToCell.setConstant(0);
    }
}

ArrayXd LocalConstRegression::getCoordBasisFunction(const ArrayXd &p_fToRegress) const
{
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        Map<const ArrayXXd>  fToRegress2D(p_fToRegress.data(), 1, p_fToRegress.size());
        ArrayXXd secMember2D = localConstSecondMemberCalculation(m_simToCell, m_mesh.cols(), fToRegress2D);
        // output
        Map<const ArrayXd > secMember(secMember2D.data(), secMember2D.size());
        ArrayXd ret = secMember / m_matReg;
        return ret;
    }
    else
    {
        ArrayXd retAverage(1);
        retAverage(0) = p_fToRegress.mean();
        return retAverage;
    }
}

ArrayXXd LocalConstRegression::getCoordBasisFunctionMultiple(const ArrayXXd &p_fToRegress) const
{
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        ArrayXXd secMember = localConstSecondMemberCalculation(m_simToCell, m_mesh.cols(), p_fToRegress);
        ArrayXXd regFunc(p_fToRegress.rows(), secMember.cols());
        for (int im = 0; im < m_matReg.size(); ++im)
            for (int nsm = 0; nsm <  p_fToRegress.rows(); ++nsm)
                regFunc(nsm, im) = secMember(nsm, im) / m_matReg(im);;
        return regFunc;
    }
    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 LocalConstRegression::reconstruction(const ArrayXd &p_basisCoefficients) const
{
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        Map<const ArrayXXd> BasisCoefficients(p_basisCoefficients.data(), 1, p_basisCoefficients.size());
        return localConstReconstruction(m_simToCell, BasisCoefficients).row(0);
    }
    else
        return ArrayXd::Constant(m_simToCell.size(), p_basisCoefficients(0));
}

ArrayXXd LocalConstRegression::reconstructionMultiple(const ArrayXXd &p_basisCoefficients) const
{
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        return localConstReconstruction(m_simToCell, p_basisCoefficients);
    }
    else
    {
        ArrayXXd retValue(p_basisCoefficients.rows(), m_simToCell.size());
        for (int nsm = 0; nsm < p_basisCoefficients.rows(); ++nsm)
            retValue.row(nsm).setConstant(p_basisCoefficients(nsm, 0));
        return retValue ;
    }
}

double LocalConstRegression::reconstructionASim(const int &p_isim, const ArrayXd   &p_basisCoefficients) const
{
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        return p_basisCoefficients(m_simToCell(p_isim)) ;
    }
    else
    {
        return p_basisCoefficients(0);
    }
}


ArrayXd LocalConstRegression::getAllSimulations(const ArrayXd &p_fToRegress) const
{
    if ((m_bZeroDate) || (m_nbMesh.size() == 0))
        return ArrayXd::Constant(p_fToRegress.size(), p_fToRegress.mean());

    Map<const ArrayXXd>  fToRegress2D(p_fToRegress.data(), 1, p_fToRegress.size());
    ArrayXXd BasisCoefficients = getCoordBasisFunctionMultiple(fToRegress2D);
    ArrayXXd  condEspectationValues = localConstReconstruction(m_simToCell,  BasisCoefficients);
    return condEspectationValues.row(0);
}

ArrayXXd LocalConstRegression::getAllSimulationsMultiple(const ArrayXXd &p_fToRegress) const
{
    if ((m_bZeroDate) || (m_nbMesh.size() == 0))
    {
        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;
    }
    ArrayXXd BasisCoefficients = getCoordBasisFunctionMultiple(p_fToRegress);
    return localConstReconstruction(m_simToCell, BasisCoefficients);
}

double LocalConstRegression::getValue(const ArrayXd &p_coordinates, const ArrayXd &p_coordBasisFunction) const
{
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        // rotation
        VectorXd x = m_svdMatrix * ((p_coordinates - m_meanX) / m_etypX).matrix();
        Map<const ArrayXXd> coordBasisFunction2D(p_coordBasisFunction.data(), 1, p_coordBasisFunction.size());
        return localConstReconstructionOnePoint(x.array(), m_mesh1D, coordBasisFunction2D)(0);
    }
    else
    {
        return p_coordBasisFunction(0);
    }
}

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

ArrayXd LocalConstRegression::getCoordBasisFunctionOneCell(const int &p_iCell, const ArrayXd &p_fToRegress) const
{
    ArrayXd retAverage(1);
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        retAverage(0) = 0. ;
        for (size_t i = 0; i <   m_simulBelongingToCell[p_iCell]->size(); ++i)
            retAverage(0) += p_fToRegress((*m_simulBelongingToCell[p_iCell])[i]);
        retAverage(0) /= m_simulBelongingToCell[p_iCell]->size();
    }
    else
    {
        retAverage(0) = p_fToRegress.mean();
    }
    return retAverage;

}

ArrayXXd LocalConstRegression::getCoordBasisFunctionMultipleOneCell(const int &p_iCell, const ArrayXXd &p_fToRegress) const
{
    ArrayXXd retAverage = ArrayXXd::Zero(p_fToRegress.rows(), 1);
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        for (size_t i = 0; i <   m_simulBelongingToCell[p_iCell]->size(); ++i)
            for (int j = 0; j < p_fToRegress.rows(); ++j)
                retAverage(j, 0) += p_fToRegress(j, (*m_simulBelongingToCell[p_iCell])[i]);
        retAverage /= m_simulBelongingToCell[p_iCell]->size();
    }
    else
    {
        for (int nsm = 0; nsm <  p_fToRegress.rows(); ++nsm)
            retAverage.row(nsm).setConstant(p_fToRegress.row(nsm).mean());
    }
    return retAverage;
}

Eigen::ArrayXd LocalConstRegression::getValuesOneCell(const Eigen::ArrayXd &, const int &p_cell, const Eigen::ArrayXXd   &p_foncBasisCoef) const
{
    if ((!m_bZeroDate) && (m_nbMesh.size() != 0))
    {
        return  p_foncBasisCoef.col(p_cell);
    }
    else
        return  p_foncBasisCoef.col(0);
}
}
