#ifndef EARRAY_H
#define EARRAY_H

#include "eutils.h"
#include "ebasicarray_dec.h"
#include "earray_dec.h"
#include "evar_dec.h"


#include "efunc_dec.h"
#include "estr.h"
#include "evarclass_dec.h"
#include "evartype.h"

#include "logger.h"
#include "ebasicarray.h"


#include "evarclass.h"

using namespace std;
class estr;

template <>
void earray<evar>::serial(estr& data) const;
template <>
size_t earray<evar>::unserial(const estr& data,size_t i);
template <>
void earray<evar>::serial(const efile& f) const;
template <>
bool earray<evar>::unserial(const efile& f);



template <class T>
void earray<T>::serial(estr& data) const
{
  serialuint(size(),data);
  if (size()==0) return;

  if (!getClasses().exists(typeid(T).name())) { lerror(estr("class not registered: ")+typeid(T).name()); return; }

  size_t ilen=data.len();

  eclassBase *pClass=&getClasses().values(typeid(T).name());
  evarType<T> *pType=new evarType<T>((T&)at(0));
  pType->cleanup=false;
  pClass->fserial(pType,data);

  size_t i;
  for (i=1; i<size(); ++i){
    pType->object=(T*)&at(i);
    pClass->fserial(pType,data);
    if (i==10 && data.len()-ilen>0){ data.reserve(ilen + (data.len()-ilen)*1.1*size()/10); } // preallocate buffer size with estimated total size using first 10 elements as a guess + 10 % over estimate
  }
  delete pType;
//    ::serial(at(i),data);
}

template <class T>
size_t earray<T>::unserial(const estr& data,size_t i)
{
  clear();
  unsigned int count;
  evar var;
//  T var;
  i=unserialuint(count,data,i);
  if (i==-1) return(-1);

  eclassBase *pClass=&getClasses().values(typeid(T).name());
//  evarType<T> *pType=new evarType<T>((T&)var);
//  pType->cleanup=false;

  var.create(typeid(T).name());
  var.var->cleanup=false;
//  cout << "var.count: " << var.var->pcount << endl;


  if (var.isNull()) { ldie("unregistered create class"); }


  while(count && i<size_t(data.len())){
    i=pClass->funserial(var.var,data,i);
    if (i==-1) { var.var->cleanup=true; var.clear(); return(-1); }
    addref(&var.get<T>());
    var.clear();
    var.create(typeid(T).name());
    var.var->cleanup=false;
    --count;
  }
  var.var->cleanup=true;
  var.clear();

  if (count) return(-1);

  return(i);
}


template <class T>
void earray<T>::serial(const efile& f) const
{
  serialuint(size(),f);
  if (size()==0) return;

  if (!getClasses().exists(typeid(T).name())) { lerror(estr("class not registered: ")+typeid(T).name()); return; }

  eclassBase *pClass=&getClasses().values(typeid(T).name());
  evarType<T> *pType=new evarType<T>((T&)at(0));
  pType->cleanup=false;
  pClass->fserialf(pType,f);

  size_t i;
  for (i=1; i<size(); ++i){
    pType->object=(T*)&at(i);
    pClass->fserialf(pType,f);
  }
  delete pType;
//    ::serial(at(i),data);
}

template <class T>
bool earray<T>::unserial(const efile& f)
{
  unsigned int count;
  if (!unserialuint(count,f)) return(false);

  evar var;
  eclassBase *pClass=&getClasses().values(typeid(T).name());

  var.create(typeid(T).name());
  var.var->cleanup=false;

  if (var.isNull()) { ldie("unregistered create class"); }

  clear();

  while(count){
    if (!pClass->funserialf(var.var,f)){ var.var->cleanup=true; var.clear(); return(false); }
    addref(&var.get<T>());
    var.clear();
    var.create(typeid(T).name());
    var.var->cleanup=false;
    --count;
  }
  var.var->cleanup=true;
  var.clear();

  if (count) return(false);

  return(true);
}


/*
template <class T>
void earray<T>::serial(estr& data) const
{
  serialint(size(),data);
  int i;
  for (i=0; i<size(); ++i)
    ::serial(at(i),data);
}

template <class T>
int earray<T>::unserial(const estr& data,int i)
{
  return(ebasearray::unserial(data,i));
  clear();
  unsigned int count;
  T var;
  i=unserialint(count,data,i);
  while(count-- && i<data.len()){
    i=::unserial(var,data,i);
    add(var);
  }
  return(i);
}
*/

template <class T>
earray<T>::earray(const ebasearray &arr): ebasicarray<T*>()
{
  evar tmpv;
  for (int i=0; i<arr.size(); ++i){
    tmpv.set(arr.getvar(i));
    addvar(tmpv);
  }
}

