/* Distributed under the Apache License, Version 2.0.
See accompanying NOTICE file for details.*/
#include <QApplication>
#include <QDockWidget>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QLayout>
#include <QMainWindow>
#include <QWidget>
#include <QPlainTextEdit>
#include <QFile>
#include <QDoubleSpinBox>
#include <QGroupBox>
#include <QScrollBar>
#include <QTimer>
#include <QThread>
#include <QPointer>
#include <QCloseEvent>
#include <QMessageBox>
#include <iomanip>


#include "MainExplorerWindow.h"
#include "ui_MainExplorerWindow.h"

#include "DynamicControlsWidget.h"
#include "controls/QPulse.h"
#include "controls/ScenarioSaveDialog.h"
#include "controls/VitalsMonitorWidget.h"

#include "pulse/engine/PulseEngine.h"
#include "pulse/engine/PulseScenario.h"
#include "pulse/cdm/engine/SEDataRequestManager.h"
#include "pulse/cdm/engine/SEEngineTracker.h"
#include "pulse/cdm/engine/SEPatientConfiguration.h"
#include "pulse/cdm/engine/SESerializeState.h"
#include "pulse/cdm/patient/SEPatient.h"
#include "pulse/cdm/properties/SEScalarTime.h"
#include "pulse/cdm/utils/FileUtils.h"
#include "QLabel"

namespace
{
int findTab(QTabWidget* widget, QString name)
{
    int result = -1;
    for (int i = 0; i <widget->count(); ++i)
    {
        if (widget->tabText(i) == name)
        {
            result = i;
            break;
        }
    }
    return result;
}
}

class MainExplorerWindow::Controls : public Ui::MainExplorerWindow
{
public:

  virtual ~Controls()
  {
    delete VitalsMonitorWidget;
    delete DynamicControls;
    delete Pulse;
  }

  QPulse*                           Pulse=nullptr;
  QPointer<QThread>                 Thread;
  QVitalsMonitorWidget*             VitalsMonitorWidget=nullptr;
  QDynamicControlsWidget*           DynamicControls=nullptr;
  std::stringstream                 Status;
  double                            CurrentSimTime_s;
};

MainExplorerWindow::MainExplorerWindow()
{
  m_Controls = new Controls();
  m_Controls->setupUi(this);
  setWindowIcon(QIcon("resource/pulse.ico"));
  // This is the widget all input widgets will use
  m_Controls->InputWidget->show();
  m_Controls->InputWidget->raise();
  m_Controls->InputWidget->setVisible(true);
  m_Controls->InputLayout->setAlignment(Qt::AlignHCenter);

  // This is the logger widget
  m_Controls->OutputWidget->show();
  m_Controls->OutputWidget->raise();
  m_Controls->OutputWidget->setVisible(true);
  m_Controls->LogBox->setFontPointSize(10);

  m_Controls->Thread = new QThread(parent());
  m_Controls->Pulse = new QPulse(*m_Controls->Thread, m_Controls->LogBox);
  m_Controls->Pulse->RegisterListener(this);
  m_Controls->Status << "Current Simulation Time : 0s";

  // Add the IDynamic Controls to the main control area
  m_Controls->DynamicControls = new QDynamicControlsWidget(*m_Controls->Pulse, this);
  m_Controls->DynamicControls->setTitleBarWidget(new QWidget());
  m_Controls->DynamicControls->setVisible(false);
  m_Controls->InputLayout->addWidget(m_Controls->DynamicControls);

  // Order is Vitals, DataRequests, Environment, Scenario
  m_Controls->TabWidget->removeTab(0);
  m_Controls->VitalsMonitorWidget = new QVitalsMonitorWidget(this);
  m_Controls->TabWidget->widget(0)->layout()->addWidget(m_Controls->VitalsMonitorWidget);
  m_Controls->TabWidget->setCurrentIndex(0);
  m_Controls->DynamicControls->AddTabWidgets(*m_Controls->TabWidget, 1);

  // HS Note, change in pattern for the Ventilator widget, it is subclassed and 
  // instantiated in the .ui file, it can be directly accessed here as control
  // is subclassed from the UI class
  // i.e. m_Controls->VentilatorWidget
  // Hack move the ventilator widget to #2

  connect(m_Controls->VentilatorWidget, SIGNAL(UpdateAction(SEAction const&, SEScalarTime const&)), m_Controls->DynamicControls, SLOT(UpdateAction(SEAction const&, SEScalarTime const&)));

  int ventTab = findTab(m_Controls->TabWidget, "Ventilator");
  if (ventTab != -1)
  {
      m_Controls->TabWidget->tabBar()->moveTab(ventTab, 1);
  }

  m_Controls->StartEngine->setVisible(false);
  m_Controls->RunInRealtime->setVisible(false);
  m_Controls->PlayPause->setVisible(false);
  m_Controls->ResetEditor->setVisible(false);
  m_Controls->ClearEditor->setVisible(false);

  connect(m_Controls->RunInRealtime, SIGNAL(clicked()), this, SLOT(RunInRealtime()));
  connect(m_Controls->PlayPause,     SIGNAL(clicked()), this, SLOT(PlayPause()));

  connect(m_Controls->StartEngine,     SIGNAL(clicked()), this, SLOT(StartEngine()));
  connect(m_Controls->ResetEditor,     SIGNAL(clicked()), this, SLOT(ResetEditor()));
  connect(m_Controls->ClearEditor,     SIGNAL(clicked()), this, SLOT(ClearEditor()));
  connect(m_Controls->RestartExplorer, SIGNAL(clicked()), this, SLOT(RestartExplorer()));


  connect(this, SIGNAL(LoadScenario()), this, SLOT(LoadScenario()));
  connect(this, SIGNAL(SaveScenario()), this, SLOT(SaveScenario()));
  // Set the initial sizes for QSplitter widgets
  QList<int> sizes;
  sizes << 1 << 999;
  m_Controls->hsplitter->setSizes(sizes);

  auto buildLabel = new QLabel(this);
  std::ostringstream buildText;
  buildText << " Explorer : " << PulseExplorerBuildInformation::Version()
      << " - " << PulseExplorerBuildInformation::Hash()
      << " Pulse : " << PulseBuildInformation::Version()
      << " - " << PulseBuildInformation::Hash();

  m_Controls->StatusBar->addPermanentWidget(buildLabel);
  buildLabel->setText(buildText.str().c_str());

  ClearEditor();

  //m_Controls->LogBox->append("Current Path is :"+QDir::currentPath());
  //m_Controls->LogBox->append("Application Path is :"+qApp->applicationDirPath());
  
}

