// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-FileCopyrightText: Copyright (c) Sandia Corporation
// SPDX-License-Identifier: BSD-3-Clause

#ifndef _pqEventDispatcher_h
#define _pqEventDispatcher_h

#include "QtTestingExport.h"

#include <QEventLoop>
#include <QObject>
#include <QTime>
#include <QTimer>

class pqEventPlayer;
class pqEventSource;

/// pqEventDispatcher is responsible for taking each "event" from the test and
/// then "playing" it using the player. The dispatcher is the critical component
/// of this playback since it decides when it's time to dispatch the next
/// "event" from the test.
/// After an event is posted, there are two options:
/// \li the default option is to simply wait for a small amount of time before
/// processing the next event. The hope is that within that time any pending
/// requests such as timers, slots connected using Qt::QueuedConnection etc.
/// will be handled and all will work well. This however it fraught with
/// problems resulting is large number of random test failures especially in
/// large and complex applications such as ParaView. In such cases the better
/// option is the second option.
/// \li we only process already posted events (using a call to
/// QApplication::processEvents() and then rely on timers being registered using
/// registerTimer(). All these timers are explicitly timed-out, if active, before
/// processing the next event.
///
/// To enable the second mode simply set the eventPlaybackDelay to 0.
/// In either mode, all timers registered using registerTimer() will be
/// timed-out before dispatching next event.
///
/// To make it easier to register timers, one can directly use pqTimer instead
/// of QTimer.
class QTTESTING_EXPORT pqEventDispatcher : public QObject
{
  Q_OBJECT
  typedef QObject Superclass;

public:
  pqEventDispatcher(QObject* parent = 0);
  ~pqEventDispatcher() override;

  /// Retrieves events from the given event source, dispatching them to
  /// the given event player for test case playback. This call blocks until all
  /// the events from the source have been played back (or failure). Returns
  /// true if playback was successful.
  bool playEvents(pqEventSource& source, pqEventPlayer& player);

  /// Set the delay between dispatching successive events. Default is set using
  /// CMake variable QT_TESTING_EVENT_PLAYBACK_DELAY.
  static void setEventPlaybackDelay(int milliseconds);
  static int eventPlaybackDelay();

  /** Wait function provided for players that need to wait for the GUI
      to perform a certain action.
      Note: the minimum wait time is 100ms. This is set to avoid timiing issues
      on slow processors that hang tests.*/
  static void processEventsAndWait(int ms);

  /** proccessEvents method for widgets and paraview to use instead of
  calling Qt version, since that will break test playback*/
  static void processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents);

  /// register a timer that needs to be ensured to have timed-out after every
  /// event dispatch.
  static void registerTimer(QTimer* timer);

  /// Disables the behavior where more test events may be dispatched
  /// if Qt starts waiting in an event loop. Warning: Setting this to
  /// true will prevent modal dialogs from functioning correctly.
  static void deferEventsIfBlocked(bool defer);

  /// Return if the Dispatcher is not playing events
  bool isPaused() const;

  /// Return Dispatcher's status
  bool status() const;

Q_SIGNALS:

  /// signal when playback starts
  void restarted();
  /// signal when playback pauses
  void paused();

protected Q_SLOTS:
  void playEventOnBlocking();

  /// Called when the mainThread is about to block.
  void aboutToBlock();

  /// Called when the mainThread wakes up.
  void awake();

public Q_SLOTS:
  /// Change the TimeStep
  void setTimeStep(int value);
  /// Method to be able to stop/pause/play the current playback script
  void run(bool value);
  void stop();
  void oneStep();

  /// Plays a single event. this->PlayBackFinished and this->PlayBackStatus are
  /// updated by this method.
  void playEvent(int indent = 0);

protected:
  bool PlayBackFinished;
  bool PlayBackPaused;
  bool PlayBackStatus;
  bool PlayBackOneStep;
  bool PlayBackStoped;
  static bool DeferMenuTimeouts;
  /// This variable says that we should not continue to process test events
  /// when the application is blocked in a Qt event loop - it is either blocked
  /// in a modal dialog or is in a long wait while also processing events
  /// (such as when waiting from Insitu server @see pqLiveInsituManager).
  static bool DeferEventsIfBlocked;

  static bool PlayingBlockingEvent;

  pqEventSource* ActiveSource;
  pqEventPlayer* ActivePlayer;
  QTimer BlockTimer;
};

#endif // !_pqEventDispatcher_h
