/*	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 "DesSubsystem.h"
#include "Des.h"
#include "DesIterator.h"
#include "SubsysDependsIterator.h"
#include "DesInterface.h"
#include "ProjectEventPool.h"
#include "SyncAlgo.h"
#include "DesIntegrityAlgo.h"

namespace DESpot
{

DesSubsystem::DesSubsystem(const std::wstring& name, ProjectEventPool& eventPool):
			m_name(name),
                        m_isValid(eIntegNotVerified),
                        m_isInterfConsist(eIConsNotVerified),
                        m_isNonBlocking(eLwNonBlockNotVerified),
                        m_isControllable(eLwCtrlNotVerified),
                        m_level(0),
                        m_syncDes(null),
                        m_interf(null),
                        m_eventPool(eventPool),
                        m_bLoadInProgress(false)
{
}

//_________________________________________________________________________________________________

DesSubsystem::DesSubsystem(const std::wstring& name, const DesInterface* interf, ProjectEventPool& eventPool):
			m_name(name),
                        m_isValid(eIntegNotVerified),
                        m_isInterfConsist(eIConsNotVerified),
                        m_isNonBlocking(eLwNonBlockNotVerified),
                        m_isControllable(eLwCtrlNotVerified),
                        m_level(0),
                        m_syncDes(null),
                        m_interf(interf),
                        m_eventPool(eventPool)
{
}

//_________________________________________________________________________________________________

//When a subsystem is destroyed it will destroy all lower-level subsystems
//as well as all its DES
DesSubsystem::~DesSubsystem(void)
{
	//Go through dependent subsystems and destroy them
	for(SubsystemDependsMapIt subsysIt = m_dependsMap.begin(); subsysIt != m_dependsMap.end(); subsysIt++)
	{
		DesSubsystem* pSubsys = subsysIt->second;
		delete pSubsys;
		pSubsys = null;
	}

	for(std::vector<DesNotificationSink*>::iterator sinkIt = m_desNotifSinks.begin(); sinkIt != m_desNotifSinks.end(); sinkIt++)
	{
		DesNotificationSink* sink = *sinkIt;
		delete sink;
	}

	if (m_syncDes)
	{
		delete m_syncDes;
		m_syncDes = null;
	}

	//Go through all the supervisor DES and destroy them
	for(DesMapIt desIt = m_supDesMap.begin(); desIt != m_supDesMap.end(); desIt++)
	{
		Des* pDes = desIt->second;
		delete pDes;
		pDes = null;
	}

	//Go through all plant DES and destroy them
	for(DesMapIt desIt = m_plantDesMap.begin(); desIt != m_plantDesMap.end(); desIt++)
	{
		Des* pDes = desIt->second;
		delete pDes;
		pDes = null;
	}
}

//_________________________________________________________________________________________________

std::wstring DesSubsystem::getName() const
{
	return m_name;
}

//_________________________________________________________________________________________________

void DesSubsystem::setName(const std::wstring& name)
{
	if (name.empty())
		throw EX("Cannot give a subsystem an empty name")

	m_name = name;
}

//_________________________________________________________________________________________________

bool DesSubsystem::isValid() const
{
	return m_isValid == eIntegYes;
}

//_________________________________________________________________________________________________

void DesSubsystem::setValid(bool setValid /*= true*/) const
{
	m_isValid = setValid ? eIntegYes : eIntegNo;
}

//_________________________________________________________________________________________________

void DesSubsystem::clearValid() const
{
	m_isValid = eIntegNotVerified;
}

//_________________________________________________________________________________________________

Integrity DesSubsystem::getValidProp() const
{
	return m_isValid;
}

//_________________________________________________________________________________________________

//Non-blocking
bool DesSubsystem::isNonBlocking() const
{
	return m_isNonBlocking == eLwNonBlockYes;
}

//_________________________________________________________________________________________________

void DesSubsystem::setNonBlocking(bool isNonBlock) const
{
	m_isNonBlocking = isNonBlock ? eLwNonBlockYes : eLwNonBlockNo;
}

