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

#ifndef tools_rroot_branch
#define tools_rroot_branch

#include "base_leaf"
#include "basket"
#include "obj_array"
#include "seek"
#include "ifile"
#include "ifac"
#include "../mnmx"
#include "../forit"
#include "../sprintf"
#include "dummy" //for TIOFeatures streaming.

#include <map>

namespace tools {
namespace rroot {

class branch : public virtual iro {
public:
  static const std::string& s_class() {
    static const std::string s_v("tools::rroot::branch");
    return s_v;
  }
public: //iro
  virtual void* cast(const std::string& a_class) const {
    if(void* p = cmp_cast<branch>(this,a_class)) return p;
    return 0;
  }
  virtual const std::string& s_cls() const {return s_class();}
public:
  static cid id_class() {return branch_cid();}
  virtual void* cast(cid a_class) const {
    if(void* p = cmp_cast<branch>(this,a_class)) {return p;}
    else return 0;
  }
public:
  virtual iro* copy() const {return new branch(*this);}
  virtual bool stream(buffer& a_buffer) {
    _clear();

    int fCompress;
    int fBasketSize;
    int fSplitLevel;
    uint32 fMaxBaskets;
    int fOffset;

    unsigned int _s,_c;
    short vers;
    if(!a_buffer.read_version(vers,_s,_c)) return false;

    if (vers > 5) {
    }
    //====process old versions before automatic schema evolution

   {uint32 old = a_buffer.length();
    uint32 id;
    uint32 m_bits;
    if(!Object_stream(a_buffer,id,m_bits)) return false;
    a_buffer.set_offset(old);}

    if(!Named_stream(a_buffer,m_name,m_title)) return false;


    if(vers<=5) {
      if(!a_buffer.read(fCompress)) return false;
      if(!a_buffer.read(fBasketSize)) return false;
      if(!a_buffer.read(fEntryOffsetLen)) return false;
      if(!a_buffer.read(fMaxBaskets)) return false;
      if(!a_buffer.read(m_write_basket)) return false;
      if(!a_buffer.read(m_entry_number)) return false;
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }
      if(!a_buffer.read(fOffset)) return false;

    } else if(vers<=6) {
      if(!a_buffer.read(fCompress)) return false;
      if(!a_buffer.read(fBasketSize)) return false;
      if(!a_buffer.read(fEntryOffsetLen)) return false;
      if(!a_buffer.read(m_write_basket)) return false;
      if(!a_buffer.read(m_entry_number)) return false;
      if(!a_buffer.read(fOffset)) return false;
      if(!a_buffer.read(fMaxBaskets)) return false;
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }

    } else if(vers<=7) {
      if(!a_buffer.read(fCompress)) return false;
      if(!a_buffer.read(fBasketSize)) return false;
      if(!a_buffer.read(fEntryOffsetLen)) return false;
      if(!a_buffer.read(m_write_basket)) return false;
      if(!a_buffer.read(m_entry_number)) return false;
      if(!a_buffer.read(fOffset)) return false;
      if(!a_buffer.read(fMaxBaskets)) return false;
      if(!a_buffer.read(fSplitLevel)) return false;
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }

    } else if(vers<=9) {
     {short color,style;
      if(!AttFill_stream(a_buffer,color,style)) return false;}
      if(!a_buffer.read(fCompress)) return false;
      if(!a_buffer.read(fBasketSize)) return false;
      if(!a_buffer.read(fEntryOffsetLen)) return false;
      if(!a_buffer.read(m_write_basket)) return false;
      if(!a_buffer.read(m_entry_number)) return false;
      if(!a_buffer.read(fOffset)) return false;
      if(!a_buffer.read(fMaxBaskets)) return false;
      if(!a_buffer.read(fSplitLevel)) return false;
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }
     {double v;
      if(!a_buffer.read(v)) return false;
      }

    } else if(vers<=10) {
     {short color,style;
      if(!AttFill_stream(a_buffer,color,style)) return false;}

      if(!a_buffer.read(fCompress)) return false;
      if(!a_buffer.read(fBasketSize)) return false;
      if(!a_buffer.read(fEntryOffsetLen)) return false;
      if(!a_buffer.read(m_write_basket)) return false;
     {uint64 v;
      if(!a_buffer.read(v)) return false;
      m_entry_number = uint32(v);}
      if(!a_buffer.read(fOffset)) return false;
      if(!a_buffer.read(fMaxBaskets)) return false;
      if(!a_buffer.read(fSplitLevel)) return false;

     {uint64 v;
      if(!a_buffer.read(v)) return false;
      }
     {uint64 v;
      if(!a_buffer.read(v)) return false;
      }
     {uint64 v;
      if(!a_buffer.read(v)) return false;
      }

    } else { //vers>=11

     {short color,style;
      if(!AttFill_stream(a_buffer,color,style)) return false;}

      if(!a_buffer.read(fCompress)) return false;
      if(!a_buffer.read(fBasketSize)) return false;
      if(!a_buffer.read(fEntryOffsetLen)) return false;
      if(!a_buffer.read(m_write_basket)) return false;
     {uint64 v;
      if(!a_buffer.read(v)) return false;
      m_entry_number = uint32(v);}
      if(vers>=13) {
        dummy _dummy;
        if(!_dummy.stream(a_buffer)) { //TIOFeatures fIOFeatures
          m_out << "tools::rroot::branch::stream : can't read (dummy) TIOFeatures." << std::endl;
          return false;
        }
      }
      if(!a_buffer.read(fOffset)) return false;
      if(!a_buffer.read(fMaxBaskets)) return false;
      if(!a_buffer.read(fSplitLevel)) return false;
     {uint64 v;
      if(!a_buffer.read(v)) return false;
      }
     {uint64 v;
      if(!a_buffer.read(v)) return false;} //fFirstEntry
     {uint64 v;
      if(!a_buffer.read(v)) return false;
      }
     {uint64 v;
      if(!a_buffer.read(v)) return false;
      }
    }

    //TObjArray
   {ifac::args args;
    if(!m_branches.stream(a_buffer,args)) {
      m_out << "tools::rroot::branch::stream :"
            << " can't read branches."
            << std::endl;
      return false;
    }}

    //TObjArray
   {ifac::args args;
    if(!m_leaves.stream(a_buffer,args)) {
      m_out << "tools::rroot::branch::stream :"
            << " can't read leaves."
            << std::endl;
      return false;
    }}

    //TObjArray
    //IMPORTANT : accept_null=true
   {ifac::args args;
    if(!m_streamed_baskets.stream(a_buffer,args,true)) {
      m_out << "tools::rroot::branch::stream :"
            << " can't read baskets."
            << std::endl;
      return false;
    }}

    if(fMaxBaskets<=0) {
      m_out << "tools::rroot::branch::stream :"
            << " fMaxBaskets null."
            << std::endl;
      return false;
    }

    fBasketEntry = new int[fMaxBaskets];
    fBasketBytes = new int[fMaxBaskets];
    fBasketSeek = new seek[fMaxBaskets];
   {for(uint32 i=0;i<fMaxBaskets;i++) {
      fBasketEntry[i] = 0;
      fBasketBytes[i] = 0;
      fBasketSeek[i] = 0;
    }}

    if(vers<6) {
     {uint32 n;
      if(!a_buffer.read_array<int>(fMaxBaskets,fBasketEntry,n)) {
        _clear();
        return false;
      }}
      if(vers<=4) {
        for (uint32 i=0;i<fMaxBaskets;i++) fBasketBytes[i] = 0;
      } else {
        uint32 n;
        if(!a_buffer.read_array<int>(fMaxBaskets,fBasketBytes,n)) {
          _clear();
          return false;
        }
      }
      if(vers<2) {
        m_out << "tools::rroot::branch::stream :"
              << " v < 2. Not (yet) handled."
              << std::endl;
        _clear();
        return false;
      } else {
        int n;
        if(!a_buffer.read(n)) {
          _clear();
          return false;
        }
        for(int i=0;i<n;i++) {
          seek32 _seek;
          if(!a_buffer.read(_seek)) {
            _clear();
            return false;
          }
          fBasketSeek[i] = _seek;
        }
      }

    } else if(vers<=9) {
      // See TStreamerInfo::ReadBuffer::ReadBasicPointer
     {char isArray;
      if(!a_buffer.read(isArray)) {
        _clear();
        return false;
      }
      if(isArray) {
        if(!a_buffer.read_fast_array<int>(fBasketBytes,fMaxBaskets)) {
          _clear();
          return false;
        }
      }}

     {char isArray;
      if(!a_buffer.read(isArray)) {
        _clear();
        return false;
      }
      if(isArray) {
        if(!a_buffer.read_fast_array<int>(fBasketEntry,fMaxBaskets)) {
          _clear();
          return false;
        }
      }}

     {char isBigFile;
      if(!a_buffer.read(isBigFile)) {
        _clear();
        return false;
      }
      if(isBigFile==2) {
        if(!a_buffer.read_fast_array<seek>(fBasketSeek,fMaxBaskets)) {
          _clear();
          return false;
        }
      } else {
        for(uint32 i=0;i<fMaxBaskets;i++) {
          seek32 _seek;
          if(!a_buffer.read(_seek)) {
            _clear();
            return false;
          }
          fBasketSeek[i] = _seek;
        }
      }}

    } else { //vers>=10
      // See TStreamerInfo::ReadBuffer::ReadBasicPointer
     {char isArray;
      if(!a_buffer.read(isArray)) {
        _clear();
        return false;
      }
      if(isArray) {
        if(!a_buffer.read_fast_array<int>(fBasketBytes,fMaxBaskets)) {
          _clear();
          return false;
        }
      }}

     {char isArray;
      if(!a_buffer.read(isArray)) {
        _clear();
        return false;
      }
      if(isArray) {
        uint64* v = new uint64[fMaxBaskets];
        if(!a_buffer.read_fast_array<uint64>(v,fMaxBaskets)) {
          _clear();
          return false;
        }
        for(uint32 i=0;i<fMaxBaskets;i++) fBasketEntry[i] = int(v[i]);
        delete [] v;
      }}

     {char isArray;
      if(!a_buffer.read(isArray)) {
        _clear();
        return false;
      }
      if(isArray) {
        uint64* v = new uint64[fMaxBaskets];
        if(!a_buffer.read_fast_array<uint64>(v,fMaxBaskets)) {
          _clear();
          return false;
        }
        for(uint32 i=0;i<fMaxBaskets;i++) fBasketSeek[i] = v[i];
        delete [] v;
      }}
    }

    if(vers>2) {
      //TString
      std::string fileName;
      if(!a_buffer.read(fileName)) {
        _clear();
        return false;
      }
    }

    //FIXME if(vers<4) SetAutoDelete(kTRUE);
    //FIXME gROOT->SetReadingObject(kFALSE);
    if(!a_buffer.check_byte_count(_s, _c,"TBranch")) {
      _clear();
      return false;
    }

    //GB : analyse fBasketEntry.
    m_first_last.clear();
   {// There are (m_write_basket+1) sensitive elements in fBasketEntry :
    // (Do we have to check that ?)
    for(uint32 i=0;i<m_write_basket;i++) {
      uint64 first = fBasketEntry[i];
      uint64 last = fBasketEntry[i+1]-1;
      m_first_last.push_back(std::pair<uint64,uint64>(first,last));
    }
    if(m_entry_number) {
      uint64 first = fBasketEntry[m_write_basket];
      uint64 last = m_entry_number-1;
      m_first_last.push_back(std::pair<uint64,uint64>(first,last));
    }}
    //_dump_first_last();

    //GB : analyse fBasketSeek :
   {uint32 num = 0;
    uint32 mxi = 0;
    for(uint32 i=0;i<fMaxBaskets;i++) {
      if(!fBasketSeek[i]) continue;
      num++;
      mxi = max_of<uint32>(i,mxi);
    }
    if(m_write_basket) {
      if((num!=m_write_basket)||(mxi!=(m_write_basket-1))) {
        m_out << "tools::rroot::branch::stream :"
              << " fBasketSeek[] inconsistent with m_write_basket."
              << " m_write_basket " << m_write_basket
              << " num " << num
              << " mxi " << mxi
              << std::endl;
        _clear();
        return false;
      }
    }}

    //GB : analyse m_streamed_baskets :
   {int index = 0;
    tools_vforcit(basket*,m_streamed_baskets,it) {
      if(*it) {
        if(!(*it)->buf()||!(*it)->buf_size()) {
          m_out << "tools::rroot::branch::stream :"
                << " expect a basket with a not empty buffer."
                << std::endl;
          return false;
        }
        //in the below, false is to say m_baskets is not owner of *it.
        m_baskets[index] = std::pair<basket*,bool>(*it,false);
      }
      index++;
    }}

    return true;
  }

