/**
 * EXTERNAL DISK ARRAY TEMPLATE...
 *
 * Author: Matthew W. Coan
 * Date: Thu Nov 17 00:04:46 EST 2016
 *
 */

#ifndef _D_VECTOR_H
#define _D_VECTOR_H

#include "d_db.h"
#include "d_alloc.h"
#include <iostream>

using namespace std;

namespace disk_tools {


template< class T >
class d_vector_assign {
   offset_type data;
   d_database * db_ptr;
   d_alloc * alloc;
   bfstream * file;
   offset_type addr;

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

   d_vector_assign() {
   }

   d_vector_assign(const d_vector_assign< T > & cp) {
      data = cp.data;
      db_ptr = cp.db_ptr;
      alloc = cp.alloc;
      file = cp.file;
   }

   ~d_vector_assign() {
   }

   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_vector_assign< T > & operator=(const T & value) {
      alloc->deallocate(data);
      data = alloc->allocate(get_size(value));
      file->seek(addr);
      *file << data;
      file->seek(data);
      *file << value;
      return *this;
   }

   operator T() {
      T dat;
      file->seek(addr);
      offset_type off;
      *file >> off;
      file->seek(off);
      *file >> dat;
      return dat;
   }
};


template< class T >
class d_vector_iterator {
   bfstream * file;
   d_alloc * alloc;
   d_database * db_ptr;
   offset_type addr;
   offset_type index;
   T data;

public:
   d_vector_iterator(const offset_type & a,
                     const offset_type & i, 
                     d_env * env = p_d_env) {
      addr = a;
      index = i;
      env->get_db()->grab2(*this);
   }

   d_vector_iterator() {
      addr = -1L;
      index = -1L;
   }
  
   d_vector_iterator(const d_vector_iterator< T > & cp) {
      file = cp.file;
      alloc = cp.alloc;
      db_ptr = cp.db_ptr;
      addr = cp.addr;
      index = cp.index;
   }

   ~d_vector_iterator() {
   }

   d_vector_iterator< T > & operator=(const d_vector_iterator< T > & cp) {
      file = cp.file;
      alloc = cp.alloc;
      db_ptr = cp.db_ptr;
      addr = cp.addr;
      index = cp.index;
      return *this;
   }

   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_vector_iterator< T > & operator++() {
      addr += sizeof(offset_type);
      index++;
      return *this;
   }

   d_vector_iterator< T > & operator++(int arg) {
      addr += sizeof(offset_type);
      index++;
      return *this;
   }

   d_vector_iterator< T > & operator--() {
      addr -= sizeof(offset_type);
      index--;
      return *this;
   }

   d_vector_iterator< T > & operator--(int arg) {
      addr -= sizeof(offset_type);
      index--;
      return *this;
   }

   bool operator==(const d_vector_iterator< T > & ptr) const {
      return index == ptr.index;
   }

   bool operator!=(const d_vector_iterator< T > & ptr) const {
      return index != ptr.index;
   }

   T * operator->() {
      file->seek(addr);
      *file >> data;
      return &data;
   }

   const T & operator*() {
      file->seek(addr);
      *file >> data;
      return data;
   }

   d_vector_assign< T > operator*(int arg) {
      file->seek(addr);
      *file >> data;
      return data;
   }

   offset_type get_index() const {
      return index;
   }
};

template< class T >
class DefaultLess {
public:
   bool operator()(const T & arg1,
                   const T & arg2) const {
      bool ret = false;
      if(arg1 < arg2) 
         ret = true;
      return ret;
   }
};


template< class T >
class d_vector {
public:
   typedef d_vector_iterator< T > iterator;

private:
   d_alloc * alloc;
   d_database * db_ptr;
   offset_type addr;
   bfstream * file;
   offset_type the_size;
   offset_type table_size;

public:
   d_vector(d_env * env = p_d_env) {
      env->get_db()->grab(*this);
   }

   d_vector(offset_type addr, d_env * env = p_d_env) {
      env->get_db()->grab(addr, *this);
   }

   d_vector(const string & name, const offset_type n = 1000, d_env * env = p_d_env) {
      table_size = n;
      env->get_db()->grab(name, *this);
   }

   d_vector(const d_vector< T > & cp) {
      file = cp.file;
      addr = cp.addr;
      db_ptr = cp.db_ptr; 
      alloc = cp.alloc;
   }

   ~d_vector() {
   }

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

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

   const d_vector< T > & operator=(const d_vector< T > & cp) {
       file = cp.file;
       addr = cp.addr;
       db_ptr = cp.db_ptr;
       alloc = cp.alloc;
       return *this;
   }

   d_vector_assign< T > operator[](const offset_type & index) {
      offset_type temp = index * sizeof(offset_type) 
			+ sizeof(offset_type) + sizeof(offset_type); 
      file->seek(addr + temp);
      offset_type a;
      *file >> a;
      return d_vector_assign< T >(addr + temp, a);
   }

   void push_back(const T & value) {
      offset_type temp = the_size * sizeof(offset_type) 
			+ sizeof(offset_type) + sizeof(offset_type); 
      offset_type address = alloc->allocate(get_size(value));
      file->seek(addr + temp);
      *file << address;
      the_size++;
      file->seek(addr+sizeof(offset_type));
      *file << the_size;
      file->seek(address);
      *file << value;
   }

