/****************************************************************************

 Copyright (C) 2002-2014 Gilles Debunne. All rights reserved.

 This file is part of the QGLViewer library version 2.6.3.

 http://www.libqglviewer.com - contact@libqglviewer.com

 This file may be used under the terms of the GNU General Public License 
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the LICENSE file included in the packaging of this file.
 In addition, as a special exception, Gilles Debunne gives you certain 
 additional rights, described in the file GPL_EXCEPTION in this package.

 libQGLViewer uses dual licensing. Commercial/proprietary software must
 purchase a libQGLViewer Commercial License.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

#include "eventRecorder.h"
#include "replayInterface.h"
#include "viewer.h"
#include <qmessagebox.h>
#include <qspinbox.h>
#include <qcombobox.h>
#include <qradiobutton.h>
#include <qfiledialog.h>
#include <qlineedit.h>
#include <qpushbutton.h>
#include <qlabel.h>
#include <qpoint.h>

using namespace qglviewer;

/*! Creates an EventRecorder associated to the provided \c qglviewer.

  All \p qglviewer events will then be filtered by the created EventRecorder. See \c
  QObject::installEventFilter() for details. */
EventRecorder::EventRecorder(Viewer* const qglviewer)
  : qglviewer_(qglviewer), eventRecords_(25000)
{
  if (!qglviewer)
    {
      qWarning("NULL QGLViewer in EventRecorder constructor");
      return;
    }

  qglviewer->installEventFilter(this);

  eventIndex_ = 0;
  isRecording_ = false;
  recordDuration_ = 0;

  setCameraIsRestored();
  setManipulatedFrameIsRestored();
  setSavesSnapshots();
  setSnapshotFrameRate(25);
  setSaveAtGivenFrameRate(true);
  setReplaySpeed(1.0);

  predefinedSettings_.push_back(PredefinedSettings(0,   0,   0));  // Custom, dummy values
  predefinedSettings_.push_back(PredefinedSettings(720, 576, 25)); // PAL
  predefinedSettings_.push_back(PredefinedSettings(720, 480, 30)); // NTSC
  predefinedSettings_.push_back(PredefinedSettings(640, 480, 20)); // VGA
  predefinedSettings_.push_back(PredefinedSettings(360, 288, 12)); // 1/2 PAL
  predefinedSettings_.push_back(PredefinedSettings(360, 240, 15)); // 1/2 NTSC
  predefinedSettings_.push_back(PredefinedSettings(320, 240, 10)); // 1/2 VGA
  predefinedSettings_.push_back(PredefinedSettings(160, 120, 8));  // 1/4 VGA
}

/*! Destructor deletes all recorded events. */
EventRecorder::~EventRecorder()
{
  if (replayInterface_)
    delete replayInterface_;

  for (QValueVector<Event>::iterator it=eventRecords_.begin(), end=eventRecords_.end(); it != end; ++it)
    delete (*it).event.keyEvent; // or any other pointer
}

EventRecorder::FrameState::FrameState(Frame* const fr)
  : frame(fr)
{
  state.setTranslation(fr->position());
  state.setRotation(fr->orientation());
}

/*! Calls startRecording() or stopRecording() depending on whether the EventRecorder isRecording()
  or not.

  This method is called by pressing 'R'. */
void EventRecorder::toggleRecording()
{
  if (isRecording())
    stopRecording();
  else
    startRecording();
}

/*! Starts recording events for the associated qglviewer(). The events are recorded using
  eventFilter(), recordCustomEvent() and recordFrameState().

  All previously recorded events are lost. Use saveEventRecord() to save current event recording.

  The \e current QGLViewer::camera() and QGLViewer::manipulatedFrame() (if any) state is stored, so
  that it can be restored at the next call to replay(). See cameraIsRestored() and
  manipulatedFrameIsRestored().

  Use stopRecording() or toggleRecording() to stop recording and isRecording() to know current
  state. */
