/*
 * $Id:$
 */

#if !defined(READSERIALIZATIONFILE)
#define READSERIALIZATIONFILE

#include <string>
#include <iostream>
#include <fstream>

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>

#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>

#include <boost/filesystem.hpp>

#if defined(SPLITSERIALIZEXML)
#define ARCFORMAT xml
#else
#define ARCFORMAT binary
#endif

#define cat(x,y)                x##y
#define wrapped_cat(x,y)        cat(x,y)

#ifdef MULTH
# include <omp.h>
#endif

#include "Header.hh"

#include "HeaderBase.hh"
#include "ElementContainer.hh"
#include "ElementContainerArray.hh"
#include "ElementContainerMatrix.hh"
#include "UInt4Container.hh"
#include "UInt4ContainerArray.hh"
#include "UInt4ContainerMatrix.hh"


/** proxy class to read Manyo-lib data container by its serialization methods.
 *
 * This class is proxy/utility class to use Boost serialization features
 * from python scripts. It is also enabled to use it from C++ codes.
 *
 * Following types are supported
 * UInt4, Int4, std::string, Double,
 * std::vector<UInt4>, std::vector<Int4>, std::vector<std::string>, std::vector<Double>
 * HeaderBase, ElementContainer, ElementContainerArray, ElementContainerMatrix
 * UInt4Container, UInt4ContainerArray, UInt4ContainerMatrix.
 *
 * Split serialization modes are implemented by distinct methods.
 */
template <class Archive>
class ReadSerializationFile {
private:
  std::ifstream ifil;    //!< file stream to read
  Archive ia;            //!< archive object used for the serialization
  /*<
   * template class Archive should be {binary,text,xml}_iarchive
   */

  const std::string _ifname;    //!< filename for read (used to SplitLoad())
public:
  ReadSerializationFile(const char *filename)
    : ifil(filename, std::ios_base::binary), ia(ifil), _ifname(filename)
  {};

  void Load(UInt4 &data) {
    ia >> boost::serialization::make_nvp("UInt4Val", data);
  }

  void Load(Int4 &data) {
    ia >> boost::serialization::make_nvp("Int4Val", data);
  }

  void Load(std::string &data) {
    ia >> boost::serialization::make_nvp("StringVal", data);
  }

  void Load(Double &data) {
    ia >> boost::serialization::make_nvp("DoubleVal", data);
  }

  void Load(std::vector<UInt4> &data) {
    ia >> boost::serialization::make_nvp("UInt4VectorVal", data);
  }

  void Load(std::vector<Int4> &data) {
    ia >> boost::serialization::make_nvp("Int4VectorVal", data);
  }

  void Load(std::vector<std::string> &data) {
    ia >> boost::serialization::make_nvp("StringVectorVal", data);
  }

  void Load(std::vector<Double> &data) {
    ia >> boost::serialization::make_nvp("DoubleVectorVal", data);
  }



  UInt4 LoadUInt4(void) {
    UInt4 data;
    Load(data);
    return data;
  }

  std::vector<UInt4> LoadUInt4Vector(void) {
    std::vector<UInt4> data;
    Load(data);
    return data;
  }

  Int4 LoadInt4(void) {
    Int4 data;
    Load(data);
    return data;
  }

  std::vector<Int4> LoadInt4Vector(void) {
    std::vector<Int4> data;
    Load(data);
    return data;
  }

  std::string LoadStr(void) {
    std::string data;
    Load(data);
    return data;
  }

  std::vector<std::string> LoadStringVector(void) {
    std::vector<std::string> data;
    Load(data);
    return data;
  }

  Double LoadDouble(void) {
    Double data;
    Load(data);
    return data;
  }

  std::vector<Double> LoadDoubleVector(void) {
    std::vector<Double> data;
    Load(data);
    return data;
  }



  void Load(HeaderBase &data) {
    ia >> boost::serialization::make_nvp("HeaderBase", data);
  }

  void Load(ElementContainer &data) {
    ia >> boost::serialization::make_nvp("ElementContainer", data);
  }

  void Load(ElementContainerArray &data) {
    ia >> boost::serialization::make_nvp("ElementContainerArray", data);
  }

  void Load(ElementContainerMatrix &data) {
    ia >> boost::serialization::make_nvp("ElementContainerMatrix", data);
  }

  void Load(UInt4Container &data) {
    ia >> boost::serialization::make_nvp("UInt4Container", data);
  }

  void Load(UInt4ContainerArray &data) {
    ia >> boost::serialization::make_nvp("UInt4ContainerArray", data);
  }

  void Load(UInt4ContainerMatrix &data) {
    ia >> boost::serialization::make_nvp("UInt4ContainerMatrix", data);
  }


