// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef _BINARYFILEARCHIVESTOPT_H
#define _BINARYFILEARCHIVESTOPT_H
#include "geners/BinaryFileArchive.hh"
#include "geners/Record.hh"
#include "geners/Reference.hh"
#include "geners/stringArchiveIO.hh"
#include "StOpt/regression/BaseRegression.h"
#include "StOpt/regression/BaseRegressionGeners.h"
#include "StOpt/regression/LocalLinearRegressionGeners.h"
#include "StOpt/regression/LocalConstRegressionGeners.h"
#include "StOpt/regression/LocalSameSizeLinearRegressionGeners.h"
#include "StOpt/regression/LocalSameSizeConstRegressionGeners.h"
#include "StOpt/regression/SparseRegressionGeners.h"
#include "StOpt/regression/GridAndRegressedValue.h"
#include "StOpt/regression/GridAndRegressedValueGeners.h"
#include "StOpt/regression/ContinuationCuts.h"
#include "StOpt/regression/ContinuationCutsGeners.h"
#include "StOpt/tree/ContinuationValueTree.h"
#include "StOpt/tree/ContinuationValueTreeGeners.h"
#include "StOpt/regression/ContinuationCutsGridAdaptNonConcave.h"
#include "StOpt/regression/ContinuationCutsGridAdaptNonConcaveGeners.h"
#include "StOpt/tree/GridTreeValue.h"
#include "StOpt/tree/GridTreeValueGeners.h"
#include "StOpt/core/utils/eigenGeners.h"
#include "StOpt/core/grids/SpaceGrid.h"
#include "StOpt/core/grids/FullGrid.h"
#ifdef SDDPPYTHON
#include "StOpt/sddp/LocalLinearRegressionForSDDP.h"
#include "StOpt/sddp/LocalLinearRegressionForSDDPGeners.h"
#include "StOpt/sddp/SDDPVisitedStatesGeners.h"
#endif
#include  "StOpt/python/Pybind11VectorAndList.h"
#include "StOpt/python/ContinuationCutsGridAdaptNonConcaveWrap.h"


/// \file BinaryFileArchiveStOpt.h
/// \brief defines a wrapping for geners binary file
///        permits to extend the binary archive for StOpt

// create a class to encapsulate a Binary Archive
class BinaryFileArchiveStOpt: public gs::BinaryFileArchive
{


public:

    BinaryFileArchiveStOpt(const char *p_fileName, const char *p_type): gs::BinaryFileArchive(p_fileName, p_type)
    {
    }

 
  //  print archive structure
  void print(bool fullDump = false)
  {
    std::map<std::string,unsigned> typecount;
    const unsigned long long first = this->smallestId();
    const unsigned long long last = this->largestId();    
    for (unsigned long long id = first; id <= last; ++id)
      {
	if (!this->itemExists(id)) continue;

	std::shared_ptr<const gs::CatalogEntry> e = this->catalogEntry(id);
	if (fullDump)
	  {
	    if (id != first) std::cout << '\n';
	    e->humanReadable(std::cout);
	  }
	else 
	  std::cout << e->id() << "  "
		    << e->type().name() << "  " << '"' << e->name() << '"'
		    << "  " << '"' << e->category() << '"' << std::endl;
      }
  }
         
     // to get distinct Names in archive used for Continuation values
    std::vector< std::string> getNamesForGridAndRegressedValue(const std::string &p_nameRegex)
    {
        std::set<std::string> set;
        auto reference = gs::Reference<std::vector<StOpt::GridAndRegressedValue>>(*this, std::regex(p_nameRegex), std::regex(".*"));
        for (int i = 0; i < reference.size(); ++i)
        {
            set.emplace(reference.indexedCatalogEntry(i)->name());
        }
        std::vector<std::string> ret(set.begin(), set.end());
        return ret;
    }