void EventRecorder::startRecording()
{
  eventIndex_ = 0;
  isRecording_ = true;
  initialCameraFrame_.setPosition(qglviewer()->camera()->frame()->position());
  initialCameraFrame_.setOrientation(qglviewer()->camera()->frame()->orientation());
  if (qglviewer()->manipulatedFrame())
    {
      initialManipulatedFrame_.setPosition(qglviewer()->manipulatedFrame()->position());
      initialManipulatedFrame_.setOrientation(qglviewer()->manipulatedFrame()->orientation());
    }
  viewerWidth_  = qglviewer()->width();
  viewerHeight_ = qglviewer()->height();

  time_.start();
}

/*! Stops a recording initiated with startRecording() or toggleRecording(). */
void EventRecorder::stopRecording()
{
  isRecording_ = false;
  recordDuration_ = time_.elapsed();

  // Check that last record was the TOGGLE_RECORDING key press.
  // Give it a fake time so that it is never replayed back.
  if (eventRecords_[eventIndex_-1].type == QEvent::KeyPress)
    {
      eventRecords_[eventIndex_-1].time = recordDuration_+10000;
      eventIndex_--;
    }
}

/*! Reimplementation of the \c QObject function. Always returns \p false (meaning event is \e not
  handled and should be forwarded to the \p qglviewer).

  When EventRecorder isRecording(), the following \c QEvent::type are recorded in an internal data
  structure: \c KeyPress, \c KeyRelease, \c MouseButtonPress, \c MouseButtonRelease, \c
  MouseButtonDblClick, \c MouseMove, \c Wheel and \c Timer (used by QGLViewer::animate()). Does
  nothing when isRecording() is \c false.

  You may overload this method to filter other \c QEvent::type (your implementation should probably
  call this function).

  Other specific events can be recorded using recordFrameState() and recordCustomEvent(). */
bool EventRecorder::eventFilter(QObject *, QEvent *e)
{
  if (isRecording())
    {
      bool record = true;
      switch (e->type())
	{
	case QEvent::KeyPress:
	case QEvent::KeyRelease:
	  {
	    QKeyEvent* ke = (QKeyEvent*)(e);
	    eventRecords_[eventIndex_].event.keyEvent = new QKeyEvent(e->type(), ke->key(), ke->ascii(), int(ke->state()));
	    break;
	  }
	case QEvent::MouseButtonPress:
	case QEvent::MouseButtonRelease:
	case QEvent::MouseButtonDblClick:
	case QEvent::MouseMove:
	  {
	    QMouseEvent* me = (QMouseEvent*)(e);
	    eventRecords_[eventIndex_].event.mouseEvent = new QMouseEvent(e->type(), me->pos(), int(me->button()), int(me->state()));
	    break;
	  }
	case QEvent::Wheel:
	  {
	    QWheelEvent* we = (QWheelEvent*)(e);
	    eventRecords_[eventIndex_].event.wheelEvent = new QWheelEvent(we->pos(), we->delta(), int(we->state()));
	    break;
	  }
	case QEvent::Timer:
	  {
	    eventRecords_[eventIndex_].event.keyEvent = NULL; // or any other pointer
	    break;
	  }
	default:
	    record = false;
	  break;
	}

      if (record)
	{
	  eventRecords_[eventIndex_].type = e->type();
	  eventRecords_[eventIndex_].time = time_.elapsed();
	  eventIndex_++;
	}
    }
  return false;
}

/*! Saves current recorded events in a file in XML format. If no \p filename is provided (default),
  opens a file dialog to ask for a name. See also loadEventRecord(). */
