/*************************************************************************
 * This file is part of Graphic des EDitor project
 *
 * Project created in conformity with the requirements for the Degree of
 * Master of Engineering in Software Engineering, Computing and Software
 * Department, McMaster University 2004 - 2008
 *
 * Author:	Xiao Ma
 * Supervisor: Dr. Ryan Leduc
*************************************************************************/

/*
 NAME
   GedDesScene.h - Ged DES scene class implementation.
 FUNCTION
 NOTES
 MODIFIED
   xma	    09/01/07 - CREATION
*/

#include <QtGui>
#include <sstream>
#include <string>
#include <math.h>

#include "GedDesScene.h"
#include "GedDesTrans.h"
#include "DesStateEditorDlg.h"
#include "GedDesEditor.h"
#include "GedDesTransitionEditorDlg.h"
#include "DesSerializer.h"
#include "GedDesTransEditor.h"
#include "GedDesWorkspace.h"


namespace DESpot
{

GedDesScene::GedDesScene(GedDesWorkspace *workspace, QMenu *stateMenu,
						 QMenu *transMenu, Des *des, QObject *parent)
    : QGraphicsScene(parent), desWorkspace(workspace), m_desHelper(des)
{
    stateContextMenu = stateMenu;
	transContextMenu = transMenu;

    sceneMode = MoveItem;
    gedStateType = GedDesState::RegularState;
    line = 0;
    textItem = 0;
	desEditor = desWorkspace->desEditor();
	this->des = des;
	stateMap =& des->getGedInfo();
	modified=false;

	m_pMsg = new QGraphicsSimpleTextItem(tr("Simulation view was opened. Close and open editor to see DES again."));
	m_pMsg->setPos(QPointF(20.0, 20.0));

}

GedDesScene::~GedDesScene()
{
	// Have to remove items explicitly to maintain ownership and preventing
	// scene from calling delete on the graphics items within the Des::GedInfo
	deleteAllItems();
}


void GedDesScene::setMode(Mode mode)
{
    sceneMode = mode;
}

void GedDesScene::setCompType(GedDesState::GedStateType type)
{
    gedStateType = type;
}

void GedDesScene::editorLostFocus(DiagramTextItem *item)
{
    QTextCursor cursor = item->textCursor();
    cursor.clearSelection();
    item->setTextCursor(cursor);

    if (item->toPlainText().isEmpty()) {
        removeItem(item);
        item->deleteLater();
    }
}

void GedDesScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
    if (mouseEvent->button() != Qt::LeftButton)
        return;

    GedDesState *item = 0;
	DesStateEditorDlg stateEditorDlg(desEditor);

//add by bini
	stateEditorDlg.setTemplate(des->isTemplate());

	// QList<QGraphicsItem *> clickedItems;
	//QPointF mousePos = mouseEvent->scenePos();
	//QList<QGraphicsItem *> startItems;
	//QPainterPath selectArea;

