/*	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 <QMenu>
#include <QMenuBar>
#include <QToolBar>
#include <QIcon>
#include <QStatusBar>
#include <QMessageBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QInputDialog>

#include "CommonDefinitions.h"
#include "ProjectEditor.h"
#include "MainForm.h"
#include "EventPoolForm.h"
#include "ProjectWorkspace.h"
#include "ProjStructureUiPart.h"
#include "DesEditor.h"
#include "NameValidator.h"
#include "MeetAlgo.h"
#include "SyncAlgo.h"
#include "CtrlStdAlgo.h"
#include "OutputUiPart.h"
#include "ProjectPrinter.h"
#include "ProjIntegrityAlgo.h"
#include "WaitCursor.h"
#include "NonBlockingAlgo.h"
#include "MultiCtrlAlgo.h"
#include "MultiNonBlockingAlgo.h"

#include "GedDesEditor.h"
#include "SimConfig.h"

namespace DESpot
{

ProjectEditor::ProjectEditor(MainForm* pMainForm, FormType formType) :
			DespotForm(pMainForm, this, pMainForm, formType),
			m_pWorkspace(null), m_pCrtDes(null)
{
	//set form attributes
	setAttribute(Qt::WA_DeleteOnClose);

	m_pEventPoolForm = null;
	m_pProject = null;
}

//_________________________________________________________________________________________________

ProjectEditor::ProjectEditor(DesProject* project, MainForm* pMainForm, FormType formType) :
		DespotForm(pMainForm, this, pMainForm, formType),
		m_pProject(project), m_pWorkspace(null), m_pCrtDes(null)
{
	//set form attributes
	setAttribute(Qt::WA_DeleteOnClose);

	m_pEventPoolForm = null;
}
//_________________________________________________________________________________________________

ProjectEditor::~ProjectEditor(void)
{
	if (m_pWorkspace)
	{
		delete m_pWorkspace;
		m_pWorkspace = null;
	}
}
//_________________________________________________________________________________________________

DesProject* ProjectEditor::project()
{
	return m_pProject;
}

//_________________________________________________________________________________________________

QString ProjectEditor::getDocName()
{
	return QString::fromStdWString(m_pProject->getName());
}

//_________________________________________________________________________________________________

QString ProjectEditor::getDocFileName()
{
	return QString::fromStdWString(m_pProject->getFileName());
}

//_________________________________________________________________________________________________
bool ProjectEditor::eventFilter(QObject* target, QEvent* event)
{
	if (event->type() == QEvent::Destroy)
	{
		if (target == (QObject*)m_pEventPoolForm)
		{
			//the event pool is being destroyed; release the pointer
			m_pEventPoolForm = null;
		}
	}

	return QMainWindow::eventFilter(target, event);
}

//_________________________________________________________________________________________________

void ProjectEditor::closeEvent(QCloseEvent *event)
{
	if (m_pProject->isModified())
	{
		QString msg = STR_SAVE_MODIFIED_PROJECT(QString::fromStdWString(m_pProject->getName()));
		switch (QMessageBox::question(this, STR_DESPOT, msg, QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel))
		{
			case QMessageBox::Yes:
				if (attemptSaveProject())
				{
					event->accept();
				}
				else
				{
					event->ignore();
					return;
				}
				break;

			case QMessageBox::Cancel:
				event->ignore();
				return;

			case QMessageBox::No:
				event->accept();
				break;

                        default:
                                event->ignore();
                                break;

		}
	}
	else
	{
		event->accept();
	}

	if (onCloseForm() == false)
	{
		event->ignore();
		return;
	}

}

//_________________________________________________________________________________________________
OutputUiPart& ProjectEditor::output()
{
	return m_pWorkspace->outputUiPart();
}

//_________________________________________________________________________________________________

UpdateProgressInterface* ProjectEditor::progressInterface()
{
	return static_cast<UpdateProgressInterface*>(m_pWorkspace->progressWidget());
}

//_________________________________________________________________________________________________

void ProjectEditor::setupConnections()
{
	connect(&m_pWorkspace->projStructureUiPart(), SIGNAL(onChangedCurrentDes(Des* , Des*)),
			this, SLOT(onChangedCurrentDes(Des* , Des*)));
}

//_________________________________________________________________________________________________

void ProjectEditor::updateWindowTitle()
{
	QString projName = QString::fromStdWString(m_pProject->getName());
	QString projFileName = QString::fromStdWString(m_pProject->getFileName());
	QString windowTitle;

	if (projFileName.isEmpty())
	{
		windowTitle = projName + "*" + STR_DESPOT;
	}
	else
	{
		QFileInfo fileInfo(projFileName);

		windowTitle = projName + " - " + fileInfo.fileName() + " - " + fileInfo.path();
	}
	setWindowTitle(windowTitle);
}

//_________________________________________________________________________________________________

bool ProjectEditor::attemptSaveProject()
{
	//If the project has never been saved before run Save As to have the use select a file name
	if (m_pProject->isNew())
		return attemptSaveProjectAs();

	//before saving the project make sure all DES in the project are saved as the project
	//references those DES
	if (saveProjectDes() == false)
	{
		//the user canceled the save process
		return false;
	}

	m_pProject->save();

	return true;
}

//_________________________________________________________________________________________________

bool ProjectEditor::attemptSaveProjectAs()
{
	QString absoluteFileName = m_pProject->isNew()? QString::fromStdWString(m_pProject->getName()) : QString::fromStdWString(m_pProject->getFileName()); 
	if (MainForm::getSaveFileName(this, STR_SAVE_PROJ_AS_DLG_TITLE, STR_PROJ_FILE_DLG_FILTER, absoluteFileName) == false)
	{
		//user canceled the save as
		return false;
	}

	NameValidator fileNameValidator(new AlphaNumValidator(new WordSepValidator(new DotValidator())));
	std::wstring fileName = QFileInfo(absoluteFileName).fileName().toStdWString();
	if (fileNameValidator.validate(fileName) == false)							  
	{
		std::wstring message = L"Invalid project file name (";
		message += fileName;
		message += L"). Use an alpha-numeric string (a-z;A-Z;0-9;.-_)";
		throw message;
	}

	//before saving the project make sure all DES in the project are saved as the project
	//references those DES
	if (saveProjectDes() == false)
	{
		//the user canceled the save process
		return false;
	}

	m_pProject->save(absoluteFileName.toStdWString());

	return true;
}

//_________________________________________________________________________________________________

bool ProjectEditor::saveProjectDes()
{
	//count the modified DES that need to be saved
	int totalModifiedDes = 0;
	DesProject::DesIteratorPtr desIt = m_pProject->createDesIterator();
	for (desIt->first(); desIt->notDone(); desIt->next())
	{
		if (desIt->currentItem().isModified())
		{
			totalModifiedDes++;
		}
	}

	//check to see if any DES need to be modified
	if (totalModifiedDes == 0)
	{
		return true;
	}

	//setup the defaults that the user can modify in the process of answering the confirmation question
	bool askForConfirmation = true;
	QMessageBox::StandardButton defaultUserAnswer = QMessageBox::Yes;

	//go through each modified des and save them
	desIt = m_pProject->createDesIterator();
	for (desIt->first(); desIt->notDone(); desIt->next())
	{
		Des& crtDes = desIt->currentItem();
		
		if (crtDes.isModified())
		{
			if (crtDes.isNew())
			{
				//mark the project as modified since this DES is new and even if nothing else
				//was modified on the project since opening this DES doesn't have a file and the file
				//will be provided upon saving the DES. However if the project is not set as modified
				//the DES file name will not be saved in the project file
				m_pProject->setModified();
			}
			
			//a DES editor is not opened so manually ask for confirmation
			QMessageBox::StandardButton userAnswer = defaultUserAnswer;
			
			if (askForConfirmation)
			{
				//ask the user if they want to save the DES and save it if they agree
				//get confirmation from the user on saving all modified DES or one-by-one. Note that there could be 
				//a lot of DES that need to be saved.
				QString msg = STR_SAVE_MODIFIED_DES(QString::fromStdWString(crtDes.getName()));		
				msg += STR_SAVE_MODIFIED_DES_REMAINING(totalModifiedDes - 1);
				userAnswer = QMessageBox::question(this, STR_DESPOT_WARNING, msg, 
																  QMessageBox::Yes | QMessageBox::YesToAll | 
																  QMessageBox::No | QMessageBox::NoToAll | 
																  QMessageBox::Cancel, 
																  QMessageBox::Cancel);
			}

			switch(userAnswer)
			{
				case QMessageBox::Yes:	
				{
					DesEditor* pDesEditor = null;
					if (mainForm()->editorOpened(&crtDes, pDesEditor))
					{			
						//this will save without asking for confirmation if the des is not NEW. If the des is new
						//the save as dialog will be displayed
						pDesEditor->onSaveDes();						
					}
					else
					{				
						DesEditor::onAttemptSaveDes(&crtDes, this);						
					}
					break;
				}

				case QMessageBox::YesToAll:
				{
					DesEditor* pDesEditor = null;
					if (mainForm()->editorOpened(&crtDes, pDesEditor))
					{			
						//this will save without asking for confirmation if the des is not NEW. If the des is new
						//the save as dialog will be displayed
						pDesEditor->onSaveDes();						
					}
					else
					{				
						DesEditor::onAttemptSaveDes(&crtDes, this);						
					}
					defaultUserAnswer = QMessageBox::Yes;
					askForConfirmation = false;
					break;
				}

				case QMessageBox::No:
					//go to the next DES
					break;

				case QMessageBox::NoToAll:
					defaultUserAnswer = QMessageBox::No;
					askForConfirmation = false;
					break;

				case QMessageBox::Cancel:
                                        return false;

                                default:
                                        return false;
			}

			totalModifiedDes--;
		}
	}

	//we have saved all DES that the user wanted to save
	return true;
}

//_________________________________________________________________________________________________

void ProjectEditor::onChangedCurrentDes(Des* pCrtDes, Des* pOldCrtDes)
{
	//Remove unused warning in release
	pOldCrtDes = pOldCrtDes;

	//change the current component. The old current component should match with what the editor
	//had as the current componenent
	assert(m_pCrtDes == pOldCrtDes);
	m_pCrtDes = pCrtDes;
}

//_________________________________________________________________________________________________

void ProjectEditor::onSaveProject()
{
	try
	{
		if (attemptSaveProject())
		{
			updateWindowTitle();
		}
	}
	catch_display_xml_ex()
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onSaveProjectAs()
{
	try
	{
		if (attemptSaveProjectAs())
		{	
			updateWindowTitle();
		}
	}
	catch_display_xml_ex()
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onDisplayEventPool()
{
	if (m_pEventPoolForm == null)
	{
		m_pEventPoolForm = new EventPoolForm(m_pMainForm, this);
		m_pEventPoolForm->installEventFilter(this);
	}

	m_pEventPoolForm->open();
}

//_________________________________________________________________________________________________

void ProjectEditor::onProjectSetName()
{
	try
	{
		QString projName = QInputDialog::getText(this, STR_PROJ_NAME_DLG_TITLE,
						STR_PROJ_NAME_DLG_LABEL, QLineEdit::Normal,
						QString::fromStdWString(m_pProject->getName()));

		//empty project names are not allowed. If the user has left the name empty put the default name
		if (projName.isEmpty() == false)
		{
			//change the name of the project. If use has not supplied a name change it to the default name
			m_pProject->setName(projName.toStdWString());

			//update the title of the window
			updateWindowTitle();
		}
	}
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onPrintProject()
{
	try
	{
		QString absoluteFileName;
		if (m_pProject->isNew())
		{
			absoluteFileName = QString::fromStdWString(m_pProject->getName()) + ".txt";
		}
		else
		{
			QFileInfo projFileInfo(QString::fromStdWString(m_pProject->getFileName()));
			absoluteFileName = projFileInfo.absolutePath() + "/" + projFileInfo.completeBaseName() + ".txt";
		}

		if (MainForm::getSaveFileName(this, STR_PRINT_TO_FILE_PROJ_DLG_TITLE, STR_TXT_FILE_DLG_FILTER, absoluteFileName))
		{

		  //getSaveFileName  is adding the .desp extension, so we need
		  // to remove that
		  if (absoluteFileName.endsWith(".desp",Qt::CaseInsensitive)) 
		    {
		      absoluteFileName.remove((absoluteFileName.length()-5), 5);
		    }

			ProjectPrinter printer(*m_pProject);
			printer.print(absoluteFileName.toStdWString());
		}
	}
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onCloseProject()
{
	close();
}

//_________________________________________________________________________________________________

void ProjectEditor::onAddDes()
{
	//Implemented in specific project editors (flat or HISC)
}

//_________________________________________________________________________________________________

void ProjectEditor::onEditDes()
{
	try
	{
		if (m_pCrtDes)
		{

			if (m_pWorkspace->useGraphEditor())
			{
				GedDesEditor* pDesEditor = null;

				//if (mainForm()->editorOpened(m_pCrtDes, pDesEditor) == false)
				//{
					//create an editor that will load the DES from the selected file
					pDesEditor = new GedDesEditor(m_pCrtDes, this, mainForm());
				//}

				//display the DES to the user
				pDesEditor->open();
			}
			else
			{
				DesEditor* pDesEditor = null;
				if (mainForm()->editorOpened(m_pCrtDes, pDesEditor) == false)
				{
					//create an editor that will load the DES from the selected file
					pDesEditor = new DesEditor(m_pCrtDes, this, mainForm());
				}

				//display the DES to the user
				pDesEditor->open();
			}
		}
		else
		{
			//this handler shoudn't be called if no component is selected - it's corresponding action
			//should have been disabled
			assert(false);
		}
	}
	catch_display_xml_ex()
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onRemoveDes()
{
	try
	{
		if (m_pCrtDes)
		{
			std::wstring crtDesName = m_pCrtDes->getName();

			//ask the user for confirmation
			QString delDesWarning = STR_DELETE_DES_FROM_PROJ(QString::fromStdWString(crtDesName));
			if (QMessageBox::question(this, STR_DESPOT, delDesWarning, 
				QMessageBox::Yes|QMessageBox::No) == QMessageBox::No)
			{
				return; //the user canceled the operation
			}

			Des* removedDes = m_pCrtDes;
			m_pProject->deleteDes(crtDesName);

			//make sure the current DES doesn't point to the one that we've just deleted
			if (m_pCrtDes == removedDes)
			{
				assert(m_pCrtDes != removedDes); //the current des should have been changed by the deletion notification)
				m_pCrtDes = null;
			}
		}
	}
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onCheckProjectIntegrity()
{
	//this should be implemented in sub-classes so they can create the proper algorithm
	//Derived classes can call the overload that receives the integrity algorithm as a parameter
	//If a derived class doesn't implement this it will display as not implemented
	QMessageBox::information(this, "DESpot","Feature not implemented yet in this editor.");
}

//_________________________________________________________________________________________________

void ProjectEditor::onCleanProject()
{
	try
	{
		m_pProject->clean();
		QMessageBox::information(this, STR_DESPOT, STR_PROJECT_CLEANED);
	}
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onCheckProject()
{
	//this should be implemented in sub-classes so they can create the proper algorithm
	//Derived classes can call the overload that receives the integrity algorithm as a parameter
	//If a derived class doesn't implement this it will display as not implemented
	QMessageBox::information(this, "DESpot","Feature not implemented yet in this editor.");
}

//_________________________________________________________________________________________________

void ProjectEditor::onCheckProjectIntegrity(ProjIntegrityAlgo& integAlgo)
{
	try
	{
		WaitCursor wait(this);

		integAlgo.provideProgress(progressInterface());

		m_pProject->checkIntegrity(integAlgo);

		output().show(integAlgo);
	}
	catch(const std::wstring& e)
	{
		output().show(OutputUiPart::eProjectIntegrityCheck, L"An error occurred while checking the project integrity", e);
	}
	catch_display_all()
}

//_________________________________________________________________________________________________

void ProjectEditor::onRunMeetTool()
{
	try
	{
		WaitCursor wait(this);

		MeetAlgo meetAlgo(m_pProject->createDesIterator());
		if (meetAlgo.runEx(progressInterface()))
		{
			Des* resultDes = meetAlgo.returnResult();

			//create an editor that will load the DES from the selected file
			DesEditor* pDesEditor = new DesEditor(resultDes, mainForm());

			//display the DES to the user
			pDesEditor->open();

		}
	}
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onRunSyncProductTool()
{
	try
	{
		WaitCursor wait(this);

		SyncAlgo syncAlgo(m_pProject->createDesIterator());
		if (syncAlgo.runEx(progressInterface()))
		{
			Des* resultDes = syncAlgo.returnResult();

			//create an editor that will load the DES from the selected file
			DesEditor* pDesEditor = new DesEditor(resultDes, mainForm());

			//display the DES to the user
			pDesEditor->open();
		}
	}
	catch_display_ex()
}

//_________________________________________________________________________________________________

void ProjectEditor::onRunNonBlockingTool()
{
	try
	{
		WaitCursor wait(this);







      /*
		//check each DES in the flat project to make sure it is valid
		//make sure that all input DES are valid
		const DesProject::DesIteratorPtr desIterator = m_pProject->createDesIterator();
		for(desIterator->first(); desIterator->notDone(); desIterator->next())
		{
			const Des& crtDes = desIterator->currentItem();
			if (crtDes.getIntegrity() != eIntegYes)
			{
				QMessageBox::critical(this, STR_DESPOT_ERROR, STR_INPUT_DES_INVALID(QString::fromStdWString(crtDes.getName())));
				return;
			}
		}
		*/

		MultiNonBlockingAlgo flatNonBlock;
