/*
 * SimGedViewerScene.cpp
 *
 *  Created on: Aug 7, 2009
 *      Author: alex
 */

#include <QtGui/QPainter>
#include "SimGedViewerScene.h"
#include "Des.h"
#include "GedDesState.h"
#include "GedDesTrans.h"
#include "SimCompState.h"


namespace DESpot
{

  static const double DefaultMinValue = 500000000.0;
  static const double colWeight = 500.0;
  static const double rowWeight = 250.0;


SimGedViewerScene::SimGedViewerScene(Des *pDes, QWidget* parent)
        : QWidget(parent), m_pDes(pDes),  m_desHelper(pDes),
	  m_pCurGedState(0), m_pChosenTrans(0), m_animCount(0),
	  m_itemOffset(0.0,0.0)
{
	m_pAnimTimer = new QTimer(this);
	connect(m_pAnimTimer, SIGNAL(timeout()), this, SLOT(animate()));
}

SimGedViewerScene::~SimGedViewerScene()
{
}

void SimGedViewerScene::paintEvent(QPaintEvent* /*event*/)
{
	bool isInconsistent = false;
	QPainter painter(this);

	painter.fillRect(geometry(), QBrush(Qt::white));

	DesState::Count stateCnt = m_pDes->getStateCount();

	//Calculate the position for each state if no graphical info
        unsigned int maxCol= (unsigned int)ceil(sqrt(double(stateCnt)));
	qreal sceneHeight = maxCol * rowWeight;
	qreal sceneWidth = maxCol * colWeight;
	qreal gap= qMin(sceneHeight,sceneWidth) / (maxCol + 1);

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

	if (!m_pCurState) { return; }

	Des::StateIteratorPtr stateIt = m_pDes->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;
		}

		pGedState->draw(&painter, m_itemOffset);

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

	//handle transitions excluding global self-loops
	bool isNew = false;
	Des::TransIteratorPtr transIt = m_pDes->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);
		  isInconsistent = true;
		}

		pGedTrans->draw(&painter, m_itemOffset);

	}

	/*
	if (isInconsistent)
	{
		QMessageBox::warning(this, tr("Display Error"),
			tr("Some elements don't contain graphical information and could not be displayed."),
			QMessageBox::Ok,
			QMessageBox::Ok);
	}
	*/

}

//  suggest geometry big enough for DES.  ALso sets offset to reduce
//  whitespace
QRect  SimGedViewerScene::suggestGeometry()
{
        // start off with very large number so will get eliminated by first qMin
        qreal minX = DefaultMinValue,minY = DefaultMinValue;
	qreal maxX = 0.0,maxY = 0.0;
	qreal x1 = 0.0,y1 = 0.0,x2 = 0.0,y2 = 0.0;
	QRectF tmpRec;
	QPointF tmpPoint(0.0,0.0);

	DesState::Count stateCnt = m_pDes->getStateCount();

	//Calculate the position for each state  if no graphical info
        unsigned int maxCol= (unsigned  int)ceil(sqrt(double(stateCnt)));
	qreal sceneHeight = maxCol * rowWeight;
	qreal sceneWidth = maxCol * colWeight;
	qreal gap= qMin(sceneHeight,sceneWidth) / (maxCol + 1);

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

	Des::StateIteratorPtr stateIt = m_pDes->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));
		}

		tmpRec = pGedState->boundingRect();
		tmpPoint = pGedState->pos();
		tmpRec.getCoords(&x1,&y1,&x2,&y2);
		minX = qMin(minX,x1+tmpPoint.rx());
		minY = qMin(minY,y1+tmpPoint.ry());
		maxX = qMax(maxX,x2+tmpPoint.rx());
		maxY = qMax(maxY,y2+tmpPoint.ry());
		tmpRec = pGedState->labelboundingRect();
		tmpRec.getCoords(&x1,&y1,&x2,&y2);
		tmpPoint = pGedState->mapToScene(pGedState->labelPos());
		minX = qMin(minX,x1+tmpPoint.rx());
		minY = qMin(minY,y1+tmpPoint.ry());
		maxX = qMax(maxX,x2+tmpPoint.rx());
		maxY = qMax(maxY,y2+tmpPoint.ry());

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

	//handle transitions excluding global self-loops
	bool isNew = false;
	Des::TransIteratorPtr transIt = m_pDes->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);
		}

		//for debug
		tmpPoint = QPointF(0.0,0.0);

		tmpRec = pGedTrans->boundingRect();
		tmpRec.getCoords(&x1,&y1,&x2,&y2);
		if (pGedTrans->isSelfLoop())
		  {
         	    tmpPoint = pGedTrans->getStartState()->pos();
		  }
		else
		  {
		    tmpPoint = pGedTrans->pos();
		  }

		minX = qMin(minX,x1+tmpPoint.rx());
		minY = qMin(minY,y1+tmpPoint.ry());
		maxX = qMax(maxX,x2+tmpPoint.rx());
		maxY = qMax(maxY,y2+tmpPoint.ry());
		tmpRec = pGedTrans->labelboundingRect();
		tmpRec.getCoords(&x1,&y1,&x2,&y2);
		tmpPoint = pGedTrans->mapToScene(pGedTrans->labelPos());
		minX = qMin(minX,x1+tmpPoint.rx());
		minY = qMin(minY,y1+tmpPoint.ry());
		maxX = qMax(maxX,x2+tmpPoint.rx());
		maxY = qMax(maxY,y2+tmpPoint.ry());
	}

 	// make sure the minx,y are not still the default value
	if (minX >= DefaultMinValue)
	  minX = 0.0;
	if (minY >= DefaultMinValue)
	  minY = 0.0;

	minX = qMax(minX-10,0.0);
	minY = qMax(minY-10,0.0);
	maxX += 35;
	maxY += 35;

	//  shift the DES viewer to this point to get rid of whitespace
	m_itemOffset = QPointF(minX,minY);

	tmpRec = QRectF(QPointF(minX,minY),QPointF(maxX,maxY));
	
	QRect retRect(QPoint(ceil(minX),ceil(minY)),QPoint(ceil(maxX),ceil(maxY)));
	return retRect;
}

