/*	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 <QHeaderView>
#include <QMessageBox>
#include <QResizeEvent>

#include "StateViewerWidget.h"
#include "Des.h"

namespace DESpot
{

const short StateViewerWidget::cStateEditorColumnCount	= 4;
const short StateViewerWidget::cStateNameColumnIdx		= 0;
const short StateViewerWidget::cStateAliasColumnIdx		= 1;
const short StateViewerWidget::cStateInitColumnIdx		= 2;
const short StateViewerWidget::cStateMarkedColumnIdx	= 3;

const QString StateViewerWidget::cPropCheckSymbol = tr("x");

const QString StateViewerWidget::cStateNameColName = "Name";
const QString StateViewerWidget::cStateAliasColName = "Alias";
const QString StateViewerWidget::cStateInitColName = "Init";
const QString StateViewerWidget::cStateMarkedColName = "Mark";

const QString StateViewerWidget::cYes = "Yes";
const QString StateViewerWidget::cNo = "No";
const QString StateViewerWidget::cUnknown = "Unknown";

const QString StateViewerWidget::cStateInitPropLabel = "Initial State";
const QString StateViewerWidget::cStateMarkedPropLabel = "Marked State";
const QString StateViewerWidget::cStateReachablePropLabel = "Reachable";



//_________________________________________________________________________________________________

StateViewerWidget::StateViewerWidget(QWidget* parent /*= 0*/) : DespotTreeWidget(parent)
{
	setupConnections();
	initWidget();
}

//_________________________________________________________________________________________________

StateViewerWidget::~StateViewerWidget(void)
{
}

//_________________________________________________________________________________________________
			
void StateViewerWidget::setAllowEdit(bool allowEdit /*= true*/)
{
	m_allowEdit = allowEdit;
}

//_________________________________________________________________________________________________
			
void StateViewerWidget::loadStates(Des* des)
{
	Des::StateIteratorPtr stateIt = des->createStateIterator();
	for(stateIt->first(); stateIt->isDone() == false; stateIt->next())
	{
		const DesState& state = stateIt->currentItem();
		QTreeWidgetItem* pUiItem = createUiItemFromState(state);	
		addTopLevelItem(pUiItem);
	}
}

//_________________________________________________________________________________________________
			
QTreeWidgetItem* StateViewerWidget::addState(const DesState& state)
{
	QTreeWidgetItem* pStateUiItem = createUiItemFromState(state);
	addTopLevelItem(pStateUiItem);
	return pStateUiItem;
}

//_________________________________________________________________________________________________
			
QTreeWidgetItem* StateViewerWidget::updateState(const DesState& state)
{
	QTreeWidgetItem* pStateUiItem = getUiItemFromState(state);

	//found UI item corresponding to the changed state. update it
	fillStateUiItem(*pStateUiItem, state);

	return pStateUiItem;
}

//_________________________________________________________________________________________________

void StateViewerWidget::updateAllStates()
{
	for (int iRow = 0; iRow < topLevelItemCount(); iRow++)
	{
		QTreeWidgetItem* pStateUiItem = topLevelItem(iRow);
		assert(pStateUiItem != null);

		const DesState& state  = getStateFromUiItem(*pStateUiItem);

		//update the item with the information from its state which must have changed
		fillStateUiItem(*pStateUiItem, state);
	}
}

//_________________________________________________________________________________________________
			
void StateViewerWidget::removeState(const DesState& state)
{
	QTreeWidgetItem* pStateItem = getUiItemFromState(state);
	if (pStateItem == null)
	{
		assert(false);
		return; //nothing to delete; there is no current state
	}

	if (pStateItem == currentItem())
	{
		setCurrentItem(getNextSibling(currentItem()));
	}


	//Remove the item from the state viewer which corresponds to the state to be deleted
	takeTopLevelItem(indexOfTopLevelItem(pStateItem));
}

//_________________________________________________________________________________________________

void StateViewerWidget::initWidget()
{
	//Setup header with 4 columns: Name | Alias | Initial | Marked
	setColumnCount(cStateEditorColumnCount);
	QStringList headers;
	headers << cStateNameColName << cStateAliasColName << cStateInitColName << cStateMarkedColName;
	setHeaderLabels(headers);

	//size the columns properly
	resizeHeaders(geometry().width());
	
	//align Initi and Mark columns which show only a flag to center
	headerItem()->setTextAlignment(cStateInitColumnIdx, Qt::AlignHCenter);
	headerItem()->setTextAlignment(cStateMarkedColumnIdx, Qt::AlignHCenter);
	
	//do not show the sorting indicator as it takes too much space in the column header
	header()->setSortIndicatorShown(false);
}