public:
  //virtual for branch_element

  virtual bool read_leaves(ifile&,buffer& a_buffer){
    tools_vforcit(base_leaf*,m_leaves,it) {
      if(!(*it)->read_buffer(a_buffer)) {
        m_out << "tools::rroot::branch::read_leaves :"
              << " read_buffer failed."
              << std::endl;
        return false;
      }
    }
    return true;
  }

  virtual bool find_entry(ifile& a_file,uint64 a_entry,uint32& a_nbytes){
    // Read all leaves of entry and return total number of bytes :
    // The input argument entry is the entry serial number in the current tree.
    a_nbytes = 0;
    if(a_entry>=m_entry_number) {
      return true; //CERN-ROOT does not consider it is an error.
    }
    if(!m_entry_number || m_first_last.empty() ) { //GB
      m_out << "tools::rroot::branch::find_entry :"
            << " nothing to read."
            << std::endl;
      return false; //nothing to read.
    }

    if(m_read_basket>=m_first_last.size()) {
      m_out << "tools::rroot::branch::find_entry :"
            << " bad m_first_last access."
            << std::endl;
      return false;
    }

    uint64 first = m_first_last[m_read_basket].first;
    uint64 last = m_first_last[m_read_basket].second;

    // Are we still in the same ReadBasket?
    if((a_entry<first)||(a_entry>last)) {
      m__read_basket = 0;
      uint32 old_read_basket = m_read_basket;

      //look first in the next basket window :
      bool found = false;
      if((m_read_basket+1)<m_first_last.size()) {
        first = m_first_last[m_read_basket+1].first;
        last = m_first_last[m_read_basket+1].second;
        if((a_entry>=first)&&(a_entry<=last)) {
          m_read_basket++;
          found = true;
        }
      }
      if(!found) {
        uint32 count = 0;
        for(auto it = m_first_last.cbegin(); it != m_first_last.cend(); ++it) {
          first = (*it).first;
          last = (*it).second;
          if((a_entry>=first)&&(a_entry<=last)) {
            m_read_basket = count;
            found = true;
            break;
          }
          count++;
        }
      }
      if(!found) { //something weird in fBasketEntry.
        m_out << "tools::rroot::branch::find_entry :"
              << " fancy fBasketEntry."
              << std::endl;
        return false;
      } else {
        // if found, erase m_baskets[old_read_basket] to avoid
        // having all data in memory !
        std::map<uint32, std::pair<basket*,bool> >::iterator it = m_baskets.find(old_read_basket);
        if(it!=m_baskets.end()) {
          if((*it).second.second) {
            basket* bsk = (*it).second.first;
            m_baskets.erase(it);
            delete bsk;
          }
        }
      }
    }

    if(!m__read_basket) {

      basket* bsk = 0;
      std::map<uint32, std::pair<basket*,bool> >::iterator it = m_baskets.find(m_read_basket);
      if(it!=m_baskets.end()) {
        bsk = (*it).second.first;
      } else {
        if(m_read_basket>=m_write_basket) {
          m_out << "tools::rroot::branch::find_entry :"
                << " basket lacking !"
                << " wanting index " << m_read_basket
                << ". fBasketSeek entries " << m_write_basket
                << std::endl;
          return false;
        }
        if(!fBasketSeek[m_read_basket]) {
          m_out << "tools::rroot::branch::find_entry :"
                << " fBasketSeek is null for index " << m_read_basket
                << std::endl;
          return false;
        }
        if(!fBasketBytes[m_read_basket]) {
          m_out << "tools::rroot::branch::find_entry :"
                << " fBasketBytes is null for index " << m_read_basket
                << std::endl;
          return false;
        }

        bsk = get_basket(a_file,fBasketSeek[m_read_basket],fBasketBytes[m_read_basket]);
        if(!bsk) {
          m_out << "tools::rroot::branch::find_entry :"
                << " can't read basket " << m_read_basket
                << " at file pos " << fBasketSeek[m_read_basket]
                << " and size " << fBasketBytes[m_read_basket]
                << std::endl;
          return false;
        }
        m_baskets[m_read_basket] = std::pair<basket*,bool>(bsk,true);
      }

      m__read_basket = bsk;
    }

    uint32 bufbegin;
   {int* entry_offset = m__read_basket->entry_offset();
    if(entry_offset) {
      uint32 index = uint32(a_entry-first);
      if(index>=m__read_basket->nev()) {
        m_out << "tools::rroot::branch::find_entry :"
              << " can't access entry offset " << index
              << ". nev " << m__read_basket->nev()
              << std::endl;
        return false;
      }
      bufbegin = entry_offset[index];
    } else {
      bufbegin = (uint32)(m__read_basket->key_length() + (a_entry-first)*m__read_basket->nev_buf_size());
    }}

   {int* displacement = m__read_basket->displacement();
    if(displacement) {
      m_out << "tools::rroot::branch::find_entry :"
            << " not null displacement. Not yet handled."
            << std::endl;
    } else {
    }}


    buffer _buffer(m_out,a_file.byte_swap(),m__read_basket->buf_size(),m__read_basket->buf(),0,false);
    _buffer.set_offset(bufbegin);
    _buffer.set_map_objs(true);  //for MEMPHYS/applications/read.icc

    if(!read_leaves(a_file,_buffer)) {
      m_out << "tools::rroot::branch::find_entry :"
            << " can't read leaves for entry " << a_entry
            << ". read_basket was " << m_read_basket
            << ", first " << first
            << ", last " << last
            << "."
            << std::endl;
      return false;
    }

    a_nbytes = _buffer.length() - bufbegin;

    return true;
  }

  virtual bool show(std::ostream& a_out,ifile& a_file,uint64 a_entry) {
    // Print values of all active leaves for entry :
    // if entry==-1, print current entry (default)
    uint32 n;
    if(!find_entry(a_file,a_entry,n)) return false;

    tools_vforcit(base_leaf*,m_leaves,it) {
      base_leaf* bl = *it;

      uint32 num = bl->num_elem();
      num = min_of<uint32>(num,10);
      if(!num) continue;

     {std::string _s;
      uint32 len = uint32(bl->name().size())+128;
      sprintf(_s,len," %-15s = ",bl->name().c_str());
      a_out << _s;}

      for(uint32 i=0;i<num;i++) {
        if(i) a_out << ", ";
        bl->print_value(a_out,i);
      }

      a_out << std::endl;
    }

    return true;
  }
