// Copyright (C) 2025 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef USE_MPI
#define BOOST_TEST_MODULE testGasStorageCutGridAdapt1D
#endif
#define BOOST_TEST_DYN_LINK
#ifdef USE_MPI
#include <boost/mpi.hpp>
#endif
#define _USE_MATH_DEFINES
#include <math.h>
#include <functional>
#include <memory>
#include <boost/timer/timer.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/timer/timer.hpp>
#include <Eigen/Dense>
#include "StOpt/core/grids/OneDimRegularSpaceGrid.h"
#include "StOpt/core/grids/OneDimData.h"
#include "StOpt/regression/LocalLinearRegression.h"
#include "StOpt/core/grids/GridAdapt1DGeners.h"
#include "StOpt/regression/LocalLinearRegressionGeners.h"
#include "StOpt/core/utils/constant.h"
#include "test/c++/tools/simulators/MeanRevertingSimulator.h"
#include "test/c++/tools/dp/DynamicProgrammingByRegressionCutGridAdapt1D.h"
#include "test/c++/tools/dp/SimulateRegressionCutGridAdapt1D.h"
#include "test/c++/tools/dp/OptimizeGasStorageCutGridAdapt1D.h"

using namespace std;
using namespace Eigen ;
using namespace StOpt;

double accuracyClose = 1.;
double accuracyEqual = 0.0001;





#if defined   __linux
#include <fenv.h>
#define enable_abort_on_floating_point_exception() feenableexcept(FE_DIVBYZERO | FE_INVALID)
#endif

double   testGasStorageCutGridAdapt1D(vector<shared_ptr< GridAdapt1D > > &p_grids, const double &p_maxLevelStorage, const size_t &p_nbsimulOpt, const size_t &p_nbsimulSim, const int &p_nbMesh, const bool &p_bGatherConcaveGrid, const bool &p_bRefine)
{
#ifdef USE_MPI
    boost::mpi::communicator world;
#endif
    // storage
    /////////
    double injectionRateStorage = 20000;
    double withdrawalRateStorage = 15000;
    double injectionCostStorage = 0.35;
    double withdrawalCostStorage = 0.35;

    double maturity = 1.;
    size_t nstep = p_grids.size();
    // define  a time grid
    shared_ptr<OneDimRegularSpaceGrid> timeGrid = make_shared< OneDimRegularSpaceGrid>(0., maturity / nstep, nstep);
    // future values
    shared_ptr<vector< double > > futValues(new vector<double>(nstep + 1));
    // periodicity factor
    int iPeriod = 52;
    for (size_t i = 0; i < nstep + 1; ++i)
        (*futValues)[i] = 50. + 5 * sin((M_PI * i * iPeriod) / nstep);
    // define the future curve
    shared_ptr<OneDimData<OneDimRegularSpaceGrid, double> > futureGrid = make_shared< OneDimData< OneDimRegularSpaceGrid, double> >(timeGrid, futValues);
    // one dimensional factors
    int nDim = 1;
    VectorXd sigma = VectorXd::Constant(nDim, 0.00001);
    VectorXd mr = VectorXd::Constant(nDim, 0.29);
    // no actualization
    double r = 0 ;
    // a backward simulator
    ///////////////////////
    bool bForward = false;
    shared_ptr< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > backSimulator =   make_shared<MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > (futureGrid, sigma, mr, r, maturity, nstep, p_nbsimulOpt, bForward);
    // optimizer
    ///////////
    shared_ptr< OptimizeGasStorageCutGridAdapt1D< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> >  > > storage =
        make_shared<  OptimizeGasStorageCutGridAdapt1D< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> >  > > (injectionRateStorage, withdrawalRateStorage, injectionCostStorage, withdrawalCostStorage, p_bGatherConcaveGrid, p_bRefine);

    // regressor
    ///////////
    ArrayXi nbMesh = ArrayXi::Constant(1, p_nbMesh);
    shared_ptr< BaseRegression > regressor(new LocalLinearRegression(nbMesh));

    // initial values
    ArrayXd initialStock = ArrayXd::Constant(1, p_maxLevelStorage);

    vector< shared_ptr<GridAdaptBase> > gridsE(p_grids.size() + 1);
    // fake initial grid
    gridsE[0] = make_shared<GridAdapt1D>(initialStock(0) - tiny, initialStock(0), 1);
    for (size_t i = 0; i < p_grids.size(); ++i)
        gridsE[i + 1] = p_grids[i];

    // Optimize
    ///////////
    string fileToDump = "CondCut";
    // link the simulations to the optimizer
    storage->setSimulator(backSimulator);
    double valueOptim;
    {
        boost::timer::auto_cpu_timer t;
        valueOptim =  DynamicProgrammingByRegressionCutGridAdapt1D(gridsE, storage, regressor,  fileToDump
#ifdef USE_MPI
                      , world
#endif
                                                                  );
    }

    // a forward simulator
    ///////////////////////
    bForward = true;
    shared_ptr< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > forSimulator = make_shared< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > (futureGrid, sigma, mr, r, maturity, nstep, p_nbsimulSim, bForward);

    // link the simulations to the optimizer
    storage->setSimulator(forSimulator);
    double valSimu = SimulateRegressionCutGridAdapt1D(storage, initialStock,  fileToDump
#ifdef USE_MPI
                     , world
#endif
                                                     ) ;
#ifdef USE_MPIx
    if (world.rank() == 0)
#endif
        cout << " Optim " << valueOptim << " valSimu " << valSimu <<  endl ;
    BOOST_CHECK_CLOSE(valueOptim, valSimu, accuracyClose);

    return valueOptim;
}