void EventRecorder::saveEventRecord(const QString& filename) const
{
  QString name;
  bool ok = false;
  if (filename.isEmpty())
    while (!ok)
      {
	name = QFileDialog::getSaveFileName();
	if (name.isEmpty()) // means cancel
	  return;

	ok = true;

	QFileInfo info(name);
	if (info.exists())
	  ok = (QMessageBox::warning(qglviewer(),"Overwrite file ?",
				     "File "+info.fileName()+" already exists.\nSave anyway ?",
				     QMessageBox::Yes,
				     QMessageBox::Cancel) == QMessageBox::Yes);
      }
  else
    name = filename;

  // Create the DOM document
  QDomDocument doc("EVENTRECORDER");

  QDomElement de = doc.createElement("EventRecorder");
  de.setAttribute("nbEvents", eventIndex_);
  de.setAttribute("duration", recordDuration_);

  QDomElement replay = doc.createElement("Replay");
  replay.setAttribute("restoreCamera", (cameraIsRestored()?"true":"false"));
  replay.setAttribute("restoreManipulatedFrame", (manipulatedFrameIsRestored()?"true":"false"));
  replay.setAttribute("savesSnapshot", (savesSnapshots()?"true":"false"));
  replay.setAttribute("saveAtGivenFrameRate", (saveAtGivenFrameRate()?"true":"false"));
  replay.setAttribute("replaySpeed", replaySpeed());
  replay.appendChild(initialCameraFrame_.domElement("InitialCamera", doc));
  replay.appendChild(initialManipulatedFrame_.domElement("InitialManipulatedFrame", doc));

  QDomElement viewer = doc.createElement("Viewer");
  viewer.setAttribute("width", viewerWidth_);
  viewer.setAttribute("height", viewerHeight_);
  replay.appendChild(viewer);

  de.appendChild(replay);

  QDomElement events = doc.createElement("Events");
  for (int i=0; i<eventIndex_; ++i)
    {
      QDomElement event = doc.createElement("event");

      switch (eventRecords_[i].type)
	{
	case QEvent::KeyPress:
	case QEvent::KeyRelease:
	  event.setAttribute("key",   eventRecords_[i].event.keyEvent->key());
	  event.setAttribute("ascii", eventRecords_[i].event.keyEvent->ascii());
	  event.setAttribute("state", int(eventRecords_[i].event.keyEvent->state()));
	  break;
	case QEvent::MouseButtonPress:
	case QEvent::MouseButtonRelease:
	case QEvent::MouseButtonDblClick:
	case QEvent::MouseMove:
	  event.setAttribute("posx",   eventRecords_[i].event.mouseEvent->x());
	  event.setAttribute("posy",   eventRecords_[i].event.mouseEvent->y());
	  event.setAttribute("button", int(eventRecords_[i].event.mouseEvent->button()));
	  event.setAttribute("state",  int(eventRecords_[i].event.mouseEvent->state()));
	  break;
	case QEvent::Wheel:
	  event.setAttribute("posx",  eventRecords_[i].event.wheelEvent->x());
	  event.setAttribute("posy",  eventRecords_[i].event.wheelEvent->y());
	  event.setAttribute("delta", int(eventRecords_[i].event.wheelEvent->delta()));
	  event.setAttribute("state", int(eventRecords_[i].event.wheelEvent->state()));
	  break;
	case QEvent::Timer:
	  break;
	case QEvent::MaxUser: // Actually means Frame state
	  {
	    QString frameName = "unknown";
	    if (eventRecords_[i].event.frameState->frame == qglviewer()->camera()->frame())
	    frameName = "camera";
	    else if ((eventRecords_[i].event.frameState->frame == qglviewer()->manipulatedFrame()) && (qglviewer()->manipulatedFrame()))
	      frameName = "manipulatedFrame";
	    event = eventRecords_[i].event.frameState->state.domElement("event", doc);
	    event.setAttribute("name", frameName);
	    break;
	  }
	case QEvent::User:
	  event.setAttribute("id", (int)(eventRecords_[i].event.frameState));
	  break;
	default:
	  qWarning("Unknown event type " + QString::number(eventRecords_[i].type));
	  break;
	}
      event.setAttribute("time", eventRecords_[i].time);
      event.setAttribute("type", eventRecords_[i].type);
      events.appendChild(event);
    }

  de.appendChild(events);
  doc.appendChild(de);

  // Write the DOM tree to file
  QFile f(name);
  if (f.open(IO_WriteOnly))
    {
      QTextStream out(&f);
      doc.save(out, 2);
      f.flush();
      f.close();
    }
  else
#if QT_VERSION < 0x030200
    QMessageBox::warning(qglviewer(), "Save to file error", "Unable to save to file "+name);
#else
  QMessageBox::warning(qglviewer(), "Save to file error", "Unable to save to file "+name+":\n"+f.errorString());
#endif
}


