/* 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 <QtCharts/QChart>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include <QCloseEvent>
#include <QMessageBox>
#include <iomanip>

#include "PulsePhysiologyEngine.h"
#include "PulseScenario.h"
#include "engine/SEDataRequestManager.h"
#include "engine/SEEngineTracker.h"
#include "properties/SEScalarTime.h"
#include "utils/FileUtils.h"

#include "MainExplorerWindow.h"
#include "ui_MainExplorerWindow.h"
#include "controls/QPulse.h"
#include "controls/ExplorerIntroWidget.h"
#include "controls/VitalsMonitorWidget.h"
#include "controls/DynamicControlsWidget.h"

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

  virtual ~Controls()
  {
#ifdef PARAVIEW
    delete GeometryView;
    delete ParaViewRenderView;
#endif
    delete ExplorerIntroWidget;
    delete VitalsMonitorWidget;
    delete DynamicControls;
    delete Pulse;
  }

#ifdef PARAVIEW  
  QPointer<GeometryView>            GeometryView;
  pqRenderView*                     ParaViewRenderView;
#endif
  QPulse*                           Pulse=nullptr;
  QPointer<QThread>                 Thread;
  QExplorerIntroWidget*             ExplorerIntroWidget=nullptr;
  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";

  m_Controls->ExplorerIntroWidget = new QExplorerIntroWidget(this);
  m_Controls->ExplorerIntroWidget->setTitleBarWidget(new QWidget());
  m_Controls->ExplorerIntroWidget->setVisible(true);
  m_Controls->InputLayout->addWidget(m_Controls->ExplorerIntroWidget);

  // 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(*m_Controls->LogBox, this);
  m_Controls->TabWidget->widget(0)->layout()->addWidget(m_Controls->VitalsMonitorWidget);
  m_Controls->TabWidget->setCurrentIndex(0);
  m_Controls->DynamicControls->AddTabWidgets(*m_Controls->TabWidget, 1);

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

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

  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);
}

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::Save()
{
  m_Controls->Pulse->Save();
}

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->RunInRealtime->setChecked(true);
  m_Controls->PlayPause->setText("Pause");
  m_Controls->LogBox->clear();

  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->Save->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->RunInRealtime->setChecked(true);
  m_Controls->PlayPause->setText("Pause");
  m_Controls->LogBox->clear();
}


void MainExplorerWindow::RestartExplorer()
{
  m_Controls->Pulse->Clear();
  m_Controls->Pulse->SetMode(ExplorerMode::None);
  m_Controls->VitalsMonitorWidget->Reset();

  m_Controls->TabWidget->setCurrentIndex(0);
  // end
  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()));
  m_Controls->StartEngine->setVisible(false);
  m_Controls->RunInRealtime->setVisible(false);
  m_Controls->PlayPause->setVisible(false);
  m_Controls->Save->setVisible(false);
  m_Controls->ResetEditor->setVisible(false);
  m_Controls->ClearEditor->setVisible(false);
  m_Controls->RestartExplorer->setVisible(false);
  m_Controls->ExplorerIntroWidget->setVisible(true);
  m_Controls->DynamicControls->setVisible(false);
  m_Controls->DynamicControls->Clear();
}


void MainExplorerWindow::StartShowcase()
{
  m_Controls->ExplorerIntroWidget->setVisible(false);
  m_Controls->DynamicControls->setVisible(true);
  m_Controls->StartEngine->setVisible(false);
  m_Controls->PlayPause->setVisible(true);
  m_Controls->Save->setVisible(true);
  m_Controls->RunInRealtime->setVisible(true);
  m_Controls->ResetEditor->setVisible(true);
  m_Controls->ClearEditor->setVisible(true);
  m_Controls->RestartExplorer->setVisible(true);

  if (!m_Controls->DynamicControls->SetupShowcase(m_Controls->ExplorerIntroWidget->GetSelectedShowcase()))
  {
    QMessageBox msgBox(this);
    msgBox.setWindowTitle("Error!");
    QString err = "Unable to setup the : " + m_Controls->ExplorerIntroWidget->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->ExplorerIntroWidget->setVisible(false);
  m_Controls->DynamicControls->setVisible(true);
  m_Controls->StartEngine->setVisible(true);
  m_Controls->PlayPause->setVisible(false);
  m_Controls->Save->setVisible(false);
  m_Controls->RunInRealtime->setVisible(false);
  m_Controls->ResetEditor->setVisible(false);
  m_Controls->ClearEditor->setVisible(true);
  m_Controls->RestartExplorer->setVisible(true);

  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->Save->setVisible(true);
  m_Controls->RunInRealtime->setVisible(true);
  m_Controls->RunInRealtime->setChecked(true);

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

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

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

void MainExplorerWindow::LoadScenario()
{
  QString scenarioFilename = QFileDialog::getOpenFileName(this,
    "Open Scenario", "./", "Pulse Scenario (*.json *.pbb)");
  if (scenarioFilename.isEmpty())
    return;

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

void MainExplorerWindow::SaveScenario()
{
  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;
  }

  QString fileName = QFileDialog::getSaveFileName(this, "Save file", "./scenarios", "Scenario Files (*.json *.pbb)");
  if (fileName.isEmpty())
    return;
  QStringList pieces = fileName.split("/");
  QStringList name = pieces.value(pieces.length() - 1).split(".");

  SEScenario scenario;
  scenario.SetName(name.value(0).toStdString());
  m_Controls->DynamicControls->SaveScenario(scenario);

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

void MainExplorerWindow::UpateAction(const SEAction& action, const SEScalarTime& pTime)
{
  if(!m_Controls->Pulse->IsRunning())
    m_Controls->TabWidget->setCurrentIndex(3);
  m_Controls->DynamicControls->UpateAction(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);
}
void MainExplorerWindow::AtSteadyStateUpdateUI()
{// This is called from a slot, you can update UI here
  m_Controls->DynamicControls->AtSteadyStateUpdateUI();
  m_Controls->VitalsMonitorWidget->AtSteadyStateUpdateUI();

  m_Controls->PlayPause->setEnabled(true);
  m_Controls->Save->setEnabled(true);
  m_Controls->ResetEditor->setEnabled(true);
  m_Controls->ClearEditor->setEnabled(true);
  m_Controls->RestartExplorer->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->CurrentSimTime_s = pulse.GetSimulationTime(TimeUnit::s);
}
void MainExplorerWindow::PhysiologyUpdateUI()
{// This is called from a slot, you can update UI here
  m_Controls->DynamicControls->PhysiologyUpdateUI();
  m_Controls->VitalsMonitorWidget->PhysiologyUpdateUI();

  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()
{
  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;
  }
}