   void push_front(const T & arg_value) {
      offset_type temp = addr + sizeof(offset_type) + sizeof(offset_type);
      file->seek(temp);
      offset_type temp2;
      *file >> temp2;
      offset_type value = alloc->allocate(get_size(arg_value));
      file->seek(value); 
      *file << arg_value;
      offset_type val;
      temp = addr + sizeof(offset_type)
             + sizeof(offset_type);
      file->seek(temp);
      *file >> val;
      file->seek(temp);
      *file << value;
      for(offset_type index = 1; index <= the_size; index++) {
         temp = addr + index * sizeof(offset_type) + sizeof(offset_type)
                + sizeof(offset_type);
         file->seek(temp);
         *file >> value;
         file->seek(temp);
         *file << val;
         val = value;
      }
      the_size++;
      file->seek(addr+sizeof(offset_type));
      *file << the_size;
   }

   void pop_front() {
      file->seek(addr + sizeof(offset_type) + sizeof(offset_type));
      offset_type data;
      *file >> data;

      alloc->deallocate(data);

      the_size--;
      file->seek(addr+sizeof(offset_type));
      *file << the_size;  

      offset_type temp0,temp1;

      for(offset_type index = 0; index < the_size; index++) {
         temp0 = addr + index * sizeof(offset_type) + sizeof(offset_type)
                + sizeof(offset_type);

         temp1 = addr + (index+1) * sizeof(offset_type) + sizeof(offset_type)
                + sizeof(offset_type);

         file->seek(temp1);
         *file >> data;

         file->seek(temp0);
         *file << data;
      }
   }

   void pop_back() {
      file->seek(addr + ((the_size-1) * sizeof(offset_type)) + sizeof(offset_type) + sizeof(offset_type));
      offset_type data;
      *file >> data;
      alloc->deallocate(data);
      the_size--;
      file->seek(addr+sizeof(offset_type));
      *file << the_size;
   }

   T front() {
      file->seek(addr + sizeof(offset_type) + sizeof(offset_type));
      offset_type address;
      *file >> address;
      file->seek(address);
      T data;
      *file >> data;
      return data;
   }

   T back() {
      file->seek(addr + ((the_size-1) * sizeof(offset_type)) + sizeof(offset_type) + sizeof(offset_type));
      offset_type address;
      *file >> address;
      file->seek(address);
      T data;
      *file >> data;
      return data;
   }

   void insert(iterator ptr, const T & arg_value) {
      offset_type temp0 = addr + (ptr.get_index() * sizeof(offset_type)) + sizeof(offset_type) + sizeof(offset_type);
      file->seek(temp0);
      offset_type temp2,temp1;
      *file >> temp2;
      offset_type value = alloc->allocate(get_size(arg_value));
      file->seek(value);
      *file << arg_value;
      file->seek(temp0);
      offset_type val2;
      *file >> val2;
      file->seek(temp0);
      *file << value;
      offset_type val = val2;
      for(offset_type index = ptr.get_index(); index <= the_size; index++) {
         temp0 = addr + index * sizeof(offset_type) + sizeof(offset_type)
                + sizeof(offset_type);
         file->seek(temp0);
         *file >> value;
         file->seek(temp0);
         *file << val;
         val = value;
      }
      the_size++;
      file->seek(addr+sizeof(offset_type));
      *file << the_size;
   }

   void erase(iterator ptr) {
      file->seek(addr + (ptr.get_index() * sizeof(offset_type)) + sizeof(offset_type) + sizeof(offset_type));
      offset_type data;
      *file >> data;

      alloc->deallocate(data);

      the_size--;
      file->seek(addr+sizeof(offset_type));
      *file << the_size;

      offset_type temp0,temp1;

      for(offset_type index = ptr.get_index(); index < the_size; index++) {
         temp0 = addr + index * sizeof(offset_type) + sizeof(offset_type)
                + sizeof(offset_type);

         temp1 = addr + (index+1) * sizeof(offset_type) + sizeof(offset_type)
                + sizeof(offset_type);

         file->seek(temp1);
         *file >> data;

         file->seek(temp0);
         *file << data;
      }
   }

   iterator begin() { 
      return d_vector_iterator< T >(addr + sizeof(offset_type) + sizeof(offset_type), 0);
   }

   iterator end() {
      return d_vector_iterator< T >(addr + the_size * sizeof(offset_type) + sizeof(offset_type) + sizeof(offset_type), the_size);
   }

   const offset_type & get_address() const {
      return addr;
   }

   template< class CompareFunction >
   void sort(CompareFunction comp) {
      offset_type i,j,temp0,temp1;
      offset_type data0,data1;
      for(i = 0L; i < the_size; i++) {
         for(j = i+1; j < the_size; j++) {
             if(comp((T)(*this)[j], (T)(*this)[i])) {
                temp0 = addr + i * sizeof(offset_type) + sizeof(offset_type) + sizeof(offset_type);
                temp1 = addr + j * sizeof(offset_type) + sizeof(offset_type) + sizeof(offset_type);
                file->seek(temp0);
                *file >> data0;
                file->seek(temp1);
                *file >> data1;
		file->seek(temp0);
                *file << data1;
		file->seek(temp1);
                *file << data0;
             }
         }
      }
   }

   void sort() {
      DefaultLess< T > comp;
      sort(comp);
   }
};

}

#endif /* _D_VECTOR_H */