MainExplorerWindow::~MainExplorerWindow()
{
  
  delete m_Controls;
}


void MainExplorerWindow::closeEvent(QCloseEvent *event)
{
  // Are these questions too annoying?
  /*QMessageBox::StandardButton resBtn = QMessageBox::question(this, "Explorer",
    tr("Are you sure you want to exit?\n"),
    QMessageBox::No | QMessageBox::Yes,
    QMessageBox::Yes);
  if (resBtn != QMessageBox::Yes) {
    event->ignore();
  }
  else {
    event->accept();
  }*/
  m_Controls->Pulse->Stop();
  QMainWindow::closeEvent(event);
}

void MainExplorerWindow::PlayPause()
{
  if (m_Controls->Pulse->PlayPause())
    m_Controls->PlayPause->setText("Play");
  else
    m_Controls->PlayPause->setText("Pause");
}

void MainExplorerWindow::RunInRealtime()
{
  if (m_Controls->Pulse->ToggleRealtime())
    m_Controls->RunInRealtime->setChecked(true);
  else
    m_Controls->RunInRealtime->setChecked(false);
}

void MainExplorerWindow::ClearEditor()
{
  m_Controls->Pulse->Clear();
  m_Controls->DynamicControls->Clear();
  m_Controls->VitalsMonitorWidget->Reset();
  m_Controls->VentilatorWidget->Reset();
  m_Controls->RunInRealtime->setChecked(true);
  m_Controls->PlayPause->setText("Pause");
  m_Controls->LogBox->clear();
  m_Controls->Status.str("");
  m_Controls->Status << "Current Simulation Time : 00:00:00";
  m_Controls->StatusBar->showMessage(QString(m_Controls->Status.str().c_str()));

  switch (m_Controls->Pulse->GetMode())
  {
  case ExplorerMode::Showcase:
    StartShowcase();
    break;
  case ExplorerMode::Editor:
    StartPulseEditor();
    break;
  }
}