	switch (sceneMode) {
		case MoveItem:
			// clickedItems.clear();
			// clickedItems = selectedItems();

			// Create a block so that mousePos doesn't pollute rest of switch.
			// If the DES is modified, preserve this fact. Should hopefully
			// shortcircuit and not check next condition.
			// If there is something directly beneath the mouse during the
			// click, then this means the resulting drag will move items and
			// change the DES. Set modified to reflect this
			{
			QPointF mousePos = mouseEvent->scenePos();
			modified = modified
				|| items(QRectF(mousePos.x()-5,
				mousePos.y()-5,10,10)).size() > 0;
			}

			/*
			startRect = new QGraphicsRectItem(QRectF(mousePos.x()-5,mousePos.y()-5,10,10));
			startRect->setPen(QPen(Qt::black, 1, Qt::DashLine));
			//startRect->setVisible(false);
			addItem(startRect);

			startItems = items(QRectF(mousePos.x()-5,mousePos.y()-5,10,10));

			if (startItems.count() > 0) // && startItems.first()->type() == GedDesTrans::Type)
			{
				foreach(QGraphicsItem* item, startItems)
					item->setSelected(true);
			}

			selectArea.addRect(QRectF(mousePos.x()-5,mousePos.y()-5,10,10));
			setSelectionArea(selectArea);
			*/

			break;

		case InsertState:
			modified=true;
			if(gedStateType==GedDesState::InitState)
				stateEditorDlg.setDefaultProps(true,false);
			if(gedStateType==GedDesState::MarkedState)
				stateEditorDlg.setDefaultProps(false,true);
			if(gedStateType==GedDesState::InitMarkedState)
				stateEditorDlg.setDefaultProps(true,true);
			if(gedStateType==GedDesState::InitMarkedState ||
				gedStateType==GedDesState::InitState)
				if (des->hasInitialState()){
                                        QMessageBox::warning(desEditor, tr("Add initial state"),
						tr("The DES has an initial state already!"),
						//QMessageBox::Discard,
						QMessageBox::Ok);

					//workaround of cancel insert. We need to uncheck the botton
					//item = new GedDesState(myDesDiagram,gedStateType,stateContextMenu);
					item = new GedDesState(gedStateType,stateContextMenu);
					emit stateInserted(item);
					delete item;
					break;
				}
			if (stateEditorDlg.exec() == QDialog::Accepted)
			{
				DesState myState=stateEditorDlg.resultState();
				if (isStateValid(myState.getName())){
					const DesState& state = des->addState(myState);
					item=insertState(state,mouseEvent->scenePos());
					emit stateInserted(item);
				}
				else
				{
					QString str = "Invalid state: ";
					str += QString::fromStdWString(myState.getName());
					QMessageBox::warning(desEditor, tr("Add state"),
						str,
						//QMessageBox::Discard,
						QMessageBox::Ok);
				}

			}
			else
			{
				//workaround of cancel insert. We need to uncheck the botton
				//item = new GedDesState(myDesDiagram,gedStateType,stateContextMenu);
				item = new GedDesState(gedStateType,stateContextMenu);
				emit stateInserted(item);
				delete item;
			}
            break;

        case InsertTrans:
		case InsertTransCurve:
            line = new QGraphicsLineItem(QLineF(mouseEvent->scenePos(),
                                        mouseEvent->scenePos()));
            line->setPen(QPen(Qt::black, 2));
            addItem(line);

            break;

		case ChangeTrans:
			line = new QGraphicsLineItem(QLineF(mouseEvent->scenePos(),
                                        mouseEvent->scenePos()));
			startRect = new QGraphicsRectItem(QRectF(line->line().p1().x()-10,
													 line->line().p1().y()-10,
													 20, 20));
			startRect->setPen(QPen(Qt::black, 1, Qt::DashLine));
			addItem(startRect);
            line->setPen(QPen(Qt::black, 2, Qt::DashLine));
            addItem(line);

            break;

        case InsertText:
			modified=true;
            textItem = new DiagramTextItem();
			textItem->setTextInteractionFlags(Qt::NoTextInteraction);
            textItem->setZValue(1000.0);
            connect(textItem, SIGNAL(lostFocus(DiagramTextItem *)),
                    this, SLOT(editorLostFocus(DiagramTextItem *)));
            connect(textItem, SIGNAL(selectedChange(QGraphicsItem *)),
                    this, SIGNAL(itemSelected(QGraphicsItem *)));
            addItem(textItem);
            textItem->setPos(mouseEvent->scenePos());
            emit textInserted(textItem);
		default:
        ;
    }
    QGraphicsScene::mousePressEvent(mouseEvent);
}

void GedDesScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
{
	if ((sceneMode == InsertTrans
		|| sceneMode == InsertTransCurve
		|| sceneMode == ChangeTrans) && line != 0) {
        QLineF newLine(line->line().p1(), mouseEvent->scenePos());
        line->setLine(newLine);
    } else if (sceneMode == MoveItem) {
        QGraphicsScene::mouseMoveEvent(mouseEvent);
    }
}

void GedDesScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
{

	// If the 

	// Not in InsertTrans/(Curve) or ChangeTrans mode or no line
	if ((sceneMode != InsertTrans
		&& sceneMode != ChangeTrans
		&& sceneMode != InsertTransCurve)
		|| line == 0) {
		if (line != 0){
			removeItem(line);
			delete line;
			line = null;
		}
	    QGraphicsScene::mouseReleaseEvent(mouseEvent);
		return;
	}

	//QList<QGraphicsItem *> startItems = items(line->line().p1());
	modified = true;
	if  (sceneMode == InsertTrans || sceneMode == InsertTransCurve)
	{

		QList<QGraphicsItem *> startItems = items(line->line().p1());
		if (startItems.count() > 0 && startItems.first() == line)
			startItems.removeFirst();
		QList<QGraphicsItem *> endItems = items(line->line().p2());
		if (endItems.count() > 0 && endItems.first() == line)
			endItems.removeFirst();

		// Both ends are State, we insert a transition, including selfloop
		if (startItems.count() > 0 && endItems.count() > 0 &&
			startItems.first()->type() == GedDesState::Type &&
			endItems.first()->type() == GedDesState::Type)
		{
			GedDesState *startState =
				qgraphicsitem_cast<GedDesState *>(startItems.first());
			GedDesState *endState =
				qgraphicsitem_cast<GedDesState *>(endItems.first());

			// Use DESspot interface to add trans. If add is ok, we add the
			// the graphic component to the scene
                        //const DesTransition* m_pCrtSelTrans=0;
			GedDesTransEditorDlg transEditorDlg
				(startState->getDesState(),endState->getDesState(), *des, desEditor);
			if (transEditorDlg.exec() == QDialog::Accepted)
			{
				const DesTransition& newTrans = transEditorDlg.resultTransition();
				if(desEditor->onAddTransition(newTrans))
				{
					insertTrans(&newTrans, sceneMode == InsertTransCurve);
				}
			}
		} //Both end are states
	}
	else //ChangeTrans mode
	{
		// If one end is transition, we modify the event shape
		QList<QGraphicsItem *> startItems = items(line->line().p1().x()-5,
											      line->line().p1().y()-5,
												  10,
												  10);

		//QList<QGraphicsItem *> startItems = startRect->collidingItems();

                //int typ = startItems.first()->type();
                //bool t1 = (startItems.first()->type() == GedDesTrans::Type);
                //int t2 = startItems.count();
		
		// If there is a relevent transition, find it
		if (startItems.count() > 0) // && startItems.first()->type() == GedDesTrans::Type)
		{
			foreach(QGraphicsItem* item, startItems)
				if (item->type() == GedDesTrans::Type)
				{
					GedDesTrans *trans =
						qgraphicsitem_cast<GedDesTrans *>(item);
					trans->setLine(line->line());
					trans->updatePosition();
					// add so the label gets
					//update after rotation
					update();
					break;
				}
		}
		removeItem(startRect);
		delete startRect;
		startRect = 0;
	}
    removeItem(line);
    delete line;
	line = 0;
	QGraphicsScene::mouseReleaseEvent(mouseEvent);
}

bool GedDesScene::isItemChange(int type)
{
    foreach (QGraphicsItem *item, selectedItems()) {
        if (item->type() == type)
            return true;
    }
    return false;
}