BOOST_AUTO_TEST_CASE(testSimpleStorageCutNoMerge)
{
    // storage
    /////////
    double maxLevelStorage  = 40000;
    // grid
    //////
    int nGrid = 10;
    double xMin = 0.;
    double xMax = 40000.;
    size_t nbsimulOpt = 2;
    size_t nbsimulSim = 2 ;
    int nbMesh = 1 ;
    // number of time step
    int nStep = 20 ;
    vector<shared_ptr<GridAdapt1D>> theGrids;
    theGrids.reserve(nStep);
    for (int i = 0; i < nStep; ++i)
        theGrids.push_back(make_shared<GridAdapt1D>(xMin, xMax, nGrid));
    bool bGatherConcaveGrids = false ; // dont merge grids
    bool bRefine = false; // don't test refinement
    double valOptim = testGasStorageCutGridAdapt1D(theGrids, maxLevelStorage, nbsimulOpt, nbsimulSim, nbMesh, bGatherConcaveGrids, bRefine);
    cout << "val optim " << valOptim << endl ;

}

BOOST_AUTO_TEST_CASE(testSimpleStorageCutMerge)
{
    // storage
    /////////
    double maxLevelStorage  = 40000;
    // grid
    //////
    int nGrid = 10;
    double xMin = 0.;
    double xMax = 40000.;
    size_t nbsimulOpt = 2;
    size_t nbsimulSim = 2 ;
    int nbMesh = 1 ;
    // number of time step
    int nStep = 20 ;
    vector<shared_ptr<GridAdapt1D>> theGrids;
    theGrids.reserve(nStep);
    for (int i = 0; i < nStep; ++i)
        theGrids.push_back(make_shared<GridAdapt1D>(xMin, xMax, nGrid));
    bool bGatherConcaveGrids = false ; // dont merge grids
    bool bRefine = false; // don't test refinement
    double valOptim = testGasStorageCutGridAdapt1D(theGrids, maxLevelStorage, nbsimulOpt, nbsimulSim, nbMesh, bGatherConcaveGrids, bRefine);
    cout << "val optim " << valOptim << endl ;
    bGatherConcaveGrids  = true;
    double valOptimSecond = testGasStorageCutGridAdapt1D(theGrids, maxLevelStorage, nbsimulOpt, nbsimulSim, nbMesh, bGatherConcaveGrids, bRefine);
    BOOST_CHECK_CLOSE(valOptim, valOptimSecond, accuracyClose);
}

BOOST_AUTO_TEST_CASE(testSimpleStorageCutMergeWithRefinement)
{
    // storage
    /////////
    double maxLevelStorage  = 40000;
    // grid
    //////
    int nGrid = 10;
    double xMin = 0.;
    double xMax = 40000.;
    size_t nbsimulOpt = 2;
    size_t nbsimulSim = 2 ;
    int nbMesh = 1 ;
    // number of time step
    int nStep = 20 ;
    vector<shared_ptr<GridAdapt1D>> theGrids;
    theGrids.reserve(nStep);
    for (int i = 0; i < nStep; ++i)
        theGrids.push_back(make_shared<GridAdapt1D>(xMin, xMax, nGrid));
    bool bGatherConcaveGrids = false ; // dont merge grids
    bool bRefine = true; // refine meshes
    double valOptim = testGasStorageCutGridAdapt1D(theGrids, maxLevelStorage, nbsimulOpt, nbsimulSim, nbMesh, bGatherConcaveGrids, bRefine);
    cout << "val optim " << valOptim << endl ;
    bGatherConcaveGrids  = true;
    double valOptimSecond = testGasStorageCutGridAdapt1D(theGrids, maxLevelStorage, nbsimulOpt, nbsimulSim, nbMesh, bGatherConcaveGrids, bRefine);
    BOOST_CHECK_CLOSE(valOptim, valOptimSecond, accuracyClose);
}


#ifdef USE_MPI
// (empty) Initialization function. Can't use testing tools here.
bool init_function()
{
    return true;
}

int main(int argc, char *argv[])
{
#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif
    boost::mpi::environment env(argc, argv);
    return ::boost::unit_test::unit_test_main(&init_function, argc, argv);
}

#endif
