/* Distributed under the Apache License, Version 2.0.
See accompanying NOTICE file for details.*/
#include "controls/ScenarioActionsEditorWidget.h"
#include "controls/CollapsableWidget.h"
#include "controls/ScalarQuantityWidget.h"
#include "ui_ScenarioActionsEditor.h"

#include <QFileDialog>
#include <QMessageBox>
#include <QLabel>
#include <iomanip>

#include "pulse/cdm/CommonDefs.h"
#include "pulse/cdm/engine/SEAction.h"
#include "pulse/cdm/engine/SEAdvanceTime.h"
#include "pulse/cdm/engine/SESerializeState.h"
#include "pulse/cdm/properties/SEScalarTime.h"
#include "pulse/cdm/patient/actions/SEChestOcclusiveDressing.h"
#include "pulse/cdm/patient/actions/SEHemorrhage.h"
#include "pulse/cdm/patient/actions/SENeedleDecompression.h"
#include "pulse/cdm/patient/actions/SESubstanceBolus.h"
#include "pulse/cdm/patient/actions/SESubstanceCompoundInfusion.h"
#include "pulse/cdm/patient/actions/SESubstanceInfusion.h"
#include "pulse/cdm/patient/actions/SETensionPneumothorax.h"
#include "pulse/cdm/substance/SESubstance.h"
#include "pulse/cdm/substance/SESubstanceCompound.h"

class QScenarioActionsEditorWidget::ScenarioAction
{
public:
  ScenarioAction() {};
  ~ScenarioAction() { delete Action; }
  const SEAction*     Action = nullptr;
  SEScalarTime        ProcessTime;
  int                 Column=0;
  bool                Selected = false;
  QCollapsableWidget* Widget = nullptr;
  QPushButton*        EditButton = nullptr;
  QPushButton*        DeleteButton = nullptr;
};

class QScenarioActionsEditorWidget::Controls : public Ui::ScenarioActionsEditorWidget
{
public:
  Controls(QPulse& qp) : Pulse(qp) {}
  ~Controls() { }
  QPulse&                Pulse;
  SEScalarTime           FinalAdvanceTime;
  QScalarQuantityWidget<TimeUnit>* EndDuration=nullptr;
  std::map<ushort, std::vector<ScenarioAction*>> ScenarioActions;// Key is sim time in seconds to process the action vector
};

QScenarioActionsEditorWidget::QScenarioActionsEditorWidget(QPulse& qp, QWidget *parent, Qt::WindowFlags flags) : QDockWidget(parent,flags)
{
  m_Controls = new Controls(qp);
  setAcceptDrops(true);
  m_Controls->setupUi(this);
  connect(m_Controls->LoadScenario, SIGNAL(clicked()), parentWidget()->parentWidget(), SLOT(LoadScenario()));
  connect(m_Controls->SaveScenario, SIGNAL(clicked()), parentWidget()->parentWidget(), SLOT(SaveScenario()));
  connect(m_Controls->ClearActions, SIGNAL(clicked()), this, SLOT(ClearActions()));
  Clear();
}

QScenarioActionsEditorWidget::~QScenarioActionsEditorWidget()
{
  ClearActions();
  delete m_Controls;
}

void QScenarioActionsEditorWidget::Clear()
{
  ClearActions();
  EnableControls(false);
  m_Controls->ScenarioName->setText("");
  m_Controls->FinalAdvanceTime.SetValue(0, TimeUnit::min);
}

void QScenarioActionsEditorWidget::EnableControls(bool b)
{
  m_Controls->ScenarioName->setEnabled(b);
  m_Controls->LoadScenario->setEnabled(b);
  m_Controls->SaveScenario->setEnabled(b);
  m_Controls->ClearActions->setEnabled(b);
  m_Controls->EndDuration->setEnabled(b);
  m_Controls->EndDuration->EnableInput(b);

  for (auto& vitr : m_Controls->ScenarioActions)
  {
    for (auto aitr : vitr.second)
    {
        aitr->EditButton->setEnabled(b);
        aitr->DeleteButton->setEnabled(b);
    }
  }
}

QString QScenarioActionsEditorWidget::GetName()
{
  return m_Controls->ScenarioName->text();
}

