/*
$Id: NeutronVector.cc 2332 2012-02-16 01:05:03Z jisuzuki $
*/


#include "NeutronVector.hh"
#ifndef NEUTRONVECTOR_CC
#define NEUTRONVECTOR_CC

//#include <time.h>
#include <algorithm>
#include <numeric>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/serialization/nvp.hpp>
#include <nexus/napi.h>

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

///////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T,H>::
NeutronVector()
{
  header = new H ;

#ifdef MULTH
  omp_set_num_threads(
    std::min(MULTH,  std::min(omp_get_max_threads(), omp_get_num_procs()) )
  );
#endif
}
///////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T,H>::
NeutronVector( H pheader)
{
  header = new H ( pheader );

#ifdef MULTH
  omp_set_num_threads(
    std::min(MULTH,  std::min(omp_get_max_threads(), omp_get_num_procs()) )
  );
#endif
}
///////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T,H>::
NeutronVector( const NeutronVector &ob )
{
  header = new H ( *ob.header );

#ifdef MULTH
  omp_set_num_threads(
    std::min(MULTH,  std::min(omp_get_max_threads(), omp_get_num_procs()) )
  );
#endif

  UInt4 TableSize = (UInt4)(ob.v.size());
  v.resize(TableSize);

#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
  for( UInt4 i=0; i<TableSize; i++){
#else
  for( Int4 i=0; i<(Int4)TableSize; i++){
#endif
    T *value = new T(*( ob.v[i] ));
    v[i] = value;
  }
}
///////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T,H> NeutronVector<T,H>::
operator=( const NeutronVector &ob )
{
  *header = *ob.header;
  UInt4 TableSize = ob.PutSize();
  Resize(TableSize);

#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
  for( UInt4 i=0; i<TableSize; i++){
#else
  for( Int4 i=0; i<(Int4)TableSize; i++){
#endif
    *v[i] = *ob.v[i];
  }

/*
  delete header;
  header = new H ( *ob.header );

  erase();
  UInt4 TableSize = ob.v.size();
  v.resize(TableSize);

#pragma omp parallel for
  for( UInt4 i=0; i<TableSize; i++){
    T *value = new T(*( ob.v[i] ));
    v[i] = value;
  }
*/
  return *this;
}
///////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T,H>::~NeutronVector()
{
  delete header;
  erase();
}
///////////////////////////////////////////////////////////
template< class T, class H  >
void NeutronVector<T,H>::InputHeader( H pheader )
{
  delete header;
  header = new H();
  *header = pheader;
}
///////////////////////////////////////////////////////////
template< class T, class H  >
UInt4 NeutronVector<T,H>::AddPointer( T *value )
{
  v.push_back( value );
  return ( (UInt4)(v.size()) );
}
///////////////////////////////////////////////////////////
template< class T, class H  >
UInt4 NeutronVector<T,H>::Add( NeutronVector<T,H> &value ){

  UInt4 size = value.PutSize();
  for( UInt4 i=0; i<size; i++ ){
    Add( value.Put( i ) );
  }
  return ( (UInt4)(v.size()) );
}
///////////////////////////////////////////////////////////
template< class T, class H  >
UInt4 NeutronVector<T,H>::Add( T value )
{
  T* tmpValue;
  try{
    tmpValue = new T(value);
  }
  catch( const std::bad_alloc& error ){
    std::cout << "NeutronVector::Add(T)"          << std::endl;
    std::cout << "Out of memory " << error.what() << std::endl;
    return( (UInt4)(v.size()) );
  }

  v.push_back( tmpValue );
  return (UInt4)( v.size() );
}
///////////////////////////////////////////////////////////
template< class T, class H  >
T NeutronVector<T,H>::Put( UInt4 index )
{
  if( index >= (UInt4)(v.size()) ){
    std::cout << "The argument is too large "
         << "to return the pointer "
         << "at NeutronVector::get()" << std::endl;
    T tmpT;
    return tmpT;
  }

  return *( v[ index ] );
}
///////////////////////////////////////////////////////////
template< class T, class H  >
T* NeutronVector<T,H>::PutPointer( UInt4 index )
{
  UInt4 size = (UInt4)v.size();
  if( index >= size ){
    std::cout << "The argument is too large "
         << "to return the pointer "
         << "at NeutronVector::get()" << std::endl;
    return 0;
  }

  return v[index];
}
///////////////////////////////////////////////////////////
template< class T, class H  >
void NeutronVector<T,H>::
erase()
{
  UInt4 size = (UInt4)(v.size());
  if( size != 0 ){
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
      for( UInt4 i=0; i<size; i++ )
#else
      for( Int4 i=0; i<(Int4)size; i++ )
#endif
    delete v[i];
  }
  v.clear();
}
///////////////////////////////////////////////////////////
template< class T, class H  >
void NeutronVector<T,H>::
Allocate( UInt4 size )
{
  erase();
  for( UInt4 i=0; i<size; i++ ){
    v.push_back( NULL );
  }
}
///////////////////////////////////////////////////////////
template< class T, class H  >
void NeutronVector<T,H>::
Set( UInt4 index, T* value )
{
  v[ index ] = value;
}
///////////////////////////////////////////////////////////
template< class T, class H  >
UInt4 NeutronVector<T,H>::
EraseElement( UInt4 i )
{
  UInt4 OrigSize = PutTableSize();
  T **TempArray = new T* [ OrigSize ];

  for( UInt4 j=0; j<OrigSize; j++)
    TempArray[j] = v[j]; // copy all elements

  for( UInt4 j=0; j<i; j++)
    v[j] = TempArray[j];
  for( UInt4 j=i; j<OrigSize-1; j++)
    v[j] = TempArray[j+1];

  v.pop_back();

  delete    TempArray[i];

  delete [] TempArray;
  return (UInt4)(v.size());
}
///////////////////////////////////////////////////////////
template< class T, class H  >
UInt4 NeutronVector<T,H>::
ClearElement( UInt4 i )
{
  delete    v[i];

  v[i] = NULL;
  return (UInt4)(v.size());
}
///////////////////////////////////////////////////////////
template< class T, class H  >
UInt4 NeutronVector<T,H>::
copy( UInt4 i )
{
  if( i >= v.size() ){ // check a index number
    std::cout << "The argument is too large "
         << "to return the pointer "
         << "at NeutronVector::copy()" << std::endl;
    return 0;
  }

    T *value = new T;
    *value = *( PutPointer(i) );
    AddPointer( value );

    return (UInt4)(v.size());
}
///////////////////////////////////////////////////////////
/*
template< class T, class H  >
T* NeutronVector<T,H>::operator[]( UInt4 index )
{
  UInt4 size = v.size();
  if( index >= size ){
    std::cout << "NeutronVector::operator[]"  << std::endl;
    std::cout << "The argument is too large " << std::endl;
    return NULL;
  }

  return v[index];
}
*/
/*
template< class T, class H  >
T NeutronVector<T,H>::operator[]( UInt4 index )
{
  UInt4 size = v.size();
  if( index >= size ){
    std::cout << "NeutronVector::operator[]"  << std::endl;
    std::cout << "The argument is too large " << std::endl;
    T tmpT;
    return tmpT;
  }

  return *( v[index] );
}
*/
///////////////////////////////////////////////////////////
template< class T, class H  >
H NeutronVector<T,H>::
PutHeader()
{
  H h;

  if( header == NULL ){
    H ph = H();
    InputHeader( ph );
  }

  h = *header;
  return h;
}
///////////////////////////////////////////////////////////



/////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> &NeutronVector<T, H> ::
operator+=(const NeutronVector<T, H> &r )
{
  UInt4 size = (UInt4)(v.size());

  if( ! ( r.PutSize() == size ) ){
    std::cerr << "Sorry, the calculation in the operator+= failed." << std::endl;
    return *this;
  }
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
  for( UInt4 i=0; i<size; i++ ){
#else
  for( Int4 i=0; i<(Int4)size; i++ ){
#endif
    ref(i) += r.ref(i);
  }
  return *this;
}
/////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> &NeutronVector<T, H> ::
operator-=(const NeutronVector<T, H> &r )
{
  UInt4 size = (UInt4)v.size();

  if( ! ( r.PutSize() == size ) ){
    std::cerr << "Sorry, the calculation in the operator-= failed." << std::endl;
    return *this;
  }
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
  for( UInt4 i=0; i<size; i++ ){
#else
  for( Int4 i=0; i<(Int4)size; i++ ){
#endif
    ref(i) -= r.ref(i);
  }
  return *this;
}
/////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> &NeutronVector<T, H> ::
operator*=(const NeutronVector<T, H> &r )
{
  UInt4 size = (UInt4)v.size();

  if( ! ( r.PutSize() == size ) ){
    std::cerr << "Sorry, the calculation in the operator*= failed." << std::endl;
    return *this;
  }
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
  for( UInt4 i=0; i<size; i++ ){
#else
  for( Int4 i=0; i<(Int4)size; i++ ){
#endif
    ref(i) *= r.ref(i);
  }
  return *this;
}
/////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> &NeutronVector<T, H> ::
operator/=(const NeutronVector<T, H> &r )
{
  UInt4 size = (UInt4)v.size();

  if( ! ( r.PutSize() == size ) ){
    std::cerr << "Sorry, the calculation in the operator/= failed." << std::endl;
    return *this;
  }
#pragma omp parallel for
#if (_OPENMP >= 200805)  // OpenMP 3.0 and later
  for( UInt4 i=0; i<size; i++ ){
#else
  for( Int4 i=0; i<(Int4)size; i++ ){
#endif
    ref(i) /= r.ref(i);
  }
  return *this;
}

/////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> NeutronVector<T, H> ::
operator+( NeutronVector<T, H> &r )
{
  NeutronVector<T, H> result(*this);
  result += r;

  return result;
}
////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> NeutronVector<T, H> ::
operator-( NeutronVector<T, H> &r )
{
  NeutronVector<T, H> result(*this);
  result -= r;

  return result;
}
////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> NeutronVector<T, H> ::
operator*( NeutronVector<T, H> &r )
{
  NeutronVector<T, H> result(*this);
  result *= r;

  return result;
}
////////////////////////////////////////////////////////
template< class T, class H  >
NeutronVector<T, H> NeutronVector<T, H> ::
operator/( NeutronVector<T, H> &r )
{
  NeutronVector<T, H> result(*this);
  result /= r;

  return result;
}

/////////////////////////////////////////////////////////
template< class T, class H  >
template <class Func>
void NeutronVector<T, H>::
Transform(const std::string &Key, std::vector< Func > F, const std::string &newunit) {
  UInt4 n = PutSize();
  for (UInt4 i=0;i<n;++i) {
    v[i]->Transform(Key, F[i], newunit);
  }
}

/////////////////////////////////////////////////////////
template< class T, class H  >
void NeutronVector<T, H>::
Reverse(void)
{
  UInt4 n=(UInt4)(v.size());
  for (UInt4 i=0;i<n;++i)
    v[i]->Reverse();
}

/////////////////////////////////////////////////////////
template< class T, class H  >
void NeutronVector<T, H>::
Resize(UInt4 newsize)
{
  UInt4 currentsize = PutSize();

  for (unsigned int i=newsize;i<currentsize;++i) // (currentsize > newsize)
    delete v[i];
  v.resize(newsize);
  for (unsigned int i=currentsize;i<newsize;++i) // (currentsize < newsize)
    v[i] = new T;
}

////////////////////////////////////////////////
template< class T, class H  >
template <class Archive>
void NeutronVector<T,H>::
serialize(Archive &ar, unsigned int ver) {
  ar & boost::serialization::make_nvp("Header", *header);

  ar & boost::serialization::make_nvp("StoredSubContainers", v);
}

////////////////////////////////////////////////
template< class T, class H  >
template <class Archive>
std::vector<std::string> NeutronVector<T,H>::
presave(Archive &ar, const std::string &masterfile, const unsigned int splitnum) const {
  boost::filesystem::path masterarchive(masterfile);
  // Boost 1.74 OK -> Boost 1.84 Error
  //masterarchive.normalize();
  masterarchive.lexically_normal();

  //boost::filesystem::path serializepath = masterarchive.branch_path();
  boost::filesystem::path serializepath = masterarchive.parent_path();
  //std::string filebase = boost::filesystem::basename(masterarchive);
  std::string filebase = masterarchive.filename().generic_string();

#if defined(DEBUG)
  std::cerr << "Masters name: " << masterfile << std::endl
            << "path: " << masterarchive.string() << std::endl
            << "complete: " << boost::filesystem::system_complete(masterarchive).string() << std::endl
            //<< "node: " << masterarchive.branch_path().string() << std::endl
            << "node: " << masterarchive.parent_path().string() << std::endl
            << "leaf: " << masterarchive.leaf() << std::endl
            << "serializepath: " << serializepath << std::endl
            << "filebase: " << filebase << std::endl
  ;
#endif

  unsigned int size = std::min(splitnum, (unsigned int)v.size());

  std::vector<std::string> S;
  for (unsigned int i=0;i<size+1;++i)
    S.push_back(filebase + "." + boost::io::str(boost::format("%04d") % (i)));

  ar & boost::serialization::make_nvp("SerializationPath",
         boost::filesystem::system_complete(serializepath).string());
  ar & boost::serialization::make_nvp("Filenames", S);


//  S.push_back(serializepath.string());
  return S;
}
////////////////////////////////////////////////
template< class T, class H  >
template <class Archive>
void NeutronVector<T,H>::
preload(Archive &ar,
    std::vector<std::string> &S, std::vector<UInt4> &csize) {
  std::string _savedpath;
  ar & boost::serialization::make_nvp("SerializationPath", _savedpath);
  // SerializationPath is no longer used
  ar & boost::serialization::make_nvp("Filenames", S);
  ar & boost::serialization::make_nvp("ContainerSizes", csize);
}

////////////////////////////////////////////////
template< class T, class H  >
template <class Write>
void NeutronVector<T,H>::
NXwrite(Write &W) const {
  if (!header->Empty())
  W.WriteData("Header_in_NeutronVector", *header);

  UInt4 size = PutTableSize();
  W.WriteData( "size", size );
  static char index[128];
  for( UInt4 i=0; i<size; i++ ){
    std::snprintf(index, sizeof(index), "Vector_in_NeutronVector%d", i);
    W.WriteData(std::string(index), *v[i]);
  }

}

////////////////////////////////////////////////
template< class T, class H  >
template <class Read>
void NeutronVector<T,H>::
NXread(Read &R) {
  Int4 newsize = R.ReadDataInt4("size");
  Resize(newsize);

  NXname _name, _class;
  int _id, _status;
  while ((_status=R.GetNextEntry2(_name, _class, &_id))==1) {
//    std::cerr << "NEXTENTRY: " << _name << "," << _class << ", "
//              << _id << "," << _status << std::endl;
    if (strcmp(_name, "Header_in_NeutronVector")==0)
      R.ReadData("Header_in_NeutronVector", *header);
    else if (strncmp(_name, "Vector_in_NeutronVector", 23)==0) {
      char *c = &(_name[23]);
      Int4 _i = atoi(c);
      R.ReadData(_name, *v[_i]);
    }

  }
}

#endif
