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

#ifndef toolx_hdf5_pages
#define toolx_hdf5_pages

#include "T_tools"
#include "tools"

#include <tools/S_STRING>

#ifdef TOOLS_MEM
#include <tools/mem>
#endif

#include <ostream>

namespace toolx {
namespace hdf5 {

TOOLS_GLOBAL_STRING(pages)
TOOLS_GLOBAL_STRING(entries)
TOOLS_GLOBAL_STRING(columns)
TOOLS_GLOBAL_STRING(names)
TOOLS_GLOBAL_STRING(forms)

class pages {
  TOOLS_SCLASS(toolx::hdf5::pages)
public:
  pages(std::ostream& a_out,
        hid_t a_group,const std::string& a_name,const std::string& a_form,
        bool a_write,unsigned int a_compress)
  :m_out(a_out),m_name(a_name),m_form(a_form)
  //,m_type(0),m_size(0)
  ,m_group(-1),m_dataset(-1),m_write(a_write),m_compress(a_compress),m_entries(0),m_pos(0){
#ifdef TOOLS_MEM
    tools::mem::increment(s_class().c_str());
#endif
    if(m_write) {
      m_group = toolx_H5Gcreate(a_group,m_name.c_str(),0);
      if(m_group<0) {
        m_out << "pages::pages : can't create group for column " << m_name << "." << std::endl;
        m_group = -1;
	return;
      }
      if(!write_atb(m_group,"class",s_class())) {
        m_out << "pages::pages : write_atb(class) failed." << std::endl;
        ::H5Gclose(m_group);
        m_group = -1;
        return;
      }
      int v = 1;
      if (!write_scalar_atb<int>(m_group,"version",v)) {
        m_out << "pages::pages : write_scalar_atb(version) failed." << std::endl;
        ::H5Gclose(m_group);
        m_group = -1;
        return;
      }
    } else {
      m_group = toolx_H5Gopen(a_group,m_name.c_str());
      if(m_group<0) {
        m_out << "pages::pages : can't open group for column " << m_name << "." << std::endl;
        m_group = -1;
	return;
      }
      if(!read_scalar<tools::uint64>(m_group,s_entries(),m_entries)) {
        m_out << "pages::pages : read_scalar(entries) failed." << std::endl;
        ::H5Gclose(m_group);
        m_group = -1;
	return;
      }
    }
  }
  virtual ~pages(){
    if(m_write) {
      if(!write_scalar<tools::uint64>(m_group,s_entries(),m_entries)) {
        m_out << "pages::~pages : write_scalar(entries) failed." << std::endl;
      }
      if(m_dataset>=0) ::H5Dclose(m_dataset);
    }
    ::H5Gclose(m_group);
#ifdef TOOLS_MEM
    tools::mem::decrement(s_class().c_str());
#endif
  }
protected:
  pages(const pages& a_from):m_out(a_from.m_out){
#ifdef TOOLS_MEM
    tools::mem::increment(s_class().c_str());
#endif
  }
  pages& operator=(const pages&){return *this;}
public:
  bool is_valid() const {return m_group<0?false:true;}

  const std::string& name() const {return m_name;}
  const std::string& form() const {return m_form;}
  //int type() const {return m_type;}
  //unsigned int size() const {return m_size;}
  tools::uint64 entries() const {return m_entries;}
  tools::uint64 pos() const {return m_pos;}
  void reset_pos() {m_pos = 0;}

  template <class TYPE>
  bool write_page(size_t a_size,const TYPE* a_array) {
    if(!m_pos) {
      if(!write_array<TYPE>(m_group,s_pages(),a_size,a_array,a_size?a_size:32,m_compress)) {
        m_out << "pages::write_page : write_array<TYPE>() failed. Pos " << m_pos << std::endl;
        return false;
      }
      m_dataset = toolx_H5Dopen(m_group,s_pages().c_str());
      if(m_dataset<0) {
        m_out << "pages::write_page : H5Dopen failed. Pos " << m_pos << std::endl;
        return false;
      }
    } else {
      if(!write_append_array_dataset<TYPE>(m_dataset,a_size,a_array)) {
        m_out << "pages::write_page : write_append_array_dataset<TYPE>() failed. Pos " << m_pos << std::endl;
        return false;
      }
    }
    m_pos += a_size;
    m_entries = m_pos;
    return true;
  }

  template <class TYPE>
  bool read_page(size_t a_size,TYPE* a_array) {
    //it is assumed that a_array can contain a_size*sizeof(TYPE) bytes.
    unsigned int _size = a_size;
    unsigned int n = 0;
    TYPE* array = 0;
    if(!read_sub_array<TYPE>(m_group,s_pages(),(unsigned int)m_pos,(unsigned int)_size,n,array)) {
      m_out << "pages::read_page : read_sub_array<TYPE>() failed." << std::endl;
      return false;
    }
    if(n!=_size) {
      m_out << "pages::read_page : size mismatch. Requested " << _size << ", got "<< n << "." << std::endl;
      delete [] array;
      return false;
    }

   {TYPE* rpos = (TYPE*)array;
    TYPE* wpos = (TYPE*)a_array;
    for(size_t i=0;i<n;i++,rpos++,wpos++) *wpos = *rpos;
    for(size_t i=n;i<_size;i++,rpos++,wpos++) *wpos = TYPE();}

    delete [] array;

    m_pos += n;
    return true;
  }