//_________________________________________________________________________________________________

void DesSubsystem::clearNonBlocking() const
{
	m_isNonBlocking = eLwNonBlockNotVerified;
}

//_________________________________________________________________________________________________

LwNonBlockProp DesSubsystem::getLwNonBlockProp() const
{
	return m_isNonBlocking;
}

//_________________________________________________________________________________________________

//Controllability
bool DesSubsystem::isControllable() const
{
	return m_isControllable == eLwCtrlYes;
}

//_________________________________________________________________________________________________

void DesSubsystem::setControllable(bool isCtrl) const
{
	m_isControllable = isCtrl ? eLwCtrlYes : eLwCtrlNo;
}

//_________________________________________________________________________________________________

void DesSubsystem::clearControllable() const
{
	m_isControllable = eLwCtrlNotVerified;
}

//_________________________________________________________________________________________________

LwCtrlProp DesSubsystem::getLwCtrlProp() const
{
	return m_isControllable;
}

//_________________________________________________________________________________________________

//Interface-Consitent
bool DesSubsystem::isInterfConsist() const
{
	return m_isInterfConsist == eIConsYes;
}

//_________________________________________________________________________________________________

void DesSubsystem::setInterfConsist(bool isIConsist) const
{
	m_isInterfConsist = isIConsist ? eIConsYes : eIConsNo;
}

//_________________________________________________________________________________________________

void DesSubsystem::clearInterfConsist() const
{
	m_isInterfConsist = eIConsNotVerified;
}

//_________________________________________________________________________________________________

IConsistProp DesSubsystem::getIConsistProp() const
{
	return m_isInterfConsist;
}

//_________________________________________________________________________________________________

bool DesSubsystem::isRoot() const
{
	return (m_level == 0);
}

//_________________________________________________________________________________________________

int DesSubsystem::getLevel() const
{
	return m_level;
}

//_________________________________________________________________________________________________

void DesSubsystem::setLevel(int level)
{
	m_level = level;
}

//_________________________________________________________________________________________________

void DesSubsystem::setInterface(const DesInterface& interf)
{
	m_interf = &interf;
}

//_________________________________________________________________________________________________

const DesInterface& DesSubsystem::getInterface() const
{
	return *m_interf;
}

//_________________________________________________________________________________________________

bool DesSubsystem::implementsInterface() const
{
	return m_interf != null;
}

//_________________________________________________________________________________________________

//Returns the sync product of the all the DES components. If it needs to be
//calculated it calculates it
Des& DesSubsystem::getSyncDes() const
{
	if (m_syncDes == null)
	{
		//the sync product needs to be computed
		SyncAlgo syncAlgo(createDesIterator(), false);

		syncAlgo.runAlgo();

		m_syncDes = syncAlgo.returnResult();
	}

	return *m_syncDes;
}


//_________________________________________________________________________________________________

//Invalidates the sync product if it was computed already such that next time
//a client asks for it a new one is produced. Used primarly when the interface is modified
void DesSubsystem::clean(bool deepClean)
{
	//properties are reset when the interface changes. However change done while loading
	//the interface from a project file cannot be taken into consideration
	if (m_bLoadInProgress)
		return;

	clearValid();
	clearInterfConsist();
	clearNonBlocking();
	clearControllable();

	if (m_syncDes)
	{
		delete m_syncDes;
		m_syncDes = null;
	}

	if (deepClean)
	{
		//Go through all the supervisor DES and reset their properties
		for(DesMapIt desIt = m_supDesMap.begin(); desIt != m_supDesMap.end(); desIt++)
		{
			Des* pDes = desIt->second;
			pDes->clean();
		}

		//Go through all plant DES and reset their properties
		for(DesMapIt desIt = m_plantDesMap.begin(); desIt != m_plantDesMap.end(); desIt++)
		{
			Des* pDes = desIt->second;
			pDes->clean();
		}
	}
}

//_________________________________________________________________________________________________