    std::vector< std::string> getAllNamesForGridAndRegressedValue()
    {
        std::set<std::string> set;
        auto reference = gs::Reference<std::vector<StOpt::GridAndRegressedValue>>(*this, std::regex(".*"), std::regex(".*"));
        for (int i = 0; i < reference.size(); ++i)
        {
            set.emplace(reference.indexedCatalogEntry(i)->name());
        }
        std::vector<std::string> ret(set.begin(), set.end());
        return ret;
    }

    // to get distinct Names in archive used for Continuation values (cuts)
    std::vector< std::string> getNamesForContinuationCut(const std::string &p_nameRegex)
    {
        std::set<std::string> set;
        auto reference = gs::Reference<std::vector<StOpt::ContinuationCuts>>(*this, std::regex(p_nameRegex), std::regex(".*"));
        for (int i = 0; i < reference.size(); ++i)
        {
            set.emplace(reference.indexedCatalogEntry(i)->name());
        }
        std::vector<std::string> ret(set.begin(), set.end());
        return ret;
    }

   std::vector< std::string> getAllNamesForContinuationCut()
    {
        std::set<std::string> set;
        auto reference = gs::Reference<std::vector<StOpt::ContinuationCuts>>(*this, std::regex(".*"), std::regex(".*"));
        for (int i = 0; i < reference.size(); ++i)
        {
            set.emplace(reference.indexedCatalogEntry(i)->name());
        }
        std::vector<std::string> ret(set.begin(), set.end());
        return ret;
    }

   std::vector< std::string> getAllNamesForContinuationCutGridAdapt()
    {
        std::set<std::string> set;
        auto reference = gs::Reference<StOpt::ContinuationCutsGridAdaptNonConcave>(*this, std::regex(".*"), std::regex(".*"));
        for (int i = 0; i < reference.size(); ++i)
        {
            set.emplace(reference.indexedCatalogEntry(i)->name());
        }
        std::vector<std::string> ret(set.begin(), set.end());
        return ret;
    }

  void dumpGridAndRegressedValue(const std::string &p_name, const int &p_iStep, const  pybind11::list  &p_phiIn,
                                   const   std::shared_ptr<StOpt::BaseRegression>    &p_condExp,
                                   const std::shared_ptr< StOpt::SpaceGrid >   &p_grid)
    {
        // convert python to c++ object
        std::vector< std::shared_ptr< Eigen::ArrayXXd > >  phiIn(convertFromListShPtr<Eigen::ArrayXXd>(p_phiIn));

        // values to regress
        std::vector< StOpt::GridAndRegressedValue  > contVal(phiIn.size());
        for (size_t iReg = 0; iReg < phiIn.size(); ++iReg)
            contVal[iReg] =  StOpt::GridAndRegressedValue(p_grid, p_condExp, *phiIn[iReg]);
        std::string stepString = boost::lexical_cast<std::string>(p_iStep) ;
        *this << gs::Record(contVal, p_name.c_str(), stepString.c_str()) ;
        this->flush() ; // necessary for python mapping
    }

    // read continuation values
    std::vector< std::shared_ptr<StOpt::ContinuationCuts > >  readGridAndRegressedCuts(const int &p_iStep, const std::string &p_nameCont)
    {
        std::vector< StOpt::ContinuationCuts >  continuationObj;
        gs::Reference< std::vector<StOpt::ContinuationCuts  > >(*this, p_nameCont.c_str(), boost::lexical_cast<std::string>(p_iStep).c_str()).restore(0, &continuationObj);
        std::vector< std::shared_ptr<StOpt::ContinuationCuts >>  continuationObjWrap(continuationObj.size());
        for (size_t i = 0; i < continuationObjWrap.size(); ++i)
            continuationObjWrap[i] = std::make_shared<StOpt::ContinuationCuts>(continuationObj[i]);
        return continuationObjWrap;
    }
    // read continuation values
    StOpt::ContinuationCutsGridAdaptNonConcaveWrap   readGridAndRegressedCutsGridAdapt(const int &p_iStep, const std::string &p_nameCont)
    {
        StOpt::ContinuationCutsGridAdaptNonConcave   continuationObj;
	gs::Reference< StOpt::ContinuationCutsGridAdaptNonConcave   >(*this, p_nameCont.c_str(), boost::lexical_cast<std::string>(p_iStep).c_str()).restore(0, &continuationObj);
        return StOpt::ContinuationCutsGridAdaptNonConcaveWrap(continuationObj);
    }