/*! Loads a previously recorded event list from a file. If no \p filename is provided (default), opens a file
  dialog to select a file. See also saveEventRecord().

  \attention The loaded event record is independent of the viewer and of the scene. Trying to
  replay() an event list recorded in an other context is most likely to produce unexpected results.
  Only the initial camera and manipulatedFrame (if any) states are loaded back and can be restored (see
  cameraIsRestored() and manipulatedFrameIsRestored()). */
void EventRecorder::loadEventRecord(const QString& filename)
{
  QString name;
  if (filename.isEmpty())
    {
      name = *((QFileDialog::getOpenFileNames()).begin());
      if (name.isEmpty()) // means cancel
	return;
    }
  else
    name = filename;

  // Create the DOM document
  QDomDocument doc("EVENTRECORDER");

  // Write the DOM tree to file
  QFile f(name);
  if (f.open(IO_ReadOnly) == true)
    {
      doc.setContent(&f);
      f.close();
      // Parse the DOM tree
      QDomElement root = doc.documentElement();

      eventIndex_ = (root.attribute("nbEvents", "0")).toInt();
      recordDuration_ = (root.attribute("duration", "0")).toInt();

      QDomElement child=root.firstChild().toElement();
      while (!child.isNull())
	{
	  if (child.tagName() == "Replay")
	    {
	      setCameraIsRestored((child.attribute("restoreCamera", "true") == "true")?true:false);
	      setManipulatedFrameIsRestored((child.attribute("restoreManipulatedFrame", "true") == "true")?true:false);
	      setSavesSnapshots((child.attribute("savesSnapshots", "true") == "true")?true:false);
	      setSaveAtGivenFrameRate((child.attribute("saveAtGivenFrameRate", "true") == "true")?true:false);
	      setReplaySpeed(child.attribute("replaySpeed", "1.0").toFloat());

	      QDomElement subChild=child.firstChild().toElement();
	      while (!subChild.isNull())
		{
		  if (subChild.tagName() == "InitialCamera")
		    initialCameraFrame_.initFromDOMElement(subChild);
		  if (subChild.tagName() == "InitialManipulatedFrame")
		    initialManipulatedFrame_.initFromDOMElement(subChild);
		  if (subChild.tagName() == "Viewer")
		    {
		      viewerWidth_  = subChild.attribute("width",  QString::number(qglviewer()->width())).toInt();
		      viewerHeight_ = subChild.attribute("height", QString::number(qglviewer()->height())).toInt();
		    }

		  subChild = subChild.nextSibling().toElement();
		}
	    }

	  if (child.tagName() == "Events")
	    {
	      int count = 0;
	      QDomElement event=child.firstChild().toElement();
	      while (!event.isNull())
		{
		  eventRecords_[count].type = QEvent::Type((event.attribute("type", "0")).toInt());
		  eventRecords_[count].time = (event.attribute("time", "0")).toInt();

		  switch (eventRecords_[count].type)
		    {
		    case QEvent::KeyPress:
		    case QEvent::KeyRelease:
		      {
			int key = (event.attribute("key", "0")).toInt();
			int ascii = (event.attribute("ascii", "0")).toInt();
			int state = (event.attribute("state", "0")).toInt();
			eventRecords_[count].event.keyEvent = new QKeyEvent(eventRecords_[count].type, key, ascii, state);
			break;
		      }
		    case QEvent::MouseButtonRelease:
		    case QEvent::MouseButtonDblClick:
		    case QEvent::MouseMove:
		      {
			int posx = (event.attribute("posx", "0")).toInt();
			int posy = (event.attribute("posy", "0")).toInt();
			int button = (event.attribute("button", "0")).toInt();
			int state = (event.attribute("state", "0")).toInt();
			eventRecords_[count].event.mouseEvent = new QMouseEvent(eventRecords_[count].type, QPoint(posx, posy), button, state);
			break;
		      }
		    case QEvent::Wheel:
		      {
			int posx = (event.attribute("posx", "0")).toInt();
			int posy = (event.attribute("posy", "0")).toInt();
			int delta = (event.attribute("delta", "0")).toInt();
			int state = (event.attribute("state", "0")).toInt();
			eventRecords_[count].event.wheelEvent = new QWheelEvent(QPoint(posx, posy), delta, state);
			break;
		      }
		    case QEvent::Timer:
		      eventRecords_[count].event.wheelEvent = NULL;
		      break;
		    case QEvent::MaxUser: // Actually means Frame state
		      {
			QString frameName = event.attribute("name", "unknown");
			if (frameName == "camera")
			  eventRecords_[count].event.frameState = new EventRecorder::FrameState(qglviewer()->camera()->frame());
			else
			  if ((frameName == "manipulatedFrame") && (qglviewer()->manipulatedFrame()))
			    eventRecords_[count].event.frameState = new EventRecorder::FrameState(qglviewer()->manipulatedFrame());
			  else
			    qWarning("Unable to affect Frame state event record");
			eventRecords_[count].event.frameState->state.initFromDOMElement(event);
			break;
		      }
		    case QEvent::User:
		      {
			int id = (event.attribute("id", "0")).toInt();
			eventRecords_[count].event.frameState = (FrameState*)(id);
			break;
		      }
		    default:
		      qWarning("Unknown event type " + QString::number(eventRecords_[count].type));
		      break;
		    }
		  count++;
		  event = event.nextSibling().toElement();
		}
	    }
	  child = child.nextSibling().toElement();
	}
    }
  else
#if QT_VERSION < 0x030200
    QMessageBox::warning(qglviewer(), "Open file error", "Unable to open file "+name);
#else
  QMessageBox::warning(qglviewer(), "Open file error", "Unable to open file "+name+":\n"+f.errorString());
#endif
}

