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

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

   This file is part of the QGLViewer library version 2.3.5.

   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_++;
  }
}
