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

#ifndef tools_rroot_buffer
#define tools_rroot_buffer

#include "rbuf"
#include "ifac"
#include "iro"
#include "../sout"
#include "../mapmanip"
#ifdef TOOLS_MEM
#include "../mem"
#endif

#include <string>
#include <vector>
#include <ostream>
#include <utility>

// in TOOLS_STL, we don't have (yet) a performant map.

#ifdef tools_stl_vector
#define TOOLS_RROOT_OBJ_MAP_HASH_TABLE
//#define TOOLS_RROOT_OBJ_MAP_OMAP
#else
#define TOOLS_RROOT_OBJ_MAP_STD_MAP
#endif

#ifdef TOOLS_RROOT_OBJ_MAP_STD_MAP
#include <map>
#endif
#ifdef TOOLS_RROOT_OBJ_MAP_OMAP
#include "../omap"
#endif
#ifdef TOOLS_RROOT_OBJ_MAP_HASH_TABLE
#include "../hash_table"
#include "../hash"
#endif

namespace tools {
namespace rroot {

class buffer : public rbuf {
  typedef rbuf parent;

  static const std::string& s_class() {
    static const std::string s_v("tools::rroot::buffer");
    return s_v;
  }

#ifdef TOOLS_RROOT_OBJ_MAP_STD_MAP
  typedef std::map<uint32,iro*> obj_map;
#endif
#ifdef TOOLS_RROOT_OBJ_MAP_OMAP
  typedef omap<uint32,iro*> obj_map;
#endif
#ifdef TOOLS_RROOT_OBJ_MAP_HASH_TABLE
  class obj_map : public hash_table<uint32,iro*> {
    typedef hash_table<uint32,iro*> parent;
  protected:
    virtual unsigned int hash(const uint32& a_key) {
      return hash(&a_key,sizeof(uint32));
    }
    virtual bool cmp(const uint32& a_key1,const uint32& a_key2) {
      return a_key1==a_key2?true:false;
    }
  public:
    obj_map():parent(){}
    virtual ~obj_map(){}
  private:
    obj_map(const obj_map& a_from):parent(a_from){}
    obj_map& operator=(const obj_map& a_from){
      parent::operator=(a_from);
      return *this;
    }
  };
#endif

public:
  buffer(std::ostream& a_out,bool a_byte_swap,uint32 a_size,char* a_buffer,uint32 a_klen,bool a_verbose)
  :parent(a_out,a_byte_swap,a_buffer+a_size,m_pos)
  ,m_byte_swap(a_byte_swap)
  ,m_verbose(a_verbose)
  //,m_verbose(true)
  ,m_size(a_size)     //expect a not zero size.
  ,m_buffer(a_buffer) //don't get ownership.
  ,m_pos(a_buffer)
  ,m_klen(a_klen) //to compute refs (used in read_class, read_object)
//,m_map_objs(false)
  ,m_map_objs(true)
  {
#ifdef TOOLS_MEM
    mem::increment(s_class().c_str());
#endif
  }
  virtual ~buffer(){
    m_objs.clear();
#ifdef TOOLS_MEM
    mem::decrement(s_class().c_str());
#endif
  }
protected:
  buffer(const buffer& a_from)
  :parent(a_from.m_out,a_from.m_byte_swap,0,m_pos)
  ,m_byte_swap(a_from.m_byte_swap)
  ,m_map_objs(a_from.m_map_objs)
  {
#ifdef TOOLS_MEM
    mem::increment(s_class().c_str());
#endif
  }
  buffer& operator=(const buffer&){return *this;}
public:
  bool byte_swap() const {return m_byte_swap;}
  bool verbose() const {return m_verbose;}

  void set_offset(unsigned int a_off) {m_pos = m_buffer+a_off;}

  //char* buffer() const {return m_buffer;}
  uint32 length() const {return uint32(m_pos-m_buffer);}
  uint32 size() const {return m_size;}

  void set_map_objs(bool a_value) {m_map_objs = a_value;}
  bool map_objs() const {return m_map_objs;}
protected:
  // on first_int :
  static uint32 kNullTag() {return 0;}
  static uint32 kByteCountMask() {return 0x40000000;}
  // on tag :
  static uint32 kNewClassTag() {return 0xFFFFFFFF;}
  static uint32 kClassMask() {return 0x80000000;  }
  static uint32 kMapOffset() {return 2;}
  static short kByteCountVMask() {return 0x4000;}