    // read continuation values
    std::vector< std::shared_ptr<StOpt::GridAndRegressedValue > >  readGridAndRegressedValue(const int &p_iStep,  const std::string &p_nameCont)
    {
        std::vector< StOpt::GridAndRegressedValue >  continuationObj;
        gs::Reference< std::vector<StOpt::GridAndRegressedValue  > >(*this, p_nameCont.c_str(), boost::lexical_cast<std::string>(p_iStep).c_str()).restore(0, &continuationObj);
        std::vector< std::shared_ptr<StOpt::GridAndRegressedValue >>  continuationObjWrap(continuationObj.size());
        for (size_t i = 0; i < continuationObjWrap.size(); ++i)
            continuationObjWrap[i] = std::make_shared<StOpt::GridAndRegressedValue>(continuationObj[i]);
        return continuationObjWrap;
    }
    // to get distinct Names in archive used for Continuation values for Tree
    std::vector< std::string> getNamesForGridTreeValue(const std::string &p_nameRegex)
    {
        std::set<std::string> set;
        auto reference = gs::Reference<std::vector<StOpt::GridTreeValue>>(*this, std::regex(p_nameRegex), std::regex(".*"));
        for (int i = 0; i < reference.size(); ++i)
        {
            set.emplace(reference.indexedCatalogEntry(i)->name());
        }
        std::vector<std::string> ret(set.begin(), set.end());
        return ret;
    }

    // to get distinct Names in archive used for Continuation values (cuts) for tree
    std::vector< std::string> getNamesForContinuationCutTree(const std::string &p_nameRegex)
    {
        std::set<std::string> set;
        auto reference = gs::Reference<std::vector<StOpt::GridTreeValue>>(*this, std::regex(p_nameRegex), std::regex(".*"));
        for (int i = 0; i < reference.size(); ++i)
        {
            set.emplace(reference.indexedCatalogEntry(i)->name());
        }
        std::vector<std::string> ret(set.begin(), set.end());
        return ret;
    }

    void dumpGridTreeValue(const std::string &p_name, const int &p_iStep, const  pybind11::list  &p_phiIn,
                           const   std::shared_ptr<StOpt::Tree>    &p_condExp,
                           const std::shared_ptr< StOpt::SpaceGrid >   &p_grid)
    {
        // convert python to c++ object
        std::vector< std::shared_ptr< Eigen::ArrayXXd > >  phiIn(convertFromListShPtr<Eigen::ArrayXXd>(p_phiIn));

        // values to regress
        std::vector< StOpt::GridTreeValue  > contVal(phiIn.size());
        for (size_t iReg = 0; iReg < phiIn.size(); ++iReg)
        {
            Eigen::ArrayXXd phiExp =  p_condExp->expCondMultiple(phiIn[iReg]->transpose()).transpose();
            contVal[iReg] =  StOpt::GridTreeValue(p_grid, phiExp);
        }
        std::string stepString = boost::lexical_cast<std::string>(p_iStep) ;
        *this << gs::Record(contVal, p_name.c_str(), stepString.c_str()) ;
        this->flush() ; // necessary for python mapping
    }

    // read continuation values
    std::vector< std::shared_ptr<StOpt::GridTreeValue > >  readGridTreeValue(const int &p_iStep,  const std::string &p_nameCont)
    {
        std::vector< StOpt::GridTreeValue >  continuationObj;
        gs::Reference< std::vector<StOpt::GridTreeValue  > >(*this, p_nameCont.c_str(), boost::lexical_cast<std::string>(p_iStep).c_str()).restore(0, &continuationObj);
        std::vector< std::shared_ptr<StOpt::GridTreeValue >>  continuationObjWrap(continuationObj.size());
        for (size_t i = 0; i < continuationObjWrap.size(); ++i)
            continuationObjWrap[i] = std::make_shared<StOpt::GridTreeValue>(continuationObj[i]);
        return continuationObjWrap;
    }