public:
  branch(std::ostream& a_out,ifac& a_fac)
  :m_out(a_out)
  ,m_fac(a_fac)

  ,m_streamed_baskets(m_fac)
  ,m__read_basket(0)

  //,m_bits(0)
  ,m_name("")
  ,m_title("")
  ,fAutoDelete(false)

  ,m_branches(m_fac)
  ,m_leaves(m_fac)

  ,fEntryOffsetLen(0)
  ,m_write_basket(0)
  ,m_entry_number(0)
  ,m_read_basket(0)
  ,fBasketBytes(0)
  ,fBasketEntry(0)
  ,fBasketSeek(0)
  {
  }
  virtual ~branch(){
    _clear();
  }
protected:
  branch(const branch& a_from)
  :iro(a_from)
  ,m_out(a_from.m_out),m_fac(a_from.m_fac)
  ,m_streamed_baskets(m_fac)
  ,m__read_basket(0)
  ,fAutoDelete(false)
  ,m_branches(m_fac)
  ,m_leaves(m_fac)
  ,fEntryOffsetLen(1000)
  ,m_write_basket(0)
  ,m_entry_number(0)
  ,m_read_basket(0)
  ,fBasketBytes(0)
  ,fBasketEntry(0)
  ,fBasketSeek(0)
  {}
  branch& operator=(const branch&){return *this;}