/*
	DesSubsystem::DesIteratorPtr hDesIt = m_pProject->createDesIterator();
	for(hDesIt->first(); hDesIt->notDone(); hDesIt->next())
	{
		const Des& hDes = hDesIt->currentItem();

		if (hDes.getIntegrity() != eIntegYes)
		{
			throw EX("The DES need to be varified to be valid");
		}
		flatNonBlock.addInputDes(&hDes);
	}

*/

			DesSubsystem::DesIteratorPtr hDesIt = m_pProject->createDesIterator();
	for(hDesIt->first(); hDesIt->notDone(); hDesIt->next())
	{
		const Des& hDes = hDesIt->currentItem();

		flatNonBlock.addInputDes(&hDes);
	}





	        flatNonBlock.provideProgress(NULL);



		m_pProject->checkNonBlocking(flatNonBlock);

		//output().showMeasurements(&m_pProject->getFlatSystemDes());
		output().show(flatNonBlock);
	}
	catch(const std::wstring& e)
	{
		output().show(OutputUiPart::eNonBlockingCheck, L"An error occurred while running the nonblocking algorithm", e);
	}
	catch_display_all()
}

//_________________________________________________________________________________________________

void ProjectEditor::onRunMultiCtrlTool()
{
	try
	{
		WaitCursor wait(this);

		MultiCtrlAlgo ctrlAlgo(m_pProject->createDesIterator(ePlantDes),
							   m_pProject->createDesIterator(eSupervisorDes));	

		ctrlAlgo.provideProgress(NULL);

		m_pProject->checkControllable(ctrlAlgo);

		output().show(ctrlAlgo);

	}
	catch(const std::wstring& e)
	{
		output().show(OutputUiPart::eControllabilityCheck, L"An error occurred while running the controllability algorithm", e);
	}
	catch_display_all()
}

