/**
 *
 * DISK BASED HASHTABLE DATA STRUCTURE...
 *
 * Author Matthew W. Coan
 * Date: Mon Nov 14 01:29:42 EST 2016
 *
 */

#ifndef _D_HASHMAP_H
#define _D_HASHMAP_H

#include "disk_tools.h"
#include "d_alloc.h"
#include "d_env.h"
#include "d_db.h"
#include "bsstream.h"
#include "bfstream.h"

namespace disk_tools {

using namespace io_tools;
using namespace text_tools;

template< class K, class D >
class d_hashmap_assign {
   offset_type addr;
   offset_type addr0;
   bfstream * file;
   d_alloc * alloc;
   d_database * db_ptr;

public:
   d_hashmap_assign(const offset_type & addr, const offset_type & addr0, d_env * env = p_d_env) {
      env->get_db()->grab2(*this);
      this->addr = addr;
      this->addr0 = addr0;
   }

   d_hashmap_assign() {
      addr = 0L;
      file = 0;
      alloc = 0;
      db_ptr = 0;
   }

   void grab(bfstream * file, offset_type addr, d_alloc * alloc, d_database * db_ptr) {
      this->file = file;
      this->alloc = alloc;
      this->db_ptr = db_ptr;
   }

   d_hashmap_assign(const d_hashmap_assign< K, D > & cp) {
      addr = cp.addr;
      file = cp.file;
      alloc = cp.alloc;
      db_ptr = cp.db_ptr;
   }

   ~d_hashmap_assign() {
   }

   d_hashmap_assign< K, D > & operator=(const d_hashmap_assign< K, D > & cp) {
      addr = cp.addr;
      file = cp.file;
      alloc = cp.alloc;
      db_ptr = cp.db_ptr;
      return *this;
   }

   d_hashmap_assign< K, D > & operator=(const D & d) {
      K k;
      offset_type next;
      file->seek(addr);
      *file >> next >> k;
      addr = alloc->alloc(sizeof(offset_type) + get_size(k) + get_size(d));
      file->seek(addr0);
      *file << addr;
      file->seek(addr);
      *file << next << k << d;
      return *this;
   }

   operator D() {
      return get_data();
   }

   D get_data() {
      K k;
      D d;
      file->seek(addr);
      offset_type next;
      *file >> next >> k >> d;
      return d;
   }
};

template< class T_ostream, class K, class D >
inline T_ostream & operator<<(T_ostream & out, d_hashmap_assign< K, D > value) {
   out << value.get_data();
   return out;
}

template< class K, class D, class Hash >
class d_hashmap {
public:
   enum { DEFAULT_TABLE_SIZE = 1000 };

private:
   bfstream * file;
   d_alloc * alloc;
   offset_type addr;
   offset_type addr0;
   d_database * db_ptr;
   offset_type the_size;
   offset_type table_size;
   Hash hash;
   d_env * env;

public:
   d_hashmap(const string & name, const offset_type & table_size = DEFAULT_TABLE_SIZE, d_env * env = p_d_env) {
      this->env = env;
      this->table_size = table_size;
      env->get_db()->grab(name, *this); 
   }

   ~d_hashmap() {
      if(file) {
         file->seek(addr);
         *file << addr;
      }
   }

   offset_type get_address() {
      return addr;
   }

   void grab(bfstream * file, offset_type addr, d_alloc * alloc, d_database * db_ptr) {
      this->file = file;
      this->alloc = alloc;
      this->db_ptr = db_ptr;
      this->addr = addr;
      file->seek(addr);
      offset_type temp;
      *file >> temp;
      if(temp == -1L) {
         temp = create_hashmap(table_size, db_ptr);
         file->seek(addr);
         *file << temp;
         this->addr = temp;
         the_size = 0;
      }
      else {
         this->addr = temp;
         file->seek(temp);
         *file >> the_size >> table_size;
      }
   }

   offset_type get(const K & key, D & data) {
      K k;
      D d;
      offset_type ret = 0L;
      offset_type h = hash(key); 
//cout << "get::hash=" << (h % table_size) << endl;
      offset_type a = addr + ((h % table_size) * sizeof(offset_type)) + sizeof(offset_type) + sizeof(offset_type);
      offset_type next, t = a;
      file->seek(a);
      *file >> next;
      if(next == -1L) {
         data = D();
      }
      else {
         bool found = false;
         while(next != -1L) {
//cout << "get::next=" << next << endl;
            file->seek(next);
            addr0 = t;
            t = next;
            *file >> next >> k;
            if(k == key) {
               *file >> data;
               ret = t;
               found = true;
               break;
            }
         }
         if(!found) data = D();
      }
      return ret;
   }

    offset_type put(const K & key, const D & data) {
      offset_type h = hash(key);
//cout << "put::hash=" << (h % table_size) << endl;
      offset_type a = addr + ((h % table_size) * sizeof(offset_type)) + sizeof(offset_type) + sizeof(offset_type);
      offset_type next,last=a,start,temp,last_last = last;
      bool found = false;
      file->seek(a);
      *file >> next;
      start = next;
      K k;
      D d;
      while(next != -1L) {
//cout << "put::next=" << next << endl;
         last_last = last;
         last = next;
         file->seek(next);
         *file >> next >> k;
         if(k == key) {
            bstringstream buf;
            buf << next << key << data;
            alloc->deallocate(last);
            a = alloc->allocate(buf.size(), buf);
            file->seek(last_last);
            *file << a;
            //file->seek(a);
            //buf.write(*file);
            //*file << next << key << data;
            found = true;
            the_size++;
            //file->seek(addr);
            //*file << the_size;
            break;
         }
      }
      if(!found) {
         bstringstream buf;
         buf << start << key << data;
         temp = a;
         a = alloc->allocate(buf.size(), buf);
         //file->seek(a);
         //*file << start << key << data;
         //buf.write(*file);
         file->seek(temp);
         *file << a;
         the_size++;
         //file->seek(addr);
         //*file << the_size;
      }
      return a;
   }

   void erase(const K & key) {
       K k;
       D d;
       offset_type h = hash(key), t;
       offset_type a = addr + ((h % table_size) * sizeof(offset_type)) + sizeof(offset_type) + sizeof(offset_type);
       file->seek(a);
       offset_type next, last = a;
       *file >> next;
       if(next == -1L)
          ;
       else {
          offset_type temp;
          while(next != -1L) {
             file->seek(next);
             *file >> temp >> k >> d;
             if(k == key) {
                alloc->dealloc(next);
                file->seek(last);
                *file << temp;
                break;
             } 
             last = next; 
             next = temp;
          }
          file->seek(addr);
          the_size--;
          *file << the_size;
       }
   }

   d_hashmap_assign< K, D > operator[](const K & k) {
      offset_type a = 0L;
      D data = D();
      if(!(a = get(k, data))) {
         a = put(k, data);
      }
      return d_hashmap_assign< K, D >(a,addr0,env);
   }

   const offset_type & size() const {
      return the_size;
   }

   const offset_type & size() {
      return the_size;
   }

   const offset_type & get_table_size() const {
      return table_size;
   }

   const offset_type & get_table_size() {
      return table_size;
   }
};

}

#endif /* _D_HASHMAP_H */
