/*
 * $Id:$
 */

#if !defined(WRITESERIALIZATIONFILE)
#define WRITESERIALIZATIONFILE

#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 write 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 WriteSerializationFile {
private:
  std::ofstream ofil;    //!< file stream to write
  Archive oa;        //!< archive object used for the serialization
  // template class Archive should be {binary,text,xml}_oarchive

  const std::string _ofname;    //!< filename for write (used to SplitSave())
public:
  WriteSerializationFile(const char *filename)
    : ofil(filename, std::ios_base::binary), oa(ofil), _ofname(filename)
  {};

  void Save(const UInt4 &data) {
    oa << boost::serialization::make_nvp("UInt4Val", data);
  }

  void Save(const Int4 &data) {
    oa << boost::serialization::make_nvp("Int4Val", data);
  }

  void Save(const std::string &data) {
    oa << boost::serialization::make_nvp("StringVal", data);
  }

  void Save(const Double &data) {
    oa << boost::serialization::make_nvp("DoubleVal", data);
  }

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

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

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

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

  void Save(const HeaderBase &data) {
    oa << boost::serialization::make_nvp("HeaderBase", data);
  }

  void Save(const ElementContainer &data) {
    oa << boost::serialization::make_nvp("ElementContainer", data);
  }

  void Save(const ElementContainerArray &data) {
    oa << boost::serialization::make_nvp("ElementContainerArray", data);
  }

  void Save(const ElementContainerMatrix &data) {
    oa << boost::serialization::make_nvp("ElementContainerMatrix", data);
  }

  void Save(const UInt4Container &data) {
    oa << boost::serialization::make_nvp("UInt4Container", data);
  }

  void Save(const UInt4ContainerArray &data) {
    oa << boost::serialization::make_nvp("UInt4ContainerArray", data);
  }

  void Save(const UInt4ContainerMatrix &data) {
    oa << boost::serialization::make_nvp("UInt4ContainerMatrix", data);
  }

private:
  template <class pContainer, class cContainer>
  void SplitSave(const pContainer &data, const unsigned int splitnum);

public:
  void SplitSave(const ElementContainerArray  &data, const unsigned int splitnum=1) {
    SplitSave<ElementContainerArray, ElementContainer>(data, splitnum);
  }

  void SplitSave(const ElementContainerMatrix &data, const unsigned int splitnum=1) {
    SplitSave<ElementContainerMatrix, ElementContainerArray>(data, splitnum);
  }

  void SplitSave(const UInt4ContainerArray  &data, const unsigned int splitnum=1) {
    SplitSave<UInt4ContainerArray, UInt4Container>(data, splitnum);
  }

  void SplitSave(const UInt4ContainerMatrix &data, const unsigned int splitnum=1) {
    SplitSave<UInt4ContainerMatrix, UInt4ContainerArray>(data, splitnum);
  }
};



template <class Archive>
template <class pContainer, class cContainer>
void WriteSerializationFile<Archive>::SplitSave(const pContainer &data, const unsigned int splitnum) {
  std::vector<std::string> S = data.presave(oa, _ofname, splitnum);
  boost::filesystem::path serializepath(_ofname);
  // Boost 1.74 OK -> Boost 1.84 Error
  //serializepath = serializepath.branch_path();
  serializepath = serializepath.parent_path();
  unsigned int size = (unsigned int)(S.size())-1;

  {
    std::ofstream ofil((serializepath / S[0]).string().c_str());
    boost::archive::wrapped_cat(ARCFORMAT,_oarchive) obin(ofil);

    obin << boost::serialization::make_nvp("Header", *data.PutHeaderPointer());
  }

  std::vector<std::vector< cContainer *> > _EC(size);
  unsigned int vstart = 0, vnum = data.PutSize() / size;
  signed int _N = data.PutSize() - vnum * size;
  for (unsigned int i=0;i<size;++i) {
    unsigned int vnum0 = vnum;
    if (_N>0) {
      ++vnum0; --_N;
    }
    _EC[i].assign(&(data.v[vstart]), &(data.v[vstart+vnum0]));
    vstart += vnum0;
  }

  std::vector<UInt4> csize;
  for (UInt4 i=0;i<_EC.size();++i)
      csize.push_back((UInt4)(_EC[i].size()));
  oa & boost::serialization::make_nvp("ContainerSizes", csize);


#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::ofstream ofil((serializepath / S[i+1]).string().c_str());
    boost::archive::wrapped_cat(ARCFORMAT,_oarchive) A(ofil);
    A << boost::serialization::make_nvp("StoredContainers", _EC[i]);
    ofil.close();
  }
}

#endif /* WRITESERIALIZATIONFILE */