  HeaderBase LoadHeaderBase(void) {
    HeaderBase data;
    Load(data);
    return data;
  }

  ElementContainer LoadElementContainer(void) {
    ElementContainer data;
    Load(data);
    return data;
  }

  ElementContainerArray LoadElementContainerArray(void) {
    ElementContainerArray data;
    Load(data);
    return data;
  }

  ElementContainerMatrix LoadElementContainerMatrix(void) {
    ElementContainerMatrix data;
    Load(data);
    return data;
  }

  UInt4Container LoadUInt4Container(void) {
    UInt4Container data;
    Load(data);
    return data;
  }

  UInt4ContainerArray LoadUInt4ContainerArray(void) {
    UInt4ContainerArray data;
    Load(data);
    return data;
  }

  UInt4ContainerMatrix LoadUInt4ContainerMatrix(void) {
    UInt4ContainerMatrix data;
    Load(data);
    return data;
  }


private:
  template <class pContainer, class cContainer>
  void SplitLoad(pContainer &data);

public:
  void SplitLoad(ElementContainerArray  &data) {
    SplitLoad<ElementContainerArray, ElementContainer>(data);
  }

  void SplitLoad(ElementContainerMatrix &data) {
    SplitLoad<ElementContainerMatrix, ElementContainerArray>(data);
  }

  void SplitLoad(UInt4ContainerArray  &data) {
    SplitLoad<UInt4ContainerArray, UInt4Container>(data);
  }

  void SplitLoad(UInt4ContainerMatrix &data) {
    SplitLoad<UInt4ContainerMatrix, UInt4ContainerArray>(data);
  }

  ElementContainerArray SplitLoadElementContainerArray(void) {
    ElementContainerArray data;
    SplitLoad(data);
    return data;
  }

  ElementContainerMatrix SplitLoadElementContainerMatrix(void) {
    ElementContainerMatrix data;
    SplitLoad(data);
    return data;
  }

  UInt4ContainerArray SplitLoadUInt4ContainerArray(void) {
    UInt4ContainerArray data;
    SplitLoad(data);
    return data;
  }

  UInt4ContainerMatrix SplitLoadUInt4ContainerMatrix(void) {
    UInt4ContainerMatrix data;
    SplitLoad(data);
    return data;
  }
};

template <class Archive>
template <class pContainer, class cContainer>
void ReadSerializationFile<Archive>::SplitLoad(pContainer &data) {
  std::vector<std::string> S;
  std::vector<UInt4> csize;
  boost::filesystem::path serializepath(_ifname);
  // Boost 1.74 OK -> Boost 1.84 Error
  //serializepath = serializepath.branch_path();
  serializepath = serializepath.parent_path();
  data.preload(ia, S, csize);

  unsigned int size = (unsigned int)S.size()-1;

  {
    boost::filesystem::path F0 = serializepath / S[0];
    std::string F = (serializepath / S[0]).string();
    if (!boost::filesystem::exists(F)) {
      std::cout << "part of serialization file for NeutronVector<T,H> "
                << F << " not exist" << std::endl;
    } else {
    std::ifstream ifil(F.c_str());
    boost::archive::wrapped_cat(ARCFORMAT,_iarchive) ibin(ifil);

    ibin >> boost::serialization::make_nvp("Header", *data.PutHeaderPointer());
    }
  }

  UInt4 totalcsize=std::accumulate(csize.begin(), csize.end(), 0);

  // create/resize a vector v with a size totalcsize,
  // it shouldn't contains a pointer to objects, because
  // following code rewrite them by another pointers
  for (unsigned int i=0;i<data.PutSize();++i)
    data.EraseElement(i);
  data.Resize(totalcsize);


  std::vector<UInt4> csize2(csize.size()+1,0);
  std::partial_sum(csize.begin(), csize.end(), &csize2[1]);

  std::vector<std::vector< cContainer *> > _EC(size);

#ifdef MULTH
  omp_set_num_threads(
    std::min(MULTH,  std::min(omp_get_max_threads(), omp_get_num_procs()) )
  );
#endif
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
  for (unsigned int i=0;i<size;++i) {
#else
  for (int i=0;i<(int)size;++i) {
#endif
    std::string F = (serializepath / S[i+1]).string();
    if (!boost::filesystem::exists(F)) {
      std::cout << "part of serialization file for NeutronVector<T,H> "
                << F << " not exist" << std::endl;
      continue;
    }
    std::ifstream ifil(F.c_str());
    boost::archive::wrapped_cat(ARCFORMAT,_iarchive) A(ifil);
    A >> boost::serialization::make_nvp("StoredContainers", _EC[i]);
    ifil.close();

    std::copy(_EC[i].begin(),_EC[i].end(), &data.v[csize2[i]]);
  }
}

#endif /* READSERIALIZATIONFILE */