//returns the number of DES in the subsystem
int DesSubsystem::getDesCount() const
{
	return getSupDesCount() + getPlantDesCount();
}

//_________________________________________________________________________________________________

//returns the number of supervisor DES in the subsystem
int DesSubsystem::getSupDesCount() const
{
	return m_supDesMap.size();
}

//_________________________________________________________________________________________________

//returns the number of plant DES in the subystem
int DesSubsystem::getPlantDesCount() const
{
	return m_plantDesMap.size();
}

//_________________________________________________________________________________________________

bool DesSubsystem::findDes(const std::wstring& desName, Des** out_pDes /*= null*/, DesLevel* out_desLevel /*= null*/)
{
	//try to find it in the supervisor map
	DesMapIt supDesIt = m_supDesMap.find(desName);
	if (supDesIt != m_supDesMap.end())
	{
		//DES was found in the supervisor map
		if (out_desLevel)
		{
			*out_desLevel = eSupervisorDes;
		}

		if (out_pDes)
		{
			*out_pDes = supDesIt->second;
		}

		return true;
	}

	//Des was not found in the supervisor map try to find it in the plant
	DesMapIt plantDesIt = m_plantDesMap.find(desName);
	if (plantDesIt != m_plantDesMap.end())
	{
		//DES was found in the plant map
		if (out_desLevel)
		{
			*out_desLevel = ePlantDes;
		}

		if (out_pDes)
		{
			*out_pDes = plantDesIt->second;
		}

		return true;
	}

	return false;
}

//_________________________________________________________________________________________________

bool DesSubsystem::findDes(const std::wstring& desName, const Des** out_pDes /*= null*/, DesLevel* out_desLevel /*= null*/) const
{
	//try to find it in the supervisor map
	DesMapCIt supDesIt = m_supDesMap.find(desName);
	if (supDesIt != m_supDesMap.end())
	{
		//DES was found in the supervisor map
		if (out_desLevel)
		{
			*out_desLevel = eSupervisorDes;
		}

		if (out_pDes)
		{
			*out_pDes = supDesIt->second;
		}

		return true;
	}

	//Des was not found in the supervisor map try to find it in the plant
	DesMapCIt plantDesIt = m_plantDesMap.find(desName);
	if (plantDesIt != m_plantDesMap.end())
	{
		//DES was found in the plant map
		if (out_desLevel)
		{
			*out_desLevel = ePlantDes;
		}

		if (out_pDes)
		{
			*out_pDes = plantDesIt->second;
		}

		return true;
	}

	return false;
}

//_________________________________________________________________________________________________

Des& DesSubsystem::getDes(const std::wstring& desName, DesLevel* out_desLevel /*= null*/)
{
	Des* pDes = null;

	if (findDes(desName, &pDes, out_desLevel) == false)
	{
		//Des wasn't found in any of the two maps
		throw EX("The subsystem has no DES with the given name")
	}

	return *pDes;
}

//_________________________________________________________________________________________________

const Des& DesSubsystem::getDes(const std::wstring& desName, DesLevel* out_desLevel /*= null*/) const
{

	const Des* pDes = null;

	if (findDes(desName, &pDes, out_desLevel) == false)
	{
		//Des wasn't found in any of the two maps
		throw EX("The subsystem has no DES with the given name")
	}

	return *pDes;
}

//_________________________________________________________________________________________________

//Create an iterator that goes through all the internal DES of this subsystem
DesSubsystem::DesIteratorPtr DesSubsystem::createDesIterator() const
{
	//create a supervisor DES iterator and a plant one
	DesMapIterator* pSupDesIt = new DesMapIterator(m_supDesMap);
	DesMapIterator* pPlantDesIt = new DesMapIterator(m_plantDesMap);

	//Compose the two iterators in one
	DesIterator* pDesIt = new DESpot::DesIterator(pSupDesIt, pPlantDesIt);

	//Return the composite iterator
	return DesIteratorPtr(pDesIt);
}

//_________________________________________________________________________________________________