  bool read_class_tag(std::string& a_class) {
    a_class.clear();

    uint32 tag;
    if(!parent::read(tag)) return false;

    if(tag==kNewClassTag()) {
      char _s[80];
      if(!read_string(_s, 80)) {
        m_out << "tools::rroot::read_class_tag :"
              << " read string." << std::endl;
        return false;
      }
      //m_out << "tools::rroot::read_class_tag :"
      //      << " tag kNewClassTag"
      //      << " class " << _s
      //      << std::endl;
      a_class = _s;
      return true;

    } else if(tag & kClassMask()) {
      //m_out << "tools::rroot::read_class_tag :"
      //      << " tag & kClassMask"
      //      << " ref " << (uint32)(tag & ~kClassMask)
      //      << " recurse..."
      //      << std::endl;

      unsigned int cl_offset = (tag & ~kClassMask());
      cl_offset -= kMapOffset();
      cl_offset -= m_klen;
      char* old_pos = m_pos;
      m_pos = m_buffer + cl_offset;
      //std::string scls;
      //uint32 ref;
      if(!read_class_tag(a_class)) return false;
      m_pos = old_pos;

      return true;

    } else {

      std::ios::fmtflags old_flags = m_out.flags();
      m_out << "tools::rroot::read_class_tag :"
            << " tag unknown case ! "
            << tag << " hex " << std::hex << tag
            << std::endl;
      m_out.flags(old_flags);

      return false;
    }
  }