void QScenarioActionsEditorWidget::LoadScenario(SEScenario& scenario)
{
  ClearActions();
  double time_s = 0;
  m_Controls->ScenarioName->setText(QString::fromStdString(scenario.GetName()));
  if (!scenario.GetActions().empty())
  {
    for (SEAction const* action : scenario.GetActions())
    {
      SEAdvanceTime const* adv = dynamic_cast<SEAdvanceTime const*>(action);
      if (adv != nullptr)
      {
        time_s += adv->GetTime(TimeUnit::s);
        continue;
      }

      ScenarioAction* new_sa = new ScenarioAction();
      new_sa->ProcessTime.SetValue(time_s, TimeUnit::s);
      new_sa->Action = SEAction::Copy(*action, m_Controls->Pulse.GetScenario().GetSubstanceManager());

#ifdef Q_OS_MAC 
      // Do this logic if we are in the app
      if(!QCoreApplication::applicationDirPath().contains("Innerbuild"))
      {
        // Check to see if the action is a state serialization action with a relative path
        // We will need to full enumerate the path if we are running in a MacOS app
        // We cannot save a file inside the app
        SESerializeState const* css = dynamic_cast<SESerializeState const*>(new_sa->Action);
        if (css != nullptr)
        {
          std::string fname = css->GetFilename();
          if (fname.find(".") == 0)
          {
            fname = QPulse::GetSaveDir().toStdString()+fname.substr(1);
            ((SESerializeState*)css)->SetFilename(fname);
          }
        }
      }
#endif
      m_Controls->ScenarioActions[time_s].push_back(new_sa);
    }
    // Check if the last action is an advance time action, that is our ending duration
    SEAdvanceTime const* adv = dynamic_cast<SEAdvanceTime const*>(scenario.GetActions()[scenario.GetActions().size() - 1]);
    if (adv)
    {
      double time_s = adv->GetTime(TimeUnit::s);
      if (time_s <= 120)
        m_Controls->FinalAdvanceTime.SetValue(time_s, TimeUnit::s);
      else
      {
        double time_min = adv->GetTime(TimeUnit::min);
        if (time_min <= 120)
          m_Controls->FinalAdvanceTime.SetValue(time_min, TimeUnit::min);
        else
          m_Controls->FinalAdvanceTime.SetValue(adv->GetTime(TimeUnit::hr), TimeUnit::hr);
      }
    }
    else
      m_Controls->FinalAdvanceTime.SetValue(0, TimeUnit::s);
  }
  m_Controls->EndDuration->SetValue(m_Controls->FinalAdvanceTime);
  DrawScenario();
}
void QScenarioActionsEditorWidget::SaveScenario(SEScenario& scenario)
{
  double lastTime_s = 0;
  for (auto& itr : m_Controls->ScenarioActions)
  {
    if (itr.first > 0)
    {
      SEAdvanceTime adv;
      adv.GetTime().SetValue(itr.first- lastTime_s, TimeUnit::s);
      scenario.AddAction(adv);
      lastTime_s = itr.first;
    }
    for (ScenarioAction* sa : itr.second)
    {
      scenario.AddAction(*sa->Action);
    }
  }
  m_Controls->EndDuration->GetValue(m_Controls->FinalAdvanceTime);
  if (m_Controls->FinalAdvanceTime.IsPositive())
  {
    SEAdvanceTime adv;
    adv.GetTime().Set(m_Controls->FinalAdvanceTime);
    scenario.AddAction(adv);
  }
}

void QScenarioActionsEditorWidget::ClearActions()
{
  for (auto& vitr : m_Controls->ScenarioActions)
    for (auto aitr : vitr.second)
      delete aitr;
  m_Controls->ScenarioActions.clear();
  m_Controls->FinalAdvanceTime.SetValue(0, TimeUnit::min);
  if(m_Controls->EndDuration != nullptr)
    m_Controls->EndDuration->SetValue(m_Controls->FinalAdvanceTime);
  DrawScenario();
}

