// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifdef USE_MPI
#include <fstream>
#include <boost/mpi.hpp>
#include <memory>
#include <functional>
#include <boost/lexical_cast.hpp>
#include <Eigen/Dense>
#include "geners/BinaryFileArchive.hh"
#include "StOpt/core/grids/FullGrid.h"
#include "StOpt/regression/LocalConstRegression.h"
#include "StOpt/regression/GridAndRegressedValue.h"
#include "StOpt/dp/FinalStepDPDist.h"
#include "StOpt/dp/TransitionStepDPDist.h"
#include "StOpt/core/parallelism/reconstructProc0Mpi.h"
#include "test/c++/tools/dp/OptimizePortfolioDP.h"

using namespace std;
using namespace Eigen;

double  DynamicProgrammingPortfolioDist(const shared_ptr<StOpt::FullGrid> &p_grid,
                                        const shared_ptr<OptimizePortfolioDP> &p_optimize,
                                        const ArrayXi &p_nbMesh,
                                        const function<double(const int &, const ArrayXd &, const ArrayXd &)>  &p_funcFinalValue,
                                        const ArrayXd &p_initialPortfolio,
                                        const string   &p_fileToDump,
                                        const bool &p_bOneFile,
                                        const boost::mpi::communicator &p_world
                                       )
{
    // initialize simulation
    p_optimize->initializeSimulation();
    // store regressor
    shared_ptr<StOpt::LocalConstRegression> regressorPrevious;

    // store final regressed values in  object valuesStored
    shared_ptr< vector< ArrayXXd > > valuesStored = make_shared< vector<ArrayXXd> >(p_optimize->getNbRegime());
    {
        vector< shared_ptr< ArrayXXd > >  valuesPrevious = StOpt::FinalStepDPDist(p_grid, p_optimize->getNbRegime(), p_optimize->getDimensionToSplit(), p_world)(p_funcFinalValue, *p_optimize->getCurrentSim());
        // regressor operator
        regressorPrevious = make_shared<StOpt::LocalConstRegression>(false, *p_optimize->getCurrentSim(), p_nbMesh);
        for (int iReg = 0; iReg < p_optimize->getNbRegime(); ++iReg)
            (*valuesStored)[iReg] = regressorPrevious->getCoordBasisFunctionMultiple(valuesPrevious[iReg]->transpose()).transpose();
    }
    string toDump = p_fileToDump ;
    // test if one file generated
    if (!p_bOneFile)
        toDump +=  "_" + boost::lexical_cast<string>(p_world.rank());
    shared_ptr<gs::BinaryFileArchive> ar;
    if ((!p_bOneFile) || (p_world.rank() == 0))
        ar = make_shared<gs::BinaryFileArchive>(toDump.c_str(), "w");
    // name for object in archive
    string nameAr = "OptimizePort";
    // iterate on time steps
    for (int iStep = 0; iStep < p_optimize->getNbStep(); ++iStep)
    {
        // step backward for simulations
        p_optimize->oneStepBackward();
        // create regressor at the given date
        bool bZeroDate = (iStep == p_optimize->getNbStep() - 1);
        shared_ptr<StOpt::LocalConstRegression> regressorCur = make_shared<StOpt::LocalConstRegression>(bZeroDate, *p_optimize->getCurrentSim(), p_nbMesh);
        // transition object
        StOpt::TransitionStepDPDist transStep(p_grid, p_grid, regressorCur, regressorPrevious, p_optimize, p_world);
        pair< shared_ptr< vector< ArrayXXd> >, shared_ptr< vector< ArrayXXd > > > valuesAndControl = transStep.oneStep(*valuesStored);
        // dump control values
        transStep.dumpValues(ar, nameAr, iStep,  *valuesAndControl.second, p_bOneFile);
        valuesStored = valuesAndControl.first;
        // shift regressor
        regressorPrevious = regressorCur;
    }
    // interpolate at the initial stock point and initial regime( 0 here)  (take first particle)
    shared_ptr<ArrayXXd> topRows = make_shared<ArrayXXd>((*valuesStored)[0].topRows(1));
    return StOpt::reconstructProc0Mpi(p_initialPortfolio, p_grid, topRows, p_optimize->getDimensionToSplit(), p_world).mean();
}
#endif
