/*	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
*/

#define _CRT_SECURE_NO_WARNINGS
#include <time.h>
#include "DesProject.h"
#include "DesInterface.h"
#include "DesSubsystem.h"
#include "DesProjectNotifications.h"
#include "ProjectIndex.h"
#include "ProjectSerializer.h"
#include "ProjectEventPool.h"
#include "ControllabilityAlgo.h"
#include "NonBlockingAlgo.h"
#include "ProjIntegrityAlgo.h"
#include "MultiNonBlockingAlgo.h"
#include "MultiCtrlAlgo.h"
#include "GedDesTrans.h"
#include "GedDesHelper.h"


namespace DESpot
{

NameValidator DesProject::m_nameValidator(new AlphaNumValidator(new WordSepValidator()));

DesProject::DesProject(const std::wstring& name, ProjectType type): 
		m_type(type), 
		m_nonBlockProp(eNonBlockNotVerified),
                m_ctrlProp(eCtrlNotVerified),
                m_pRootSubsys(null),
		m_isModified(true),
                m_nameIndex(*new ProjectIndex()),
                m_bLoadInProgress(false)
{
	setName(name);	

	m_eventPool = new ProjectEventPool(this, this);
}

//_________________________________________________________________________________________________

DesProject::~DesProject(void)
{
	try
	{
		delete &m_nameIndex;

		delete m_pRootSubsys;
		m_pRootSubsys = null;

		delete m_eventPool;
		m_eventPool = null;
	}
	catch(...) {}
}

//_________________________________________________________________________________________________

//The loading method is static because it will create the project type depending 
//on the file contents
DesProject* DesProject::load(const std::wstring& fileName)
{
	ProjectSerializer projLoader;
	DesProject* proj = projLoader.load(fileName);		
	proj->setFileName(fileName);
	proj->setModified(false);
	return proj;
}

//_________________________________________________________________________________________________

void DesProject::configureSerializer(ProjectSerializer* serializer)
{
	serializer->configure(this);
}

//_________________________________________________________________________________________________

// added by rjl - remove when bddhisc
// integrated with despot 
void DesProject::exportToBDDhisc(const std::wstring& fileName)
{

  ProjectSerializer projLoader(*this);
  projLoader.exportToBDDhisc(fileName);

}

//  remove to here - RJL

//_________________________________________________________________________________________________

// added by rjl - remove when bddsd
// integrated with despot 
void DesProject::exportToBDDsd(const std::wstring& fileName)
{

  ProjectSerializer projLoader(*this);
  projLoader.exportToBDDsd(fileName);

}

//  remove to here - RJL


//_________________________________________________________________________________________________

//Save method doesn't need to be static as it saves the contents of a particular object
//that already exists
void DesProject::save(const std::wstring& fileName)
{
	if (m_fileName.empty() || m_fileName != fileName)
		setModified(true);

	m_fileName = fileName;

	save();
}

//_________________________________________________________________________________________________

void DesProject::save()
{
	//save the project if it was modified
	if (isModified())
	{
		ProjectSerializer projLoader(*this);
		projLoader.save(m_fileName);
		setModified(false);
	}

	//make sure that all DES that have been modified and have a file name are also saved
	DesIteratorPtr desIt = createDesIterator();
	for (desIt->first(); desIt->notDone(); desIt->next())
	{
		Des& crtDes = desIt->currentItem();
		if (crtDes.isModified() && crtDes.getFileName().empty() == false)
		{
			//this des has been modified and it has a file so save it
			crtDes.save();
		}
	}
}

//_________________________________________________________________________________________________

bool DesProject::isModified() const
{
	if (m_isModified)
	{
		//the project itself was modified
		return true;
	}
	else
	{
		//the project itself was not modified but check each contained DES
		DesIteratorPtr desIt = createDesIterator();
		for (desIt->first(); desIt->notDone(); desIt->next())
		{
			if (desIt->currentItem().isModified())
			{
				//this des has been modified
				return true;
			}
		}

		//no DES was modified either so the project is not modified
		return false;
	}
}

//_________________________________________________________________________________________________

void DesProject::setModified(bool isModified /*= true*/) 
{ 
	m_isModified = isModified; 
}

//_________________________________________________________________________________________________

//The name of the project
std::wstring DesProject::getName() const
{
	return m_name;
}

//_________________________________________________________________________________________________

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

	if (m_nameValidator.validate(name))
	{
		std::wstring oldName = m_name;
		m_name = name;
		
		if (m_name != oldName)
		{
			onProjectNameChanged(m_name, oldName);
		}
	}
	else
	{
		std::wstring message = L"Invalid project name (";
		message += name;
		message += L"). Use an alpha-numeric string (a-z;A-Z;0-9;.-_)";
		throw message;
	}
}

