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

#ifndef tools_wroot_key
#define tools_wroot_key

#include "seek"
#include "date"
#include "ifile"
#include "wbuf"
#include "../sout"

#include <ostream>

namespace tools {
namespace wroot {

//doc :
//////////////////////////////////////////////////////////////////////////
//                                                                      //
//  The Key class includes functions to book space on a file,           //
//   to create I/O buffers, to fill these buffers                       //
//   to compress/uncompress data buffers.                               //
//                                                                      //
//  Before saving (making persistent) an object on a file, a key must   //
//  be created. The key structure contains all the information to       //
//  uniquely identify a persistent object on a file.                    //
//     fNbytes    = number of bytes for the compressed object+key       //
//     version of the Key class                                         //
//     fObjlen    = Length of uncompressed object                       //
//     fDatime    = Date/Time when the object was written               //
//     fKeylen    = number of bytes for the key structure               //
//     fCycle     = cycle number of the object                          //
//     fSeekKey   = Address of the object on file (points to fNbytes)   //
//                  This is a redundant information used to cross-check //
//                  the data base integrity.                            //
//     fSeekPdir  = Pointer to the directory supporting this object     //
//     fClassName = Object class name                                   //
//     fName      = name of the object                                  //
//     fTitle     = title of the object                                 //
//                                                                      //
//  The Key class is used by ROOT to:                                   //
//    - to write an object in the Current Directory                     //
//    - to write a new ntuple buffer                                    //
//                                                                      //
//////////////////////////////////////////////////////////////////////////
class key {
  static uint32 class_version() {return 2;}
  static const std::string& s_class() {
    static const std::string s_v("tools::wroot::key");
    return s_v;
  }
public:
  static unsigned int std_string_record_size(const std::string& x) {
    // Returns size string will occupy on I/O buffer.
    if (x.size() > 254)
      return uint32(x.size()+sizeof(unsigned char)+sizeof(int));
    else
      return uint32(x.size()+sizeof(unsigned char));
  }
public:
  key(std::ostream& a_out,
      seek a_seek_directory,
      const std::string& a_object_name,
      const std::string& a_object_title,
      const std::string& a_object_class) //for basket cstor.
  :m_out(a_out)
  ,m_buf_size(0)
  ,m_buffer(0)
  // Record :
  ,m_nbytes(0)
  ,m_version(class_version())
  ,m_object_size(0)
  ,m_date(0)
  ,m_key_length(0)
  ,m_cycle(0)
  ,m_seek_key(0)
  ,m_seek_directory(0)
  ,m_object_class(a_object_class)
  ,m_object_name(a_object_name)
  ,m_object_title(a_object_title)
  {
    if(a_seek_directory>START_BIG_FILE()) m_version += big_file_version_tag();

    m_key_length = record_size(m_version);

    initialize_zero();

    m_seek_directory = a_seek_directory;
  }

  key(std::ostream& a_out,
      ifile& a_file,
      seek a_seek_directory,
      const std::string& a_object_name,
      const std::string& a_object_title,
      const std::string& a_object_class,
      uint32 a_object_size) //uncompressed data size.
  :m_out(a_out)
  ,m_buf_size(0)
  ,m_buffer(0)
  // Record :
  ,m_nbytes(0)
  ,m_version(class_version())
  ,m_object_size(a_object_size)
  ,m_date(0)
  ,m_key_length(0)
  ,m_cycle(0)
  ,m_seek_key(0)
  ,m_seek_directory(0)
  ,m_object_class(a_object_class)
  ,m_object_name(a_object_name)
  ,m_object_title(a_object_title)
  {
    if(a_object_size) {
      if(a_file.END()>START_BIG_FILE()) m_version += big_file_version_tag();
    }
    if(m_version>big_file_version_tag()) {
    } else {
      if(a_seek_directory>START_BIG_FILE()) m_version += big_file_version_tag();
    }

    m_key_length = record_size(m_version);

    initialize(a_file,a_object_size);

    m_seek_directory = a_seek_directory;
  }
  virtual ~key(){
    delete [] m_buffer;
  }
protected:
  key(const key& a_from):m_out(a_from.m_out){
  }
  key& operator=(const key &){return *this;}
public:
  uint16 cycle() const {return m_cycle;}
  void set_cycle(uint16 a_cycle) {m_cycle = a_cycle;}

  const std::string& object_name() const {return m_object_name;}
  const std::string& object_title() const {return m_object_title;}
  const std::string& object_class() const {return m_object_class;}

  bool write_self(ifile& a_file) {
    char* buffer = m_buffer;
    wbuf wb(m_out,a_file.byte_swap(),eob(),buffer);
    return to_buffer(wb,a_file.verbose());
  }