template <class T>
void earray<T>::addvar(evar& var)
{
  if (var.getTypeid()==typeid(T))
    add(var.get<T>());
  else {
    evar cvar(var.convert(typeid(T)));
    if (!cvar.isNull())
      add(cvar.get<T>());
    else
      lerror("unable to convert: "+estr(var.getTypeid().name())+" to: "+typeid(T).name());
  }
}
template <class T>
evar earray<T>::getvar(size_t i) const
{
  return(evarRef<T>((T&)at(i)));
}

template <>
void earray<evar>::addvar(evar& var);
template <>
evar earray<evar>::getvar(size_t i) const;


template <class T>
void earray<T>::init(size_t count)
{
  size_t i;
  clear();
  reserve(count);
  for (i=0; i<count; ++i)
    vector<T*>::push_back(new T());
}

template <class T>
void earray<T>::init(size_t count,const T& initval)
{
  size_t i;
  clear();
  reserve(count);
  for (i=0; i<count; ++i)
    vector<T*>::push_back(new T(initval));
}

template <class T>
inline void earray<T>::erase(size_t i) { delete ebasicarray<T*>::at(i); ebasicarray<T*>::erase(i); }

template <class T>
ostream &operator<<(ostream &stream,const earray<T> &var)
{
  stream<<"{ "<<endl;
  size_t i;

  if (var.size()){
    for (i=0; i<var.size()-1; ++i){
      stream<<var[i]<<", "<<endl;
    }
    stream<<var[i]<<endl;
  }
  stream<<" }";
  return(stream);
}


template <class T>
earray<T>::earray(): ebasicarray<T*>()
{
}

/*
template <class T>
earray<T>::earray(const T* arr,int n): ebasicarray<T*>()
{
  int i;
  for (i=0; i<n; ++i)
    *this += arr[i];
}
*/

template <class T>
earray<T>::earray(const earray<T> &arr): ebasicarray<T*>()
{
  *this += arr;
}


/*
template <class T>
earray<T>::earray(const earray<evar> &varr): ebasicarray<T*>()
{
  for (int i=0; i<varr.size(); ++i)
    vector<T*>::push_back(new T(varr.get<T>()));
}
*/

template <class T>
earray<T>::earray(const T &value): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2,const T &value3): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
  vector<T*>::push_back(new T(value3));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2,const T &value3,const T& value4): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
  vector<T*>::push_back(new T(value3));
  vector<T*>::push_back(new T(value4));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2,const T &value3,const T& value4,const T& value5): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
  vector<T*>::push_back(new T(value3));
  vector<T*>::push_back(new T(value4));
  vector<T*>::push_back(new T(value5));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2,const T &value3,const T& value4,const T& value5,const T& value6): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
  vector<T*>::push_back(new T(value3));
  vector<T*>::push_back(new T(value4));
  vector<T*>::push_back(new T(value5));
  vector<T*>::push_back(new T(value6));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2,const T &value3,const T& value4,const T& value5,const T& value6,const T& value7): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
  vector<T*>::push_back(new T(value3));
  vector<T*>::push_back(new T(value4));
  vector<T*>::push_back(new T(value5));
  vector<T*>::push_back(new T(value6));
  vector<T*>::push_back(new T(value7));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2,const T &value3,const T& value4,const T& value5,const T& value6,const T& value7,const T& value8): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
  vector<T*>::push_back(new T(value3));
  vector<T*>::push_back(new T(value4));
  vector<T*>::push_back(new T(value5));
  vector<T*>::push_back(new T(value6));
  vector<T*>::push_back(new T(value7));
  vector<T*>::push_back(new T(value8));
}

template <class T>
earray<T>::earray(const T &value1,const T &value2,const T &value3,const T& value4,const T& value5,const T& value6,const T& value7,const T& value8,const T& value9): ebasicarray<T*>()
{
  vector<T*>::push_back(new T(value1));
  vector<T*>::push_back(new T(value2));
  vector<T*>::push_back(new T(value3));
  vector<T*>::push_back(new T(value4));
  vector<T*>::push_back(new T(value5));
  vector<T*>::push_back(new T(value6));
  vector<T*>::push_back(new T(value7));
  vector<T*>::push_back(new T(value8));
  vector<T*>::push_back(new T(value9));
}

template <class T>
earray<T>::~earray()
{
  clear();
}

template <class T>
void earray<T>::clear()
{
  size_t i;
  size_t s=size();
  for (i=0; i<s; ++i)
    delete ebasicarray<T*>::at(i);

  ebasicarray<T*>::clear();
}