public:
  uint32 entry_number() const {return m_entry_number;}
  const std::string& name() const {return m_name;}
  const std::string& title() const {return m_title;}
  const std::vector<base_leaf*>& leaves() const {return m_leaves;}
  const std::vector<branch*>& branches() const {return m_branches;}
protected:
  basket* get_basket(ifile& a_file,seek a_pos,uint32 a_len) {
    if(!a_len) return 0;

    basket* _basket = new basket(m_out,a_pos,a_len); //basket is a key.
    if(!_basket->read_file(a_file)) {
      m_out << "tools::rroot::branch::get_basket :"
            << " read_file() failed."
            << std::endl;
      delete _basket;
      return 0;
    }
   {buffer _buffer(m_out,a_file.byte_swap(),a_len,_basket->buf(),0,false);
    if(!_basket->stream(_buffer)) {
      m_out << "tools::rroot::branch::get_basket :"
            << " basket stream failed."
            << std::endl;
      delete _basket;
      return 0;
    }}
    unsigned int sz;
    char* buf = _basket->get_object_buffer(a_file,sz); //basket owns buf.
    if(!buf) {
      m_out << "tools::rroot::branch::get_basket :"
            << " get_object_buffer() failed."
            << std::endl;
      delete _basket;
      return 0;
    }

    if(_basket->seek_key()!=a_pos) { //consistency check.
      m_out << "tools::rroot::branch::get_basket :"
            << " seek anomaly."
            << " a_pos " << a_pos
            << " seek_key() " << _basket->seek_key()
            << std::endl;
      delete _basket;
      return 0;
    } 

    if(fEntryOffsetLen) {
    if(!_basket->read_offset_tables(a_file.byte_swap())) {
      m_out << "tools::rroot::branch::get_basket :"
            << " read_offset_tables failed."
            << std::endl;
      delete _basket;
      return 0;
    }}

    return _basket;
  }
