// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_wroot_element
#define tools_wroot_element

#include "buffer"
#include "named"
#include "../snpf"

namespace tools {
namespace wroot {

namespace streamer__info {

  enum Type {              // sizeof :
    BASE = 0,              //  x
    ARRAY = 20,            //  ?
    POINTER = 40,          //  4
    POINTER_INT = 43,      //  4
    POINTER_FLOAT = 45,    //  4
    POINTER_DOUBLE = 48,   //  4
    COUNTER =  6,          //  4
    CHAR =  1,             //  1
    SHORT =  2,            //  2
    INT =  3,              //  4
    FLOAT =  5,            //  4
    DOUBLE =  8,           //  8
    UNSIGNED_CHAR =  11,   //  1
    UNSIGNED_SHORT =  12,  //  2
    UNSIGNED_INT = 13,     //  4
    BOOL = 18,             //  1 ?
    OBJECT = 61,           //  ?
    OBJECT_ANY = 62,       //  ?
    OBJECT_ARROW = 63,     //  ?
    OBJECT_POINTER = 64,   //  ?
    _TSTRING = 65,         //  8  //NOTE : TSTRING clashes with a cpp macro in cfitsio.
    TOBJECT = 66,          // 12
    TNAMED = 67            // 28
  };

  const int size_FLOAT = 4;
  const int size_DOUBLE = 8;
  const int size_INT = 4;
  const int size_UINT = 4;
  const int size_SHORT = 2;
  const int size_BOOL = 4; //(Bool_t = 1 + 3 for alignement)
  //uuu ? const int size_BOOL = 1;

