// Copyright (C) 2021 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#include <utility>
#include <vector>
#include <array>
#include <cassert>
#include <iostream>
#include <Eigen/Dense>
#include <StOpt/core/utils/constant.h>
#include "Capa.h"

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


Capa::Capa(const ArrayXd   &p_factorMul,  const ArrayXd   &p_xGrid, const ArrayXd &p_valOnGrid): m_bTimeDependent(false), m_factorMul(p_factorMul), m_xGrid(p_xGrid), m_valOnGrid(p_valOnGrid)
{

}



double Capa::getValue(const int   &p_idate,  const double &p_level) const
{
    if (m_bTimeDependent)
    {
        // position in tunnel :
        int iposData  = m_fromDateToData[p_idate];
        if (m_data.size() > 0)
        {
            // first test if it is last point
            double minLevel = m_data[ iposData].first[0];
            double stepLevel =  m_data[ iposData].first[1];
            int iLevel = static_cast<int>((p_level - minLevel + tiny) / stepLevel) ;
            if (iLevel == m_data[ iposData].second.size() - 1)
                return m_data[ iposData].second(m_data[ iposData].second.size() - 1);
            double xBary = (minLevel + (iLevel + 1) * stepLevel - p_level) / stepLevel;
            assert(xBary <= 1 + small);
            assert(xBary >= -small);
            return xBary * m_data[ iposData].second[iLevel] + (1 - xBary) * m_data[ iposData].second[iLevel + 1];
        }
        else
        {
            if (p_level < (m_dataReduc[ iposData](0, 0) - tiny))
            {
                std::cout << "Storage level below  the minimal used for capacity description" << std::endl;
                abort();
            }
            if (p_level > (m_dataReduc[ iposData](m_dataReduc[ iposData].rows() - 1, 0) + tiny))
            {
                std::cout << "Storage level above  the maximal used for capacity description" << std::endl;
                abort();
            }
            int iposStock = 0;
            while (p_level > m_dataReduc[ iposData](iposStock, 0) + tiny)
            {
                iposStock += 1;
                if (iposStock == m_dataReduc[ iposData].rows())
                {
                    break;
                }
            }
            // go under
            iposStock -= 1;
            // interpolate above
            double xBary = (m_dataReduc[ iposData](iposStock + 1, 0) - p_level) / (m_dataReduc[ iposData](iposStock + 1, 0) - m_dataReduc[ iposData](iposStock, 0));
            assert(xBary <= 1 + small);
            assert(xBary >= -small);
            return xBary * m_dataReduc[ iposData](iposStock, 1) + (1 - xBary) * m_dataReduc[ iposData](iposStock + 1, 1);
        }
    }
    else
    {
        int ipos = 1;
        while (m_xGrid(ipos) <= p_level)
        {
            if (ipos == m_xGrid.size() - 1)
                break;
            ipos += 1;
        }
        ipos -= 1;
        double xBary = (p_level - m_xGrid(ipos)) / (m_xGrid(ipos + 1) - m_xGrid(ipos));
        assert(xBary >= -1e-10);
        assert(xBary <= 1 + 1e-10);
        return (m_valOnGrid(ipos + 1) * xBary + (1 - xBary) * m_valOnGrid(ipos)) * m_factorMul(p_idate);
    }
}


void Capa::print(const int   &p_idate) const
{
    if (m_bTimeDependent)
    {
        if (m_data.size() > 0)
        {
            // position in tunnel :
            int iposData  = m_fromDateToData[p_idate];
            double minLevel = m_data[ iposData].first[0];
            double stepLevel =  m_data[ iposData].first[1];
            for (int iStep = 0; iStep < m_data[ iposData].second.size(); ++iStep)
                cout << "Level " << minLevel + iStep *stepLevel << " Capacity " << m_data[ iposData].second(iStep) << endl ;
        }
        else
        {
            int iposData  = m_fromDateToData[p_idate];
            for (int iStep = 0; iStep < m_dataReduc[ iposData].rows(); ++iStep)
                cout << "Level " << m_dataReduc[ iposData](iStep, 0)  << " Capacity " << m_dataReduc[ iposData](iStep, 1) << endl ;
        }
    }
}