/*! Use this method to make the EventRecorder record Frame related events that are not generated by
a keyboard or mouse event. It is needed when a Frame (or a ManipulatedFrame(), or any derived class)
is modified by external events, such as a QTimer. These events will not be properly reproduced using
the standard keyboard and mouse events replay.

A typical example is when a Frame is interpolated using a KeyFrameInterpolator (which regularily
updates the Frame position). To record these update events you would use:
/code
connect(myFrame, SIGNAL( interpolated() ), myViewer->eventRecorder(), SLOT(recordFrameState()));
/encode

Each time the Frame is interpolated, the recordFrameState() method will record the Frame position
and orientation, so that it can be restored back during replay().

Note that the QGLViewer::camera() and QGLViewer::manipulatedFrame() Frame::interpolated() and
SpinningFrame::spinned() signals are automatically connected to this method.

Use recordCustomEvent() to record arbitrary events. Keyboard and mouse events are recorded by
eventFilter(). */
void EventRecorder::recordFrameState()
{
  if (isRecording())
    {
      const Frame* frame = dynamic_cast<const Frame *>(sender());

      if (!frame)
	{
	  qWarning("Unable to cast to Frame *");
	  return;
	}

      eventRecords_[eventIndex_].type = QEvent::MaxUser; // Dummy
      eventRecords_[eventIndex_].time = time_.elapsed();
      eventRecords_[eventIndex_].event.frameState = new EventRecorder::FrameState(const_cast<Frame *>(frame));
      eventIndex_++;
    }
}