  const int size_TString = 8;
}


class streamer_element : public virtual ibo {
  static const std::string& s_class() {
    static const std::string s_v("tools::wroot::streamer_element");
    return s_v;
  }
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerElement");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!Named_stream(aBuffer,fName,fTitle)) return false;
    if(!aBuffer.write(fType)) return false;
    if(!aBuffer.write(fSize)) return false;
    if(!aBuffer.write(fArrayLength)) return false;
    if(!aBuffer.write(fArrayDim)) return false;
    if(!aBuffer.write_fast_array<int>(fMaxIndex,5)) return false;
    if(!aBuffer.write(fTypeName)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public:
  virtual streamer_element* copy() const = 0;
public:
  virtual void out(std::ostream& aOut) const {
    std::string _fname;
    fullName(_fname);
    char _s[256];
    snpf(_s,sizeof(_s),"  %-14s%-15s offset=%3d type=%2d %-20s",fTypeName.c_str(),_fname.c_str(),fOffset,fType,fTitle.c_str());
    aOut << _s << std::endl;
  }
public:
  streamer_element(const std::string& aName,const std::string& aTitle,
                   int aOffset,int aType,const std::string& aTypeName)
  :fName(aName),fTitle(aTitle),fType(aType)
  ,fSize(0),fArrayLength(0),fArrayDim(0),fOffset(aOffset)
  ,fTypeName(aTypeName){
    for(int i=0;i<5;i++) fMaxIndex[i] = 0;
  }
  virtual ~streamer_element(){
  }
protected:
  streamer_element(const streamer_element& a_from)
  :ibo(a_from)
  ,fName(a_from.fName),fTitle(a_from.fTitle)
  ,fType(a_from.fType),fSize(a_from.fSize)
  ,fArrayLength(a_from.fArrayLength)
  ,fArrayDim(a_from.fArrayDim),fOffset(a_from.fOffset)
  ,fTypeName(a_from.fTypeName){
    for(int i=0;i<5;i++) fMaxIndex[i] = a_from.fMaxIndex[i];
  }
  streamer_element& operator=(const streamer_element& a_from){
    fName = a_from.fName;
    fTitle = a_from.fTitle;
    fType = a_from.fType;
    fSize = a_from.fSize;
    fArrayLength = a_from.fArrayLength;
    fArrayDim = a_from.fArrayDim;
    fOffset = a_from.fOffset;
    fTypeName = a_from.fTypeName;
    for(int i=0;i<5;i++) fMaxIndex[i] = a_from.fMaxIndex[i];
    return *this;
  }
public:
  virtual void setArrayDimension(int aDimension){
    fArrayDim = aDimension;
    if(aDimension) fType += streamer__info::ARRAY;
    //fNewType = fType;
  }
  virtual void setMaxIndex(int aDimension,int aMaximum){
    //set maximum index for array with dimension dim
    if (aDimension < 0 || aDimension > 4) return;
    fMaxIndex[aDimension] = aMaximum;
    if (fArrayLength == 0)  fArrayLength  = aMaximum;
    else                    fArrayLength *= aMaximum;
  }

  virtual void fullName(std::string& a_s) const {
    a_s = fName;
    for (int i=0;i<fArrayDim;i++) {
      char cdim[32];
      snpf(cdim,sizeof(cdim),"[%d]",fMaxIndex[i]);
      a_s += cdim;
    }
  }
protected: //Named
  std::string fName;
  std::string fTitle;
protected:
  int fType;            //element type
  int fSize;            //sizeof element
  int fArrayLength;     //cumulative size of all array dims
  int fArrayDim;        //number of array dimensions
  int fMaxIndex[5];     //Maximum array index for array dimension "dim"
  int fOffset;          //!element offset in class
  //FIXME Int_t         fNewType;         //!new element type when reading
  std::string fTypeName;        //Data type name of data member
};

class streamer_base : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerBase");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(3,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.write(fBaseVersion)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_base(*this);}
public:
  streamer_base(const std::string& aName,const std::string& aTitle,int aOffset,int aBaseVersion)
  :streamer_element(aName,aTitle,aOffset,streamer__info::BASE,"BASE")
  ,fBaseVersion(aBaseVersion)
  {
    if (aName=="TObject") fType = streamer__info::TOBJECT;
    if (aName=="TNamed") fType = streamer__info::TNAMED;
  }
  virtual ~streamer_base(){}
public:
  streamer_base(const streamer_base& a_from)
  :ibo(a_from)
  ,streamer_element(a_from)
  ,fBaseVersion(a_from.fBaseVersion)
  {}
  streamer_base& operator=(const streamer_base& a_from){
    streamer_element::operator=(a_from);
    fBaseVersion = a_from.fBaseVersion;
    return *this;
  }
protected:
  int fBaseVersion;         //version number of the base class
};

class streamer_basic_type : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerBasicType");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_basic_type(*this);}
public:
  streamer_basic_type(const std::string& aName,const std::string& aTitle,
                      int aOffset,int aType,const std::string& aTypeName)
  :streamer_element(aName,aTitle,aOffset,aType,aTypeName)
  {}
  virtual ~streamer_basic_type(){}
public:
  streamer_basic_type(const streamer_basic_type& a_from)
  :ibo(a_from),streamer_element(a_from)
  {}
  streamer_basic_type& operator=(const streamer_basic_type& a_from){
    streamer_element::operator=(a_from);
    return *this;
  }
};

class streamer_short : public streamer_basic_type {
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_short(*this);}
public:
  streamer_short(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::SHORT,"Short_t")
  {}
  streamer_short(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::SHORT,"Short_t")
  {
    aOffset += streamer__info::size_SHORT;
  }
  virtual ~streamer_short(){}
public:
  streamer_short(const streamer_short& a_from):ibo(a_from),streamer_basic_type(a_from){}
  streamer_short& operator=(const streamer_short& a_from){streamer_basic_type::operator=(a_from);return *this;}
};

class streamer_int : public streamer_basic_type {
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_int(*this);}
public:
  streamer_int(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::INT,"Int_t")
  {}
  streamer_int(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::INT,"Int_t")
  {
    aOffset += streamer__info::size_INT;
  }
  virtual ~streamer_int(){}
public:
  streamer_int(const streamer_int& a_from):ibo(a_from),streamer_basic_type(a_from){}
  streamer_int& operator=(const streamer_int& a_from){streamer_basic_type::operator=(a_from);return *this;}
};

class streamer_uint : public streamer_basic_type {
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_uint(*this);}
public:
  streamer_uint(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::UNSIGNED_INT,"UInt_t")
  {}
  streamer_uint(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::UNSIGNED_INT,"UInt_t")
  {
    aOffset += streamer__info::size_UINT;
  }
  virtual ~streamer_uint(){}
public:
  streamer_uint(const streamer_uint& a_from):ibo(a_from),streamer_basic_type(a_from){}
  streamer_uint& operator=(const streamer_uint& a_from){streamer_basic_type::operator=(a_from);return *this;}
};

class streamer_float : public streamer_basic_type {
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_float(*this);}
public:
  streamer_float(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::FLOAT,"Float_t")
  {}
  streamer_float(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::FLOAT,"Float_t")
  {
    aOffset += streamer__info::size_FLOAT;
  }
  virtual ~streamer_float(){}