void QScenarioActionsEditorWidget::UpdateAction(const SEAction& action, const SEScalarTime& pTime)
{
  // Invoked when either a new action needs to be added or a current action is edited
  // Maintains constraint that there should not be two different actions of the same kind at the same time
  // e.g only one Ketamine Bolus will at a certain time slot, `IsSame` checks for this kind of equality
  // 
  if (!pTime.IsValid() && !m_Controls->Pulse.IsRunning())
  {
    m_Controls->Pulse.LogToGUI("Scenario action does not have a valid apply time");
    return;
  }
  ushort time_s = 0;
  ScenarioAction* new_sa = new ScenarioAction();
  new_sa->Action = SEAction::Copy(action, m_Controls->Pulse.GetScenario().GetSubstanceManager());
  if (m_Controls->Pulse.IsRunning())
  {
    new_sa->Column = 2;// User Column
    // Might need a mutex for this...
    time_s = m_Controls->Pulse.GetEngine().GetSimulationTime(TimeUnit::s);
    new_sa->ProcessTime.SetValue(time_s, TimeUnit::s);
  }
  else
  {
    new_sa->ProcessTime.Set(pTime);
    time_s = pTime.GetValue(TimeUnit::s);
    // Do we have a selected action?
    for (auto& itr : m_Controls->ScenarioActions)
    {
      size_t i = 0;
      for (ScenarioAction* sa : itr.second)
      {
        // If sa->Selected is true we are somehwere in an edit, if the action type
        // that is incoming `action` is the same as the action that is being edited 
        // then we will assume that it is an "edit" action otherwise we assume
        // that a new action was added 
        if (sa->Selected)
        {
          if (SameType(*sa->Action,action))
          {
            // Check to see if this row already has one of these actions it the destination row
            if (time_s != itr.first)
            {
              size_t d = 0;
              auto& dstActions = m_Controls->ScenarioActions[time_s];
              for (ScenarioAction* saDst : dstActions)
              {
                if (SameAction(*saDst->Action, action))
                {
                  delete saDst;
                  dstActions.erase(dstActions.begin() + d);
                  break;
                }
                d++;
              }
            }
            // Copy column and process time
            new_sa->Column = sa->Column;
            new_sa->ProcessTime.Set(pTime);
            // Out with the old
            delete sa;
            itr.second.erase(itr.second.begin() + i);
            break;
          }
        }
        i++;
      }
    }
  }
 
  // One "row" is a certain time slot in the timeline, this section makes sure 
  // only one of one kind of action is performed during a slot
  auto& row = m_Controls->ScenarioActions[time_s];
  if (row.empty())
    row.push_back(new_sa);
  else
  {
    bool found = false;
    for (ScenarioAction* sa : row)
    {
      if (SameAction(*sa->Action,action))
      {
        // Swap actions
        SEAction const* old_action = sa->Action;
        sa->Action = new_sa->Action;
        new_sa->Action = old_action;
        // Clean up!
        delete new_sa; // This will delete the old action too
        found = true;
      }
    }
    if(!found)
      row.push_back(new_sa);
  }
  DrawScenario();
}

void QScenarioActionsEditorWidget::EditAction()
{
  // Find the active action and emit it
  for (auto& itr : m_Controls->ScenarioActions)
  {
    for (ScenarioAction* sa : itr.second)
    {
      if (sender() == sa->EditButton && !sa->Selected)
      {
        sa->Selected = true;
        sa->Widget->setStyleSheet("QCollapsableWidget {border-top: 2px solid blue;}");
        emit(OpenAction(*sa->Action, sa->ProcessTime));
        sa->EditButton->setText("Cancel");
      }
      else
      {
        sa->Selected = false;
        sa->Widget->setStyleSheet("");
        sa->EditButton->setText("Edit");
      }
    }
  }
}

void QScenarioActionsEditorWidget::DeleteAction()
{
  // Find the active action and delete it
  for (auto& itr : m_Controls->ScenarioActions)
  {
    size_t i = 0;
    for (ScenarioAction* sa : itr.second)
    {
      if (sender() == sa->DeleteButton)
      {
        itr.second.erase(itr.second.begin() + i);
        delete sa;
        DrawScenario();
        return;
      }
      i++;
    }
  }
}