//Create an iterator that goes through all the internal DES of this subsystem
DesSubsystem::DesIteratorPtr DesSubsystem::createDesIterator()
{
	//create a supervisor DES iterator and a plant one
	DesMapIterator* pSupDesIt = new DesMapIterator(m_supDesMap);
	DesMapIterator* pPlantDesIt = new DesMapIterator(m_plantDesMap);

	//Compose the two iterators in one
	DesIterator* pDesIt = new DESpot::DesIterator(pSupDesIt, pPlantDesIt);

	//Return the composite iterator
	return DesIteratorPtr(pDesIt);
}

//_________________________________________________________________________________________________
//Create an iterator that goes through either the plant or the supervisor DES
DesSubsystem::DesIteratorPtr DesSubsystem::createDesIterator(DesLevel desLevel) const
{
	DesMapIterator* pDesIt = new DesMapIterator(getDesMap(desLevel));
	return DesIteratorPtr(pDesIt);
}

//_________________________________________________________________________________________________
//Create an iterator that goes through either the plant or the supervisor DES
DesSubsystem::DesIteratorPtr DesSubsystem::createDesIterator(DesLevel desLevel)
{
	DesMapIterator* pDesIt = new DesMapIterator(getDesMap(desLevel));
	return DesIteratorPtr(pDesIt);
}

//_________________________________________________________________________________________________

void DesSubsystem::addDes(Des* pDes, DesLevel desLevel)
{
	if (pDes == null)
		throw EX("Invalid argument. Cannot add null DES.")

	DesMap& desMap = getDesMap(desLevel);

	//make sure the DES is not there already
	if (desMap.find(pDes->getName()) != desMap.end())
		throw EX("A DES with the same name is already part of the subsystem. Cannot add duplicates")


	//set the owner of this DES to be this subsystem
	pDes->setOwner(this);

	// move to after setOwner call so if call creates an
	// exception, we have not already added the DES.
	desMap[pDes->getName()] = pDes;


	//update the project event pool
	m_eventPool.addDesEvents(pDes, this);

	sinkDesEvents(pDes);

	clean(false);
}

//_________________________________________________________________________________________________

//locates the DES and changes it's name updating in the same time all internal
//references to DES's name
void DesSubsystem::changeDesName(Des& des, const std::wstring& newDesName)
{
	Des* pLocatedDes = null;
	DesMap* pDesMap = null;
	DesMapIt desMapIt;
	if (findDes(des.getName(), &pLocatedDes, &pDesMap, &desMapIt) == false)
	{
		throw EX("Cannot find a DES with the given name in this subsystem");
	}

		//The DES was found. Erase it from the map...
	if (&des != pLocatedDes)
	{
		assert(&des == pLocatedDes);
		throw EX("A different DES with the same name was found. Cannot rename DES")
	}

	//erase the DES from its map since we need to add it under the new name
	pDesMap->erase(desMapIt);

	//change the des name
	des.setName(newDesName);

	//add the des in the map under the new name
	(*pDesMap)[newDesName] = &des;

}

//_________________________________________________________________________________________________

//Event sync from when a name of a DES that is part of this subsystem is changed using
//the DES itself instead of the changeDesName(...) method. In this case the name  of 
//the DES has already been changed but the subsystem's internal maps reference it by the old name
//It is these internal maps that need to be updated
void DesSubsystem::onDesNameChanged(Des* des, const std::wstring& oldName, const std::wstring& newName)
{
	Des* pLocatedDes = null;
	DesMap* pDesMap = null;
	DesMapIt desMapIt;
	if (findDes(oldName, &pLocatedDes, &pDesMap, &desMapIt) == false)
	{
		if (findDes(newName, &pLocatedDes, &pDesMap, &desMapIt))
		{
			//the name has already been updated
			return;
		}
		else
		{
			throw EX("Cannot find a DES with the given name in this subsystem");
		}
	}

	//The DES was found. Erase it from the map...
	if (des != pLocatedDes)
	{
		assert(des == pLocatedDes);
		throw EX("A different DES with the same name was found. Cannot rename DES")
	}

	//erase the DES from its map since we need to add it under the new name
	pDesMap->erase(desMapIt);

	//NOTE, the name of the DES has already been changed so no need to do anything about the DES
	assert(des->getName() == newName);

	//add the des in the map under the new name
	(*pDesMap)[newName] = des;

}