public:
  streamer_float(const streamer_float& a_from):ibo(a_from),streamer_basic_type(a_from){}
  streamer_float& operator=(const streamer_float& a_from){streamer_basic_type::operator=(a_from);return *this;}
};

class streamer_double : public streamer_basic_type {
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_double(*this);}
public:
  streamer_double(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::DOUBLE,"Double_t")
  {}
  streamer_double(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::DOUBLE,"Double_t")
  {
    aOffset += streamer__info::size_DOUBLE;
  }
  virtual ~streamer_double(){}
public:
  streamer_double(const streamer_double& a_from):ibo(a_from),streamer_basic_type(a_from){}
  streamer_double& operator=(const streamer_double& a_from){streamer_basic_type::operator=(a_from);return *this;}
};

class streamer_stat_t : public streamer_basic_type {
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_stat_t(*this);}
public:
  streamer_stat_t(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::DOUBLE,"Stat_t")
  {}
  streamer_stat_t(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::DOUBLE,"Stat_t")
  {
    aOffset += streamer__info::size_DOUBLE;
  }
  virtual ~streamer_stat_t(){}
public:
  streamer_stat_t(const streamer_stat_t& a_from):ibo(a_from),streamer_basic_type(a_from){}
  streamer_stat_t& operator=(const streamer_stat_t& a_from){streamer_basic_type::operator=(a_from);return *this;}
};

class streamer_bool : public streamer_basic_type {
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_bool(*this);}
public:
  streamer_bool(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::UNSIGNED_CHAR,"Bool_t")
  {}
  streamer_bool(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_basic_type(aName,aTitle,aOffset,streamer__info::UNSIGNED_CHAR,"Bool_t")
  {
    aOffset += streamer__info::size_BOOL;
  }
  virtual ~streamer_bool(){}
public:
  streamer_bool(const streamer_bool& a_from):ibo(a_from),streamer_basic_type(a_from){}
  streamer_bool& operator=(const streamer_bool& a_from){streamer_basic_type::operator=(a_from);return *this;}
};

class streamer_basic_pointer : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerBasicPointer");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.write(fCountVersion)) return false;
    if(!aBuffer.write(fCountName)) return false;
    if(!aBuffer.write(fCountClass)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_basic_pointer(*this);}
public:
  streamer_basic_pointer(const std::string& aName,const std::string& aTitle,
                         int aOffset,int aType,
                         const std::string& aCountName,
                         const std::string& aCountClass,
                         int aCountVersion,
                         const std::string& aTypeName)
  :streamer_element(aName,aTitle,aOffset,aType+streamer__info::POINTER,aTypeName)
  ,fCountVersion(aCountVersion)
  ,fCountName(aCountName)
  ,fCountClass(aCountClass)
  {}
  virtual ~streamer_basic_pointer(){}
public:
  streamer_basic_pointer(const streamer_basic_pointer& a_from)
  :ibo(a_from),streamer_element(a_from)
  ,fCountVersion(a_from.fCountVersion)
  ,fCountName(a_from.fCountName)
  ,fCountClass(a_from.fCountClass)
  {}
  streamer_basic_pointer& operator=(const streamer_basic_pointer& a_from){
    streamer_element::operator=(a_from);
    fCountVersion = a_from.fCountVersion;
    fCountName    = a_from.fCountName;
    fCountClass   = a_from.fCountClass;
    return *this;
  }
protected:
  int fCountVersion;       //version number of the class with the counter
  std::string fCountName;  //name of data member holding the array count
  std::string fCountClass; //name of the class with the counter
};

class streamer_string : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerString");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_string(*this);}
public:
  streamer_string(const std::string& aName,const std::string& aTitle,int aOffset)
  :streamer_element(aName,aTitle,aOffset,streamer__info::_TSTRING,"TString")
  {}
  streamer_string(int& aOffset,const std::string& aName,const std::string& aTitle)
  :streamer_element(aName,aTitle,aOffset,streamer__info::_TSTRING,"TString")
  {
    aOffset += streamer__info::size_TString;
  }
  virtual ~streamer_string(){}
public:
  streamer_string(const streamer_string& a_from):ibo(a_from),streamer_element(a_from){}
  streamer_string& operator=(const streamer_string& a_from){streamer_element::operator=(a_from);return *this;}
};