protected:
  void _clear() {
    delete [] fBasketEntry;
    delete [] fBasketBytes;
    delete [] fBasketSeek;
    fBasketEntry = 0;
    fBasketBytes = 0;
    fBasketSeek = 0;

   {for(auto it = m_baskets.begin(); it != m_baskets.end(); ++it) {
      if((*it).second.second) delete (*it).second.first;
    }
    m_baskets.clear();}

    m_branches.cleanup();
    m_leaves.cleanup();
    m_streamed_baskets.cleanup();
  }

  void _dump_first_last() {
    m_out << "tools::rroot::branch::_dump_first_last :"
          << " first_last " << m_first_last.size()
          << std::endl;
    for(auto it = m_first_last.cbegin(); it != m_first_last.cend(); ++it) {
      uint64 first = (*it).first;
      uint64 last = (*it).second;
      m_out << "tools::rroot::branch::stream :"
            << "   first " << first
            << "   last " << last
            << std::endl;
    }
  }

protected:
  std::ostream& m_out;
  ifac& m_fac;
  std::vector< std::pair<uint64,uint64> > m_first_last;
  std::map<uint32, std::pair<basket*,bool> > m_baskets;
  obj_array<basket> m_streamed_baskets;
  basket* m__read_basket; //optimization
protected:
  std::string m_name;
  std::string m_title;

  bool fAutoDelete;
  obj_array<branch> m_branches;
  obj_array<base_leaf> m_leaves;
  uint32 fEntryOffsetLen;  //  Initial Length of fEntryOffset table in the basket buffers
  uint32 m_write_basket;   //  Last basket number written
  uint32 m_entry_number;   // Current entry number (last one filled in this branch)
  uint32 m_read_basket;   //! Current basket number when reading
  int* fBasketBytes;    //[fMaxBaskets] Length of baskets on file
  int* fBasketEntry;    //[fMaxBaskets] Table of first entry in eack basket
  seek* fBasketSeek;    //[fMaxBaskets] Addresses of baskets on file
};

}}

#endif