void GedDesScene::open()
{
	deleteAllItems();

	//Get the #state and #transition
	DesState::Count stateCnt = des->getStateCount();

	//Get graphic view size
	qreal sceneHeight = height();
	qreal sceneWidth = width();

	//Calculate the position for each state
        unsigned int maxCol= (unsigned int)ceil(sqrt(double(stateCnt)));
	qreal gap= qMin(sceneHeight,sceneWidth) / (maxCol + 1);

	//Insert states
	unsigned int row=1;
	unsigned int col=1;

	bool isInconsistent = false;

	Des::StateIteratorPtr stateIt = des->createStateIterator();
	for(stateIt->first(); stateIt->isDone() == false; stateIt->next())
	{
		const DesState &state = stateIt->currentItem();
		GedDesState* pGedState= const_cast<GedDesState*> (m_desHelper.gedState(&state));

		if (!pGedState)
		{
			// No graphical info, generate new one
			pGedState = m_desHelper.insertState(&state, QPointF(col*gap,row*gap));
			isInconsistent = true;
		}

		initGedState(pGedState);
		addItem(pGedState);

		col++;
		if (col > maxCol){
			col = 1;
			row ++;
		}
	}

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

		if (!pGedTrans)
		{
			// No graphical info, generate new one
			pGedTrans = m_desHelper.insertTrans(&trans, isNew, DEFAULT_TRANS_IS_CURVE);
			isInconsistent = true;
		}

		initGedTrans(pGedTrans);
		addItem(pGedTrans);
	}
}

void GedDesScene::save()
{
	DesSerializer saver(*des, this, DesSerializer::Graphics);
	QString fname = QString::fromStdWString(des->getFileName());

	saver.save(fname.toStdWString());

	modified = false;

	// may consider scene modified?
	//des->setModified(false);
}

void GedDesScene::toggleInitState(bool toSet)
{
    if (isItemChange(GedDesState::Type)) {
      if (selectedItems().count() == 1) {
        GedDesState *item =
	  qgraphicsitem_cast<GedDesState *>(selectedItems().first());
	item->setStateType(GedDesState::InitState,toSet);
        update();
      }
    }
}

void GedDesScene::toggleMarkedState(bool toSet)
{
    if (isItemChange(GedDesState::Type)) {
      if (selectedItems().count() == 1) {
        GedDesState *item =
	  qgraphicsitem_cast<GedDesState *>(selectedItems().first());
	item->setStateType(GedDesState::MarkedState,toSet);
        update();
      }
    }
}

void GedDesScene::editItem()
{
    foreach (QGraphicsItem *item, selectedItems())
	{
        if (item->type() == GedDesState::Type)
		{
			GedDesState *state=qgraphicsitem_cast<GedDesState *>(item);
			editState(state);
			// only edit one of selected items
			break;
		}
		if (item->type() == GedDesTrans::Type)
		{
			GedDesTrans *trans = qgraphicsitem_cast<GedDesTrans *>(item);
			editTransition(trans);
			// only edit one of selected items
			break;
        }
    }
}

void GedDesScene::editState(GedDesState *state)
{
	const DesState *myDesState=state->getDesState();
	DesStateEditorDlg stateEditorDlg(*myDesState, desEditor);
	if (stateEditorDlg.exec() == QDialog::Accepted)
	{
		des->changeState(myDesState->getId(), stateEditorDlg.resultState());
	}
	state->updateState();
}

//delete state from DES. Scene item is handled in deleteItem()
void GedDesScene::deleteState(GedDesState *state)
{
	const DesState *myDesState=state->getDesState();
	des->deleteState(myDesState->getId());
}

void GedDesScene::editTransition(GedDesTrans *trans)
{
    GedDesTransEditor *dialog = new GedDesTransEditor(des, trans, desEditor);
    if (dialog->exec() && dialog->isSelectedEventsEmpty())
		deleteTransition(trans);
    delete dialog;
}

void GedDesScene::deleteTransition(GedDesTrans *trans)
{
     trans->clearEvents();
	 removeItem(trans);
	 delete trans;
}

void GedDesScene::deleteItem()
{
	//De-select label, otherwise, coredump
	foreach (QGraphicsItem *item, selectedItems()){
		if (item->type() == GedDesLabel::Type)
			item->setSelected(false);
	}

    foreach (QGraphicsItem *item, selectedItems()) {
        if (item->type() == GedDesState::Type) {
			GedDesState *state = qgraphicsitem_cast<GedDesState *>(item);
            state->removeGedDesTrans();
			deleteState(state);
			stateMap->remove(state->getDesState());
			removeItem(item);
        }
        if (item->type() == GedDesTrans::Type) {
			GedDesTrans *tran = qgraphicsitem_cast<GedDesTrans *>(item);
            tran->clearEvents();
			tran->getStartState()->removeGedDesTrans(tran);
			tran->getEndState()->removeGedDesTrans(tran);
			removeItem(item);
        }
		// delete eariler will core dump
		if (item->type() == GedDesState::Type)
	  		delete 	qgraphicsitem_cast<GedDesState *>(item);
		else
			delete qgraphicsitem_cast<GedDesTrans *>(item);
    }
}