void SimGedViewerScene::updateView(SimCompState* pCurState, bool isRunningMode)
{
	m_pCurState = pCurState;
	m_isRunningMode = isRunningMode;

	if (!m_pCurState) { return; }

	// We are going to animate transition so until switchState happens
	// current states and events represent the events that will be taken

	SimCompState::StateTuple* stateMap = pCurState->getCurrentState();
	DesState* pState = (*stateMap)[m_pDes];
	if (!pState)
	{
		return;
	}

	m_pCurGedState = const_cast<GedDesState*>(m_pDes->getGedInfo().value(pState, 0));
	if (!m_pCurGedState)
	{
		return;
	}

	// for each transition in the state find the one that contains the event
	// used to get to it

	m_pChosenTrans = 0;

	SimCompState* prevCompState = pCurState->getPrevState();

	if (prevCompState)
	{
		DesState* prevDesState = (*prevCompState->getCurrentState())[m_pDes];
		GedDesState* prevGedState = const_cast<GedDesState*>(m_pDes->getGedInfo().value(prevDesState, 0));
		ProjectEvent* chosenEvent = prevCompState->getChoosenEvent();
		ProjectEvent::SourceIteratorPtr evtIt = chosenEvent->createSourceIterator();
		for (evtIt->first(); !evtIt->isDone(); evtIt->next())
		{
			ProjectEvent::Source src = evtIt->currentItem();

			QList<GedDesTrans*> transList = m_pCurGedState->getTrans();
			foreach (GedDesTrans* pTrans, transList)
			{
				if (pTrans->findEvent(src.event) &&
						pTrans->getEndState() == m_pCurGedState &&
						pTrans->getStartState() == prevGedState)
				{
					m_pChosenTrans = pTrans;
					m_pChosenTransType = m_pChosenTrans->getSimType();
					break;
				}
			}
		}

		if (m_pChosenTrans)
		{
			m_animCount = 0;
			//m_pAnimTimer->start(150);
			animate();
		}
		else
		{
			// The last event didn't occur in this scene, but update
			// the viewer anyway to remove the pink-colored last transition
			switchState();
			update(geometry());
		}
	}
	else
	{
		switchState();
		update(geometry());
	}
}

void SimGedViewerScene::animate()
{
	if (m_animCount > 6) {
		m_pAnimTimer->stop();
		m_animCount = 0;
		switchState();
	} else {
		if (m_pChosenTrans)
		{
			m_pChosenTrans->setSimType(m_animCount % 2 == 0
						? GedDesTrans::RegularSimTrans
						: m_pChosenTransType);
		}

		m_pAnimTimer->start(150);
	}

	m_animCount++;

	update(geometry());
}

void SimGedViewerScene::switchState()
{
	SimCompState::StateTuple* stateMap = m_pCurState->getCurrentState();
	DesState* pState = (*stateMap)[m_pDes];
	if (!pState)
	{
		return;
	}

	m_pCurGedState = const_cast<GedDesState*>(m_pDes->getGedInfo().value(pState, 0));
	if (!m_pCurGedState)
	{
		return;
	}

	std::set<ProjectEvent*>& eligEvents = m_pCurState->getEligEvents();

	ProjectEvent* pPrevEvent = m_pCurState->getReverseEvent();
	DesState* pPrevState = 0;
	if (pPrevEvent)
	{
		SimCompState* pPrevSimState = m_pCurState->getPrevState();
		SimCompState::StateTuple* prevStateMap = pPrevSimState->getCurrentState();
		pPrevState = (*prevStateMap)[m_pDes];
	}
	const GedDesState* pPrevGedState = m_pDes->getGedInfo().value(pPrevState, 0);

	ProjectEvent* pNextEvent = m_pCurState->getNextEvent();
	DesState* pNextState = 0;
	if (pNextEvent)
	{
		SimCompState* pNextSimState = m_pCurState->getNextState();
		SimCompState::StateTuple* nextStateMap = pNextSimState->getCurrentState();
		pNextState = (*nextStateMap)[m_pDes];
	}
	const GedDesState* pNextGedState = m_pDes->getGedInfo().value(pNextState, 0);

	Des::StateIteratorPtr stateIt = m_pDes->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 == m_pCurGedState)
			pGedState->setSimType(GedDesState::CurrentSimState);
		else
			pGedState->setSimType(GedDesState::RegularSimState);

		QList<GedDesTrans*> transList = pGedState->getTrans();
		foreach (GedDesTrans* pTrans, transList)
		{
			pTrans->setSimType(GedDesTrans::RegularSimTrans);

			if (m_isRunningMode && pTrans->getStartState() == m_pCurGedState)
				pTrans->setEligibleEvents(&eligEvents);
			else
				pTrans->setEligibleEvents(0);

			if (pPrevGedState && pTrans->getStartState() == pPrevGedState
					&& pTrans->getEndState() == m_pCurGedState)
				pTrans->setPrevEvent(pPrevEvent);
			else
				pTrans->setPrevEvent(0);

			if (!m_isRunningMode && pNextGedState
					&& pTrans->getEndState() == pNextGedState
					&& pTrans->getStartState() == m_pCurGedState)
				pTrans->setNextEvent(pNextEvent);
			else
				pTrans->setNextEvent(0);

			pTrans->updateLabel();
		}
	}

	//update(geometry());
}


} // end of namespace