//_________________________________________________________________________________________________

void StateViewerWidget::setupConnections()
{
	connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), 
		    this, SLOT(onChangedCurrentStateItem(QTreeWidgetItem*, QTreeWidgetItem*)));
	
	connect(this, SIGNAL(onActiveItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), 
		    this, SLOT(onChangedActiveStateItem(QTreeWidgetItem*, QTreeWidgetItem*)));

	connect(this, SIGNAL(itemChanged(QTreeWidgetItem*, int)), 
		    this, SLOT(onStateItemChanged(QTreeWidgetItem*, int)));

	connect(this, SIGNAL(itemClicked(QTreeWidgetItem*, int)), 
		    this, SLOT(onStateItemClicked(QTreeWidgetItem*, int)));	
}

//_________________________________________________________________________________________________

void StateViewerWidget::onChangedCurrentStateItem(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
	if (previous == current)
		return; //nothing really changed

	//previously there might not have been a selected state so we need to check
	const DesState* pOldCurrentState = null;
	if (previous)
	{
		const DesState& prevCrtState = getStateFromUiItem(*previous);
		pOldCurrentState = &prevCrtState;
	}

	const DesState* pNewCurrentState = null;
	if (current)
	{
		//obtain the currently selected state
		const DesState& crtState  = getStateFromUiItem(*current);
		pNewCurrentState = &crtState;
	}

	emit onChangedCurrentState(pNewCurrentState, pOldCurrentState);
}

//_________________________________________________________________________________________________

void StateViewerWidget::onChangedActiveStateItem(QTreeWidgetItem* current, QTreeWidgetItem* previous)
{
	if (previous == current)
		return; //nothing really changed

	//previously there might not have been a selected state so we need to check
	const DesState* pOldCurrentState = null;
	if (previous)
	{
		const DesState& prevCrtState = getStateFromUiItem(*previous);
		pOldCurrentState = &prevCrtState;
	}

	const DesState* pNewCurrentState = null;
	if (current)
	{
		//obtain the currently selected state
		const DesState& crtState  = getStateFromUiItem(*current);
		pNewCurrentState = &crtState;
	}

	emit onChangedActiveState(pNewCurrentState, pOldCurrentState);
}

//_________________________________________________________________________________________________

//This signal is received whenever the user changes the item in place (within the tree widget)
//The method ensures the data is validated and the corresponding state is changed.   Once the state
//is changed in the DES, the DES itself will send a changed state notfication that this UI part 
//subscribes too
void StateViewerWidget::onStateItemChanged ( QTreeWidgetItem * item, int column )
{
	if (item == null)
		return;

	const DesState& changedState  = getStateFromUiItem(*item);
	DesState::ID changedStateId = changedState.getId();

	//Change the state that corresponds to this item
	switch(column)
	{
		case cStateNameColumnIdx:
		{
			QString nameInItem = item->text(cStateNameColumnIdx);
			if (nameInItem.size() == 0)
			{
				//the user has erased the name making the state illegal so let the user know
				//and put back the old name
				QMessageBox::critical(parentWidget(), STR_DESPOT_ERROR, STR_STATE_NAME_MANDATORY);
				item->setText(cStateNameColumnIdx, QString::fromStdWString(changedState.getName()));
				break;
			}
			
			std::wstring newName = item->text(cStateNameColumnIdx).toStdWString();
			if (newName != changedState.getName())
			{
				try
				{
					emit onUserChangedStateName(changedState, newName);
				}
				catch(EXO)
				{
					display_ex_in(parentWidget());
					item->setText(cStateNameColumnIdx, QString::fromStdWString(changedState.getName()));
				}
			}
			break;
		}

		case cStateAliasColumnIdx:
		{
			QString aliasInItem = item->text(cStateAliasColumnIdx);
			std::wstring newAlias = aliasInItem.size() > 0 ? aliasInItem.toStdWString() : L"";
			if (newAlias != changedState.getAlias())
			{
				try
				{
					emit onUserChangedStateAlias(changedState, newAlias);
				}
				catch(EXO)
				{
					display_ex_in(parentWidget());
					item->setText(cStateAliasColumnIdx, QString::fromStdWString(changedState.getAlias()));
				}
			}
			break;
		}

		case cStateInitColumnIdx:
		{
			QString initLabel = item->text(cStateInitColumnIdx);
			if ((initLabel.size() > 0) && (initLabel != cPropCheckSymbol))
			{
				item->setText(cStateInitColumnIdx, cPropCheckSymbol);
				break; // do not change the DES state because a new notification will come due to the text correction
			}

			if ((initLabel.size() > 0) !=  (changedState.isInit() == true))
			{
				emit onUserChangedStateInit(changedState, (initLabel.size() > 0));
			}
			break;
		}

		case cStateMarkedColumnIdx:
		{
			QString markedLabel = item->text(cStateMarkedColumnIdx);
			if ((markedLabel.size() > 0) && (markedLabel != cPropCheckSymbol))
			{
				item->setText(cStateMarkedColumnIdx, cPropCheckSymbol);
				break; // do not change the DES state because a new notification will come due to the text correction
			}

			if ((markedLabel.size() > 0) !=  (changedState.isMarked() == true))
			{
				emit onUserChangedStateMarking(changedState, (markedLabel.size() > 0));
			}
			break;
		}
	}

}