//_________________________________________________________________________________________________

//removes the DES from the subsystem without destroying it
void DesSubsystem::removeDes(Des* pDes)
{
	//try to find a DES with this name either in the supervisor map or the plant map
	Des* pLocatedDes = null;
	DesMap* pDesMap = null;
	DesMapIt desMapIt;
	if (findDes(pDes->getName(), &pLocatedDes, &pDesMap, &desMapIt) == false)
	{
		throw EX("Des could not be found. Cannot delete")
	}

	//The DES was found. Erase it from the map...
	if (pDes != pLocatedDes)
	{
		assert(pDes == pLocatedDes);
		throw EX("A different DES with the same name was found. Cannot delete DES")
	}

	//erase the DES from its map
	pDesMap->erase(desMapIt);

	//update the project event pool
	m_eventPool.removeDesEventSet(pDes);

	removeDesSink(pDes);
	clean(false);
}

//_________________________________________________________________________________________________

void DesSubsystem::deleteDes(const std::wstring& desName)
{
	//try to find a DES with this name
	Des* pDes = null;
	DesMap* pDesMap = null;
	DesMapIt desMapIt;
	if (findDes(desName, &pDes, &pDesMap, &desMapIt) == false)
	{
		throw EX("Des could not be found. Cannot delete")
	}

	//The DES was found. Erase it from the map...
	pDesMap->erase(desMapIt);

	//update the project event pool
	m_eventPool.removeDesEventSet(pDes);

	removeDesSink(pDes);
	clean(false);

	//...and then destroy the DES itself
	delete pDes;
	pDes = null;
}

//_________________________________________________________________________________________________

DesSubsystem::DependIteratorPtr DesSubsystem::createDependsIterator() const
{
	DependIterator* dependIt = new SubsysDependsIterator(m_dependsMap);
	return DependIteratorPtr(dependIt);
}

//_________________________________________________________________________________________________

int DesSubsystem::getDependsCount() const
{
	return m_dependsMap.size();
}

//_________________________________________________________________________________________________

void DesSubsystem::addDependency(DesInterface* pInterface, DesSubsystem* pSubsystem)
{
	//Ensure the subsystem is not yet dependent on this interface
	SubsystemDependsMapIt dependIt = m_dependsMap.find(pInterface);
	if ( dependIt != m_dependsMap.end())
	{
		if (dependIt->second == pSubsystem)
		{
			throw EX("This dependency already exists. Cannot add duplicates")
		}
		else
		{
			throw EX("The subsystem already depends to another through the given interface. Two dependencies with the same interface are not allowed")
		}
	}

	//add the dependency
	m_dependsMap[pInterface] = pSubsystem;
	pSubsystem->setLevel(m_level + 1);
}

//_________________________________________________________________________________________________
//Verifies if the subsystem is depenent on the given interface
bool DesSubsystem::isDependentOn(const DesInterface& interf) const
{
	DesInterface* pInterf = const_cast<DesInterface*>(&interf);
	return (m_dependsMap.find(pInterf) != m_dependsMap.end());
}

//_________________________________________________________________________________________________
//Finds the dependency to a subsystem using the given interface and deletes it
void DesSubsystem::deleteDependency(DesInterface* pInterf)
{
	//Locate the dependency in the map and delete it. I need a non-const interface pointer
	SubsystemDependsMapIt dependIt = m_dependsMap.find(pInterf);
	if (dependIt != m_dependsMap.end())
	{
		dependIt->second->setLevel(0); //no dependency, the level goes back to zero
		m_dependsMap.erase(dependIt);
	}
}

