/*

(C) Copyright 2010.  All rights reserved.
This source code is the intellectual property of Matthew William Coan.

Hash table that is stored on the hard drive.

Author: Matthew William Coan
Date: Sat Nov 13 19:19:39 EST 2010

*/
#ifndef _EXTERNAL_H
#define _EXTERNAL_H

#include <iostream>

#include <unistd.h>

#include "bfstream.h"

namespace ext_tools {

using namespace std;
using namespace disk_tools;

template< class Key, class Data, class Hash >
class ext_hash_map {
   long _size;
   bfstream _stream;
   const char * _file_name;
   unsigned long _magic;

public:
   ext_hash_map(const char * file_name);
   virtual ~ext_hash_map();

   void set_size(const long sz) { _size = sz; }
   void allocate(const long & the_size);
   void erase();

   void put(const Key & k, const Data & d);
   bool get(const Key & k, Data & d);

   long size();
   void close();

   operator void*();
};

}


template< class Key, class Data, class Hash >
inline
void ext_tools::ext_hash_map< Key, Data, Hash >::close()
{
   _stream.close();
}


template< class Key, class Data, class Hash >
inline
void ext_tools::ext_hash_map< Key, Data, Hash >::erase()
{
   _stream.close();

   unlink(_file_name);

   _stream.open(_file_name);

   if(_stream) {
      allocate(_size);
   }
}

template< class Key, class Data, class Hash >
inline
void ext_tools::ext_hash_map< Key, Data, Hash >::allocate(const long & the_size)
{
   _stream.clear();

   _stream.seekg(0, ios::beg);

   long address = -1L;

   _size = the_size;

   _magic = 0xCAFEBABE;

   _stream.seekg(0, ios::beg);

   _stream << _magic;

   _stream << _size;

   _stream.flush();

   for(long i = 0; i < the_size; i++) {
      _stream << address;

      _stream.flush();
   }
}

template< class Key, class Data, class Hash >
inline
ext_tools::ext_hash_map< Key, Data, Hash >::operator void*()
{
   if(_magic != 0xCAFEBABE) {
      return NULL;
   }
   else {
      return ((void*)_stream);
   }
}

template< class Key, class Data, class Hash >
inline
ext_tools::ext_hash_map< Key, Data, Hash >::ext_hash_map(const char * file_name)
:_stream(file_name, ios::in | ios::out | ios::ate)
{
   if(!_stream) {
      cerr << "unable to open: " << file_name << endl;
      exit(1);
   }

   _file_name = file_name;

   _size = 0L;

   _stream.seekg(0L, ios::beg);

   _stream >> _magic;

   _stream >> _size;

   _stream.clear();
}

template< class Key, class Data, class Hash >
inline
ext_tools::ext_hash_map< Key, Data, Hash >::~ext_hash_map()
{
   _stream.flush();

   _stream.close();
}

template< class Key, class Data, class Hash >
inline
void ext_tools::ext_hash_map< Key, Data, Hash >::put(const Key & k, const Data & d)
{
   Key key = k;

   Data data = d;

   Hash hash;

   //long address = -1L;

   long index = hash(key) % _size;

   long addr = index * sizeof(long) + sizeof(long) + sizeof(long);

   _stream.seekg(addr, ios::beg);

   long temp = -1L;
 
   long new_addr = -1L;

   _stream >> temp;

   if(temp == -1L) {
      _stream.seekg(0L, ios::end);

      new_addr = _stream.tellg();

      _stream << temp << key << data;

      _stream.flush();

      _stream.seekg(addr, ios::beg);

      _stream << new_addr;

      _stream.flush();
   }
   else {
      _stream.seekg(0L, ios::end);
      
      new_addr = _stream.tellg();

      long temp_addr = -1L;

      _stream << temp_addr << key << data;

      _stream.flush();

      long last = temp;

      while(temp != -1L && _stream) {
         _stream.seekg(temp, ios::beg);

         last = temp;

         _stream >> temp >> key >> data;
      }

      _stream.seekg(last, ios::beg);

      _stream << new_addr;

      _stream.flush();
   }
}

template< class Key, class Data, class Hash >
inline
bool ext_tools::ext_hash_map< Key, Data, Hash >::get(const Key & k, Data & data)
{
   Key key = k;

   bool ret = false;

   Hash hash;

   long index = hash(k) % _size;

   long addr = index * sizeof(long) + sizeof(long) + sizeof(long);

   _stream.seekg(addr, ios::beg);

   long temp = -1L;

   _stream >> temp;

   if(temp != -1L) {
      long next = temp;
      long last = temp;

      while(next != -1L) {
         _stream.seekg(next, ios::beg);

         last = next;

         string temp_str;

         _stream >> next >> key >> temp_str;

         if(key == k) {
            ret = true;
            data = temp_str;
            break;
         }
      }
   }

   return ret;
}

#endif