void MainExplorerWindow::ResetEditor()
{
  m_Controls->StartEngine->setVisible(true);
  m_Controls->PlayPause->setVisible(false);
  m_Controls->RunInRealtime->setVisible(false);
  m_Controls->ResetEditor->setVisible(false);

  m_Controls->Pulse->Reset();
  m_Controls->DynamicControls->Reset();
  m_Controls->VitalsMonitorWidget->Reset();
  m_Controls->VentilatorWidget->Reset();
  m_Controls->RunInRealtime->setChecked(true);
  m_Controls->PlayPause->setText("Pause");
  m_Controls->LogBox->clear();
  m_Controls->Status.str("");
  m_Controls->Status << "Current Simulation Time : 00:00:00";
  m_Controls->StatusBar->showMessage(QString(m_Controls->Status.str().c_str()));
}

void MainExplorerWindow::RestartExplorer()
{
  m_Controls->Pulse->Clear();
  m_Controls->Pulse->SetMode(ExplorerMode::Editor);
  m_Controls->VitalsMonitorWidget->Reset();
  m_Controls->TabWidget->setCurrentIndex(0);
  // end
  m_Controls->PlayPause->setText("Pause");
  m_Controls->LogBox->clear();
  m_Controls->Status.str("");
  m_Controls->Status << "Current Simulation Time : 00:00:00";
  m_Controls->StatusBar->showMessage(QString(m_Controls->Status.str().c_str()));

  StartPulseEditor();
}


void MainExplorerWindow::StartShowcase()
{
  m_Controls->DynamicControls->setVisible(true);
  m_Controls->StartEngine->setVisible(false);
  m_Controls->PlayPause->setVisible(true);
  m_Controls->RunInRealtime->setVisible(true);
  m_Controls->ResetEditor->setVisible(false);
  m_Controls->ClearEditor->setVisible(true);
  m_Controls->ClearEditor->setText("Restart Showcase");
  m_Controls->RestartExplorer->setVisible(true);

  if (!m_Controls->DynamicControls->SetupShowcase(m_Controls->DynamicControls->GetSelectedShowcase()))
  {
    QMessageBox msgBox(this);
    msgBox.setWindowTitle("Error!");
    QString err = "Unable to setup the : " + m_Controls->DynamicControls->GetSelectedShowcase() + " showcase scenario";
    msgBox.setText(err);
    msgBox.exec();
    return;
  }
  m_Controls->Pulse->SetMode(ExplorerMode::Showcase);
  // Start up the engine right away!
  m_Controls->Pulse->Start();
}

void MainExplorerWindow::StartPulseEditor()
{
  m_Controls->DynamicControls->setVisible(true);
  m_Controls->DynamicControls->Clear();
  m_Controls->StartEngine->setVisible(true);
  m_Controls->PlayPause->setVisible(false);
  m_Controls->RunInRealtime->setVisible(false);
  m_Controls->ResetEditor->setVisible(false);
  m_Controls->ResetEditor->setText("Reset Scenario");
  m_Controls->ClearEditor->setVisible(true);
  m_Controls->ClearEditor->setText("Clear Scenario");
  m_Controls->RestartExplorer->setVisible(false);

  m_Controls->DynamicControls->SetupPulseEditor();
  m_Controls->Pulse->SetMode(ExplorerMode::Editor);
}
void MainExplorerWindow::StartEngine()
{
  // Make Sure We have a patient...
  if (!m_Controls->DynamicControls->ValidPatient())
  {
    QMessageBox msgBox(this);
    msgBox.setWindowTitle("Error!");
    QString err = "Please ensure patient has at least a name";
    msgBox.setText(err);
    msgBox.exec();
    return;
  }

  m_Controls->StartEngine->setVisible(false);
  m_Controls->PlayPause->setVisible(true);
  m_Controls->RunInRealtime->setVisible(true);
  m_Controls->RunInRealtime->setChecked(true);

  m_Controls->ResetEditor->setVisible(true);
  m_Controls->ClearEditor->setVisible(true);

  m_Controls->PlayPause->setEnabled(false);
  m_Controls->ResetEditor->setEnabled(false);
  m_Controls->ClearEditor->setEnabled(false);

  m_Controls->DynamicControls->StartEngine();
  m_Controls->VentilatorWidget->StartEngine();
  m_Controls->Pulse->Start();
}