//_________________________________________________________________________________________________

void StateViewerWidget::onStateItemClicked ( QTreeWidgetItem * item, int /*column*/ )
{
	//check if the user selected "nothing" and deselect the curent item
	if (item == null)
	{
		setItemSelected(currentItem(), false);
		setCurrentItem(null);
	}
	else
	{
		const DesState& state = getStateFromUiItem(*item);		
		emit onStateClicked(state);
	}
}

//_________________________________________________________________________________________________

QTreeWidgetItem* StateViewerWidget::createUiItemFromState(const DesState& state)
{
	//add the state information to the state editor
	QTreeWidgetItem* pStateUiItem = new QTreeWidgetItem();

	//set general UI item not related to the state itself
	pStateUiItem->setTextAlignment(cStateInitColumnIdx, Qt::AlignHCenter);
	pStateUiItem->setTextAlignment(cStateMarkedColumnIdx, Qt::AlignHCenter);

	//make the item editable
	pStateUiItem->setFlags(pStateUiItem->flags() | Qt::ItemIsEditable);

	//fill the UI item with the state attributes (name, alias, etc...)
	fillStateUiItem(*pStateUiItem, state);

	//save the state pointer in the item itsel for easy retrieval.
	//this pointer cannot be used to modify the states but only to read things from it
	pStateUiItem->setData(0, Qt::UserRole, QVariant(reinterpret_cast<unsigned long long>(&state)));

	return pStateUiItem;
}

//_________________________________________________________________________________________________

void StateViewerWidget::fillStateUiItem(QTreeWidgetItem& stateUiItem, const DesState& desState)	
{
	QString stateName = QString::fromStdWString(desState.getName());
	if (stateName != stateUiItem.text(cStateNameColumnIdx))
	{
		stateUiItem.setText(cStateNameColumnIdx, stateName);
	}
	
	QString stateAlias = QString::fromStdWString(desState.getAlias());
	if (stateAlias != stateUiItem.text(cStateAliasColumnIdx))
	{
		stateUiItem.setText(cStateAliasColumnIdx, stateAlias);
	}
	
	if ((stateUiItem.text(cStateInitColumnIdx).size() > 0) != desState.isInit())
	{
		stateUiItem.setText(cStateInitColumnIdx, desState.isInit() ? cPropCheckSymbol : "");
	}

	if ((stateUiItem.text(cStateMarkedColumnIdx).size() > 0) != desState.isMarked())
	{
		stateUiItem.setText(cStateMarkedColumnIdx, desState.isMarked() ? cPropCheckSymbol : "");		
	}
	
	//make the whole row bold if the state is marked or unbold if it is not marked anymore
	QFont font = stateUiItem.font(0);
	if (desState.isMarked() != font.bold())
	{
		font.setBold(desState.isMarked());
		for(int i = 0; i < cStateEditorColumnCount; i++)
		{
			stateUiItem.setFont(i, font);
		}
	}

	QColor textColor = Qt::black; //by default the color is black
	//make the text blue if the current state is the initial state
	if (desState.isInit())
	{
		textColor = Qt::blue;
	}
	else if (desState.isReachKnown() && desState.isReachable() == false)
	{	
		//make the text read if the state is unreachable
		textColor = Qt::red;
	}
	
	//set the text color in all columns
	for(int i = 0; i < cStateEditorColumnCount; i++)
	{
		stateUiItem.setTextColor(i, textColor);
	}

	//set the tooltip for all columns if it changed
	QString initialLabel   = desState.isInit() ? cYes : cNo;
	QString markedLabel    = desState.isMarked() ? cYes : cNo;
	QString reachableLabel = desState.isReachKnown() ? (desState.isReachable() ? cYes : cNo) : cUnknown;
	QString stateToolTip = stateName;
	if (stateAlias.size() > 0)
	{
		stateToolTip += " (" + stateAlias + ")";
	}
	stateToolTip += tr("\n\n") + cStateInitPropLabel + ":\t" + initialLabel + "\n" +
					cStateMarkedPropLabel + ":\t" + markedLabel + "\n" +
					cStateReachablePropLabel + ":\t" + reachableLabel;

	//check if the computed tooltip is different then the one actually present in the UI item
	if (stateToolTip != stateUiItem.toolTip(0))
	{
		//current item has a different tooltip => update it
		for(int i = 0; i < cStateEditorColumnCount; i++)
		{
			stateUiItem.setToolTip(i, stateToolTip);
		}
	}
}

