/**------------------------------------------------------------------------
 * @file
 *
 * Copyright 2001 by the University of Toronto (UT).
 *
 *                       All Rights Reserved
 *
 * Permission to copy and modify this software and its documentation
 * only for internal use in your organization is hereby granted,
 * provided that this notice is retained thereon and on all copies.
 * UT makes no representations as to the suitability and operability
 * of this software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 *
 * UT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
 * SHALL UT BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * No other rights, including for example, the right to redistribute
 * this software and its documentation or the right to prepare
 * derivative works, are granted unless specifically provided in a
 * separate license agreement.
 *
 * Author: Maxim Trokhimtchouk (trokhim@cs.utoronto.ca)
 *
 * $Log: diskdatabase.hxx,v $
 * Revision 1.1.1.1  2002/03/10 07:38:07  dmac
 * Imported sources
 *
 * Revision 1.1  2001/10/03 20:19:56  dmac
 * Added file
 *
 *
 *-----------------------------------------------------------------------*/

#include <vector>
#include <utility>
#include <stdlib.h>


template<class T>
DiskDatabase<T>::DiskDatabase()
{
	name = "";
	lastid = -1;
}

template<class T>
DiskDatabase<T>::~DiskDatabase()
{
  if(datafile.is_open()) datafile.close();
}

template<class T>
pair<T, bool> DiskDatabase<T>::operator[](ID id)
{
  //Check if database has been initialized
  if(name == "") return pair<T, bool>(T(),false);

  //Check if id is out of range
  if(id > lastid || id < 0) return pair<T, bool>(T(),false);

  typename list<record_pos>::iterator i;
  long start;
  long end;

  //Finds the record with the requested ID
  for(i=index.begin(); i != index.end(); i++)
  {
      //If found the record, then read it from database file and return it;
      if(i->id == id) 
	  {
		  return ReadRecord(i->start, i->end);
	  }
  }

  //If we got up to here the is no such id exist
  return  pair<T, bool>(T(),false);
}

template<class T>
ID DiskDatabase<T>::Add(const T& record)
{
  //Check if database has been initialized
  if(name == "") return NULLID;

  ///Vector that denotes whether element with id exist.
  ///idvector[id] == true if element with id exist.
  vector<bool> idvector(lastid + 2, false);
  
  typename list<record_pos>::iterator first;
  typename list<record_pos>::iterator second;
  long start, end, size;

  ID newid;
  record_pos nrp;

  //Records existing IDs
  for(first = index.begin(); first != index.end(); first++)
  {
    idvector[first->id] = true;
  }

  //Finds the first available ID
  for(newid = 0; idvector[newid] == true; newid++);

  size = record.DataSize();
  second = first = index.begin();

  //Check if the file is empty or we can write to the beginning.
  if(first == index.end() || first->start > size)
    {
      nrp = WriteRecord(0, record);
    }
  else
    {
      //Finds the available space in between records
      for(second++; second != index.end() && (second->start - first->end - 1) < size; first++, second++);

      //Write record to the space found
      nrp = WriteRecord(first->end + 1, record);

    }
    

  //If an error occured during  writing of the record,
  //than return NULLID, else add record position to the list and return the new id.
  if(nrp.start == -1)
    {
      return NULLID;
    }
  else
    {
      nrp.id = newid;
      index.insert(second, nrp);

      //check if the new id is larger that the last id, and if so, change the last id
      if(newid > lastid) lastid = newid;

      return newid;
    }
}

template<class T>
void DiskDatabase<T>::Remove(ID id)
{
  //Check if database has been initialized
  if(name == "") return;

  //Check if id is out of range
  if(id > lastid || id < 0) return;

  typename list<record_pos>::iterator i;
  long start, end;

  //Finds the record with the requested ID
  for(i = index.begin(); i != index.end() && i->id != id; i++);

  if(i != index.end()) index.erase(i);

  //Check if the id removed is equal to lastid, if so decrease lastid;
  if(lastid == id) lastid--;
}

template<class T>
void DiskDatabase<T>::RemoveAll()
{
  index.clear();
  lastid == -1;
}

