/* Distributed under the Apache License, Version 2.0.
See accompanying NOTICE file for details.*/
#pragma once

#include <QObject>
#include <QTextEdit>
#include <QDoubleSpinBox>
#include <QComboBox>

#include "pulse/cdm/CommonDefs.h"
#include "pulse/cdm/engine/SEAction.h"
#include "pulse/cdm/engine/SEAdvanceHandler.h"
#include "pulse/engine/PulseScenario.h"
#include "pulse/engine/PulseConfiguration.h"

enum class ExplorerMode { Editor = 0, Showcase };

struct PulseExplorerBuildInformation
{
  static std::string Time();
  static std::string Hash();
  static std::string Version();
};

class PulseListener
{
public:
  // This is called at the end of stabilizing a patient via the editor
  virtual void AtSteadyState(PhysiologyEngine& pulse) = 0; // Don't modify UI in here!!
  // This is where we take data that we pulled from pulse at steady state and do anything to our UI based on it
  virtual  void AtSteadyStateUpdateUI() = 0; //This is called only if you are stabilizing a new patient
  virtual void ProcessPhysiology(PhysiologyEngine& pulse) = 0; // Don't modify UI in here!!
  // This is where we pull data from pulse, and push any actions to it
  // This is where we take data that we pulled from pulse and do anything to our UI based on it
  virtual void PhysiologyUpdateUI(const std::vector<SEAction const*>& actions) = 0; // This is called every time step (after stabilization || state load)
  // Handle a Pulse Error that should stop execution
  virtual void EngineErrorUI() = 0;
};

class QPulse : public QObject, public SEAdvanceHandler
{
  Q_OBJECT
  friend class Worker;
public:
  QPulse(QThread& thread, QTextEdit* log);
  virtual ~QPulse();

  PhysiologyEngine& GetEngine();

  ExplorerMode GetMode();
  void SetMode(ExplorerMode m);

  void ScrollLogBox();
  void LogToGUI(QString msg);
  void IgnoreAction(const std::string& name);

  void Clear();
  void Reset();
  void Start();
  void Stop();
  bool IsPaused();
  bool IsRunning();
  bool ToggleRealtime();//return true=yes
  bool PlayPause();//return true=paused

  ///@{
  /// Can only be safely called before pulse is running, not threadsafe
  void RegisterListener(PulseListener* listener);
  void RemoveListener(PulseListener* listener);
  ///@}

  double GetTimeStep_s();
  void OnAdvance(double time_s) override;

  std::string GetEngineStateFilename();
  void SetEngineStateFilename(std::string const& filename);
  std::string GetPatientFilename();
  void SetPatientFilename(std::string const& filename);
  std::string GetScenarioFilename();
  void SetScenarioFilename(std::string const& filename);

  PulseScenario& GetScenario();
  // If you want to stabilize to a new patient (required for stabilization)
  SEPatientConfiguration& GetPatientConfiguration();
  // If you want to modify one or more advanced Pulse internals
  PulseConfiguration& GetPulseConfiguration();

  void AddScenarioAction(ushort time_s, SEAction const&);

  static QString GetDataDir();
  static QString GetSaveDir();
  static QString GetSaveStatesDir();
  static QString GetSavePatientsDir();
  static QString GetSaveScenariosDir();
  static QString GetSaveEnvironmentsDir();
  static QString AttemptRelativePath(QString filepath);

protected:
  void AdvanceTime();

signals:
  void AtSteadyState();
  void Advanced(std::vector<SEAction const*> actions);
  void EngineError();
protected slots :
  void AtSteadyStateUpdateUI();
  void PhysiologyUpdateUI(std::vector<SEAction const*> actions);
  void EngineErrorUI();

private:
  class Controls;
  Controls* m_Controls;
};

class Worker : public QObject
{
  Q_OBJECT
public:
  Worker(QPulse& qpulse) : _qpulse(qpulse) {};
  virtual ~Worker() {}

public slots:
  void Work();

protected:
  QPulse& _qpulse;
};