void QScenarioActionsEditorWidget::DrawScenario()
{
  // Grab the End Duration
  if(m_Controls->EndDuration != nullptr)
    m_Controls->EndDuration->GetValue(m_Controls->FinalAdvanceTime);
  // Clear everything out
  QLayoutItem* wItem;
  QGridLayout* gridLayout = static_cast<QGridLayout*>(m_Controls->ActionWidgets->layout());
  while ((wItem = gridLayout->takeAt(0)) != NULL)
  {
    delete wItem->widget();
    delete wItem;
  }
  // Set the widget to null as well
  for (auto& itr : m_Controls->ScenarioActions)
  {
    for (ScenarioAction* sa : itr.second)
    {
      sa->Widget = nullptr;
    }
  }

  // I think it would be better to have the ActionWidgets layout be a QVBoxLayout rather than a QGridLayout
  // Populatde with widges with a QGridLayout of equal stretch, and each column would have an exapnding horz spacer
  // Maybe... not enough time to experiment...

  int indx = 0;
  std::vector<QString> titles{ "Scenario Actions", "Simulation Time", "Dynamic Actions" };
  for (QString title : titles)
  {
    QWidget* vWidget = new QWidget(this);
    QWidget* hWidget = new QWidget(vWidget);
    QHBoxLayout* hBoxLayout = new QHBoxLayout();
    QVBoxLayout* vBoxLayout = new QVBoxLayout();
    hBoxLayout->setSpacing(6);
    hBoxLayout->setContentsMargins(-1, 6, -1, 6);
    hWidget->setStyleSheet("QLabel {font-weight:bold;\
                                    border-bottom-style: solid;\
                                    border-radius: 0px;}");
    //                              border - bottom - width: 1px;
    hBoxLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Fixed));
    hBoxLayout->addWidget(new QLabel(title));
    hBoxLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Fixed));
    hWidget->setLayout(hBoxLayout);
    vWidget->setLayout(vBoxLayout);

    QFrame* hBorder = new QFrame(vWidget);
    hBorder->setLineWidth(2);
    hBorder->setMidLineWidth(1);
    hBorder->setFrameShape(QFrame::HLine);
    hBorder->setFrameShadow(QFrame::Raised);
    hBorder->setPalette(QPalette(QColor(0, 0, 0)));
    vBoxLayout->addWidget(hWidget);
    vBoxLayout->addWidget(hBorder);
    gridLayout->addWidget(vWidget, 0, indx);
    gridLayout->setAlignment(Qt::AlignTop);
    indx++;
  }

  std::vector<int> rowHeights;
  // Add actions to the scenario action and user action columns
  int currentRow = 0;
  std::ostringstream actionText;
  for (auto& itr : m_Controls->ScenarioActions)
  {
    if (itr.second.empty())
      continue;// No need to make a row
    currentRow++;
    QWidget* saColWidget = new QWidget(this);
    QVBoxLayout* saColLayout = new QVBoxLayout();
    saColWidget->setLayout(saColLayout);
    saColWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    QWidget* uaColWidget = new QWidget(this);
    QVBoxLayout* uaColLayout = new QVBoxLayout();
    uaColWidget->setLayout(uaColLayout);
    uaColWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    int saHeight = 0;
    int uaHeight = 0;
    for (ScenarioAction* sa : itr.second)
    {
      // Get and split action text by returns
      actionText.str("");
      sa->Action->ToString(actionText);
      QStringList pieces = QString::fromStdString(actionText.str()).split("\n");
      // Collapsable Action Widget
      sa->Widget = new QCollapsableWidget(pieces[0], 120, this);
      if (sa->Selected)// Turn selection back on
        sa->Widget->setStyleSheet("QCollapsableWidget {border-top: 2px solid blue;}");
      QString tag = pieces[0].split(" : ")[0];
      QWidget* aWidget = new QWidget(this);
      QVBoxLayout* aLayout = new QVBoxLayout();
      aLayout->setSpacing(6);
      aLayout->setContentsMargins(-1, 6, -1, 6);
      for (int i = 1; i < pieces.length(); i++)
        aLayout->addWidget(new QLabel(pieces[i]));
      // Add Edit/Delete Buttons (before the setContentLayout call)
      {
        QWidget* btns = new QWidget(sa->Widget);
        QHBoxLayout* btnLayout = new QHBoxLayout();
        btnLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Fixed));
       sa->EditButton = new QPushButton("Edit", btns);
       sa->EditButton->setMaximumWidth(50);
        btnLayout->addWidget(sa->EditButton);
        connect(sa->EditButton, SIGNAL(clicked()), this, SLOT(EditAction()));
        sa->DeleteButton = new QPushButton("Remove", btns);
        sa->DeleteButton->setMaximumWidth(50);
        btnLayout->addWidget(sa->DeleteButton);
        connect(sa->DeleteButton, SIGNAL(clicked()), this, SLOT(DeleteAction()));
        btns->setLayout(btnLayout);
        aLayout->addWidget(btns);
        if (m_Controls->Pulse.IsRunning())
        {
          sa->EditButton->setEnabled(false);
          sa->DeleteButton->setEnabled(false);
        }
      }
      aWidget->setLayout(aLayout);
      sa->Widget->layout()->addWidget(aWidget);
      sa->Widget->setContentLayout(*aWidget->layout());
      sa->Widget->expand(true);
      sa->Widget->setVisible(true);
      if (sa->Column == 0)
      {
        saColLayout->addWidget(sa->Widget);
        saHeight += sa->Widget->getContentHeight() + sa->Widget->maximumHeight() + 25;
      }
      else
      {
        uaColLayout->addWidget(sa->Widget);
        uaHeight += sa->Widget->getContentHeight() + sa->Widget->maximumHeight();
      }
    }
    // Add a bottom borders
    //QFrame* saBorder = new QFrame(saColWidget);
    //saBorder->setLineWidth(2);
    //saBorder->setMidLineWidth(1);
    //saBorder->setFrameShape(QFrame::HLine);
    //saBorder->setFrameShadow(QFrame::Raised);
    //saBorder->setPalette(QPalette(QColor(0, 0, 0)));
    //saColLayout->addWidget(saBorder);
    //
    //QFrame* uaBorder = new QFrame(uaColWidget);
    //uaBorder->setLineWidth(2);
    //uaBorder->setMidLineWidth(1);
    //uaBorder->setFrameShape(QFrame::HLine);
    //uaBorder->setFrameShadow(QFrame::Raised);
    //uaBorder->setPalette(QPalette(QColor(0, 0, 0)));
    //uaColLayout->addWidget(uaBorder);
    // Which is the bigger height?
    int maxHeight = saHeight;
    if (uaHeight > maxHeight)
      maxHeight = uaHeight;
    saColWidget->setFixedHeight(maxHeight);
    uaColWidget->setFixedHeight(maxHeight);
    gridLayout->addWidget(saColWidget, currentRow, 0, Qt::AlignTop);
    gridLayout->addWidget(uaColWidget, currentRow, 2, Qt::AlignTop);
    rowHeights.push_back(maxHeight);
  }

  currentRow = 1;
  // Add all the action times into the Time Column
  std::ostringstream simtime;
  int hr, min, s;
  for (auto& itr : m_Controls->ScenarioActions)
  {
    if (itr.second.empty())
      continue;
    s = (int)itr.first;
    min = s / 60;
    hr = min / 60;
    min = int(min % 60);
    s = int(s % 60);
    simtime.str("");
    simtime << std::setw(2) << std::setfill('0') << hr << ":" <<
      std::setw(2) << std::setfill('0') << min << ":" <<
      std::setw(2) << std::setfill('0') << s;
    QWidget* timeWidget = new QWidget(this);
    QVBoxLayout* timeLayout = new QVBoxLayout();
    timeWidget->setLayout(timeLayout);
    timeLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding));
    timeLayout->addWidget(new QLabel("Simulation Time"), 0, Qt::AlignCenter);
    timeLayout->addWidget(new QLabel(QString::fromStdString(simtime.str())), 0, Qt::AlignCenter);
    timeLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding));
    timeWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    timeWidget->setFixedHeight(rowHeights[currentRow-1]);
    // Add a border
    QFrame* timeBorder = new QFrame(timeWidget);
    timeBorder->setLineWidth(2);
    timeBorder->setMidLineWidth(1);
    timeBorder->setFrameShape(QFrame::HLine);
    timeBorder->setFrameShadow(QFrame::Raised);
    timeBorder->setPalette(QPalette(QColor(0, 0, 0)));
    timeLayout->addWidget(timeBorder);
    gridLayout->addWidget(timeWidget, currentRow++, 1, Qt::AlignTop);
  }
  // Add the option to define scenario end time
  QWidget* timeWidget = new QWidget(this);
  QVBoxLayout* timeLayout = new QVBoxLayout();
  timeWidget->setLayout(timeLayout);
  timeLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding));
  m_Controls->EndDuration = new QScalarQuantityWidget<TimeUnit>("End Duration", 0, 500, 1, TimeUnit::min, ScalarOptionWidget::None, this);
  m_Controls->EndDuration->AddUnit(TimeUnit::s);
  m_Controls->EndDuration->AddUnit(TimeUnit::hr);
  m_Controls->EndDuration->EnableConverter(false);
  m_Controls->EndDuration->setEnabled(true);
  m_Controls->EndDuration->EnableInput(true);
  m_Controls->EndDuration->SetValue(m_Controls->FinalAdvanceTime);
  timeLayout->addWidget(m_Controls->EndDuration);
  timeLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding));
  timeWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  // Add a border
  QFrame* timeBorder = new QFrame(timeWidget);
  timeBorder->setLineWidth(2);
  timeBorder->setMidLineWidth(1);
  timeBorder->setFrameShape(QFrame::HLine);
  timeBorder->setFrameShadow(QFrame::Raised);
  timeBorder->setPalette(QPalette(QColor(0, 0, 0)));
  timeLayout->addWidget(timeBorder);
  gridLayout->addWidget(timeWidget, currentRow++, 1, Qt::AlignTop);
}