//_________________________________________________________________________________________________

bool DesProject::isNew() const
{
	return m_fileName.empty();
}

//_________________________________________________________________________________________________

std::wstring DesProject::getFileName() const
{
	return m_fileName;
}

//_________________________________________________________________________________________________

void DesProject::setFileName(const std::wstring& fileName)
{
	m_fileName = fileName;
}

//_________________________________________________________________________________________________

//Return the current date and time
std::wstring DesProject::now() const
{
    // Get time in seconds
    time_t szClock;
    time(&szClock );

    // Convert time to struct tm form 
	struct tm* newTime = localtime(&szClock);

    // Return local time as a string.
	wchar_t dateTime[128];
	wcsftime(dateTime, 128, L"%a %b %d %H:%M:%S %Y", newTime);

	return dateTime;
}

//_________________________________________________________________________________________________
void DesProject::setLoadInProgress(bool loadInProgress)
{
	m_bLoadInProgress = loadInProgress;
}

//_________________________________________________________________________________________________
//Used by the serializer to get access to the event pool
ProjectEventPool& DesProject::getEventPool()
{
	return *m_eventPool;
}

//_________________________________________________________________________________________________

//The type of project (can only be read. It is set by the actual type of object created
ProjectType DesProject::getType() const
{
	return m_type;
}

//_________________________________________________________________________________________________
//Non-blocking

bool DesProject::isNonBlocking() const
{
	return m_nonBlockProp == eNonBlockYes;
}

//_________________________________________________________________________________________________

NonBlockingProp DesProject::getNonBlockingProp() const
{
	return m_nonBlockProp;
}

//_________________________________________________________________________________________________

void DesProject::setNonBlockingProp(int nonBlockProp)
{
	m_nonBlockProp = NonBlockingProp(nonBlockProp);
}

//_________________________________________________________________________________________________

std::wstring DesProject::getNonBlockingStamp() const
{
	return m_nonBlockStamp;
}


//_________________________________________________________________________________________________

bool DesProject::checkNonBlocking(MultiNonBlockingAlgo& nonBlockAlgo)
{
	//getFlatSystemDes().checkNonBlocking(nonBlockAlgo);


	bool tmp = getIntegrity() == eIntegYes ? true : false;
    nonBlockAlgo.setIntegrity(tmp);

	bool isNonblocking = nonBlockAlgo.runAlgo();
	
	//set the controllability property
	NonBlockingProp newNonBlockProp = isNonblocking ? eNonBlockYes : eNonBlockNo;
	
	if (m_nonBlockProp != newNonBlockProp)
	{
		m_nonBlockProp = newNonBlockProp;

		//set the stamp
		m_nonBlockStamp = now();

		onNonBlockStatusChanged();
	}

	return isNonblocking;
}

//_________________________________________________________________________________________________

void DesProject::setNonBlockingStamp(const std::wstring& nonBlockStamp)
{
	m_nonBlockStamp = nonBlockStamp;
}

//_________________________________________________________________________________________________
//Controllability

bool DesProject::isControllable() const
{
	return m_ctrlProp == eCtrlYes;
}

//_________________________________________________________________________________________________

ControllableProp DesProject::getControllableProp() const
{
	return m_ctrlProp;
}

//_________________________________________________________________________________________________

void DesProject::setControllableProp(int ctrlProp)
{
	m_ctrlProp = ControllableProp(ctrlProp);
}

//_________________________________________________________________________________________________

std::wstring DesProject::getCtrlStamp() const
{
	return m_ctrlStamp;
}

//_________________________________________________________________________________________________
void DesProject::setCtrlStamp(const std::wstring& ctrlStamp)
{
	m_ctrlStamp = ctrlStamp;
}

//_________________________________________________________________________________________________

bool DesProject::checkControllable(MultiCtrlAlgo& ctrlAlgo)
{


    bool tmp = getIntegrity() == eIntegYes ? true : false;
    ctrlAlgo.setIntegrity(tmp);
    bool isControllble = ctrlAlgo.runAlgo() ;
	ControllableProp newCtrlProp = ctrlAlgo.runAlgo() ? eCtrlYes : eCtrlNo;
	
	if (m_ctrlProp != newCtrlProp)
	{
		m_ctrlProp = newCtrlProp;

		//set the stamp
		m_ctrlStamp = now();

		onCtrlStatusChanged();
	}

	return isControllble;
}

//_________________________________________________________________________________________________

bool DesProject::isValid() const
{
	return m_integStatus == eIntegYes;
}

//_________________________________________________________________________________________________