  bool read_class(std::string& a_class,uint32& a_bcnt,bool& a_is_ref){
    //m_verbose = true;
    a_class.clear();
    a_bcnt = 0;
    a_is_ref = false;

    //uint32 fVersion = 0;       //Buffer format version

    // read byte count and/or tag (older files don't have byte count)
    uint32 first_int = 0;
    if(!parent::read(first_int)) return false;

    if(m_verbose) {
      std::ios::fmtflags old_flags = m_out.flags();
      m_out << "tools::rroot::read_class :"
            << " first_int " << std::hex << first_int
            << std::endl;
      m_out.flags(old_flags);
    }

    if(first_int==kNullTag()) { //GB
      if(m_verbose) {
        m_out << "tools::rroot::read_class :"
              << " first_int is kNullTag."
              << std::endl;
      }
      a_bcnt = 0;
      return true;

  //} else if(first_int==kNewClassTag()) { // class desc follows.
    } else if(first_int & kByteCountMask()) {
      // at write :
      //   skip int to store bcnt.
      // + write class
      //     if(!write((clIdx | Rio_kClassMask))) return false;
      //   or
      //     if(!write(Rio_kNewClassTag)) return false;
      // + write object
      // + set byte cnt (cnt|kByteCountMask()) at skipped int.

      if(m_verbose) {
        m_out << "tools::rroot::read_class :"
              << " first_int & kByteCountMask."
              << std::endl;
      }

      uint32 bef_tag = uint32(m_pos-m_buffer);
      //fVersion = 1;

      std::string scls;
      if(!read_class_tag(scls)) return false;
      if(scls.empty()) {
        m_out << "tools::rroot::buffer::read_class :"
              << " read_class_tag did not find a class name."
              << std::endl;
        return false;
      }

      a_class = std::move(scls);
      a_bcnt = (first_int & ~kByteCountMask());

      if(m_verbose) {
        m_out << "tools::rroot::read_class :"
              << " kNewClassTag : read class name " << sout(a_class)
              << " a_bcnt " << a_bcnt
              << " bef_tag " << bef_tag
              << "." << std::endl;
      }

      return true;

    } else {
      if(m_verbose) {
        std::ios::fmtflags old_flags = m_out.flags();
        m_out << "tools::rroot::read_class :"
              << " first_int " << std::hex << first_int
               << ". first_int is position toward object."
               << std::endl;
        m_out.flags(old_flags);
      }
      a_bcnt = first_int; //pos toward object.
      a_is_ref = true;
      a_class.clear();
      return true;
    }

    return false;
  }
public:
  void remove_in_map(iro* a_obj) {
#ifdef TOOLS_RROOT_OBJ_MAP_HASH_TABLE
    m_out << "tools::rroot::remove_in_map : dummy." << std::endl;
#else
    find_and_remove_value(m_objs,a_obj);
#endif
  }
  bool read_object(ifac& a_fac,const ifac::args& a_args,iro*& a_obj,bool& a_created){
    a_obj = 0;
    a_created = false;

    //m_out << "debug : tools::rroot::buffer::read_object : begin :" << std::endl;

    // before reading object save start position
    uint32 startpos = (uint32)(m_pos-m_buffer);

    uint32 bcnt;
    std::string sclass;
    bool is_ref;
    if(!read_class(sclass,bcnt,is_ref)) {
      m_out << "tools::rroot::buffer::read_object :"
            << " can't read class." << std::endl;
      return false;
    }
    //::printf("debug : %d\n",length()-startpos);

    if(m_verbose) {
      m_out << "tools::rroot::buffer::read_object :"
            << " class " << sout(sclass) << ", is_ref " << is_ref
            << ", bcnt " << bcnt
            << std::endl;
    }

    if(is_ref) { //bcnt is the position toward an object or an object ref.
      //m_out << "debug : tools::rroot::buffer::read_object : is_ref : bcnt " << bcnt << std::endl;

      unsigned int obj_offset = bcnt;
      obj_offset -= kMapOffset();
      obj_offset -= m_klen;

      if(!m_map_objs) {
        m_out << "tools::rroot::buffer::read_object : warning :"
              << " class " << sout(sclass) << ", is_ref but map objs is not enabled on this buffer."
              << std::endl;
      }

      if(m_map_objs) {
#ifdef TOOLS_RROOT_OBJ_MAP_HASH_TABLE
        if(m_objs.find(obj_offset,a_obj)) {
          //m_out << "debug : tools::rroot::buffer::read_object : found object in map." << bcnt << std::endl;
          return true;
        }
#else
        obj_map::const_iterator it = m_objs.find(obj_offset);
        if(it!=m_objs.end()) {
          a_obj = (*it).second;
          //::printf("debug : find : %d %lu\n",obj_offset,a_obj);
          //stay at current m_pos ?
          return true;
        }
#endif
      }

     {char* old_pos = m_pos;

      m_pos = m_buffer + obj_offset;

      uint32 first_int;
      if(!parent::read(first_int)) {
        m_out << "tools::rroot::buffer::read_object : parent::read(first_int) failed." << std::endl;
        return false;
      }
      if(first_int & kByteCountMask()) {
        std::string scls;
        if(!read_class_tag(scls)) {
          m_out << "tools::rroot::buffer::read_object : read_class_tag() failed." << std::endl;
          return false;
	}
        if(scls.empty()) {
          m_out << "tools::rroot::buffer::read_object :"
                << " read_class_tag did not find a class name."
                << std::endl;
          return false;
        }

        //m_out << "tools::rroot::buffer::read_object :"
        //      << " is_ref : class " << sout(scls)
        //      << std::endl;

        iro* obj = a_fac.create(scls,a_args);
        if(!obj) {
          m_out << "tools::rroot::buffer::read_object : is_ref : creation of object"
                << " of class " << sout(sclass) << " failed." << std::endl;
	  return false;
	}

        if(m_map_objs) {
          //must be done before stream()
#ifdef TOOLS_RROOT_OBJ_MAP_HASH_TABLE
          if(!m_objs.insert(obj_offset,obj)) {
            m_out << "tools::rroot::buffer::read_object :"
                  << " map insert failed."
                  << std::endl;
          }
#else
          m_objs[obj_offset] = obj;
#endif
        }

        if(!obj->stream(*this)) {
          m_out << "tools::rroot::buffer::read_object :"
                << " is_ref : streamed failed for class " << sout(scls)
                << std::endl;
          //restore a good buffer position :
          //m_pos = m_buffer+startpos+sizeof(unsigned int);
          delete obj;
          return false;
        }

        //m_out << "tools::rroot::buffer::read_object :"
        //      << " is_ref : streamed ok for class " << sout(scls)
        //      << std::endl;

        a_obj = obj;
        a_created = true;

      } else {
        m_out << "tools::rroot::buffer::read_object :"
              << " is_ref : zzz"
              << std::endl;
      }

      m_pos = old_pos;}

      // in principle at this point m_pos should be
      //   m_buffer+startpos+sizeof(unsigned int)
      // but enforce it anyway :
      m_pos = m_buffer+startpos+sizeof(unsigned int);
      //check_byte_count(startpos,0,sclass) would always be ok.

      //a_obj = ???

    } else {
      if(sclass.empty()) {
        //m_out << "debug : tools::rroot::buffer::read_object : found empty class name." << std::endl;

        m_pos = m_buffer+startpos+bcnt+sizeof(unsigned int);

      } else {
        //m_out << "debug : tools::rroot::buffer::read_object : not a ref, create object." << std::endl;

        // Being not a ref, it must NOT be in the map.
        // We gain a lot by not doing the below find.
/*
        if(m_map_objs) {
#ifdef TOOLS_RROOT_OBJ_MAP_HASH_TABLE
          if(m_objs.find(startpos,a_obj)) return true;
#else
          obj_map::const_iterator it = m_objs.find(startpos);
          if(it!=m_objs.end()) {
            a_obj = (*it).second;
            ::printf("debug : find xxx : %d %lu\n",startpos,a_obj);
            //stay at current m_pos ?
            return true;
          }
#endif
        }
*/

        iro* obj = a_fac.create(sclass,a_args);
        if(!obj) {
          m_out << "tools::rroot::buffer::read_object : creation of object"
                << " of class " << sout(sclass) << " failed." << std::endl;
          return false;
        }
        //m_out << "debug : tools::rroot::buffer::read_object : object created." << std::endl;

        if(m_map_objs) {
          //must be done before stream()
#ifdef TOOLS_RROOT_OBJ_MAP_HASH_TABLE
          if(!m_objs.insert(startpos,obj)) {
            m_out << "tools::rroot::buffer::read_object :"
                  << " map insert failed."
                  << std::endl;
          }
#else
          m_objs[startpos] = obj;
#endif
        }

        //::printf("debug : map : %d %lu\n",startpos,obj);

        //NOTE : if class ref, "up there"-startpos = 8.
        if(!obj->stream(*this)) {
          m_out << "tools::rroot::buffer::read_object : object.stream() failed"
                << " for object of class " << sout(sclass) << "." << std::endl;
          //restore a good buffer position :
          //m_pos = m_buffer+startpos+bcnt+sizeof(unsigned int);
          delete obj;
          return false;
        }

        //NOTE : if obj stream ok, the below line is not needed.
        //m_pos = m_buffer+startpos+bcnt+sizeof(unsigned int);

        if(!check_byte_count(startpos,bcnt,sclass)) {
          m_out << "tools::rroot::buffer::read_object :"
                << " check_byte_count failed "
                << "for object of class "
                << sout(sclass) << "." << std::endl;
          delete obj;
          return false;
        }

        a_obj = obj;
        a_created = true;
      }

    }

    if(m_verbose) {
      m_out << "tools::rroot::buffer::read_object : end." << std::endl;
    }

    return true;
  }
public:
  bool read_version(short& a_version){
    // Read class version from I/O buffer.
    // Is read a short or three shorts.
    a_version = 0;
    short version = 0;
    // not interested in byte count
    if(!parent::read(version)) return false;

    // if this is a byte count, then skip next short and read version
    if(version & kByteCountVMask()) {
      if(!parent::read(version)) return false;
      if(!parent::read(version)) return false;
    }

    a_version = version;
    return true;
  }