void MainExplorerWindow::LoadScenario()
{
  QString scenarioFilename = QFileDialog::getOpenFileName(this,
    "Open Scenario", QPulse::GetDataDir()+"/scenarios", "JSON (*.json);;Protobuf Binary (*.pbb)");
  if (scenarioFilename.isEmpty())
    return;

  std::string s = scenarioFilename.toStdString();
  PulseScenario scenario(m_Controls->Pulse->GetEngine().GetLogger());
  if (!scenario.SerializeFromFile(s))
  {
    QMessageBox msgBox(this);
    msgBox.setWindowTitle("Error!");
    msgBox.setText("Unable to load scenario file : " + scenarioFilename);
    msgBox.exec();
    return;
  }
  
  ClearEditor();
  m_Controls->Pulse->SetScenarioFilename(s);
  m_Controls->DynamicControls->LoadScenario(scenario);
}

void MainExplorerWindow::SaveScenario()
{
  std::string scenario_name = m_Controls->DynamicControls->GetScenarioName().toStdString();

  if (scenario_name.empty())
  {
    QMessageBox msgBox(this);
    msgBox.setWindowTitle("Error!");
    QString err = "Please provide a scenario name";
    msgBox.setText(err);
    msgBox.exec();
    return;
  }

  if (!m_Controls->DynamicControls->ValidPatient())
  {
    QMessageBox msgBox(this);
    msgBox.setWindowTitle("Error!");
    QString err = "Please ensure patient has at least a name";
    msgBox.setText(err);
    msgBox.exec();
    return;
  }

  if (m_Controls->Pulse->GetScenarioFilename().empty())
    m_Controls->Pulse->SetScenarioFilename(QPulse::GetDataDir().toStdString()+"/scenarios/"+scenario_name+".json");

  ScenarioSaveDialog::Mode m;
  std::string separate_file = m_Controls->Pulse->GetEngineStateFilename();
  if (!separate_file.empty())
  {
    m = ScenarioSaveDialog::Mode::State;
  }
  else
  {
    m = ScenarioSaveDialog::Mode::Patient;
    if (m_Controls->Pulse->GetPatientFilename().empty())
      separate_file = QPulse::GetDataDir().toStdString()+"/patients/"+scenario_name+".json";
    else
      separate_file = m_Controls->Pulse->GetPatientFilename();
  }

  ScenarioSaveDialog dlg(m,
    m_Controls->Pulse->GetScenarioFilename(),
    separate_file,
    this);
  if (dlg.exec() == QDialog::Rejected)
    return;

  std::string scenario_file = dlg.GetScenarioFilename().toStdString();
  if (scenario_file.empty())
    return;

  SEScenario scenario;
  scenario.SetName(scenario_name);
  m_Controls->DynamicControls->SaveScenario(scenario);

  if (dlg.SeparateScenario())
  {
    if (m == ScenarioSaveDialog::Mode::Patient)
    {
      std::string patient_file = dlg.GetSeparateFilename().toStdString();
      if (!scenario.GetPatientConfiguration().GetPatient().SerializeToFile(patient_file))
      {
        QMessageBox msgBox(this);
        msgBox.setWindowTitle("Error!");
        QString err = "Unable to save patient";
        msgBox.setText(err);
        msgBox.exec();
        return;
      }
      scenario.GetPatientConfiguration().SetPatientFile(patient_file);
      separate_file = patient_file;
    }
    else
    {
      // TODO
    }
  }
  #ifdef Q_OS_MAC
  std::map<SESerializeState const*, std::string> fullStatePaths; 
  // Do this logic if we are in the app
  if(!QCoreApplication::applicationDirPath().contains("Innerbuild"))
  {
    // If any save state actions are enumerated inside our GetSaveData folder
    // We will save the scenario with relative paths
    // We cannot save a file inside the app
    for (SEAction const* action : scenario.GetActions())
    {
      SESerializeState const* css = dynamic_cast<SESerializeState const*>(action);
      if (css != nullptr)
      {
        std::string fname = css->GetFilename();
        std::string saveDir = QPulse::GetSaveDir().toStdString();
        if (fname.find(saveDir) == 0)
        {
          fname = "."+fname.substr(saveDir.length());
          ((SESerializeState*)css)->SetFilename(fname);
        }
      }
    }
  }
#endif

  if (!scenario.SerializeToFile(scenario_file))
  {
    QMessageBox msgBox(this);
    msgBox.setWindowTitle("Error!");
    QString err = "Unable to save scenario";
    msgBox.setText(err);
    msgBox.exec();
  }

  m_Controls->Pulse->SetScenarioFilename(scenario_file);
  if(m== ScenarioSaveDialog::Mode::Patient)
    m_Controls->Pulse->SetPatientFilename(separate_file);
  else
    m_Controls->Pulse->SetEngineStateFilename(separate_file);
}