void GedDesScene::deleteAllItems()
{
    foreach (QGraphicsItem *item, items()) {

      // should only remove items without parents as 
      // children get moved with parents

      QGraphicsItem *parent =  item->parentItem();

      if (!parent) {
	removeItem(item);
      }
    }

}

void GedDesScene::restoreTransShape()
{
    foreach (QGraphicsItem *item, selectedItems()) {
        if (item->type() == GedDesTrans::Type) {
            qgraphicsitem_cast<GedDesTrans *>(item)->restoreDefaultShape();
        }
    }
}

void GedDesScene::toggleCurvature() {
	foreach (QGraphicsItem *item, selectedItems()) {
		if (item->type() == GedDesTrans::Type) {
			qgraphicsitem_cast<GedDesTrans *>(item)->toggleCurvature();
			qgraphicsitem_cast<GedDesTrans *>(item)->update();
		}
	}
}

bool GedDesScene::isStateValid(std::wstring text)
{
	const DesState* foundState;
	return (!des->findState(text,foundState));
}

// There is only one graphic transition between two graphic states
// Each transition may represents mutliple DES transition with different
// events. That is, only one transition line is visible in graphic scene
GedDesTrans* GedDesScene::gedTrans(GedDesState *startState,
									  GedDesState *endState)
{
	return m_desHelper.gedTrans(startState, endState);
}

// Note, this and the next one matches event as well while the above one does NOT
GedDesTrans* GedDesScene::gedTrans(GedDesState *startState, const DesEvent *ev,
									  GedDesState *endState)
{
	return m_desHelper.gedTrans(startState, ev, endState);
}

//Overload version, used by XMLWrite to find gedTrans for a desTrans
GedDesTrans* GedDesScene::gedTrans(const DesTransition *desTrans)
{
	return m_desHelper.gedTrans(desTrans);
}

//Utility functions for save/open
QPointF GedDesScene::statePos(const DesState &state)
{
	return m_desHelper.statePos(state);
}

QPointF GedDesScene::stateLabelPos(const DesState &state)
{
	return m_desHelper.stateLabelPos(state);
}

QList<QPointF> GedDesScene::transPos(const DesTransition &trans)
{
	return m_desHelper.transPos(trans);
}

QPointF GedDesScene::transLabelPos(const DesTransition &trans)
{
	return m_desHelper.transLabelPos(trans);
}

qreal GedDesScene::selfloopPos(const DesTransition &trans)
{
	return m_desHelper.selfloopPos(trans);
}

const GedDesState* GedDesScene::gedState(const DesState *state) const
{
	return m_desHelper.gedState(state);
}

void GedDesScene::initGedState(GedDesState* pState)
{
	pState->setMenu(stateContextMenu);
}

void GedDesScene::initGedTrans(GedDesTrans* pTrans)
{
	pTrans->setMenu(transContextMenu);
}

GedDesState* GedDesScene::insertState(const DesState &myState, QPointF pos, QPointF labelPos)
{
	GedDesState* gedState = m_desHelper.insertState(&myState, pos, labelPos);
	initGedState(gedState);
	addItem(gedState);
	return gedState;
}

void GedDesScene::insertTrans(const DesTransition *desTrans, bool isCurve)
{
	bool isNew;
	GedDesTrans* tran = m_desHelper.insertTrans(desTrans, isNew, isCurve);
	initGedTrans(tran);
	addItem(tran);
	if (isNew)
	{
		emit transInserted(tran);
	}
}

void GedDesScene::showMessage(bool visible)
{
	if (visible)
		addItem(m_pMsg);
	else
		removeItem(m_pMsg);
}

}