    // dump a 2D  numpy array
    void dumpSome2DArray(const std::string &p_name, const int &p_iStep,  const Eigen::ArrayXXd &p_functionValues)
    {
        std::string stepString = boost::lexical_cast<std::string>(p_iStep) ;
        *this << gs::Record(p_functionValues, p_name.c_str(), stepString.c_str()) ;
        this->flush() ; // necessary for python mapping
    }

    // dump a 2D  numpy array
    void dumpSome1DArray(const std::string &p_name1, const std::string &p_name2, const Eigen::ArrayXd &p_functionValues)
    {
        *this << gs::Record(p_functionValues, p_name1.c_str(), p_name2.c_str()) ;
        this->flush() ; // necessary for python mapping
    }

    // dump a regressor and a numpy array storing a list of coefficients basis
    // p_name
    void dumpSomeRegressor(const std::string &p_name, const int &p_iStep, const std::shared_ptr<StOpt::BaseRegression>    &p_condExp)
    {
        std::string stepString = boost::lexical_cast<std::string>(p_iStep) ;
        *this << gs::Record(*p_condExp, p_name.c_str(), stepString.c_str()) ;
        this->flush() ; // necessary for python mapping
    }

    // dump a vector of strings
    // p_name
    // p_category
    void dumpSomeStringVector(const std::string &p_name, const std::string &p_category, const std::vector<std::string> &p_stringVector)
    {
        *this << gs::Record(p_stringVector, p_name.c_str(), p_category.c_str());
        this->flush(); // necessary for python mapping
    }

    // read  some basis coefficients
    Eigen::ArrayXd readSome1DArray(const std::string &p_name1, const std::string &p_name2, const int &p_iStep)
    {
        Eigen::ArrayXd basisFunctions;
        gs::Reference<  Eigen::ArrayXd >(*this, p_name1.c_str(), p_name2.c_str()).restore(p_iStep, &basisFunctions);
        return basisFunctions;
    }

    // read  some basis coefficients
    Eigen::ArrayXXd readSome2DArray(const std::string &p_name, const int &p_iStep)
    {
        Eigen::ArrayXXd basisFunctions;
        gs::Reference<  Eigen::ArrayXXd >(*this, p_name.c_str(), boost::lexical_cast<std::string>(p_iStep).c_str()).restore(0, &basisFunctions);
        return basisFunctions;
    }

    // read a regressor
    std::shared_ptr<StOpt::BaseRegression > readSomeRegressor(const std::string &p_name, const int &p_iStep)
    {
        std::shared_ptr<StOpt::BaseRegression > condExp;
        gs::Reference< StOpt::BaseRegression  > reg(*this, p_name.c_str(), boost::lexical_cast<std::string>(p_iStep).c_str());
        condExp = std::move(reg.get(0));
        return condExp;
    }

    // read a vector of strings
    // p_name
    // p_category
    const std::vector<std::string>  readSomeStringVector(const std::string &p_name, const std::string &p_category)
    {
        std::vector<std::string> ret;
        auto item = gs::Reference<std::vector<std::string>>(*this, p_name.c_str(), p_category.c_str());
        item.restore(0, &ret);
        return ret;
    }


#ifdef SDDPPYTHON
    void dumpLocalLinearRegressionForSDDP(const StOpt::LocalLinearRegressionForSDDP &p_sddpReg)
    {
        *this << gs::Record(p_sddpReg, "Regressor", "Top");
        this->flush(); // necessary in python
    }

    void dumpLocalVisitedStateForSDDP(const StOpt::SDDPVisitedStates &p_state)
    {
        *this << gs::Record(p_state, "States", "Top");
        this->flush(); // necessary in python
    }
#endif

};

#endif