void QScenarioActionsEditorWidget::AtSteadyState(PhysiologyEngine& pulse)
{
  for (auto& itr : m_Controls->ScenarioActions)
  {
    for (ScenarioAction* sa : itr.second)
    {
      m_Controls->Pulse.AddScenarioAction(itr.first, *sa->Action);
    }
  }
}
void QScenarioActionsEditorWidget::AtSteadyStateUpdateUI()
{
  m_Controls->SaveScenario->setEnabled(true);
  m_Controls->ScenarioName->setEnabled(true);
}

void QScenarioActionsEditorWidget::ProcessPhysiology(PhysiologyEngine& pulse)
{
  
}
void QScenarioActionsEditorWidget::PhysiologyUpdateUI(const std::vector<SEAction const*>& actions)
{
  
}

bool QScenarioActionsEditorWidget::SameAction(SEAction const& a, SEAction const& b)
{
  if (typeid(a) != typeid(b))
    return false;

  // Hemorrhages need to have the same cmpt and type to be the same
  SEHemorrhage const* ha = dynamic_cast<SEHemorrhage const*>(&a);
  SEHemorrhage const* hb = dynamic_cast<SEHemorrhage const*>(&b);
  if (ha != nullptr && hb != nullptr)
  {
    // If the types are different, the cmpts can be the same
    if (ha->GetType() != hb->GetType())
      return false;
    else if (ha->GetCompartment() != hb->GetCompartment())
      return false;
    return true;
  }

  SEChestOcclusiveDressing const* coda = dynamic_cast<SEChestOcclusiveDressing const*>(&a);
  SEChestOcclusiveDressing const* codb = dynamic_cast<SEChestOcclusiveDressing const*>(&b);
  if (coda != nullptr && codb != nullptr)
  {
    if (coda->GetSide() != codb->GetSide())
      return false;
    return true;
  }

  SENeedleDecompression const* nda = dynamic_cast<SENeedleDecompression const*>(&a);
  SENeedleDecompression const* ndb = dynamic_cast<SENeedleDecompression const*>(&b);
  if (nda != nullptr && ndb != nullptr)
  {
    if (nda->GetSide() != ndb->GetSide())
      return false;
    return true;
  }

  // Substances need to be the same for bolus, infusions
  SESubstanceBolus const* sba = dynamic_cast<SESubstanceBolus const*>(&a);
  SESubstanceBolus const* sbb = dynamic_cast<SESubstanceBolus const*>(&b);
  if (sba != nullptr && sbb != nullptr)
  {
    if (sba->GetSubstance().GetName() != sbb->GetSubstance().GetName())
      return false;
    return true;
  }

  SESubstanceCompoundInfusion const* scia = dynamic_cast<SESubstanceCompoundInfusion const*>(&a);
  SESubstanceCompoundInfusion const* scib = dynamic_cast<SESubstanceCompoundInfusion const*>(&b);
  if (scia != nullptr && scib != nullptr)
  {
    if (scia->GetSubstanceCompound().GetName() != scib->GetSubstanceCompound().GetName())
      return false;
    return true;
  }

  SESubstanceInfusion const* sia = dynamic_cast<SESubstanceInfusion const*>(&a);
  SESubstanceInfusion const* sib = dynamic_cast<SESubstanceInfusion const*>(&b);
  if (sia != nullptr && sib != nullptr)
  {
    if (sia->GetSubstance().GetName() != sib->GetSubstance().GetName())
      return false;
    return true;
  }
  // Side needs to be the same for pneumo
  SETensionPneumothorax const* tpa = dynamic_cast<SETensionPneumothorax const*>(&a);
  SETensionPneumothorax const* tpb = dynamic_cast<SETensionPneumothorax const*>(&b);
  if (tpa != nullptr && tpb != nullptr)
  {
    if (tpa->GetSide() != tpb->GetSide())
      return false;
    return true;
  }

  return true;
}

bool QScenarioActionsEditorWidget::SameType(SEAction const& a, SEAction const& b)
{
    return typeid(a) == typeid(b);
}