  bool read_version(short& a_version,uint32& a_start_pos,uint32& a_byte_count){
    // Read class version from I/O buffer.
    // Is read one or three shorts.
    a_version = 0;
    a_start_pos = 0;
    a_byte_count = 0;

    short version = 0;

    // before reading object save start position
    uint32 startpos = (uint32)(m_pos-m_buffer);

    // read byte count (older files don't have byte count)
    // byte count is packed in two individual shorts, this to be
    // backward compatible with old files that have at this location
    // only a single short (i.e. the version)
    union {
      unsigned int cnt;
      short vers[2];
    } v;

    if(m_byte_swap) {
      if(!parent::read(v.vers[1])) return false;
      if(!parent::read(v.vers[0])) return false;
    } else {
      if(!parent::read(v.vers[0])) return false;
      if(!parent::read(v.vers[1])) return false;
    }

    // no bytecount, backup and read version
    uint32 bcnt = 0;
    if(v.cnt & kByteCountMask()) {
      bcnt = (v.cnt & ~kByteCountMask());
    } else {
      m_pos -= sizeof(unsigned int);
    }
    if(!parent::read(version)) return false;
    //printf("Reading version=%d at pos=%d, bytecount=%d\n",
    //version,*startpos,*bcnt);

    a_version = version;
    a_start_pos = startpos;
    a_byte_count = bcnt;

    return true;
  }