  bool write_file(ifile& a_file,uint32& a_nbytes) {
    if(!a_file.set_pos(m_seek_key)) {
      a_nbytes = 0;
      return false;
    }
    if(!a_file.write_buffer(m_buffer,m_nbytes)) {
      a_nbytes = 0;
      return false;
    }

    if(a_file.verbose()) {
      m_out << "tools::wroot::key::write_file :"
            << " writing " << m_nbytes << " bytes"
            << " at address " << m_seek_key
            << " for ID=" << sout(m_object_name)
            << " Title=" << sout(m_object_title) << "."
            << std::endl;
    }

    delete [] m_buffer; //???
    m_buffer = 0;
    m_buf_size = 0;

    a_nbytes = m_nbytes;
    return true;
  }

  void set_number_of_bytes(uint32 a_n) {m_nbytes = a_n;}
  uint32 number_of_bytes() const {return m_nbytes;}

  uint32 object_size() const {return m_object_size;}

  seek seek_key() const {return m_seek_key;}
  short key_length() const {return m_key_length;}

  char* data_buffer() {return m_buffer + m_key_length;}
  const char* eob() const {return m_buffer + m_buf_size;}

  bool to_buffer(wbuf& a_wb,bool a_verbose) const {
    if(!a_wb.write(m_nbytes)) return false;
    short version = m_version;
    if(!a_wb.write(version)) return false;
    if(!a_wb.write(m_object_size)) return false;
    unsigned int _date = 0; //FIXME
    if(!a_wb.write(_date)) return false;
    if(!a_wb.write(m_key_length)) return false;
    if(!a_wb.write(m_cycle)) return false;
    if(version>(short)big_file_version_tag()) {
      if(!a_wb.write(m_seek_key)) return false;
      if(!a_wb.write(m_seek_directory)) return false;
    } else {
      if(m_seek_key>START_BIG_FILE()) {
        m_out << "tools::wroot::key::to_buffer :"
              << " attempt to write big seek "
              << m_seek_key << " on 32 bits."
              << std::endl;
        return false;
      }
      if(!a_wb.write((seek32)m_seek_key)) return false;
      if(m_seek_directory>START_BIG_FILE()) {
        m_out << "tools::wroot::key::to_buffer :"
              << " (2) attempt to write big seek "
              << m_seek_directory << " on 32 bits."
              << std::endl;
        return false;
      }
      if(!a_wb.write((seek32)m_seek_directory)) return false;
    }
    if(!a_wb.write(m_object_class)) return false;
    if(!a_wb.write(m_object_name)) return false;
    if(!a_wb.write(m_object_title)) return false;
    if(a_verbose) {
      m_out << "tools::wroot::key::to_buffer :"
            << " nbytes : " << m_nbytes
            << ", object class : " << sout(m_object_class)
            << ", object name : " << sout(m_object_name)
            << ", object title : " << sout(m_object_title)
            << ", object size : " << m_object_size
            << "."
            << std::endl;
    }
    return true;
  }

protected:
  uint32 record_size(uint32 a_version) const {
    // Return the size in bytes of the key header structure.
    uint32 nbytes = sizeof(m_nbytes);
    nbytes += sizeof(short);
    nbytes += sizeof(m_object_size);
    nbytes += sizeof(date);
    nbytes += sizeof(m_key_length);
    nbytes += sizeof(m_cycle);
    if(a_version>big_file_version_tag()) {
      nbytes += sizeof(seek);
      nbytes += sizeof(seek);
    } else {
      nbytes += sizeof(seek32);
      nbytes += sizeof(seek32);
    }
    nbytes += std_string_record_size(m_object_class);
    nbytes += std_string_record_size(m_object_name);
    nbytes += std_string_record_size(m_object_title);
    return nbytes;
  }

  bool initialize_zero() {
    uint32 nsize = m_key_length;
    m_date = get_date();
    m_seek_key = 0;
    delete [] m_buffer;
    m_buffer = new char[nsize];
    m_buf_size = nsize;
    m_nbytes = nsize;
    return true;
  }
  bool initialize(ifile& a_file,uint32 a_nbytes) {
    uint32 nsize = m_key_length+a_nbytes;

    m_date = get_date();

    if(a_nbytes) {//GB
      m_seek_key = a_file.END();
      a_file.set_END(m_seek_key+nsize);

      //NOTE : the free segment logic found in CERN-ROOT/TKey
      //       is not yet needed right now for us, since
      //       we always write at end of file. The update
      //       of the eof free_seg is done in set_END.
    } else { //basket
      m_seek_key = 0;
    }

    delete [] m_buffer;
    m_buffer = new char[nsize];
    m_buf_size = nsize;
    m_nbytes = nsize;

    return true;
  }
protected:
  std::ostream& m_out;
  uint32 m_buf_size;
  char* m_buffer;
  // Record (stored in file) :
  uint32 m_nbytes;            //Number of bytes for the object on file
  uint32 m_version;           //Key version identifier
  uint32 m_object_size;       //Length of uncompressed object in bytes
  date m_date;                //Date/Time of insertion in file
  uint16 m_key_length;        //Number of bytes for the key itself
  uint16 m_cycle;             //Cycle number
  seek m_seek_key;            //Location of object on file
  seek m_seek_directory;      //Location of parent directory on file
  std::string m_object_class; //Object Class name.
  std::string m_object_name;  //name of the object.
  std::string m_object_title; //title of the object.
};

}}

#endif