template <class T>
earray<T> earray<T>::operator[](const eintarray& arr) const
{
  earray<T> res;
  size_t i;
  for (i=0; i<arr.size(); ++i){
    if (arr[i]<0 || arr[i]>=size()) { ldwarn("element in array list out of bounds: "+estr(arr[i])); continue; }
    res.add(operator[](arr[i]));
  }
  return(res); 
}


template <class T>
earray<T> &earray<T>::operator=(const earray<T> &arr)
{
  if (this==&arr) return(*this);
  clear();
  *this += arr;
  return(*this);
}

template <class T>
earray<T> &earray<T>::operator+=(const earray<T> &arr)
{
  size_t i;
  size_t c=arr.size();
  for (i=0; i<c; ++i)
    add(arr[i]);
  return(*this);
}

template <class T>
earray<T> &earray<T>::operator-=(const earray<T> &arr)
{
  size_t ind,j;
  for (j=0l; j<arr.size(); ++j){
    if (-1l != (ind = find(arr[j])))
      ebasicarray<T*>::erase(ebasicarray<T*>::begin()+ind);
  }
  return(*this);
}

template <class T>
earray<T> earray<T>::operator+(const earray<T> &arr) const
{
  earray<T> tmpa;

  tmpa+=*this;
  tmpa+=arr;
  return(tmpa);
}

template <class T>
earray<T> earray<T>::operator-(const earray<T> &arr) const
{
  earray<T> tmpa;

  tmpa=*this;
  tmpa-=arr;
  return(tmpa);
}

/*
template <class T>
earray<T> earray<T>::operator[](const earray<int> &iarr)
{
  earray<T> tmpa;

  int i;
  for (i=0; i<iarr.size(); ++i)
    tmpa += iarr[i];

  return(tmpa);
}
*/

template <class T>
earray<T> earray<T>::subset(long i,long l) const
{
  earray<T> tmpa;
 
  if (i<0) i+=ebasicarray<T*>::size();
  if (l<0) l+=ebasicarray<T*>::size()-i+1;

  if (i<0 || l<0 || !ebasicarray<T*>::size()) return(tmpa);

  l+=i;
  if (l>ebasicarray<T*>::size()) l=ebasicarray<T*>::size();
  for(;i<l;++i)
    tmpa+=(*this)[i];
  return(tmpa);
}

template <class T>
long earray<T>::find(const T &value,long i) const
{
  if (i<0) i+=size();

  if (i<0) return(-1);

  for (;i<size(); ++i){
    if (at(i) == value)
      return(i);
  }
  return(-1);
}

template <class T>
long earray<T>::find(const T &value,long i,bool (*match)(const T &a, const T &b)) const
{
  if (i<0) i+=size();

  if (i<0) return(-1);

  for (;i<size(); ++i){
    if ( match( at(i) , value) )
      return(i);
  }
  return(-1);
}

/*
template <class T>
earray<T> earray<T>::afindall(const T &value,int i,bool (*match)(const T &a, const T &b)) const
{
  earray<T> tmpai;

  if (i<0) i+=ebasicarray<T*>::size();

  if (i<0) return(tmpai);

  for (;i<ebasicarray<T*>::size(); ++i){
    if ( match( (*this)[i] , value) )
      tmpai += (*this)[i];
  }
  return(tmpai);
}
*/

template <class T>
earray<T> earray<T>::afindall(const earray<T> &arr,long i,bool (*match)(const T &a, const T &b)) const
{
  earray<T> tmpai;

  if (i<0) i+=ebasicarray<T*>::size();
  if (i<0) return(tmpai);

  for (;i<ebasicarray<T*>::size(); ++i){
    if ( -1 != arr.find( (*this)[i] , 0 , match) )
      tmpai += (*this)[i];
  }
  return(tmpai);
}

template <class T>
eintarray earray<T>::findall(const T &value,long i,bool (*match)(const T &a, const T &b)) const
{
  eintarray tmpai;

  if (i<0) i+=ebasicarray<T*>::size();

  if (i<0) return(tmpai);

  for (;i<ebasicarray<T*>::size(); ++i){
    if ( match( (*this)[i] , value) )
      tmpai += i;
  }
  return(tmpai);
}

template <class T>
eintarray earray<T>::findall(const earray<T> &arr,long i,bool (*match)(const T &a, const T &b)) const
{
  eintarray tmpai;

  if (i<0) i+=ebasicarray<T*>::size();

  if (i<0) return(tmpai);

  for (;i<ebasicarray<T*>::size(); ++i){
    if ( -1 != arr.find( (*this)[i] , 0 , match) )
      tmpai += i;
  }
  return(tmpai);
}


/*
void erase(const earray<int> &iarr)
{
  int i;

  for (i=0; i<iarr.size(); ++i){
    vector::erase(begin() + iarr[i]);
  }

}
*/

#endif