//Project's integrity status
Integrity DesProject::getIntegrity() const
{
	return m_integStatus;
}

//_________________________________________________________________________________________________
//Project integrity stamp
std::wstring DesProject::getIntegrityStamp() const
{
	return m_integStamp;
}

//_________________________________________________________________________________________________

bool DesProject::checkIntegrity(ProjIntegrityAlgo& integAlgo)
{
	integAlgo.setInputProject(this);
	integAlgo.runAlgo();
	
	//set the controllability property
	Integrity newIntegStatus = integAlgo.isValid() ? eIntegYes : eIntegNo;

	if (m_integStatus != newIntegStatus)
	{
		m_integStatus = newIntegStatus;

		//set the stamp
		m_integStamp = now();

		onIntegStatusChanged();
	}

	return integAlgo.isValid();
}

//_________________________________________________________________________________________________

void DesProject::setIntegrity(int integStatus)
{
	m_integStatus = Integrity(integStatus);
}

//_________________________________________________________________________________________________

void DesProject::setIntegrityStamp(const std::wstring& integStamp)
{
	m_integStamp = integStamp;
}

//_________________________________________________________________________________________________
//Resets all properties and all incremental data calculated for them
//so that they can be rechecked from scratch
void DesProject::clean(bool deepClean)
{
	m_integStatus = eIntegNotVerified;
	m_integStamp = L"";
	onIntegStatusChanged();

	m_nonBlockProp = eNonBlockNotVerified;
	m_nonBlockStamp = L"";
	onNonBlockStatusChanged();

	m_ctrlProp = eCtrlNotVerified;
	m_ctrlStamp = L"";
	onCtrlStatusChanged();

	if (deepClean)
	{
		m_pRootSubsys->clean();
	}

	//notify clients that the project has been cleaned
	onProjectCleaned();

	/* RJL:  I don't think this save should be here.
                 It wasd causing project saves without the user
                 intending  and not giving user option to say no.
                 SHould only get save from menu, or when closing a
                 modified project.
	
		save();
	*/
}

//_________________________________________________________________________________________________

DesProject::EventIteratorPtr DesProject::createProjEventIterator() const
{
	return DesProject::EventIteratorPtr(m_eventPool->createIterator());
}

//_________________________________________________________________________________________________

void DesProject::changeEvent(std::wstring eventName, const DesEvent& newEvent)
{
	ProjectEvent* projectEvent = null;
	if (m_eventPool->findEvent(eventName, projectEvent) == false)
	{
		throw EX("Cannot find project event with the given name in the project event pool. Cannot change project event");
	}

	//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
	m_eventPool->changeEvent(projectEvent, newEvent);


	// need to update the GedDesTrans labels for this.
	//  Going to do blunt force and call this for every transition
	//  in every des.
    DesSubsystem::DesIteratorPtr PdesIt = createDesIterator();

    for(PdesIt->first(); PdesIt->notDone(); PdesIt->next())
      {
	Des& des = PdesIt->currentItem();
	GedDesHelper desHelper(&des);

	//update transitions excluding global self-loops
	Des::TransIteratorPtr transIt = des.createTransIterator(true);
	for(transIt->first(); transIt->isDone() == false; transIt->next())
	{
		const DesTransition& trans = transIt->currentItem();
		GedDesTrans* pGedTrans = const_cast<GedDesTrans*> (desHelper.gedTrans(&trans));

		if (pGedTrans)
		{
		  pGedTrans->updateLabel();
		}
	}
      }
 
}

//_________________________________________________________________________________________________

DesSubsystem& DesProject::getRootSubsys() const
{
	return *m_pRootSubsys;
}

//_________________________________________________________________________________________________

bool DesProject::hasRootSubsys() const
{
	return m_pRootSubsys != null;
}

//_________________________________________________________________________________________________

//returns the number of supervisor DES in the project
int DesProject::getSupDesCount()
{
	return m_pRootSubsys->getSupDesCount();
}

//_________________________________________________________________________________________________

//returns the number of plant DES in the project
int DesProject::getPlantDesCount()
{
	return m_pRootSubsys->getPlantDesCount();
}

//_________________________________________________________________________________________________

//Create an iterator that goes through all the internal Des of this subsystem
DesProject::DesIteratorPtr DesProject::createDesIterator() const
{
	return m_pRootSubsys->createDesIterator();
}

//_________________________________________________________________________________________________

//Create an iterator that goes through all the internal Des of this subsystem
DesProject::DesIteratorPtr DesProject::createDesIterator()
{
	return m_pRootSubsys->createDesIterator();
}

//_________________________________________________________________________________________________

