/*	Author: Magdin Stoica
	Supervisor: Dr. Ryan Leduc
	
	Project created in conformity with the requirements for the Degree of Master of Engineering in Software Engineering, 
	Computing and Software Department, 
	McMaster University
	2003 - 2007
*/

#include "DesEventPool.h"
#include "DesEvent.h"
#include "DesEventIterator.h"

namespace DESpot
{

EventPool::EventPool(void)
{
	m_eventCount = 0;
	m_eventCtrlCount = 0;
	m_eventDefCount = 0;
	m_eventReqCount = 0;
	m_eventAnsCount = 0;
	m_eventLdCount = 0;

	m_nextFreeID = 1;
	m_eventPool.resize(10); //start with 10 events
}

//_________________________________________________________________________________________________

EventPool::~EventPool(void)
{
	try
	{
		clear();
	}
	catch(...) 
	{
		assert(false);
	}
}

//_________________________________________________________________________________________________

//deletes all the events, reverting the transition function back to initial state after construction
void EventPool::clear()
{
	//remove all events from the pool and clean memory
	DesEventIterator eventIt(m_eventPool);
	for(eventIt.first(); eventIt.isDone() == false; eventIt.next())
	{
		DesEvent& crtEvent = eventIt.currentItem();
		delete &crtEvent;
	}

	m_eventCount = 0;
	m_eventCtrlCount = 0;
	m_eventDefCount = 0;
	m_eventReqCount = 0;
	m_eventAnsCount = 0;
	m_eventLdCount = 0;

	m_nextFreeID = 1;
}

//_________________________________________________________________________________________________

DesEvent* EventPool::getEvent(const DesEvent::ID& eventId) const
{
	if (eventId >= m_eventPool.size())
		throw EX("Event ID not found in the event pool. Use find method if the event may not be in the pool")		

	DesEvent* pEvent = m_eventPool[eventId];
	if (pEvent == null)
		throw EX("Event ID not found in the event pool. Use find method if the event may not be in the pool")
	
	return pEvent;
}

//_________________________________________________________________________________________________

bool EventPool::findEvent(const DesEvent::ID& eventId, DesEvent** ppFoundEvent)	const
{
	if (eventId >= m_eventPool.size())
		return false;		

	DesEvent* pEvent = m_eventPool[eventId];
	if (pEvent == null)
		return false;
	
	//event found: return it to the caller if it was required
	if (ppFoundEvent)
	{
		assert(*ppFoundEvent == null);
		*ppFoundEvent = pEvent;
	}

	return true; //event found
}

//_________________________________________________________________________________________________

bool EventPool::findEvent(const std::wstring& eventName, DesEvent** ppFoundEvent) const
{
	DesEventIterator eventIt(m_eventPool);
	for(eventIt.first(); eventIt.isDone() == false; eventIt.next())
	{
		DesEvent& crtEvent = eventIt.currentItem();
		if (crtEvent.getName() == eventName)
		{
			//event found; return the event pointer if it was required
			if (ppFoundEvent)
			{
				assert(*ppFoundEvent == null);
				*ppFoundEvent = &crtEvent;
			}

			return true; //event found
		}
	}

	return false;
}

//_________________________________________________________________________________________________

bool EventPool::findEvent(const DesEvent& event, DesEvent** ppFoundEvent)	const
{
	DesEvent::ID eventId = event.getId();
	if (eventId >= m_eventPool.size())
		return false;		

	DesEvent* pEvent = m_eventPool[eventId];
	if (pEvent == null)
		return false;
	
	if (*pEvent == event)
	{
		//event found: return it to the caller if it was required
		if (ppFoundEvent)
		{
			assert(*ppFoundEvent == null);
			*ppFoundEvent = pEvent;
		}
	}

	return true; //event found
}

//_________________________________________________________________________________________________

DesEvent::Count EventPool::getEventCount()  const
{
	//we cannot rely on the size of the event pool as it may contain "null" events caused by removing events
	return m_eventCount;
}

//_________________________________________________________________________________________________

DesEvent::Count EventPool::getEventCountByCtrl(bool isControllable) const
{
	return isControllable ? m_eventCtrlCount : (m_eventCount - m_eventCtrlCount);
}

//_________________________________________________________________________________________________

DesEvent::Count EventPool::getEventCountByType(EventType type) const
{
	return getEventTypeCounter(type);
}

//_________________________________________________________________________________________________

DesEventIterator* EventPool::createIterator() const
{
	return new DesEventIterator(m_eventPool);
}

//_________________________________________________________________________________________________

//returns the last used event ID 
DesEvent::ID EventPool::getLastUsedId() const
{
	return m_nextFreeID - 1;
}

//_________________________________________________________________________________________________
//The event, if it was added it becomes the property of the pool who is now responsible for destorying it
const DesEvent& EventPool::addNewEvent(const DesEvent& eventTemplate)
{
	//obtain a free ID for the new event
	DesEvent::ID newEventId = reserveNewEventID();
	
	//make a copy of the event using the new ID
	DesEvent* pEventToAdd = new DesEvent(newEventId, eventTemplate);

	addEvent(pEventToAdd);

	//return a reference to the event that was just added
	return *pEventToAdd;
}

//_________________________________________________________________________________________________

//Adds the given event as is. The ID is preserved but it is required not to be used by any existing even in the pool
void EventPool::addEvent(DesEvent* pEventToAdd)
{
	//Require: pEventToAdd cannot be null
	if (pEventToAdd == null)
		throw EX("Invalid (null) event to be added to pool")
	
	//Require: there is no event in the pool already having the name of the event to add
	if (findEvent(pEventToAdd->getName()))
		throw EX("Event with same name already exists")

	//Require: the ID of the event to not be already in use
	DesEvent::ID eventId = pEventToAdd->getId();
	if ( eventId < m_eventPool.size())
	{
		if (m_eventPool[pEventToAdd->getId()] != null)
			throw EX("A event with the same ID already exists")
	}

	//update the next available ID if necessary
	if (m_nextFreeID <=	eventId)
	{
		m_nextFreeID = eventId + 1;
	}

	//check to see if the event pool has space for another element
	if (eventId >= m_eventPool.size())
	{
		//already at the end of the pool. resize the pool
		m_eventPool.resize((m_eventPool.size()+1) * 2); //+1 ensures it works when event pool is completely empty
	}
	
	//add the event to the pool with a new ID
	m_eventPool[eventId] = pEventToAdd;

	//increase the total event count
	m_eventCount++;
	
	//increase the controllability count if the event added is controllable
	if (pEventToAdd->isControllable())
	{
		m_eventCtrlCount++;
	}

	//increase the type count for the proper type
	DesEvent::Count& typeCounter = getEventTypeCounter(pEventToAdd->getType());
	typeCounter++;	
}

//_________________________________________________________________________________________________

bool EventPool::changeEvent(const DesEvent::ID& eventId, const DesEvent& newEvent, const DesEvent** out_event /*= null*/)
{
	//require: pEventToChange to be part of this DES
	DesEvent* pEvent = getEvent(eventId);

	if (*pEvent == newEvent)
		return false; //no change is necessary, the events are the same

	//before making the change see what changed
	bool oldCtrl = pEvent->isControllable();
	bool newCtrl = newEvent.isControllable();

	EventType oldType = pEvent->getType();
	EventType newType = newEvent.getType();

	//change the event as asked by the user
	*pEvent = newEvent;

    //update the counters to reflect the change
	if (oldCtrl != newCtrl)
	{
		//the controllability of an event changed so we need to update the controllability counter
		m_eventCtrlCount = m_eventCtrlCount + (newCtrl ? 1 : -1);
	}

	if (oldType != newType)
	{
		//type event type changed
		DesEvent::Count& oldTypeCounter = getEventTypeCounter(oldType);
		DesEvent::Count& newTypeCounter = getEventTypeCounter(newType);		

		oldTypeCounter--;
		newTypeCounter++;
	}

	//return the changed event if it was required by the user
	if (out_event != null)
	{
		*out_event = pEvent;
	}

	return true;
}

//_________________________________________________________________________________________________

bool EventPool::changeEventName(const DesEvent::ID& eventId, const std::wstring& newName, const DesEvent** out_event /*= null*/)
{
	DesEvent* pEvent = getEvent(eventId);
	
	if (pEvent->getName() == newName)
		return false; //no change necessasry the names are the same
	
	pEvent->setName(newName);

	//return the changed event if it was required by the user
	if (out_event != null)
	{
		*out_event = pEvent;
	}

	return true;
}

//_________________________________________________________________________________________________

bool EventPool::changeEventAlias(const DesEvent::ID& eventId, const std::wstring& newAlias, const DesEvent** out_event /*= null*/)
{
	DesEvent* pEvent = getEvent(eventId);

	if (pEvent->getAlias() == newAlias)
		return false; //no change necessary - the aliases are the same

	pEvent->setAlias(newAlias);
	
	//return the changed event if it was required by the user
	if (out_event != null)
	{
		*out_event = pEvent;
	}

	return true;
}

//_________________________________________________________________________________________________

bool EventPool::changeEventType(const DesEvent::ID& eventId, EventType newType, const DesEvent** out_event /*= null*/)
{
	DesEvent* pEvent = getEvent(eventId);
	
	//remember the old type before changing it
	EventType oldType = pEvent->getType();

	if (oldType == newType)
		return false; // not change necessary, the type is the same

	//change the type
	pEvent->setType(newType);

	//update the counters
	DesEvent::Count& oldTypeCounter = getEventTypeCounter(oldType);
	DesEvent::Count& newTypeCounter = getEventTypeCounter(newType);		

	oldTypeCounter--;
	newTypeCounter++;

	//return the changed event if it was required by the user
	if (out_event != null)
	{
		*out_event = pEvent;
	}
	
	return true;
}

//_________________________________________________________________________________________________

bool EventPool::changeEventCtrl(const DesEvent::ID& eventId, bool isControllable, const DesEvent** out_event /*= null*/)
{
	DesEvent* pEvent = getEvent(eventId);
	
	if (pEvent->isControllable() == isControllable)
		return false; //no change is necessary, the controllabilitiy is the same

	pEvent->setControllable(isControllable);

	//update the controllability counter
	m_eventCtrlCount = m_eventCtrlCount + (isControllable ? 1 : -1);

	//return the changed event if it was required by the user
	if (out_event != null)
	{
		*out_event = pEvent;
	}

	return true;
}

//_________________________________________________________________________________________________

void EventPool::removeEvent(DesEvent* pEventToRemove)
{
	if (pEventToRemove == null)
		throw EX("Invalid (null) event. Cannot remove.")

	DesEvent* pEventRemoved = removeEvent(pEventToRemove->getId());
	assert(pEventRemoved == pEventToRemove);
	if (pEventRemoved != pEventToRemove)
	{
		//The event that was removed has the same ID with the one caller requested to be removed
		//but it was actually a different object. This should not occurr
		throw EX("Unexpected error. Two different events with the same ID were found")
	}
}

//_________________________________________________________________________________________________

DesEvent* EventPool::removeEvent(DesEvent::ID eventId)
{	
	if (eventId > m_eventPool.size())
		throw EX("Event not found. Cannot remove")

	DesEvent* pRemEvent = m_eventPool[eventId];

	//remove the event from the pool. At this point the responsibility for the event is left to the caller
	//who is now is solely responsible for freeing the memory. We cannot free the memory here as the caller
	//has a pointer to the event
	m_eventPool[eventId] = null;
	
	//Update the counters to reflect the event's removal
	m_eventCount--;
	
	//increase the controllability count if the event added is controllable
	if (pRemEvent->isControllable())
	{
		m_eventCtrlCount--;
	}

	//increase the type count for the proper type
	DesEvent::Count& typeCounter = getEventTypeCounter(pRemEvent->getType());
	typeCounter--;

	
	return pRemEvent;
}


//_________________________________________________________________________________________________
//																								 //
//									PRIVATE SECTION												 //
//_______________________________________________________________________________________________//

DesEvent::ID EventPool::reserveNewEventID()
{
	return DesEvent::ID(m_nextFreeID++);
}

//_________________________________________________________________________________________________

DesEvent::Count& EventPool::getEventTypeCounter(EventType type)
{
	switch(type)
	{
		case eDefaultEvent:
			return m_eventDefCount;

		case eRequestEvent:
			return m_eventReqCount;

		case eAnswerEvent:
			return m_eventAnsCount;

		case eLDataEvent:
			return m_eventLdCount;

		default:
			assert(false);
			throw EX("Unknown event type. Cannot return counter")
	}
}

//_________________________________________________________________________________________________

const DesEvent::Count& EventPool::getEventTypeCounter(EventType type) const
{
	switch(type)
	{
		case eDefaultEvent:
			return m_eventDefCount;

		case eRequestEvent:
			return m_eventReqCount;

		case eAnswerEvent:
			return m_eventAnsCount;

		case eLDataEvent:
			return m_eventLdCount;

		default:
			assert(false);
			throw EX("Unknown event type. Cannot return counter")
	}
}

} //end of namespace DESPot
