#ifndef _REF_H
#define _REF_H

#include "disk_tools.h"

namespace disk_tools {

using namespace std;

template< class T >
class d_ref {
   offset_type address;
   d_alloc * alloc;
   bfstream * file;
   d_database * db;
   d_env * env;
   mutable T value;
   mutable bool did_load;

public:
   d_ref(d_env * env = p_d_env) {
      this->env = env;
      db = env->get_db();
      file = env->get_file();
      alloc = env->get_alloc();
      bstringstream buf;
      buf << value;
      address = alloc->allocate(buf.size(), buf);
      did_load = false;
      db->grab(address, *this);
   }

   d_ref(offset_type & off, d_env * env = p_d_env) {
      address = off;
      file = env->get_file();
      this->env = env;
      db = env->get_db();
      alloc = env->get_alloc();
      did_load = false;
      db->grab(address, *this);
   }

   d_ref(const T & value, d_env * env = p_d_env) {
      this->value = value;
      this->env = env;
      db = env->get_db();
      file = env->get_file();
      alloc = env->get_alloc();
      did_load = false;
      bstringstream str;
      str << value;
      address = alloc->allocate(str.size(), str); 
      db->grab(address, *this);
   }

   d_ref(const d_ref< T > & cp) {
      value = cp.value;
      address = cp.address;
      env = cp.env;
      db = cp.db;
      file = cp.file;
      alloc = cp.alloc;
      did_load = false;
      db->grab(address, *this);
   }

   void grab(bfstream * file, offset_type addr, d_alloc * alloc, d_database * db_ptr) {
      this->file = file;
      this->alloc = alloc;
      this->address = addr;
      this->db = db_ptr;
      if(address != -1L) {
         file->seek(address);
         *file >> value;
      }
   }

   const offset_type & get_address() { 
      return address; 
   }

   void set_address(offset_type off) {
      address = off;
      did_load = false;
   }

   d_ref< T > & operator=(const T & right) {
      if(address != -1L) {
         alloc->deallocate(address);
      }
      value = right;
      bstringstream bss;
      bss << right;
      address = alloc->allocate(bss.size(), bss);
      return *this;
   }

   d_ref< T > & operator=(const d_ref< T > & right) {
      address = right.address;
      file->seek(address);
      *file >> value; 
      address = right.address;
      env = right.env;
      db = right.db;
      file = right.file;
      alloc = right.alloc;
      did_load = right.did_load;
      return *this;
   }

   T get_data() const {
      if(!did_load) {
         file->seek(address);
         *file >> value;
         did_load = true;
      }
      return value;
   }

   bool operator<(const d_ref< T > & right) const {
      return get_data() < right.get_data();
   }

   bool operator<=(const d_ref< T > & right) const {
      return get_data() <= right.get_data();
   }

   bool operator>(const d_ref< T > & right) const {
      return get_data() > right.get_data();
   }

   bool operator>=(const d_ref< T > & right) const {
      return get_data() >= right.get_data();
   }

   bool operator==(const d_ref< T > & right) const {
      return get_data() == right.get_data();
   }

   bool operator!=(const d_ref< T > & right) const {
      return get_data() != right.get_data();
   }

   T * operator->() {
      if(!did_load) {
         file->seek(address);
         *file >> value;
         did_load = true;
      }
      return &value;
   }

   operator T() {
      if(!did_load) {
         file->seek(address);
         *file >> value;
         did_load = true;
      }
      return value;
   }
};

template< class T, class T_ostream >
T_ostream & operator<<(T_ostream & out, d_ref< T > & ref)
{
   out << ref.get_address();
   return out;
}

template< class T, class T_istream >
T_istream & operator>>(T_istream & in, d_ref< T > & ref)
{
   offset_type temp;
   in >> temp;
   ref.set_address(temp);
   return in;
}

}

#endif /* _REF_H */