//_________________________________________________________________________________________________

void ProjectEditor::onRunCtrlTool()
{
	try
	{
		WaitCursor wait(this);
		//SyncAlgo plantSyncAlgo(m_pProject->createDesIterator(ePlantDes));	
		//plantSyncAlgo.runEx(progressInterface());

		//SyncAlgo supSyncAlgo(m_pProject->createDesIterator(eSupervisorDes));
		//supSyncAlgo.runEx(progressInterface());

		//MultiCtrlAlgo ctrlAlgo(plantSyncAlgo.getOutputDes(), supSyncAlgo.getOutputDes());

		
		
		MultiCtrlAlgo ctrlAlgo(m_pProject->createDesIterator(ePlantDes),m_pProject->createDesIterator(eSupervisorDes));
		ctrlAlgo.provideProgress(NULL);
		m_pProject->checkControllable(ctrlAlgo);

		output().show(ctrlAlgo);

	}
	catch(const std::wstring& e)
	{
		output().show(OutputUiPart::eControllabilityCheck, L"An error occurred while running the controllability algorithm", e);
	}
	catch_display_all()
}

//_________________________________________________________________________________________________

void ProjectEditor::onRunSynthesisTool()
{
	try
	{
		QMessageBox::information(this, "DESpot","Feature not implemented yet.");
	}
	catch_display_ex()
}

