// Copyright (C) 2017 EDF
// All Rights Reserved
#include <memory>
#include <iostream>
#include <Eigen/Dense>
#include "StOpt/regression/BaseRegression.h"
#include "StOpt/core/grids/SpaceGrid.h"
#include "StOpt/core/grids/Interpolator.h"
#include "StOpt/core/utils/constant.h"

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


std::pair<ArrayXd, ArrayXXd> globalL2HedgeMinimize(const ArrayXXd &p_difS,
        const Eigen::ArrayXXd &p_asset,
        const ArrayXd &p_delta,
        const Eigen::ArrayXd &p_spread1,
        const Eigen::ArrayXd &p_spread2,
        const SpaceGrid   &p_commandGrid,
        const shared_ptr< BaseRegression > &p_regressor,
        const SpaceGrid &p_gridNext,
        const ArrayXXd &p_hMinusGainNext)
{
    int nbSim = p_regressor->getParticles().cols();
    // to store the minimization value calculated per simulation
    ArrayXd minVar =  ArrayXd::Constant(nbSim, infty);
    // store the hedge
    ArrayXXd hedge(p_delta.size(), nbSim);
    ArrayXd  hMinusGain = ArrayXd::Constant(nbSim, infty);
    shared_ptr<GridIterator> iterGrid = p_commandGrid.getGridIterator();
    while (iterGrid->isValid())
    {
        // next position to test
        Eigen::ArrayXd deltaNext = iterGrid->getCoordinate();
        // get interpolator associated to grid at the next time
        shared_ptr<Interpolator> interpolator = p_gridNext.createInterpolator(deltaNext);
        // interpolate (H-G) at the position at next date
        ArrayXd hMinGInterp = interpolator->applyVec(p_hMinusGainNext);
        //  (H-G) at the next and  subtract trading gains
        ArrayXd currentHMGain = hMinGInterp - (p_difS.matrix().transpose() * deltaNext.matrix()).array() ;
        // spread by quantity
        ArrayXd dQuantAbs = (deltaNext - p_delta).abs();
        ArrayXd spreadQuant1 = p_spread1 * dQuantAbs;
        ArrayXd spreadQuant2 = p_spread2 * dQuantAbs;
        // to H-G  add transaction cost
        currentHMGain += spreadQuant1.sum();
        currentHMGain += (p_asset.matrix().transpose() * spreadQuant2.matrix()).array();
        // calculate conditional expectation for this command
        ArrayXd espHMGain = p_regressor->getAllSimulations(currentHMGain);
        // now do the arbitrage cell by cell by conditional expectation
        ArrayXd  diffSquaredEsp = p_regressor->getAllSimulations((currentHMGain - espHMGain).pow(2.));
        // arbitrage
        for (int isim = 0; isim < nbSim; ++isim)
        {
            if (diffSquaredEsp(isim) < minVar(isim))
            {
                hedge.col(isim) = deltaNext;
                minVar(isim) = diffSquaredEsp(isim);
                hMinusGain(isim) = currentHMGain(isim);
            }
        }
        iterGrid->next();
    }
    return make_pair(hMinusGain, hedge.transpose());
}