void MainExplorerWindow::UpdateAction(const SEAction& action, const SEScalarTime& pTime)
{
  if (!m_Controls->Pulse->IsRunning())
  {
    auto tab = findTab(m_Controls->TabWidget, "Scenario");
    if (tab > -1)
      m_Controls->TabWidget->setCurrentIndex(tab);
  }
  m_Controls->DynamicControls->UpdateAction(action, pTime);
}

void MainExplorerWindow::AtSteadyState(PhysiologyEngine& pulse)
{// This is called from a thread, you should NOT update UI here
 // This is where we pull data from pulse, and push any actions to it
  m_Controls->DynamicControls->AtSteadyState(pulse);
  m_Controls->VitalsMonitorWidget->AtSteadyState(pulse);
  m_Controls->VentilatorWidget->AtSteadyState(pulse);
  
}
void MainExplorerWindow::AtSteadyStateUpdateUI()
{// This is called from a slot, you can update UI here
  m_Controls->DynamicControls->AtSteadyStateUpdateUI();
  // Turn everything on in the vitals
  m_Controls->VitalsMonitorWidget->ShowHeartRate(true);
  m_Controls->VitalsMonitorWidget->ShowBloodPressure(true);
  m_Controls->VitalsMonitorWidget->ShowSpO2(true);
  m_Controls->VitalsMonitorWidget->ShowETCO2(true);
  m_Controls->VitalsMonitorWidget->ShowRespirationRate(true);
  m_Controls->VitalsMonitorWidget->ShowTemperature(true);

  m_Controls->VitalsMonitorWidget->ShowECGWaveform(true);
  m_Controls->VitalsMonitorWidget->ShowPLETHWaveform(true);
  m_Controls->VitalsMonitorWidget->ShowETCO2Waveform(true);
  m_Controls->VitalsMonitorWidget->AtSteadyStateUpdateUI();

  m_Controls->VentilatorWidget->AtSteadyStateUpdateUI();

  m_Controls->PlayPause->setEnabled(true);
  m_Controls->ResetEditor->setEnabled(true);
  m_Controls->ClearEditor->setEnabled(true);

}

void MainExplorerWindow::ProcessPhysiology(PhysiologyEngine& pulse)
{// This is called from a thread, you should NOT update UI here
 // This is where we pull data from pulse, and push any actions to it
  m_Controls->DynamicControls->ProcessPhysiology(pulse);
  m_Controls->VitalsMonitorWidget->ProcessPhysiology(pulse);
  m_Controls->VentilatorWidget->ProcessPhysiology(pulse);
  m_Controls->CurrentSimTime_s = pulse.GetSimulationTime(TimeUnit::s);
}
void MainExplorerWindow::PhysiologyUpdateUI(const std::vector<SEAction const*>& actions)
{// This is called from a slot, you can update UI here
  m_Controls->DynamicControls->PhysiologyUpdateUI(actions);
  m_Controls->VitalsMonitorWidget->PhysiologyUpdateUI(actions);
  m_Controls->VentilatorWidget->PhysiologyUpdateUI(actions);

  int s = m_Controls->CurrentSimTime_s;
  int min = s / 60;
  int hr = min / 60;
  min = int(min % 60);
  s = int(s % 60);
  m_Controls->Status.str("");
  m_Controls->Status << "Current Simulation Time : " << 
                        std::setw(2) << std::setfill('0') << hr << ":" <<
                        std::setw(2) << std::setfill('0') << min << ":" <<
                        std::setw(2) << std::setfill('0') << s;
  m_Controls->StatusBar->showMessage(QString(m_Controls->Status.str().c_str()));
}
void MainExplorerWindow::EngineErrorUI()
{
  QMessageBox::warning(this, tr("Engine Error"),
    tr("The engine has stopped due to an internal error,\n"
        "please check the log for more information."));
  QString log = m_Controls->LogBox->toPlainText();
  switch (m_Controls->Pulse->GetMode())
  {
  case ExplorerMode::Showcase:
    StartShowcase();
    break;
  case ExplorerMode::Editor:
    ResetEditor();
    m_Controls->LogBox->append(log);
    m_Controls->LogBox->append("!! Errors Running Pulse !!");
    m_Controls->LogBox->append("Please check this log for more details");
    break;
  }
}
