#ifndef DISKVECTOR_H__
#define DISKVECTOR_H__


/* (c) 2005, Thomas Mølhave (thomasm@daimi.au.dk)
 *  Vector for storing huge amounts of data, all you 
 *  need is enough space on your disk.
 *
 *  Version 0.01 -  Jan 27, 2005.
 */


#include <exception>
#include <fstream>
#include <stdexcept>
#include <string>
#include <iostream>

namespace Th {

  /* Specification */

  template<typename T> class disk_vector
  {
    public:
      disk_vector() : mBlockSize(0), mSize(0), mCurrentOffset(0), mCurrentBlock(0) { }

      /* Use operator[] to acces elements in the vector. */
      T& operator[](unsigned int i);

      /* Returns the size of the vector. */
      unsigned int size() { return mSize; }

      /* Initializes the vector
       * size: The number of elements in the vector
       * blockSize: The number of elements that you want to keep in memory at a time
       * file: The file to store the vector in.
       */
      void init(unsigned int size, unsigned int blockSize = 100, std::string file = "/tmp/iofile");

    private:
      std::string mFileName;
      std::fstream mFile;
      unsigned int mBlockSize;
      unsigned int mSize;
      unsigned int mCurrentOffset;
      T* mCurrentBlock;
  };

  /* Implementation */

  template<typename T>
    inline void disk_vector<T>::init(unsigned int size, unsigned int blockSize, std::string file)
    {
      mCurrentBlock = new T[blockSize];
      if (mCurrentBlock == 0)
        throw std::bad_alloc();

      mFileName = file;

      mFile.open(file.c_str(), std::ios::trunc | std::ios::in | std::ios::out | std::ios::binary);

      if (!mFile)
        throw std::runtime_error("Could not open IO file: " + file);

      mBlockSize = blockSize;
      mSize = size;
      mCurrentOffset = 0;

      if (mSize > 0) //Make sure file has the correct length
      {
        mFile.seekp(mSize*sizeof(T)-1, std::ios::beg);
        mFile.write("A", 1);
      }
      mFile.seekg(0, std::ios::beg);
      mFile.seekp(0, std::ios::beg);
    }

  template<typename T>
    inline T& disk_vector<T>::operator[](unsigned int i)
    {
      if (i >= mSize) throw std::logic_error("Index out of bounds");

      //Check if wee need to fetch a new block
      if (i >= mCurrentOffset && i < mCurrentOffset+mBlockSize)
      {
        //It is already in memory, return element
        return mCurrentBlock[i-mCurrentOffset];
      } else {
        //We need a new block, write the old one back to disk
        mFile.seekp(mCurrentOffset*sizeof(T), std::ios::beg);
        mFile.write((char*)mCurrentBlock, mBlockSize*sizeof(T));

        //reseek to new offset
        mCurrentOffset = i;
        mFile.seekg(i*sizeof(T), std::ios::beg);
        mFile.read((char*)mCurrentBlock, mBlockSize*sizeof(T));
        return this->operator[](i);
      }
    }

}

#endif