#ifndef SLOTSTORAGE_H
#define SLOTSTORAGE_H

#include "Param.h"
#include <map>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

//SlotStorage maps given slots to data. It is designed to reduce reproduction of data while preserving the order of entry.
//The name "NULL_INDEX" is reserved, do not try to use it.
template<class T> class SlotStorage {
 public:
  SlotStorage();
  ~SlotStorage();

  //destroys all data
  void clear();

  //Sets dataIter to the data found in the slot.
  //returns false if the data doesn't exist.
  bool get (int slot, typename map<string, T>::iterator & dataIter);
  bool get (const string & name, typename map<string, T>::iterator & dataIter);

  //add data. options contains the various options to add.
  //options include:
  //Max Size (int, default -1): If the number of names is equal or more than this, data is not added and return false
  //Unique (bool, default false): Set to true. This will return false if there is another entry with that name.
  //Overwrite Slot (bool, default true): If true, remove the data at slot and put this in its place. Otherwise return false and do nothing.
  bool add (const string & name, int slot, Param & options, T & data);

  //returns true if data with this key exists.
  bool exists (const string & name);
  bool exists (int slot);
  //returns the number of slots.
  int getNumberOfSlots();
  //returns the number of assigned slots.
  int getNumberOfAssignedSlots();
  //returns the number of unique entries.
  int getNumberOfUniqueData();

  //removes the slot (set it to "NULL_INDEX") and removes from dataStorage if there are no more references.
  //return true if it exists before removing.
  bool remove(int slot);
  //removes data from dataStorage. Removes all slots from names.
  bool remove(const string & name);

 private:
  vector<string> names;
  map<string, T> dataStorage;
};

template<class T> SlotStorage<T>::SlotStorage()
{
  clear();
}

template<class T> SlotStorage<T>::~SlotStorage()
{
}


//destroys all data
template<class T> void SlotStorage<T>::clear()
{
  names.clear();
  dataStorage.clear();
}


//Sets dataIter to the data found in the slot.
//returns false if the data doesn't exist.
template<class T> bool SlotStorage<T>::get (int slot, typename map<string, T>::iterator & dataIter)
{
  if (slot < 0 || slot >= int(names.size()))
    return false;
  return get(names[slot], dataIter);
}

template<class T> bool SlotStorage<T>::get (const string & name, typename map<string, T>::iterator & dataIter)
{
  if (name=="NULL_INDEX")
    return false;
  dataIter = dataStorage.find (name);
  return (dataIter != dataStorage.end());
}

//add data. options contains the various options to add. If slot is negative, look for the first available slot.
//options include:
//Max Size (int, default -1): If the number of names is equal or more than this, data is not added and return false
//Unique (bool, default false): Set to true. This will return false if there is another entry with that name.
//Overwrite Slot (bool, default true): If true, remove the data at slot and put this in its place. Otherwise return false and do nothing.
template<class T> bool SlotStorage<T>::add (const string & name, int slot, Param & options, T & data)
{
  int maxSize=-1, firstEmptySlot=-1;
  bool uniqueName=false, overwriteSlot=true;
  typename map<string, T>::iterator dataIter;

  //NULL_INDEX is a reserved name
  if (name=="NULL_INDEX")
    return false;

  options.getInt("Max Size", maxSize);
  options.getBool("Unique", uniqueName);
  options.getBool("Overwrite Slot", overwriteSlot);

  dataIter = dataStorage.find (name);

  //if slot is negative, set to the first empty slot.
  bool done=false;
  for (firstEmptySlot=0; slot < 0 && firstEmptySlot < int(names.size()) && !done; firstEmptySlot++)
    {
      if (names[firstEmptySlot]=="NULL_INDEX")
        done=true;
    }
  if (slot<0)
    slot=firstEmptySlot;

  //maximum size check. If we have to make a new empty slot and we're full, bail.
  if (maxSize != -1 && firstEmptySlot >= maxSize)
    return false;

  //unique name check.
  if (dataIter != dataStorage.end() && uniqueName)
    return false;

  //overwrite slot behavior
  if (overwriteSlot)
    if (dataIter != dataStorage.end())
      remove (name);
  else
    if (dataIter != dataStorage.end())
      return false;

  //resize names size to match the slot.
  if (slot >= int(names.size()))
    names.resize(slot+1, "NULL_INDEX");

  //now you can add it.
  names[slot]=name;

  //add to dataStorage if it doesn't exist already.
  dataIter = dataStorage.find (name);
  if (dataIter == dataStorage.end())
    dataStorage[name]=data;
  return true;
}

//removes the slot (set it to "NULL_INDEX") and removes from dataStorage if there are no more references.
//return true if it exists before removing.
template<class T> bool SlotStorage<T>::remove(int slot)
{
  if (slot < 0 || slot >= names.size())
    return false;

  string name=names[slot];
  names[slot]="NULL_INDEX";
  //if that was the only occurence in names, remove the data.
  vector <string> :: iterator otherOccurences;
  typename map <string, T> :: iterator pointInData;
  otherOccurences=find(names.begin(), names.end(), name);
  if (otherOccurences==names.end())
    {
      pointInData=dataStorage.find(name);
      if (pointInData!=dataStorage.end())
        dataStorage.erase(pointInData);
    }
  return true;
}

//removes data from dataStorage. Removes all slots from names.
template<class T> bool SlotStorage<T>::remove(const string & name)
{
  typename map <string, T> :: iterator pointInData;
  pointInData=dataStorage.find(name);
  if (pointInData==dataStorage.end())
    return false;
  dataStorage.erase(pointInData);
  vector<string>::iterator slotSpaces;
  slotSpaces=find(names.begin(), names.end(), name);
  while (slotSpaces!=names.end())
    {
      (*slotSpaces)="NULL_INDEX";
      slotSpaces=find(names.begin(), names.end(), name);
    }
  return true;
}

//returns true if data with this key exists.
template<class T> bool SlotStorage<T>::exists (const string & name)
{
  typename map <string, T> :: iterator pointInData;
  pointInData=dataStorage.find(name);
  return (pointInData!=dataStorage.end());
}

template<class T> bool SlotStorage<T>::exists (int slot)
{
  //check for the size of the array
  if (slot < -1 || slot >= int(names.size()))
    return false;

  //check to see if this entry is null
  string name=names[slot];
  if (name=="NULL_INDEX")
    return false;
  //now see if the data with that name exists.
  return exists(name);
}

//returns the number of slots.
template<class T> int SlotStorage<T>::getNumberOfSlots()
{
  return names.size();
}

//returns the number of assigned slots.
template<class T> int SlotStorage<T>::getNumberOfAssignedSlots()
{
  int count =0;
  for (int i =0; i < names.size(); i++)
    {
      if (names[i]!="NULL_INDEX")
        count++;
    }
  return count;
}

//returns the number of unique entries.
template<class T> int SlotStorage<T>::getNumberOfUniqueData()
{
  return dataStorage.size();
}
#endif