  bool check_byte_count(uint32 a_start_pos,uint32 a_byte_count,const std::string& a_store_cls){
    if(!a_byte_count) return true;

    size_t len = a_start_pos + a_byte_count + sizeof(unsigned int);

    size_t diff = size_t(m_pos-m_buffer);

    if(diff==len) return true;

    if(diff<len) {
      m_out << "tools::rroot::buffer::check_byte_count :"
            << " object of class " << sout(a_store_cls)
            << " read too few bytes ("
            << long_out(long(len-diff)) << " missing)."
            << std::endl;
    }
    if(diff>len) {
      m_out << "tools::rroot::buffer::check_byte_count :"
            << " object of class " << sout(a_store_cls)
            << " read too many bytes ("
            << long_out(long(diff-len)) << " in excess)." << std::endl;
    }

    m_out << "tools::rroot::buffer::check_byte_count :"
          << " " << sout(a_store_cls)
          << " streamer not in sync with data on file, fix streamer."
          << std::endl;

    m_pos = m_buffer+len;

    return false;
  }
protected:
  bool read_string(char* a_string,uint32 a_max) {
    // Read string from I/O buffer. String is read till 0 character is
    // found or till max-1 characters are read (i.e. string s has max
    // bytes allocated).
    int nr = 0;
    while (nr < int(a_max-1)) {
      char ch;
      if(!parent::read(ch)) return false;
      // stop when 0 read
      if(ch == 0) break;
      a_string[nr++] = ch;
    }
    a_string[nr] = 0;
    return true;
  }
protected:
  // buffer objects cannot be copied or assigned
  //void checkCount(unsigned int);
  //unsigned int checkObject(unsigned int,const IClass*,bool = false);
protected:
  bool m_byte_swap;
  bool m_verbose;
  uint32 m_size;
  char* m_buffer;
  char* m_pos;
  uint32 m_klen; //GB
  bool m_map_objs;
  obj_map m_objs;
};

inline bool dummy_TXxx_pointer_stream(buffer& a_buffer,ifac& a_fac) {
  ifac::args args;
  iro* obj;
  bool created;
  bool status = a_buffer.read_object(a_fac,args,obj,created);
  if(obj) {
    if(created) {
      if(a_buffer.map_objs()) a_buffer.remove_in_map(obj);
      delete obj;
    }
  }
  return status;
}

template <class T>
inline bool pointer_stream(buffer& a_buffer,
                           ifac& a_fac,ifac::args& a_args,
                           const std::string& a_T_class,
                           T*& a_obj,bool& a_created){
  iro* obj;
  if(!a_buffer.read_object(a_fac,a_args,obj,a_created)) {
    a_buffer.out() << "tools::rroot::pointer_stream : read_object failed." << std::endl;
    a_obj = 0;
    a_created = false;
    return false;
  }
  if(!obj) {
    a_obj = 0;
    a_created = false;
  } else {
    a_obj = (T*)obj->cast(a_T_class);
    if(!a_obj) {
      a_buffer.out() << "tools::rroot::pointer_stream : "
                     << " tools::cast to " << a_T_class << " failed."
                     << ". Object is a " << obj->s_cls() << "."
                     << std::endl;
      if(a_created) delete obj;
      a_created = false;
      return false;
    }
  }
  return true;
}

template <class T>
inline bool pointer_stream(buffer& a_buffer,
                           ifac& a_fac,ifac::args& a_args,
                           cid a_T_class,
                           T*& a_obj,bool& a_created){
  iro* obj;
  if(!a_buffer.read_object(a_fac,a_args,obj,a_created)) {
    a_buffer.out() << "tools::rroot::pointer_stream : read_object failed." << std::endl;
    a_obj = 0;
    a_created = false;
    return false;
  }
  if(!obj) {
    a_obj = 0;
    a_created = false;
  } else {
    a_obj = (T*)obj->cast(a_T_class);
    if(!a_obj) {
      a_buffer.out() << "tools::rroot::pointer_stream : "
                     << " tools::cast to " << a_T_class << " failed."
                     << ". Object is a " << obj->s_cls() << "."
                     << std::endl;
      if(a_created) delete obj;
      a_created = false;
      return false;
    }
  }
  return true;
}

template <class T>
inline bool pointer_stream(buffer& a_buffer,ifac& a_fac,ifac::args& a_args,T*& a_obj,bool& a_created){
  //return pointer_stream(a_buffer,a_fac,a_args,T::s_class(),a_obj,a_created);
  return pointer_stream(a_buffer,a_fac,a_args,T::id_class(),a_obj,a_created);
}

template <class T>
inline bool fixed_array_stream(buffer& a_buffer,int a_n,T*& a_v){
  delete [] a_v;
  a_v = 0;
  char is_array;
  if(!a_buffer.read(is_array)) return false;
  if(!is_array) return true;
  if(!a_n) return true;
  a_v = new T[a_n];
  if(!a_buffer.read_fast_array<T>(a_v,a_n)) {
    delete [] a_v;
    a_v = 0;
    return false;
  }
  return true;
}

}}

#endif
