// Copyright (C) 2026 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef SIMULATEPRICEANDCONTROLUSINGCONTROLSTORAGESENSI_H
#define SIMULATEPRICEANDCONTROLUSINGCONTROLSTORAGESENSI_H
#include <functional>
#include <fstream>
#include <tuple>
#ifdef USE_MPI
#include <boost/mpi.hpp>
#endif
#include <Eigen/Dense>
#include <geners/BinaryFileArchive.hh>
#include <StOpt/core/grids/FullGrid.h>
#include <StOpt/dp/SimulateStepRegressionControl.h>
#include "StOpt/dp/OptimizerDPBase.h"
#include "test/c++/tools/utils/Tunnel.h"
#include "test/c++/tools/simulators/MeanRevertingNDSimulator.h"


/* \file simulatePriceControlUsingControlSensi.h
 * Simulate the optimal control using the Control values calculated in  optimization
 *
 * \author Xavier warin
 */

using typeRetSPCUCS = std::tuple< Eigen::ArrayXXd, Eigen::ArrayXXd, double,  Eigen::ArrayXd, Eigen::Array2d> ;

/// \brief Simulate the storage management using the Control values calculated by Dynamic Programming
///        Residual stock in storage has no value
/// \param p_tunnel    the tunnel for the storage
/// \param p_optimize  the optimizer
/// \param p_pointStock initial position in storage
/// \param p_world      MPI communicator
typeRetSPCUCS simulatePriceControlUsingControlStorageSensi(const Tunnel &p_tunnel,
        const std::shared_ptr<StOpt::OptimizerDPBase  > &p_optimizer,
        const Eigen::ArrayXd &p_pointStock,
        const std::string    &p_fileForControl
#ifdef USE_MPI

        , const boost::mpi::communicator &p_world
#endif
                                                          )
{
    // from the optimizer get back the simulator
    std::shared_ptr< MeanRevertingNDSimulator> simulator =  std::static_pointer_cast<MeanRevertingNDSimulator>(p_optimizer->getSimulator());
    int nbStep = simulator->getNbStep();
    std::vector< StOpt::StateWithStocks<double>> states;
    states.reserve(simulator->getNbSimul());
    {
        Eigen::ArrayXXd initPart =  simulator->getParticles();
        for (int is = 0; is < simulator->getNbSimul(); ++is)
            states.push_back(StOpt::StateWithStocks<double>(0, p_pointStock, initPart.col(is)));
    }
    Eigen::ArrayXd stockNext = Eigen::ArrayXd::Constant(simulator->getNbSimul(), p_pointStock(0));

    // test if one file generated
    gs::BinaryFileArchive ar(p_fileForControl.c_str(), "r");
    // name for continuation object in archive
    std::string nameAr = "Continuation";
    // gain function trajectories
    Eigen::ArrayXXd gainAndContFunction = Eigen::ArrayXXd::Zero(p_optimizer->getSimuFuncSize(), simulator->getNbSimul());
    std::shared_ptr< std::fstream> fileForResultsPosition;
    std::shared_ptr< std::fstream> fileForResultsPrice ;

    Eigen::ArrayXXd  prices(simulator->getNbSimul(), nbStep);
    Eigen::ArrayXXd  control(simulator->getNbSimul(), nbStep);
    Eigen::ArrayXd delta(nbStep);
    Eigen::Array2d derVol = Eigen::Array2d::Zero();

    // use one Control file
    // bool bOneFile = true;
    for (int istep = 0; istep < nbStep; ++istep)
    {

        prices.col(istep) = simulator->getSpot();

        // grid on next step
        // std::shared_ptr<StOpt::FullGrid> gridNextCurr=  p_tunnel.getGrid(istep);
        std::shared_ptr<StOpt::FullGrid> gridNextStep =  p_tunnel.getGrid(istep + 1);
        // optimal control
        StOpt::SimulateStepRegressionControl(ar, istep, nameAr,  gridNextStep,  std::static_pointer_cast<StOpt::OptimizerDPBase>(p_optimizer)
#ifdef USE_MPI
                                             , p_world
#endif
                                            ).oneStep(states, gainAndContFunction);

        // step forward
        // new stochastic state for next step
        Eigen::ArrayXXd particules =  simulator->stepForwardAndGetParticles();
        // for next step : new stochastic state
        for (int is = 0; is < simulator->getNbSimul(); ++is)
            states[is].setStochasticRealization(particules.col(is));

        control.col(istep) = gainAndContFunction.row(1).transpose();

        // delta
        delta(istep) = gainAndContFunction.row(3).mean();
        // vol derivative
        derVol(0) += gainAndContFunction.row(4).mean();
        derVol(1) += gainAndContFunction.row(5).mean();
    }
    return std::make_tuple(prices, control, gainAndContFunction.row(0).mean(), delta, derVol);
}
#endif
