/*	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 "ProjectEventPool.h"
#include "DesProject.h"
#include "InterfaceEvent.h"
#include "SubsystemEvent.h"
#include "DesHierProject.h"

namespace DESpot
{

ProjectEventPool::ProjectEventPool(DesProject* proj, EventPoolAccess* projAccess) : m_project(proj), m_projAccess(projAccess), m_processNotifications(true)
{
}

//_________________________________________________________________________________________________

ProjectEventPool::~ProjectEventPool(void)
{
	//delete all des notification sinks
	std::vector<ProjectEventPool::DesNotificationSink*>::iterator sinkIt;
	for(sinkIt = m_desNotifSinks.begin(); sinkIt != m_desNotifSinks.end(); sinkIt++)
	{
		DesNotificationSink* sink = *sinkIt;
		delete sink;
	}
}

//_________________________________________________________________________________________________

//Returns a event that must exist in the pool. Throws exception if the eventId is not found in the pool
const ProjectEvent& ProjectEventPool::getEvent(const std::wstring& eventName) const
{
	EventMapCIt eventIt = m_eventMap.find(eventName);
	if (eventIt == m_eventMap.end())
	{
		throw EX("There is no event with the given name in the project event pool.");
	}

	return *eventIt->second;
}

//_________________________________________________________________________________________________

//Returns a event that must exist in the pool. Throws exception if the eventId is not found in the pool
ProjectEvent& ProjectEventPool::getEvent(const std::wstring& eventName) 
{
	EventMapIt eventIt = m_eventMap.find(eventName);
	if (eventIt == m_eventMap.end())
	{
		throw EX("There is no event with the given name in the project event pool.");
	}

	return *eventIt->second;
}

//_________________________________________________________________________________________________

//Searches for a project event with the given name
bool ProjectEventPool::findEvent(const std::wstring& eventName, ProjectEvent*& out_projEvent)
{
	EventMapIt eventIt = m_eventMap.find(eventName);
	if (eventIt != m_eventMap.end())
	{
		out_projEvent = eventIt->second;
		return true;
	}

	return false;
}

//_________________________________________________________________________________________________

//Searches for a DES event in the project event pool
bool ProjectEventPool::findEvent(const DesEvent& desEvent, ProjectEvent*& out_projEvent)
{
	for(EventMapIt eventIt = m_eventMap.begin(); eventIt != m_eventMap.end(); eventIt++)
	{
		ProjectEvent* projEvent = eventIt->second;
		if (projEvent->findSourceEvent(&desEvent))
		{
			out_projEvent = projEvent;
			return true;
		}
	}

	return false;
}

//_________________________________________________________________________________________________

//Adds a new event to the pool with the properties of the event template. A new ID will be assigned to the new
//event (not that one in the template even if has any)
void ProjectEventPool::addEvent(ProjectEvent* projEvent)
{
	EventMapIt eventIt = m_eventMap.find(projEvent->getName());
	if (eventIt != m_eventMap.end())
	{
		throw EX("An event with the same name already exists. Cannot add duplicate project events");
	}
	
	m_eventMap[projEvent->getName()] = projEvent;
}

//_________________________________________________________________________________________________

//changes the type of project event that is in the pool form subsystem event to interface event
//or vice-versa. The new type is determined based on the input project event (the invalido one)
//all teh sources of the input event are going to be tranfered to the new event and the input
//event will be removed from the pool. The method returns the newly created event
ProjectEvent* ProjectEventPool::changeProjectEventType(ProjectEvent* invalidProjEvent)
{
	if (invalidProjEvent->isProjectEventTypeValid())
	{
		//nothing to do the given event type is valid already
		assert(false);
		return invalidProjEvent;
	}

	if (m_eventMap.find(invalidProjEvent->getName()) == m_eventMap.end())
	{
		throw EX("Cannot find the given event in the pool. Unexpected behaviour.")
	}

	//determine the new owner of the event by going through it's sources. This is determined
	//by the first source for subsystem events or is determined by the first source that comes
	//from an interface DES for interface events
	ProjectEvent*  newCorrectEvent = null;		
	ProjectEvent::SourceIteratorPtr srcIt = invalidProjEvent->createSourceIterator();

	//determine the correct type from the first source
	srcIt->first(); 
	EventType correctType = srcIt->currentItem().event->getType();
	
	//depending on the correct type, find a suitable owner for the event and create the new event
	if (correctType == eDefaultEvent)
	{
		//the first source event is a subsystem event thus the project event should also
		//be a subsystem
		newCorrectEvent = createProjectEvent(*srcIt->currentItem().event, *(srcIt->currentItem().des->getSubsystemOwner()));
	}
	else //correct type is an interface event
	{
		InterfaceEvent* newInterfEvent = new InterfaceEvent();
		newInterfEvent->setType(correctType);

		//now find the first interface source event that is owned by an interface to try and find
		//the owner
		for(srcIt->first(); srcIt->notDone() && (newCorrectEvent == null); srcIt->next())
		{
			const ProjectEvent::Source& projEventSrc = srcIt->currentItem();
			
			//The type of the source is an interface event. Howver it might be comming from a subsystem
			//DES that is using it or from the interface DES that defines
			if (projEventSrc.des->getType() == eInterfaceDes)
			{
				//we got the owner
				newInterfEvent->setOwner(*(projEventSrc.des->getInterfaceOwner()));				
			}
		}

		//NOTE: at this point the owner might not be set if we couldn't find above; the user might have changed the type
		//of event before the actual interface event was created in an interface DES
		newCorrectEvent = newInterfEvent;
	}

	//now we have the new event so we need to move all the sources from the invalid event to the corrected event
	for(srcIt->first(); srcIt->notDone(); srcIt->next())
	{
		const ProjectEvent::Source& projEventSrc = srcIt->currentItem();
		
		invalidProjEvent->removeSourceEvent(projEventSrc.event);
		newCorrectEvent->addSourceEvent(projEventSrc.event, projEventSrc.des);
	}

	//finally get rid of the invalid project event which is now empty and add the new corrected event
	removeEvent(invalidProjEvent->getName());	
	addEvent(newCorrectEvent);

	return newCorrectEvent;
}

//_________________________________________________________________________________________________

void ProjectEventPool::removeEvent(const std::wstring& eventName)
{
	EventMapIt eventIt = m_eventMap.find(eventName);
	if (eventIt != m_eventMap.end())
	{
		ProjectEvent* event = eventIt->second;
		m_eventMap.erase(eventIt);
		delete event;
		event = null;
	}
}

//_________________________________________________________________________________________________

//changes the event name and properties. If the type of event has changed then the project event
//object has to be re-created
void ProjectEventPool::changeEvent(ProjectEvent* projectEvent, const DesEvent& newEvent)
{	
	m_processNotifications = false;

	try
	{
		//the user has changed the name of the event itself so it will have to be removed from the event map and re-added
		//first attempt to change the name as this may fail if the new name is already in use for example
		changeEventName(projectEvent->getName(), newEvent.getName());

		//change the rest of the properties now that the names match
		projectEvent->changeEvent(newEvent);

		//now that sinks have been changed, check if the type is in sync with the event sources. If the type has changed
		//the project event might have to be switched between SubsystemEvent and InterfaceEvent object
		if (projectEvent->isProjectEventTypeValid() == false)
		{
			//the type of event changed: if it was subsystem now it is interface, if it is an interface 
			//now it is a subsystem event
			changeProjectEventType(projectEvent);
		}

		//update clients about the changes that were just done
		m_processNotifications = true;

		notifyPoolUpdated();
	}
	catch(...)
	{
		//make sure the notifcations are turned back on
		m_processNotifications = true;

		//re-throw the exception further
		throw;
	}
}

//_________________________________________________________________________________________________

//Changes the name of an existing event
void ProjectEventPool::changeEventName(const std::wstring& oldName, const std::wstring& newName)
{
	//try the easy way out
	if (oldName == newName) return;

	//check to make sure the new name can be used
	EventMapIt eventIt = m_eventMap.find(newName);
	if (eventIt != m_eventMap.end())
	{
		throw EX("An event with the same name already exists. Cannot change the name of the project event");
	}

	//find the event using the old (existing name)
	eventIt = m_eventMap.find(oldName);
	if (eventIt != m_eventMap.end())
	{
		ProjectEvent* projEvent = eventIt->second;
		m_eventMap.erase(eventIt);
		
		//add the event to the map using the new name
		m_eventMap[newName] = projEvent; 

		projEvent->changeEventName(newName);

	}
}

//_________________________________________________________________________________________________

//Adds all the events of the given DES to the event pool. Used by the project when a DES is
//being added to the project
void ProjectEventPool::addDesEvents(Des* des, DesSubsystem* desOwner)
{	
	Des::EventIteratorPtr eventIt = des->createEventIterator();
	for(eventIt->first(); eventIt->notDone(); eventIt->next())
	{
		const DesEvent& desEvent = eventIt->currentItem();
		
		ProjectEvent* projEvent = null;
		if (findEvent(desEvent.getName(), projEvent) == false)
		{
			projEvent = createProjectEvent(desEvent, *desOwner);
			m_eventMap[desEvent.getName()] = projEvent;
		}

		projEvent->addSourceEvent(&desEvent, des);
	}

	//create a notification listener for this des so we can tell when an event is added, changed or removed
	m_desNotifSinks.push_back(new DesNotificationSink(*this, m_processNotifications, des, m_project));

	notifyPoolUpdated();
}

//_________________________________________________________________________________________________

//Adds all the events of the given DES to the event pool. Used by the project when a DES is
//being added to the project
void ProjectEventPool::addDesEvents(Des* des, DesInterface* desOwner)
{	
	Des::EventIteratorPtr eventIt = des->createEventIterator();
	for(eventIt->first(); eventIt->notDone(); eventIt->next())
	{
		const DesEvent& desEvent = eventIt->currentItem();
		
		ProjectEvent* projEvent = null;
		if (findEvent(desEvent.getName(), projEvent) == false)
		{
			projEvent = createProjectEvent(desEvent, *desOwner);
			m_eventMap[desEvent.getName()] = projEvent;
		}
		else
		{
			//we have found a project event matching the current DES event. Since interface
			//events are shared by subsystems the project event might not have an owner yet
			//If that is the case set it now as it means we've found its owner
			InterfaceEvent* interfEvent = dynamic_cast<InterfaceEvent*>(projEvent);
			if (interfEvent && interfEvent->hasOwner() == false)
			{
				interfEvent->setOwner(*desOwner);
			}
		}

		projEvent->addSourceEvent(&desEvent, des);
	}

	//create a notification listener for this des so we can tell when an event is added, changed or removed
	m_desNotifSinks.push_back(new DesNotificationSink(*this, m_processNotifications, des, m_project));

	notifyPoolUpdated();
}

//_________________________________________________________________________________________________

//Removes all the events of the given DES to the event pool. Used by the project when a DES is
//being removed from the project
void ProjectEventPool::removeDesEventSet(Des* des)
{
	Des::EventIteratorPtr eventIt = des->createEventIterator();
	for(eventIt->first(); eventIt->notDone(); eventIt->next())
	{
		const DesEvent& desEvent = eventIt->currentItem();
		removeDesEvent(&desEvent);		
	}

	removeNotificationSink(des);

	notifyPoolUpdated();
}

//_________________________________________________________________________________________________

void ProjectEventPool::removeDesEvent(const DesEvent* desEvent)
{
	EventMapIt eventIt = m_eventMap.find(desEvent->getName());
	if (eventIt == m_eventMap.end())
	{
		throw EX("There is no event with the given name in the project event pool.");
	}

	ProjectEvent& projEvent = *eventIt->second;

	projEvent.removeSourceEvent(desEvent);
	
	//make sure the project event is not empty
	if (projEvent.sourceCount() == 0)
	{
		removeEvent(projEvent.getName()); //projEvent will not be valid after this call
	}
}

//_________________________________________________________________________________________________

ProjectEventPool::EventIteratorPtr ProjectEventPool::createIterator() const
{
	return EventIteratorPtr(new ProjectEventIterator(m_eventMap));
}

//_________________________________________________________________________________________________

//Factory methods that can create an event matching teh template event and the owner
ProjectEvent* ProjectEventPool::createProjectEvent(const DesEvent& templateEvent, const DesSubsystem& owner)
{
	ProjectEvent* projEvent = null;

	switch(templateEvent.getType())
	{
		case eDefaultEvent:
			projEvent = new SubsystemEvent(owner);
			if (m_project->getType() == eFlatProject)
			{
				projEvent->setType(eDefaultEvent);
			}
			else
			{
				projEvent->setType(owner.isRoot() ?	 eHighLevelEvent : eLowLevelEvent);
			}
			break;

		case eHighLevelEvent:
		case eLowLevelEvent:
			projEvent = new SubsystemEvent(owner);
			projEvent->setType(templateEvent.getType());
			break;

		
		case eAnswerEvent:
		case eRequestEvent:
		case eLDataEvent:
			projEvent = new InterfaceEvent(); //we don't have an owner. The owner will be establish by the Interface DES
											  //that defined this event. Interface events are shared in low and high level subsystems
											  //but they are not owned by them
			projEvent->setType(templateEvent.getType());
			break;

		default:
			assert(false);
			throw EX("Invalid event type. Cannot create project event");
	}

	return projEvent;
}

//_________________________________________________________________________________________________

ProjectEvent* ProjectEventPool::createProjectEvent(const DesEvent& templateEvent, const DesInterface& owner)
{
	ProjectEvent* projEvent = null;

	switch(templateEvent.getType())
	{
		case eAnswerEvent:
		case eRequestEvent:
		case eLDataEvent:
			projEvent = new InterfaceEvent(owner); 
			projEvent->setType(templateEvent.getType());
			break;

		default:
			assert(false);
			throw EX("Invalid event type. Cannot create project event");
	}

	return projEvent;
}


//_________________________________________________________________________________________________

void ProjectEventPool::notifyPoolUpdated()
{
	m_projAccess->onProjectEventPoolChanged();
}

//_________________________________________________________________________________________________

void ProjectEventPool::removeNotificationSink(Des* des)
{
	for(std::vector<DesNotificationSink*>::iterator sinkIt = m_desNotifSinks.begin(); sinkIt != m_desNotifSinks.end(); sinkIt++)
	{
		DesNotificationSink* sink = *sinkIt;
		if (des == sink->des())
		{
			m_desNotifSinks.erase(sinkIt);
			
			sink->unsubscribe();
			delete sink;
			
			return;
		}
	}

	assert(false);
}

//_________________________________________________________________________________________________

void ProjectEventPool::DesNotificationSink::onEventAdded(const DesEvent& addedEvent)
{
	ProjectEvent* projEvent = null;
	if (m_eventPool.findEvent(addedEvent.getName(), projEvent))
	{
		projEvent->addSourceEvent(&addedEvent, m_des);
	}
	else
	{
		projEvent = createProjectEvent(addedEvent);
		m_eventPool.addEvent(projEvent);
	}

	m_eventPool.notifyPoolUpdated();
}

//_________________________________________________________________________________________________

void ProjectEventPool::DesNotificationSink::onEventChanged(const DesEvent& changedEvent)
{
	if (m_poolProcessNotifications == false) return;

	//The DES event change may have invalidated it as the source of whatever ProjectEvent it is part of.
	//If the name has changed then it can become a new ProjectEvent or a source for a different
	//event. Note howerver that we cannot cancel the event change. Thus we have to somehow add it
	//to the project event pool even it there is a conflict.

	//Find the project event the "changed event" is a source of
	ProjectEvent* projEvent = null;
	if (m_eventPool.findEvent(changedEvent, projEvent))
	{
		if (projEvent->getName() != changedEvent.getName())
		{			
			//It looks like the name has changed thus is cannot serve as source for the same project event
			projEvent->removeSourceEvent(&changedEvent);

			//make sure the project event is not empty
			if (projEvent->sourceCount() == 0)
			{
				m_eventPool.removeEvent(projEvent->getName());
				projEvent = null; //this event has just been deleted
			}

			//see if there is any project event with the new name  or if we need to create a new project event
			ProjectEvent* projEventMatch = null;
			if (m_eventPool.findEvent(changedEvent.getName(), projEventMatch))
			{
				//found a match; add the changed event as a soruce of the matched event 
				projEventMatch->addSourceEvent(&changedEvent, m_des);
			}
			else
			{
				//there is no project event matching the changed name so we just need to create and add one
				ProjectEvent* newProjEvent = createProjectEvent(changedEvent);
				m_eventPool.addEvent(newProjEvent);
			}
		}
		else
		{
			//check if the type has changed: if the owner of the first source has a different type than the event owner
			//the type of project event needs to change too: from subsystem to interface or vice-versa. However this
			//needs to be done only if the project event was not removed at the previous step (note that users may change
			//both names and types in the same operation
			if (projEvent->isProjectEventTypeValid() == false)
			{
				//the type of event changed: if it was subsystem now it is interface, if it is an interface 
				//now it is a subsystem event
				projEvent = m_eventPool.changeProjectEventType(projEvent);
			}

			//check to see if the alias has changed
			if (projEvent->isAliasValid() == false)
			{
				projEvent->updateAliasFromSources();
			}

			//check to see if hte controllability has changed
			if (projEvent->isCtrlValid() == false)
			{
				projEvent->updateCtrlFromSources();
			}
		}
	}
	else
	{
		//can't find event? All DES events should be part of the pool
		assert(false);
	}

	m_eventPool.notifyPoolUpdated();
}

//_________________________________________________________________________________________________

void ProjectEventPool::DesNotificationSink::onRemovingEvent(const DesEvent& event)
{
	m_eventPool.removeDesEvent(&event);
	m_eventPool.notifyPoolUpdated();
}

//_________________________________________________________________________________________________

ProjectEvent* ProjectEventPool::DesNotificationSink::createProjectEvent(const DesEvent& desEvent)
{
	//we have to figure out the owner for the DES that sent out this notification
	//once we have to owner (Interface or Subsystem) we can create the appropriate 
	//project event
	ProjectEvent* projEvent = null;
	switch(m_project->getType())
	{
		case eFlatProject:
			projEvent = m_eventPool.createProjectEvent(desEvent, m_project->getRootSubsys());					
			break;

		case eHierProject:
		{
			DesHierProject* hierProj = dynamic_cast<DesHierProject*>(m_project);
			switch(m_des->getType())
			{
				case eSubsystemDes:
					projEvent = m_eventPool.createProjectEvent(desEvent, hierProj->getParentSubsystem(*m_des));	
					break;

				case eInterfaceDes:
					projEvent = m_eventPool.createProjectEvent(desEvent, hierProj->getParentInterface(*m_des));
					break;

				default:
					assert(false);
					throw EX("Invalid DES type. Cannot create project event for this DES event");						
			}
			break;
		}

		default:
			assert(false);
			throw EX("Unknown project type. Cannot create project event for this DES event");						
			
	}

	projEvent->addSourceEvent(&desEvent, m_des);
	return projEvent;
}

} // end of namespace DESPot