//Create an iterator that goes through either the plant or the supervisor Des
DesProject::DesIteratorPtr DesProject::createDesIterator(DesLevel desLevel) const
{
	return m_pRootSubsys->createDesIterator(desLevel);
}

//_________________________________________________________________________________________________

//Create an iterator that goes through either the plant or the supervisor Des
DesProject::DesIteratorPtr DesProject::createDesIterator(DesLevel desLevel)
{
	return m_pRootSubsys->createDesIterator(desLevel);
}

//_________________________________________________________________________________________________

//Returns the flat system DES: a sync product of all the DES in the project
//If the flat system DES needs to be calculated it will be calculated and cached
Des& DesProject::getFlatSystemDes() const
{
	return m_pRootSubsys->getSyncDes();
}

//_________________________________________________________________________________________________

void DesProject::revertDesToFile(Des& des)
{
	std::wstring desFileName = des.getFileName();
	if (desFileName.empty())
	{
		throw EX("DES does not have an associated file. Cannot revert to file.");
	}

	//reload the DES from file to get all states, events, transitions and properties
	Des* desFromFile = null;
	try
	{
		desFromFile = new Des();
		desFromFile->load(desFileName);
	}
	catch(...)
	{
		if (desFromFile != null)
		{
			delete desFromFile;
			desFromFile = null;
			throw;
		}
	}

	//first attempt to change the name of the file if the name of the DES in the file is different
	if (des.getName() != desFromFile->getName())
	{
		try
		{
			changeDesName(des, desFromFile->getName());
		}
		catch(...)
		{
			//failed to change the name, maybe a duplicate name exists
			//since the name can be changed later, keep the existing name even if it doesn't match the file
			desFromFile->setName(des.getName());
		}
	}

	//now that the name has been changed, revert the rest of the information
	des.revertToDes(*desFromFile);

	//now that we are done delete the temporary des
	delete desFromFile;
	desFromFile = null;

}

//_________________________________________________________________________________________________

bool DesProject::findName(const std::wstring& name) const
{
	return m_nameIndex.findName(name);
}

//_________________________________________________________________________________________________

bool DesProject::findName(const std::wstring& name, ProjectCompType& out_projCompType) const
{
	ProjectIndex::TypedComp typedComp;
	
	if (m_nameIndex.findName(name, &typedComp))
	{
		out_projCompType = ProjectCompType(typedComp.first);
		return true;
	}
	else
	{
		return false;
	}
}

//_________________________________________________________________________________________________

//The return is a cookied that must be used when unsubscribing
unsigned int DesProject::subscribe(DesProjectNotifications* pListner)
{
	m_projListeners.push_back(pListner);
	return m_projListeners.size() - 1;
}

//_________________________________________________________________________________________________

void DesProject::unsubscribe(unsigned int cookie)
{
	if (cookie >= m_projListeners.size())
		throw EX("Invalid cookied. Cannot unsubscribe")

	m_projListeners[cookie] = null;
}

//_________________________________________________________________________________________________

void DesProject::onProjectNameChanged(const std::wstring& newName, const std::wstring& oldName)
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onProjectNameChanged(newName, oldName);
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onCtrlStatusChanged()
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onCtrlStatusChanged();
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onIntegStatusChanged()
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onIntegStatusChanged();
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onNonBlockStatusChanged()
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onNonBlockStatusChanged();
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onDesAdded(const Des& addedDes, DesLevel desLevel)
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();
	clean(false);

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onDesAdded(addedDes, desLevel);
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onRemovingDes(const Des& des, DesLevel desLevel)
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onRemovingDes(des, desLevel);
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onDesRemoved(const std::wstring& desName, DesLevel desLevel)
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();
	clean(false);

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onDesRemoved(desName, desLevel);
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onDesNameChanged(const Des& des, const std::wstring& oldName)
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();
	clean(false);

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onDesNameChanged(des, oldName);
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onProjectEventPoolChanged()
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();
	clean(false);

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onProjectEventPoolChanged();
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

//_________________________________________________________________________________________________

void DesProject::onProjectCleaned()
{
	//Notifications are not sent while the project is being loaded. This important because in
	//some cases project properties are being reset when a change is done to the project which
	//is not the case while the project is being loaded
	if (m_bLoadInProgress) 
		return;

	setModified();

	for(unsigned int iListner = 0; iListner < m_projListeners.size(); iListner++)
	{
		if (m_projListeners[iListner] != null)
		{
			try
			{
				DesProjectNotifications* pListner = m_projListeners[iListner];
				if (pListner)
				{
					pListner->onProjectCleaned();
				}
			}
			catch(...){} //not interested in exceptions thrown by listners
		}
	}
}

}; //end of namespace DESpot