/*! Use this method to record a very specific event that should be reproduced during replay().

Standard mouse and keyboard events are recorded using eventFilter(). Frame (and derived classes)
modification can be recorded using recordFrameState(). This function is dedicated to other cases.

The \p id parameter will be used to differentiate the different custom events during replay().
During replay(), the replayCustomEvent() signal will be emitted when a custom event needs to be
reproduced. Connect this signal to slots that are able to reproduce this event. */
void EventRecorder::recordCustomEvent(int id)
{
  if (isRecording())
    {
      eventRecords_[eventIndex_].type = QEvent::User;
      eventRecords_[eventIndex_].time = time_.elapsed();
      eventRecords_[eventIndex_].event.frameState = (FrameState*)(id);
      eventIndex_++;
    }
}

void EventRecorder::changeImageSize(int i)
{
  if (i==0)
    {
      replayInterface_->imageWidth->setValue(originalWidth_);
      replayInterface_->imageHeight->setValue(originalHeight_);
    }
  else
    {
      replayInterface_->imageWidth->setValue(predefinedSettings_[i].width);
      replayInterface_->imageHeight->setValue(predefinedSettings_[i].height);
      replayInterface_->snapshotFrameRate->setValue(predefinedSettings_[i].frameRate);
    }

  qglviewer()->resize(replayInterface_->imageWidth->value(), replayInterface_->imageHeight->value());
  qglviewer()->updateGL();
}

int EventRecorder::predefinedFormat()
{
  for (unsigned int i=1; i<predefinedSettings_.size(); ++i)
    {
      if ((qglviewer()->width()  == predefinedSettings_[i].width) &&
	  (qglviewer()->height() == predefinedSettings_[i].height))
	{
	  replayInterface_->snapshotFrameRate->setValue(predefinedSettings_[i].frameRate);
	  return i;
	}
    }
  return 0;
}

void EventRecorder::selectSnapshotName()
{
  QString name = QFileDialog::getSaveFileName();
  if (name.isEmpty()) // cancel
    return;

  name.replace(QDir::currentDirPath()+"/", "");
  replayInterface_->snapshotFileName->setText(name);
}

/*! Opens an EventRecorder dedicated interface window. All the EventRecorder parameters can be tuned.
  The qglviewer() size can be modified to fit TV standards (useful to create movies from snapshots),
  you can saveEventRecord() and loadEventRecord() and replay() the events.

  This method is called by the associated qglviewer() by pressing 'Shift+R'.

  See also cameraIsRestored(), manipulatedFrameIsRestored() and savesSnapshots(). */
void EventRecorder::openReplayInterfaceWindow()
{
  if (!replayInterface_)
    {
      replayInterface_ = new ReplayInterface(qglviewer());
      connect(replayInterface_->imageSize, SIGNAL( activated(int) ), SLOT( changeImageSize(int) ));
      connect(replayInterface_->browseButton, SIGNAL( released() ), SLOT( selectSnapshotName() ));
      connect(replayInterface_->saveButton, SIGNAL( released() ), SLOT( saveEventRecord() ));
      connect(replayInterface_->loadButton, SIGNAL( released() ), SLOT( loadEventRecord() ));
    }

  // Image size settings
  originalWidth_ = qglviewer()->width();
  originalHeight_ = qglviewer()->height();
  replayInterface_->imageWidth->setValue(originalWidth_);
  replayInterface_->imageHeight->setValue(originalHeight_);
  replayInterface_->imageSize->setCurrentItem(predefinedFormat());
  replayInterface_->imageQuality->setValue(qglviewer()->snapshotQuality());

  // Other settings
  replayInterface_->restoreCamera->setChecked(cameraIsRestored());
  replayInterface_->restoreManip->setChecked(manipulatedFrameIsRestored());
  replayInterface_->savesSnapshots->setChecked(savesSnapshots());
  replayInterface_->snapshotFrameRate->setValue(snapshotFrameRate());
  replayInterface_->saveAtGivenFrameRate->setChecked(saveAtGivenFrameRate());
  replayInterface_->replaySpeed->setValue(int(100.0*replaySpeed()));

  if (eventIndex_ > 0)
    {
      replayInterface_->replayText->setText("Current record lasts for " + QString::number(recordDuration_/1000.0, 'f', 1)
					    + " seconds (" + QString::number(eventIndex_) + " events).");
      replayInterface_->saveButton->setEnabled(true);
      replayInterface_->replayButton->setEnabled(true);
    }
  else
    {
      replayInterface_->replayText->setText("No scenario has been recorded.");
      replayInterface_->saveButton->setEnabled(false);
      replayInterface_->replayButton->setEnabled(false);
    }

  if (replayInterface_->exec() == QDialog::Accepted)
    {
      qglviewer()->setSnapshotFileName(replayInterface_->snapshotFileName->text());
      qglviewer()->setSnapshotFormat(replayInterface_->fileFormat->currentText());
      qglviewer()->setSnapshotCounter(replayInterface_->firstNumber->value());
      qglviewer()->setSnapshotQuality(replayInterface_->imageQuality->value());

      setCameraIsRestored(replayInterface_->restoreCamera->isChecked());
      setManipulatedFrameIsRestored(replayInterface_->restoreManip->isChecked());
      setSavesSnapshots(replayInterface_->savesSnapshots->isChecked());
      setSnapshotFrameRate(replayInterface_->snapshotFrameRate->value());
      setSaveAtGivenFrameRate(replayInterface_->saveAtGivenFrameRate->isChecked());
      setReplaySpeed(replayInterface_->replaySpeed->value() / 100.0);

      replay();
    }
}