  template <class TYPE>
  bool write_vlen(size_t a_size,const TYPE* a_array) {
    if(!m_pos) {
      if(!hdf5::write_vlen<TYPE>(m_group,s_pages(),a_size,a_array,a_size?a_size:32,m_compress)) {
        m_out << "pages::write_vlen : write_vlen<TYPE>() failed. Pos " << m_pos << std::endl;
        return false;
      }
      m_dataset = toolx_H5Dopen(m_group,s_pages().c_str());
      if(m_dataset<0) {
        m_out << "pages::write_vlen : H5Dopen failed. Pos " << m_pos << std::endl;
        return false;
      }
    } else {
      if(!write_append_vlen_dataset<TYPE>(m_dataset,a_size,a_array)) {
        m_out << "pages::write_vlen : write_append_vlen_dataset<TYPE>() failed. Pos " << m_pos << std::endl;
        return false;
      }
    }
    m_pos++;
    m_entries++;
    return true;
  }

  template <class TYPE>
  bool read_vlen(size_t& a_size,TYPE*& a_array) {
    //it is assumed that a_array can contain a_size*sizeof(TYPE) bytes.
    unsigned int sz;
    if(!read_sub_vlen<TYPE>(m_group,s_pages(),(unsigned int)m_pos,sz,a_array)) {
      m_out << "pages::read_vlen : read_sub_vlen<TYPE>() failed." << std::endl;
      a_size = 0;
      a_array = 0;
      return false;
    }
    a_size = sz;
    m_pos++;
    m_entries++;
    return true;
  }

  bool write_string(const std::string& a_string) {
    if(!m_pos) {
      if(!hdf5::write_string_dataset(m_group,s_pages(),a_string,128,m_compress)) { //32=>enforce extendable.
        m_out << "pages::write_string : hdf5::write_string() failed. Pos " << m_pos << std::endl;
        return false;
      }
      m_dataset = toolx_H5Dopen(m_group,s_pages().c_str());
      if(m_dataset<0) {
        m_out << "pages::write_string : H5Dopen failed. Pos " << m_pos << std::endl;
        return false;
      }
    } else {
      if(!write_append_string_dataset(m_dataset,a_string)) {
        m_out << "pages::write_string : write_append_string_dataset() failed. Pos " << m_pos << std::endl;
        return false;
      }
    }
    m_pos++;
    m_entries++;
    return true;
  }

  bool read_string(std::string& a_string) {
    if(!read_sub_string(m_group,s_pages(),(unsigned int)m_pos,a_string)) {
      m_out << "pages::read_string : read_sub_string() failed." << std::endl;
      a_string.clear();
      return false;
    }
    m_pos++;
    m_entries++;
    return true;
  }

/*
  bool write_string(const std::string& a_string) {return write_vlen<char>(a_string.size(),a_string.c_str());}

  bool read_string(std::string& a_string) {
    size_t sz;char* _data;
    if(!read_vlen<char>(sz,_data)) {a_string.clear();return false;}
    a_string.resize(sz);
    for(size_t index=0;index<sz;index++) a_string[index] = _data[index];
    delete [] _data;
    return true;
  }
*/

/*
  bool write_strings(size_t a_number,void* a_array) {
    size_t sz = m_size*a_number;
    char* buffer = new char[sz];
   {char* pos = buffer;
    for(size_t index=0;index<sz;index++,pos++) *pos = ' ';}

   {char** strings = (char**)a_array;
    char* pos = buffer;
    size_t ls;
    for(size_t index=0;index<a_number;index++) {
      char* ss = strings[index];
      ls = ::strlen(ss);
      ::memcpy(pos,ss,ls); //do not copy the ending 0.
      *(pos+m_size-1) = 0;
      pos += m_size;
    }}

    if(!m_pos) {
      unsigned int chunked = 32*m_size; //<size>A strings.
      if(!write_array<char>(m_group,s_pages(),sz,buffer,chunked,m_compress)) {
        m_out << "pages::write_strings : write_strings() failed. Pos " << m_pos << std::endl;
        return false;
      }
      m_dataset = toolx_H5Dopen(m_group,s_pages().c_str());
      if(m_dataset<0) {
        m_out << "pages::write_strings : H5Dopen failed. Pos " << m_pos << std::endl;
        return false;
      }
    } else {
      if(!write_append_array_dataset<char>(m_dataset,sz,buffer)) {
        m_out << "pages::write_strings : write_append_strings() failed. Pos " << m_pos << std::endl;
        return false;
      }
    }
    m_pos += a_number;
    m_entries = m_pos;
    return true;
  }

  bool read_strings(size_t a_size,char* a_array) {
    unsigned int _size = a_size*m_size;
    unsigned int n = 0;
    char* array = 0;
    if(!read_sub_array<char>(m_group,s_pages(),m_pos,_size,n,array)) {
      m_out << "pages::read_strings : read_sub_array<char>() failed." << std::endl;
      return false;
    }
    if(n!=_size) {
      m_out << "pages::read_strings : size mismatch. Requested " << _size << ", got "<< n << "." << std::endl;
      delete [] array;
      return false;
    }
    char* pos = array;
   {char** strings = (char**)a_array;
    for(size_t index=0;index<a_size;index++) {
      ::memcpy(strings[index],pos,m_size*sizeof(char));
      pos += m_size;
    }}
    delete [] array;
    m_pos += n;
    return true;
  }
*/
protected:
  std::ostream& m_out;
  std::string m_name;
  std::string m_form;
  //int m_type;
  //unsigned int m_size;
  hid_t m_group;
  hid_t m_dataset;
  bool m_write;
  unsigned int m_compress; //if write.
  tools::uint64 m_entries;
  tools::uint64 m_pos;
};

}}

#endif