//_________________________________________________________________________________________________
//Finds the dependency to a subsystem with the given name, deletes the dependency
void DesSubsystem::deleteDependency(DesSubsystem* pSubsys)
{
	//Locate the dependency in the map and delete it
	for(SubsystemDependsMapIt dependIt = m_dependsMap.begin(); dependIt != m_dependsMap.end(); dependIt++)
	{
		DesSubsystem* pSubsystem = dependIt->second;
		if (pSubsys == pSubsystem)
		{
			//found it
			dependIt->second->setLevel(0); //no dependency, the level goes back to zero
			m_dependsMap.erase(dependIt);
			return;
		}
	}
}

//_________________________________________________________________________________________________

//removes all the dependencies this subsystem has on others
void DesSubsystem::clearDependencies()
{
	m_dependsMap.clear();
}

//_________________________________________________________________________________________________
//returns a lowever level subsystem depenent on this through the given interface
bool DesSubsystem::getDependent(DesInterface* interf, DesSubsystem*& out_subsys)
{
	//Locate the dependency in the map and return the subsystem
	SubsystemDependsMapIt dependIt = m_dependsMap.find(interf);
	if (dependIt != m_dependsMap.end())
	{
		out_subsys = (dependIt->second);
		return true;
	}

	return false;
}

//_________________________________________________________________________________________________

void DesSubsystem::setLoadInProgress(bool loadInProgress /*= true*/)
{
	m_bLoadInProgress = loadInProgress;
}

//________________________________________________________________________________________________
//																								 //
//					IMPLEMENTATION METHODS														 //
//_______________________________________________________________________________________________//

const DesSubsystem::DesMap& DesSubsystem::getDesMap(DesLevel desLevel) const
{
	switch(desLevel)
	{
		case eSupervisorDes:
			return m_supDesMap;

		case ePlantDes:
			return m_plantDesMap;

		default:
			assert(false);
			throw EX("Unknown DES type")
	}
}

//_________________________________________________________________________________________________

DesSubsystem::DesMap& DesSubsystem::getDesMap(DesLevel desLevel)
{
	switch(desLevel)
	{
		case eSupervisorDes:
			return m_supDesMap;

		case ePlantDes:
			return m_plantDesMap;

		default:
			assert(false);
			throw EX("Unknown DES type")
	}
}

//_________________________________________________________________________________________________
//Tries to find a DES with the given name either in the map of supervisor DES or in the
//plant ones. If the DES is found, the function returns true as a result as well as the DES
//itself, the map it was found in as well as the precise iterator pointint to it. The out parameters are
//all optional
bool DesSubsystem::findDes(const std::wstring& desName,
								 Des** out_des /*= null*/,
								 DesMap** out_desMap /*= null*/,
								 DesMapIt* out_desIt /*= null*/)
{
	//Try the supervisor map
	DesMapIt supDesIt = m_supDesMap.find(desName);
	if (supDesIt != m_supDesMap.end())
	{
		//found the DES in the list of supervisor DES
		if (out_des)
		{
			*out_des = supDesIt->second;
		}

		if (out_desMap)
		{
			*out_desMap = &m_supDesMap;
		}

		if (out_desIt)
		{
			*out_desIt = supDesIt;
		}

		return true;
	}

	//The DES wasn't found in the supervisor list -> try the plant map
	DesMapIt plantDesIt = m_plantDesMap.find(desName);
	if (plantDesIt != m_plantDesMap.end())
	{
		//found the DES in the list of supervisor DES
		if (out_des)
		{
			*out_des = plantDesIt->second;
		}

		if (out_desMap)
		{
			*out_desMap = &m_plantDesMap;
		}

		if (out_desIt)
		{
			*out_desIt = plantDesIt;
		}

		return true;
	}

	//DES not found
	return false;
}

//_________________________________________________________________________________________________

void DesSubsystem::sinkDesEvents(Des* des)
{
	//create a notification listener for this des so we can tell when the des changed
	m_desNotifSinks.push_back(new DesNotificationSink(this, des));
}

//_________________________________________________________________________________________________

void DesSubsystem::removeDesSink(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);
}

}; //end of namespace DESpot