/*! Replays the events that were recorded from the qglviewer() between the previous calls to
  startRecording() and stopRecording() (or from a scenario re-loaded from a file using
  loadEventRecord()).

  If cameraIsRestored() (resp. manipulatedFrameIsRestored()) is \c true, the QGLViewer::camera()
  (resp. QGLViewer::manipulatedFrame()) state is first restored to the position and orientation it
  had when startRecording() was called.

  The qglviewer() resetBeforeReplay() function is then called, so that the other scene and viewer
  states could be reset to their state when startRecording() was called.

  The recorded events are then played back:

  \arg If savesSnapshots(), a snapshot is generated at the snapshotFrameRate() frequency while
  events are played back. Note that the resulting replay speed may differ from the original one
  because of the calls to QGLViewer::savesSnapshots(). No events are lost anyway, since the replay
  is "paused" during snapshot savings. Modify the QGLViewer::snapshotQuality() to tune image quality
  and size (this can be done through the replay interface, see openReplayInterfaceWindow()). When
  saveAtGivenFrameRate() is \c false, a snapshot is only generated after each redraw of the
  qglviewer().

  \arg If savesSnapshots() is \c false, the recorded events are simply played back, at the same
  speed than during the recording (at a precision of a few milliseconds). This is useful to check
  the scenario before it is saved or rendered to image files using savesSnapshots(). */
void EventRecorder::replay()
{
  if (isRecording())
    return;

  bool updateGLNeeded = false;
  if (cameraIsRestored())
    {
      qglviewer()->camera()->frame()->setPosition(initialCameraFrame_.position());
      qglviewer()->camera()->frame()->setOrientation(initialCameraFrame_.orientation());
      updateGLNeeded = true;
    }

  if ((manipulatedFrameIsRestored()) && (qglviewer()->manipulatedFrame()))
    {
      qglviewer()->manipulatedFrame()->setPosition(initialManipulatedFrame_.position());
      qglviewer()->manipulatedFrame()->setOrientation(initialManipulatedFrame_.orientation());
      updateGLNeeded = true;
    }

  if (updateGLNeeded)
    qglviewer()->updateGL();

  qglviewer()->resize(viewerWidth_, viewerHeight_);
  qglviewer()->resetBeforeReplay();

  nextReplayEvent_ = 0;
  nextEventIsSaveSnapshot_ = false;

  if (savesSnapshots())
    {
      if (saveAtGivenFrameRate())
	{
	  nextEventIsSaveSnapshot_ = true;
	  QTimer::singleShot(0, this, SLOT(triggerNextEvent()));
	}
      else
	{
	  connect(qglviewer(), SIGNAL(drawFinished()), this, SLOT(saveNumberedSnapshot()));
	  QTimer::singleShot(eventRecords_[nextReplayEvent_].time, this, SLOT(triggerNextEvent()));
	}
    }
  else
    QTimer::singleShot(eventRecords_[nextReplayEvent_].time, this, SLOT(triggerNextEvent()));

  time_.start();
}

