#include <stdexcept>
#include <iostream>

#include "execution_slot_tracker.hpp"
#include "pbs_error.h"

const int  OUT_OF_RANGE = -1;
const int  INSUFFICIENT_FREE_EXECUTION_SLOTS = -2;
const int  SUBSET_TOO_LARGE = -3;

const bool OCCUPIED = true;
const bool FREE = false;

execution_slot_tracker::execution_slot_tracker(const execution_slot_tracker& est)
  {
  this->slots = est.slots;
  this->open_count = est.open_count;
  } /* END copy constructor */



execution_slot_tracker::execution_slot_tracker()
  {
  this->open_count = 0;
  } /* END default contructor */



execution_slot_tracker::execution_slot_tracker(
   
  const int size)

  {
  this->open_count = 0;
  
  for (int i = 0; i < size; i++)
    this->add_execution_slot();
  }


execution_slot_tracker& execution_slot_tracker::operator= (
	
  const execution_slot_tracker& est)

  {
  if (this == &est)
    return(*this);

  this->slots = est.slots;
  this->open_count = est.open_count;
  return(*this);
  } /* END = operator */



/*
 * unset_subset()
 * @pre-cond: subset must be of an equal or smaller size than this execution slot tracker object
 * @post-cond: all of the occupied slots in subset will become unoccupied in this
 */
int execution_slot_tracker::unset_subset(

  const execution_slot_tracker &subset)

  {
  if (subset.get_total_execution_slots() > this->get_total_execution_slots())
    return(SUBSET_TOO_LARGE);

  for (int i = 0; i < subset.get_total_execution_slots(); i++)
    {
    if (subset.slots[i] == OCCUPIED)
      this->mark_as_free(i);
    }

  return(PBSE_NONE);
  }



/*
 * mark_as_used() 
 * marks the slot at index index as currently occupied
 * @pre-cond: index must be a valid index into the vector
 * @post-cond: slots[index] will be occupied and open_count will be updated if needed
 * @return PBSE_NONE on success or OUT_OF_RANGE if index isn't a valid index
 */
int execution_slot_tracker::mark_as_used (
  
  int index)

  {
  int size = this->slots.size();

  if ((index < 0) ||
      (index >= size))
    return(OUT_OF_RANGE);

  try 
    {
    if (this->slots[index] == FREE)
      {
      this->slots[index] = OCCUPIED;
      this->open_count--;
      }

    return(PBSE_NONE);
    }
  catch (std::out_of_range &oor)
    {
    return(OUT_OF_RANGE);
    }
  }



/*
 * mark_as_free() 
 * marks the slot at index index as free 
 * @pre-cond: index must be a valid index into the vector
 * @post-cond: slots[index] will be free and open_count will be updated if needed
 * @return PBSE_NONE on success or OUT_OF_RANGE if index isn't a valid index
 */
int execution_slot_tracker::mark_as_free (

  int index)

  {
  int size = this->slots.size();

  if ((index < 0) ||
      (index >= size))
    return(OUT_OF_RANGE);
  
  try
    {
    if (this->slots[index] == OCCUPIED)
      {
      this->slots[index] = FREE;
      this->open_count++;
      }

    return(PBSE_NONE);
    }
  catch (std::out_of_range &oor)
    {
    return(OUT_OF_RANGE);
    }
  }
  


int execution_slot_tracker::reserve_execution_slot(
    
  int                     index,
  execution_slot_tracker &subset)

  {
  int rc;
 
  while (subset.get_total_execution_slots() < this->get_total_execution_slots())
    subset.add_execution_slot();

  if ((rc = this->mark_as_used(index)) == PBSE_NONE)
    {

    if ((rc = subset.mark_as_used(index)) != PBSE_NONE)
      {
      this->mark_as_free(index);
      }
      
    return(rc);
    }

  return(rc);
  }



/*
 * reserve_execution_slots()
 * reserves num_slots_to_reserve from this and marks the same ones as occupied in est
 * @pre-cond: est must be a valid execution_slot_tracker object
 * @post-cond: both this and est will have num_slots_to_reserve more slots set as
 * occupied. est will be resized if necessary to accomodate this functionality.
 * @returns INSUFFICIENT_FREE_EXECUTION_SLOTS if this object doesn't have enough
 * slots to accomodate the requested reservation or PBSE_NONE on success.
 */
int execution_slot_tracker::reserve_execution_slots(

  int                     num_slots_to_reserve,
  execution_slot_tracker &est)

  {
  int reserved_so_far = 0;

  if (this->open_count < num_slots_to_reserve)
    return(INSUFFICIENT_FREE_EXECUTION_SLOTS);

  while (est.get_total_execution_slots() < this->get_total_execution_slots())
    est.add_execution_slot();

  for (int i = 0; i < this->slots.size() && reserved_so_far < num_slots_to_reserve; i++)
    {
    if (this->slots[i] == FREE)
      {
      reserved_so_far++;
      this->mark_as_used(i);

      est.mark_as_used(i);
      }
    }

  return(PBSE_NONE);
  }


/* 
 * unreserve_execution_slots()
 *
 * @pre-cond:  subset must be smaller than or equal to this in size.
 * @post-cond: all occupied slots in subset will become free in this.
 * @return SUBSET_TOO_LARGE if subset is larger than this or PBSE_NONE
 * on success
 */
int execution_slot_tracker::unreserve_execution_slots(

  const execution_slot_tracker &subset)

  {
  if (this->slots.size() < subset.slots.size())
    return(SUBSET_TOO_LARGE);

  for (int i = 0; i < subset.slots.size(); i++)
    {
    if (subset.slots[i] == OCCUPIED)
      this->mark_as_free(i);
    }

  return(PBSE_NONE);
  }



int execution_slot_tracker::get_number_free() const
  {
  return(this->open_count);
  }



int execution_slot_tracker::get_total_execution_slots() const
  {
  return(this->slots.size());
  }



void execution_slot_tracker::add_execution_slot ()

  {
  this->slots.push_back(FREE);
  this->open_count++;
  }



int execution_slot_tracker::remove_execution_slot ()
  {
  try
    {
    if (this->slots[this->slots.size() - 1] == FREE)
      this->open_count--;

    this->slots.pop_back();

    return(PBSE_NONE);
    }
  catch (...)
    {
    return(-4);
    }
  }


/*
 * get_next_occupied_index()
 * used to iterate over this execution slot tracker object
 *
 * @param iterator: an index that we should start iterating from. -1 to begin.
 */
int execution_slot_tracker::get_next_occupied_index(

  int &iterator) const

  {
  int occupied_index = -1;

  if (iterator == -1)
    iterator = 0;

  while (iterator < this->slots.size())
    {
    if (this->slots[iterator] == OCCUPIED)
      {
      occupied_index = iterator;
      iterator++;
      break;
      }
    else
      iterator++;
    }

  return(occupied_index);
  }

bool execution_slot_tracker::is_occupied(

  int index) const

  {
  int  size = this->slots.size();
  bool occupied = false;

  if ((index < 0) ||
      (index >= size))
    return(false);
  
  try
    {
    occupied = this->slots[index] == OCCUPIED;
    }
  catch (std::out_of_range &oor)
    {
    return(false);
    }

  return(occupied);
  }