template<class T>
bool DiskDatabase<T>::Contained(ID id)
{
  //Check if database has been initialized
  if(name == "") return false;

  //Check if id is out of range
  if(id > lastid || id < 0) return false;

  typename list<record_pos>::iterator i;
  long start, end;

  //Finds the record with the requested ID
  for(i = index.begin(); i != index.end() && i->id != id; i++);

  //If not found return false. Return true otherwise
  if(i == index.end())
    return false;
  else
    return true;
}

template<class T>
string DiskDatabase<T>::GetDatabaseName()
{
	return name;
}


template<class T>
bool DiskDatabase<T>::Create(const string& n)
{
  if(datafile.is_open()) 
  {
	  datafile.close();
	  RemoveAll();
  }

  name = n;

  datafile.open(name.c_str(), ios::in | ios::out | ios::binary);

  if(datafile.fail())
    {
      name = "";
      return false;
    }

  return true;
  
}

template<class T>
istream& DiskDatabase<T>::Read(istream& is)
{
	char* namestr;
	size_t t;

    //read the name of the database
	is.read(&t,sizeof(t));
	
	namestr = new char[t];

    is.read(namestr,t);
	name = string(namestr);

	delete [] namestr;

	//Open the database, if error occured, set stream status to fail and exit.
	if(Create(name) == false) 
	  {
	    is.setstate(ios::failbit);
	    return is;
	  }

	is.read(&lastid, sizeof(lastid));

	int count;
	record_pos item;

	is.read(&count, sizeof(count));

	index.clear();

	for(; count > 0; count--)
    { 
		is.read(&(item.start), sizeof(item.start));
		is.read(&(item.end), sizeof(item.end));
		is.read(&(item.id), sizeof(item.id));
		index.push_back(item);
    }
      
	return is;
}

template<class T>
ostream& DiskDatabase<T>::Write(ostream& os)
{
	size_t t;
	record_pos item;

	//write the name of the database
	t = name.length() + 1;
	os.write(&t,sizeof(t));
	os.write(name.c_str(),t);	

	os.write(&lastid, sizeof(lastid));

	//write the index
	typename list<record_pos>::iterator i;
	int count = index.size();

	os.write(&count, sizeof(count));

	for(i = index.begin(); i != index.end() && count > 0; i++, count--)
    {
		item = *i;

		os.write(&(item.start), sizeof(item.start));
		os.write(&(item.end), sizeof(item.end));
		os.write(&(item.id), sizeof(item.id));
    }

	return os;
}

template<class T>
void DiskDatabase<T>::Close()
{
	datafile.close();
	name = "";
	lastid = -1;
}

template<class T>
pair<T, bool> DiskDatabase<T>::ReadRecord(long s, long e)
{
  T record;
  bool stat;

  datafile.seekg(s);

  record.read(datafile);

  //if stream operation has failed, set stat to false
  if(!datafile) 
    stat = false;
  else
    stat = true;

  return pair<T, bool>(record, stat);  
}

template<class T>
record_pos DiskDatabase<T>::WriteRecord(long s, const T& record)
{

  record_pos retp;

  datafile.seekp(s);
  record.write(datafile);

  //If stream operation has failed, set the retp.start to -1
  if(!datafile)
    retp.start = -1;
  else
    {
      retp.start = s;
      retp.end = datafile.tellp() - 1;
    }

  return retp;
}

template<class T>
void DiskDatabase<T>::Update()
{
  char tfname[255];
  string tfstr(tmpnam(tfname));
  string cmname;
  typename list<record_pos>::iterator i;
  T record;
  int ret;
  
  datafile.close();

  cmname = "mv " + name + " " + tfstr;
  ret = system(cmname.c_str());
  if(ret = -1 || ret = 127) 
  {
	  cerr << "There is a problem with running sh command" << endl;
	  return;
  }

  fstream tempfile(tfstr, ios::in | ios::out | ios::binary);
  datafile.open(name, ios::in | ios::out | ios::binary);
  datafile.seekp(0);

  for(i = index.begin(); i != index.end(); i++)
    {
      tempfile.seekg(i->start);
      i->start = datafile.tellp();
      
      record.read(tempfile);
      record.write(datafile);

      i->end = datafile.tellp() - 1;
    }
  
  tempfile.close();
}