//_________________________________________________________________________________________________

const DesState& StateViewerWidget::getStateFromUiItem(QTreeWidgetItem& stateUiItem)
{
	DesState* pState = reinterpret_cast<DesState*>(stateUiItem.data(0, Qt::UserRole).toULongLong());
	assert(pState != null);
	return *pState;
}

//_________________________________________________________________________________________________

QTreeWidgetItem* StateViewerWidget::getUiItemFromState(const DesState& state)
{
	for (int iRow = 0; iRow < topLevelItemCount(); iRow++)
	{
		QTreeWidgetItem* pStateUiItem = topLevelItem(iRow);
		assert(pStateUiItem != null);

		const DesState& crtState  = getStateFromUiItem(*pStateUiItem);
		if (&crtState == &state)
		{
			return pStateUiItem;
		}
	}

	//if we got here this state was not displayed. This should not happen
	throw EX("State not found in the state view widget")
}

//_________________________________________________________________________________________________

QTreeWidgetItem* StateViewerWidget::getUiItemFromState(const DesState::ID& stateId)
{
	for (int iRow = 0; iRow < topLevelItemCount(); iRow++)
	{
		QTreeWidgetItem* pStateUiItem = topLevelItem(iRow);
		assert(pStateUiItem != null);

		const DesState& crtState  = getStateFromUiItem(*pStateUiItem);
		if (crtState.getId() == stateId)
		{
			return pStateUiItem;
		}
	}

	//if we got here this state was not displayed. This should not happen
	throw EX("State not found in the state view widget")
}

//_________________________________________________________________________________________________

void StateViewerWidget::resizeEvent(QResizeEvent* event)
{
	resizeHeaders(event->size().width());
}

//_________________________________________________________________________________________________

void StateViewerWidget::resizeHeaders(int stateViewWidgWidth)
{
	int headerWidth = stateViewWidgWidth;
	
	int nameWidth = headerWidth / 3;	 //(40%)
	int nameWidthOpt = header()->sectionSizeHint(0);
	
	int aliasWidth = headerWidth / 4;  //25%
	int aliasWidthOpt = header()->sectionSizeHint(1);
	
	int initWidth       = (headerWidth - (nameWidth + aliasWidth)) / 2;
	int initWidthOpt = header()->sectionSizeHint(2);
	
	int markedWidth = (headerWidth - (nameWidth + aliasWidth + initWidth));
	int markedWidthOpt = header()->sectionSizeHint(3);
	
	header()->resizeSection(cStateNameColumnIdx, nameWidth < nameWidthOpt ? nameWidthOpt : nameWidth);
	header()->resizeSection(cStateAliasColumnIdx, aliasWidth < aliasWidthOpt ? aliasWidthOpt : aliasWidth);
	header()->resizeSection(cStateInitColumnIdx, initWidth < initWidthOpt ? initWidthOpt : initWidth);		
	header()->resizeSection(cStateMarkedColumnIdx, markedWidth < markedWidthOpt ? markedWidthOpt : markedWidth);
}

} //end of namespace DESpot