void EventRecorder::saveNumberedSnapshot() const
{
  qglviewer()->saveSnapshot(true, true);
}

void EventRecorder::triggerNextEvent()
{
  replayNextEvent();

  // Speed

  int nextTime;
  if ( (eventRecords_[nextReplayEvent_].time > nextSnapshotTime_) && savesSnapshots() && saveAtGivenFrameRate() )
    {
      nextEventIsSaveSnapshot_ = true;
      nextTime = nextSnapshotTime_;
    }
  else
    {
      nextEventIsSaveSnapshot_ = false;
      nextTime = eventRecords_[nextReplayEvent_].time;
    }

  if (nextTime <= recordDuration_)
    // QTimer::singleShot(nextTime-time-.elapsed(), this, SLOT(triggerNextEvent()));
    QTimer::singleShot(int((nextTime-time_.elapsed()) / replaySpeed()), this, SLOT(triggerNextEvent()));
  else
    if (savesSnapshots())
      {
	if (!saveAtGivenFrameRate())
	  disconnect(qglviewer(), SIGNAL(drawFinished()), this, SLOT(saveNumberedSnapshot()));

	QMessageBox::information(qglviewer(), "Replay complete", "Replay complete\n");
      }
    else
      qglviewer()->updateGL();
}

void EventRecorder::replayNextEvent()
{
  if (nextEventIsSaveSnapshot_)
    {
      qglviewer()->saveSnapshot(true, true);
      nextSnapshotTime_ += 1000 / snapshotFrameRate();
    }
  else
    {
      switch (eventRecords_[nextReplayEvent_].type)
	{
	case QEvent::KeyPress:		  qglviewer()->keyPressEvent(eventRecords_[nextReplayEvent_].event.keyEvent);	break;
	case QEvent::KeyRelease:	  qglviewer()->keyReleaseEvent(eventRecords_[nextReplayEvent_].event.keyEvent);	break;
	case QEvent::MouseButtonPress:	  qglviewer()->mousePressEvent(eventRecords_[nextReplayEvent_].event.mouseEvent);	break;
	case QEvent::MouseButtonRelease:  qglviewer()->mouseReleaseEvent(eventRecords_[nextReplayEvent_].event.mouseEvent);	break;
	case QEvent::MouseButtonDblClick: qglviewer()->mouseDoubleClickEvent(eventRecords_[nextReplayEvent_].event.mouseEvent);	break;
	case QEvent::MouseMove:		  qglviewer()->mouseMoveEvent(eventRecords_[nextReplayEvent_].event.mouseEvent);	break;
	case QEvent::Wheel:		  qglviewer()->wheelEvent(eventRecords_[nextReplayEvent_].event.wheelEvent);	break;
	case QEvent::Timer:		  qglviewer()->animate(); qglviewer()->updateGL();				break;
	case QEvent::MaxUser: // Actually means Frame state
	  eventRecords_[nextReplayEvent_].event.frameState->frame->setPosition(eventRecords_[nextReplayEvent_].event.frameState->state.translation());
	  eventRecords_[nextReplayEvent_].event.frameState->frame->setOrientation(eventRecords_[nextReplayEvent_].event.frameState->state.rotation());
	  qglviewer()->updateGL();
	  break;
	case QEvent::User:
	  Q_EMIT replayCustomEvent((int)(eventRecords_[nextReplayEvent_].event.frameState));
	  break;
	default:
	  qWarning("Unknown event type " + QString::number(eventRecords_[nextReplayEvent_].type));
	  break;
	}
      nextReplayEvent_++;
    }
}