// Add by XMA, Jul/10/08. To add entry to simulation tool
void ProjectEditor::onRunSimTool()
{
	try
	{
		if (m_pProject->getIntegrity() != eIntegYes){
			QMessageBox::information(this, "DESpot","Project is invalid or has not been validated yet.");
			return;
		}

		SimConfig* pSimConfig = new SimConfig(m_pProject, m_pMainForm, this,DespotForm::eSimConfig);
		//SimConfig* pSimConfig = new SimConfig(m_pProject, this, DespotForm::eSimConfig);

		pSimConfig->open();
		pSimConfig->initSimConfig();
	}
	catch_display_ex()
}

//#ifdef __ENABLE_BDD__

void ProjectEditor::onRunBddMeetTool()
{
	try
	{
		QMessageBox::information(this, "DESpot","Feature not implemented yet.");
	}
	catch_display_ex()
}

/* Removed by Adam. no longer used in flat only hisc (moved to HierProjectEditor.cpp)
/* Removed by Adam. no longer used in flat only hisc (moved to HierProjectEditor.cpp)
void ProjectEditor::onRunBddMultiCtrlTool()
{
	try
	{
		QMessageBox::information(this, "DESpot","Feature not implemented yet.");
	}
	catch_display_ex()
}
*/
void ProjectEditor::onRunBddSyncProductTool()
{
	try
	{
		QMessageBox::information(this, "DESpot","Feature not implemented yet.");
	}
	catch_display_ex()
}
/* Removed by Adam. no longer used in flat only hisc (moved to HierProjectEditor.cpp)
void ProjectEditor::onRunBddNonBlockingTool()
{
	try
	{
		QMessageBox::information(this, "DESpot","Feature not implemented yet.");
	}
	catch_display_ex()
}
*/
void ProjectEditor::onRunBddSynthesisTool()
{
	try
	{
		QMessageBox::information(this, "DESpot","Feature not implemented yet.");
	}
        catch_display_ex()
}
//#endif
} //end of namespace DESpot