class streamer_object : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerObject");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_object(*this);}
public:
  streamer_object(const std::string& aName,const std::string& aTitle,int aOffset,const std::string& aTypeName)
  :streamer_element(aName,aTitle,aOffset,0,aTypeName){
    fType = streamer__info::OBJECT;
    if (aName=="TObject") fType = streamer__info::TOBJECT;
    if (aName=="TNamed") fType = streamer__info::TNAMED;
  }
  virtual ~streamer_object(){}
public:
  streamer_object(const streamer_object& a_from):ibo(a_from),streamer_element(a_from){}
  streamer_object& operator=(const streamer_object& a_from){
    streamer_element::operator=(a_from);
    return *this;
  }
};

class streamer_object_pointer : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerObjectPointer");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_object_pointer(*this);}
public:
  streamer_object_pointer(const std::string& aName,const std::string& aTitle,
                          int aOffset,const std::string& aTypeName)
  :streamer_element(aName,aTitle,aOffset,streamer__info::OBJECT_POINTER,aTypeName){
    if(aTitle.substr(0,2)=="->") fType = streamer__info::OBJECT_ARROW;
  }
  virtual ~streamer_object_pointer(){}
public:
  streamer_object_pointer(const streamer_object_pointer& a_from):ibo(a_from),streamer_element(a_from){}
  streamer_object_pointer& operator=(const streamer_object_pointer& a_from){
    streamer_element::operator=(a_from);
    return *this;
  }
};

class streamer_object_any : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerObjectAny");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_object_any(*this);}
public:
  streamer_object_any(const std::string& aName,const std::string& aTitle,
                      int aOffset,const std::string& aTypeName)
  :streamer_element(aName,aTitle,aOffset,streamer__info::OBJECT_ANY,aTypeName)
  {}
  virtual ~streamer_object_any(){}
public:
  streamer_object_any(const streamer_object_any& a_from):ibo(a_from),streamer_element(a_from){}
  streamer_object_any& operator=(const streamer_object_any& a_from){
    streamer_element::operator=(a_from);
    return *this;
  }
};


class streamer_STL : public streamer_element {
public: //ibo
  virtual const std::string& store_cls() const {
    static const std::string s_v("TStreamerSTL");
    return s_v;
  }
  virtual bool stream(buffer& aBuffer) const {
    unsigned int c;
    if(!aBuffer.write_version(2,c)) return false;
    if(!streamer_element::stream(aBuffer)) return false;
    if(!aBuffer.write(fSTLtype)) return false;
    if(!aBuffer.write(fCtype)) return false;
    if(!aBuffer.set_byte_count(c)) return false;
    return true;
  }
public: //streamer_element
  virtual streamer_element* copy() const {return new streamer_STL(*this);}
protected:
  enum ESTLtype { kSTL       = 300, kSTLstring  =365,   kSTLvector = 1,
                  kSTLlist   =  2,  kSTLdeque   =  3,   kSTLmap    = 4,
                  kSTLset    =  5,  kSTLmultimap=6,     kSTLmultiset=7};

  // Instead of EDataType, we use the streamer__info::Type.
  //enum EDataType {
  //   kChar_t  = 1, kUChar_t  = 11, kShort_t = 2,  kUShort_t = 12,
  //   kInt_t   = 3, kUInt_t   = 13, kLong_t  = 4,  kULong_t  = 14,
  //   kFloat_t = 5, kDouble_t = 8,  kchar  = 10, kOther_t  = -1
  //};
public:
  streamer_STL(const std::string& aName,const std::string& aTitle,
               int aOffset,
               streamer__info::Type aType, //Must match TDataType/EDataType
               const std::string& aTypeName)
  :streamer_element(aName,aTitle,aOffset,kSTL,aTypeName){
    fSTLtype = kSTLvector;
    fCtype   = aType;
  }
  virtual ~streamer_STL(){}
public:
  streamer_STL(const streamer_STL& a_from)
  :ibo(a_from),streamer_element(a_from)
  ,fSTLtype(a_from.fSTLtype)
  ,fCtype(a_from.fCtype)
  {}
  streamer_STL& operator=(const streamer_STL& a_from){
    streamer_element::operator=(a_from);
    fSTLtype = a_from.fSTLtype;
    fCtype = a_from.fCtype;
    return *this;
  }
protected:
  int fSTLtype;       //type of STL vector
  int fCtype;         //STL contained type
};

}}

#endif
