/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes


/////////////////////// Qt includes
#include <QSettings>
#include <QMenuBar>
#include <QMenu>
#include <QDebug>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/processing/combiners/integrationscopebase.h>
#include <pappsomspp/core/processing/combiners/integrationscope.h>
#include <pappsomspp/core/processing/combiners/integrationscoperect.h>

/////////////////////// Local includes
#include "../nongui/MsRunDataSet.hpp"
#include "../nongui/QualifiedMassSpectrumVectorMassDataIntegratorToMz.hpp"
#include "Application.hpp"
#include "MsRunDataSetTableViewItem.hpp"
#include "MsRunDataSetTableViewWnd.hpp"
#include "MzIntegrationParamsDlg.hpp"
#include "ProgramWindow.hpp"
#include "ui_MsRunDataSetTableViewWnd.h"

namespace MsXpS
{
namespace MineXpert
{


//! Construct an MsRunDataSetTableViewWnd instance.
MsRunDataSetTableViewWnd::MsRunDataSetTableViewWnd(
  ProgramWindow *program_window_p,
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const QColor &color,
  QWidget *parent)
  : QMainWindow(parent),
    mp_programWindow(program_window_p),
    mp_processingFlow(new ProcessingFlow(this)),
    mcsp_msRunDataSet(ms_run_data_set_csp),
    m_color(color),
    m_ui(new ::Ui::MsRunDataSetTableViewWnd)
{
  if(program_window_p == Q_NULLPTR)
    qFatal() << "Programming error.";

  m_ui->setupUi(dynamic_cast<QMainWindow *>(this));

  if(!initialize())
    qFatal() << "Programming error.";

  // Note that we need to set the processing_flow mz integration parameters to
  // the ones found in the file during file load:

  // Set the MsRunDataSet pointer that we need when using toString().
  mp_processingFlow->setMsRunDataSetCstSPtr(ms_run_data_set_csp);

  // Heap-allocated
  pappso::MzIntegrationParams *mz_integration_params_p =
    mcsp_msRunDataSet->craftMzIntegrationParams();

  mp_processingFlow->setDefaultMzIntegrationParams(*mz_integration_params_p);

  delete mz_integration_params_p;
}

//! Destruct \c this MsRunDataSetTableViewWnd instance.
MsRunDataSetTableViewWnd::~MsRunDataSetTableViewWnd()
{
  writeSettings();
}

//! Handle the close event.
void
MsRunDataSetTableViewWnd::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  writeSettings();
  hide();
}

//! Write the settings to as to restore the window geometry later.
void
MsRunDataSetTableViewWnd::writeSettings()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup("MsRunDataSetTableViewWnd");

  settings.setValue("geometry", saveGeometry());
  settings.setValue("visible", isVisible());
  settings.setValue("topSplitter", m_ui->topSplitter->saveState());
  settings.setValue("bottomSplitter", m_ui->bottomSplitter->saveState());


  settings.endGroup();
}

//! Read the settings to as to restore the window geometry.
void
MsRunDataSetTableViewWnd::readSettings()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);
  settings.beginGroup("MsRunDataSetTableViewWnd");

  restoreGeometry(settings.value("geometry").toByteArray());

  m_ui->topSplitter->restoreState(settings.value("topSplitter").toByteArray());
  m_ui->bottomSplitter->restoreState(
    settings.value("bottomSplitter").toByteArray());


  bool wasVisible = settings.value("visible").toBool();
  setVisible(wasVisible);

  settings.endGroup();
}

//! Initialize the window.
bool
MsRunDataSetTableViewWnd::initialize()
{
  setWindowTitle("MineXpert3 - MS run data set table view");

  // The tool bar will host the main menu button.
  QToolBar *mp_toolBar = QMainWindow::addToolBar("Settings");
  mp_toolBar->setObjectName(QStringLiteral("mp_toolBar"));
  mp_toolBar->setFloatable(false);

  // Construct the context menu that will be associated to the main menu push
  // button.

  createMainMenu();

  // Finally add the main menu push button to the toolbar!
  mp_toolBar->addWidget(mp_mainMenuPushButton);

  // Computing TIC intensity values from the ms run data set table view, we
  // should receive a signal and from the carried double value we need to show a
  // message.

  connect(mp_programWindow,
          &ProgramWindow::ticIntensityValueSignal,
          this,
          &MsRunDataSetTableViewWnd::ticIntensityValue);

  // We want to show the name of the sample as a colored label.

  QPalette palette;
  palette.setColor(QPalette::WindowText, m_color);
  m_ui->sampleNameLabel->setPalette(palette);
  m_ui->sampleNameLabel->setText(
    mcsp_msRunDataSet->getMsRunId()->getSampleName());

  mp_msRunDataSetTableView = new MsRunDataSetTableView(this);

  m_ui->tableViewVerticalLayout->addWidget(mp_msRunDataSetTableView);

  m_ui->filteringOptionsGroupBox->setChecked(false);
  m_ui->filteringOptionsFrame->setVisible(false);

  mcsp_msRunDataSetTree = mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr();

  mp_msRunDataSetTableViewModel =
    new MsRunDataSetTableViewModel(mcsp_msRunDataSetTree, this);

  mp_msRunDataSetTableViewProxyModel =
    new MsRunDataSetTableViewProxyModel(this);

  mp_msRunDataSetTableViewProxyModel->setFilterKeyColumn(-1);

  mp_msRunDataSetTableView->setModel(mp_msRunDataSetTableViewProxyModel);

  mp_msRunDataSetTableView->setSortingEnabled(true);

  mp_msRunDataSetTableViewProxyModel->setSourceModel(
    mp_msRunDataSetTableViewModel);

  // Now that we have the pointer to the proxymodel, the precision widget that
  // is required to specify the precursor m/z values search tolerance.

  mp_precisionWidget = new pappso::PrecisionWidget(this);
  m_ui->precisionWidgetHorizontalLayout->addWidget(mp_precisionWidget);
  mp_precisionWidget->setToolTip(
    "Set the tolerance with which the m/z values are matched");

  // Immediately set that precision/tolerance to the proxy model, because if the
  // user executes the filtering without setting any tolerance value, the proxy
  // model will have a nullptr precision pointer member and the program will
  // crash.

  // The default value will apply

  mp_msRunDataSetTableViewProxyModel->setPrecision(mp_precision);

  // Set explanatory text for the different filtering line edit widgets.

  m_ui->indexLineEdit->setToolTip(
    "The accepted syntax: \nval\nval1--val2\n< val\n> val\n< val1 > val2\n");

  m_ui->retentionTimeLineEdit->setToolTip(
    "The accepted syntax: \nval\nval%tol (tol is the tolerance)\nval1--val2\n< "
    "val\n> val\n< val1 > val2\n");

  m_ui->driftTimeLineEdit->setToolTip(
    "The accepted syntax: \nval\nval%tol (tol is the tolerance)\nval1--val2\n< "
    "val\n> val\n< val1 > val2\n");

  m_ui->precursorIndexLineEdit->setToolTip(
    "The accepted syntax: \nval\nval1--val2\n< val\n> val\n< val1 > val2\n");

  m_ui->precursorChargeLineEdit->setToolTip(
    "The accepted syntax: \nval\nval1--val2\n< val\n> val\n< val1 > val2\n");

  // Make the connections

  connect(mp_precisionWidget,
          &pappso::PrecisionWidget::precisionChanged,
          this,
          [this](pappso::PrecisionPtr precision_p) {
            mp_precision = precision_p;
            mp_msRunDataSetTableViewProxyModel->setPrecision(mp_precision);
          });

  connect(m_ui->executeFilteringPushButton, &QPushButton::clicked, this, [&]() {
    executeFilteringPushButtonClicked();
  });

  ////////////////////// THE INTEGRATION BUTTONS ////////////////////////
  ////////

  // We will perform the connection between QAction instances and the buttons
  // arranged below the table widget. The QAction might exist already in the
  // form of an ActionId associated to a shortcut. This is handled by the
  // ActionManager (installAction()).

  // Since a given action might have been set at different places in the
  // program, when doing the connection to a given button, in the lambda, we
  // ensure that the current window is effectively active. Otherwise, when the
  // QAction triggers, the connected lamba code goes off in all the connected
  // places!

  // The code below was used to check the working of the ActionID/QAction-based
  // strategy used here. Indeed, I only understood that later tha the QAction
  // needs to be added to the button for the trigger to actually work.
  // The shortcut strategy below works even without adding anything.

#if 0
  // This works by itself. I tested this because at first the QAction-based
  // code above did not work until I actually added the action to the button.
  QShortcut *directShortcut = new QShortcut(QKeySequence("Ctrl+I, M"), this);
  connect(directShortcut, &QShortcut::activated, this, [&]() {
    qDebug() << "Direct shortcut activated!";
    m_ui->integrateToMzPushButton->click();
  });
#endif


  //////////// The variables we will need to perform each button configuration
  ProgramWindow *program_window_p =
    static_cast<Application *>(qApp)->getProgramWindow();
  QKeySequence key_sequence;
  QString action_label;
  libXpertMassGui::ActionId action_id;
  QAction *action_p = nullptr;

  /////////// Integrate to DT
  ///
  // First the button.
  connect(m_ui->integrateToDtPushButton, &QPushButton::clicked, this, [this]() {
    integrate("TO_DT");
  });
  // And now the action represented by a shortcut.
  action_label = "Integrate to an ion mobility spectrum";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, D"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToDtPushButton->addAction(action_p);
  m_ui->integrateToDtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger" << static_cast<QAction *>(QObject::sender())->text();
    // This action is set to may different buttons all over the windows,
    // we do not want that the action be triggered for any other window than
    // this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToDtPushButton->click();
  });

  /////////// Integrate to MZ
  ///
  // First the button.
  connect(m_ui->integrateToMzPushButton, &QPushButton::clicked, this, [&]() {
    integrate("TO_MZ");
  });
  // And now the action represented by a shortcut.
  action_label = "Integrate to a mass spectrum";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, M"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToMzPushButton->addAction(action_p);
  m_ui->integrateToMzPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger" << static_cast<QAction *>(QObject::sender())->text();
    // This action is set to may different buttons all over the windows,
    // we do not want that the action be triggered for any other window than
    // this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToMzPushButton->click();
  });

  /////////// Integrate to RT
  ///
  // First the button.
  connect(m_ui->integrateToRtPushButton, &QPushButton::clicked, this, [&]() {
    integrate("TO_RT");
  });
  // And now the action represented by a shortcut.
  action_label = "Integrate to a TIC/XIC chromatogram";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, R"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToRtPushButton->addAction(action_p);
  m_ui->integrateToRtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger" << static_cast<QAction *>(QObject::sender())->text();
    // This action is set to may different buttons all over the windows,
    // we do not want that the action be triggered for any other window than
    // this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToRtPushButton->click();
  });

  /////////// Integrate to INT
  ///
  // First the button.
  connect(m_ui->integrateToIntPushButton, &QPushButton::clicked, this, [&]() {
    integrate("TO_INT");
  });
  // And now the action represented by a shortcut.
  action_label = "Integrate to a TIC intensity value";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, I"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToIntPushButton->addAction(action_p);
  m_ui->integrateToIntPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger" << static_cast<QAction *>(QObject::sender())->text();
    // This action is set to may different buttons all over the windows,
    // we do not want that the action be triggered for any other window than
    // this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToIntPushButton->click();
  });

  /////////// Integrate to TO_DT_MZ
  ///
  // First the button.
  connect(m_ui->integrateToDtMzPushButton, &QPushButton::clicked, this, [&]() {
    integrate("TO_DT_MZ");
  });
  // And now the action represented by a shortcut.
  action_label = "Integrate to an ion mobility / mass spectrum color map";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, Ctrl+D"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToDtMzPushButton->addAction(action_p);
  m_ui->integrateToDtMzPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger" << static_cast<QAction *>(QObject::sender())->text();
    // This action is set to may different buttons all over the windows,
    // we do not want that the action be triggered for any other window than
    // this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToDtMzPushButton->click();
  });

  /////////// Integrate to TO_DT_RT
  ///
  // First the button.
  connect(m_ui->integrateToDtRtPushButton, &QPushButton::clicked, this, [&]() {
    integrate("TO_DT_RT");
  });
  // And now the action represented by a shortcut.
  action_label = "Integrate to an ion mobility / TIC chromatogram color map";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, Ctrl+R"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToDtRtPushButton->addAction(action_p);
  m_ui->integrateToDtRtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger" << static_cast<QAction *>(QObject::sender())->text();
    // This action is set to may different buttons all over the windows,
    // we do not want that the action be triggered for any other window than
    // this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToDtRtPushButton->click();
  });

  /////////// Integrate to TO_MZ_RT
  ///
  // First the button.
  connect(m_ui->integrateToMzRtPushButton, &QPushButton::clicked, this, [&]() {
    integrate("TO_MZ_RT");
  });
  // And now the action represented by a shortcut.
  action_label = "Integrate to a mass spectrum / TIC chromatogram color map";
  action_id.initialize("Global", "Integrations", "Any", action_label);
  action_p = program_window_p->getActionManager()->installAction(
    action_id, QKeySequence("Ctrl+I, Ctrl+M"));
  // qDebug() << "Installed action:" << action_p->text()
  //          << "label:" << action_p->shortcut();
  m_ui->integrateToMzRtPushButton->addAction(action_p);
  m_ui->integrateToMzRtPushButton->setToolTip(action_p->text());
  connect(action_p, &QAction::triggered, this, [&]() {
    qDebug() << "Trigger" << static_cast<QAction *>(QObject::sender())->text();
    // This action is set to may different buttons all over the windows,
    // we do not want that the action be triggered for any other window than
    // this one!
    if(!isActiveWindow())
      return;
    m_ui->integrateToMzRtPushButton->click();
  });

  // When the filtering group box is unchecked, reset the filters and show
  // everything

  connect(m_ui->filteringOptionsGroupBox,
          &QGroupBox::clicked,
          this,
          [&](bool checked) {
            if(!checked)
              {
                // Deactivate the filtering.
                mp_msRunDataSetTableViewProxyModel->convertFilteringStrings(
                  0, "", "", "", "", "", "");

                // Force filtering.
                mp_msRunDataSetTableViewProxyModel->invalidate();

                // Now that we have force the "defiltering", we can display the
                // number of spectra in the status bar.

                std::size_t row_count =
                  mp_msRunDataSetTableViewProxyModel->rowCount();

                // Do not set a time out for this status bar message.
                statusBar()->showMessage(
                  QString("%1 %2 in the mass spectral data set.")
                    .arg(row_count)
                    .arg(row_count > 1 ? "spectra" : "spectrum"));
              }

            m_ui->filteringOptionsFrame->setVisible(checked);
          });

  readSettings();

  return true;
}

void
MsRunDataSetTableViewWnd::show()
{
  readSettings();

  QMainWindow::show();
}

void
MsRunDataSetTableViewWnd::executeFilteringPushButtonClicked()
{
  qDebug();

  // We need to get all the strings from the various line edit widgets.
  std::size_t ms_level           = m_ui->msLevelSpinBox->value();
  QString indices                = m_ui->indexLineEdit->text();
  QString rt_values              = m_ui->retentionTimeLineEdit->text();
  QString dt_values              = m_ui->driftTimeLineEdit->text();
  QString precursor_index_values = m_ui->precursorIndexLineEdit->text();

  QString precursor_mz_values = m_ui->precursorMzLineEdit->text();
  // qDebug() << "The precursor mz values in the table cell:"
  //<< precursor_mz_values;

  QString precursor_charge_values = m_ui->precursorChargeLineEdit->text();
  // qDebug() << "The precursor charge values in the table cell:"
  //<< precursor_charge_values;

  // Now set all these values to the model proxy which will use them.
  // Note that the precision/tolerance for the mz value matches is automatically
  // set to the proxy model upon modifying values in the widget (see connection
  // at initialization).

  bool res = mp_msRunDataSetTableViewProxyModel->convertFilteringStrings(
    ms_level,
    indices,
    rt_values,
    dt_values,
    precursor_index_values,
    precursor_mz_values,
    precursor_charge_values);

  // qDebug() << "res:" << res;

  if(!res)
    {
      statusBar()->showMessage(
        "A problem with at least one filtering data entity was encountered",
        2000);

      return;
    }

  // Since the filter string conversion worked, go on forcing filtering.

  mp_msRunDataSetTableViewProxyModel->invalidate();

  std::size_t row_count = mp_msRunDataSetTableViewProxyModel->rowCount();

  // Do not set a time out for this status bar message.
  statusBar()->showMessage(QString("%1 %2 retained after filtering.")
                             .arg(row_count)
                             .arg(row_count > 1 ? "spectra" : "spectrum"));
}

void
MsRunDataSetTableViewWnd::showMzIntegrationParamsDlg()
{

  // Heap-allocated.
  pappso::MzIntegrationParams *mz_integration_params_p =
    mp_processingFlow->getSensibleMzIntegrationParams();

  Q_ASSERT(mz_integration_params_p != nullptr);

  // qDebug() << "Showing mz integration params for values:"
  //          << mz_integration_params_p->toString(
  //               /*offset*/ 0, /*spacer*/ " ");

  if(mp_mzIntegrationParamsDlg == nullptr)
    {
      mp_mzIntegrationParamsDlg =
        new MzIntegrationParamsDlg(this, *mz_integration_params_p, m_color);

      connect(
        mp_mzIntegrationParamsDlg,
        &MzIntegrationParamsDlg::mzIntegrationParamsDlgShouldBeDestroyedSignal,
        [this]() {
          // qDebug();
          delete QObject::sender();
          mp_mzIntegrationParamsDlg = nullptr;
        });

      connect(mp_mzIntegrationParamsDlg,
              &MzIntegrationParamsDlg::mzIntegrationParamsChangedSignal,
              this,
              &MsRunDataSetTableViewWnd::mzIntegrationParamsChanged);

      mp_mzIntegrationParamsDlg->show();
    }
}

void
MsRunDataSetTableViewWnd::mzIntegrationParamsChanged(
  const pappso::MzIntegrationParams &mz_integration_params)
{
  // qDebug().noquote() << "The validated mz integration parameters:"
  //                    << mz_integration_params.toString(/*offset*/ 0,
  //                                                      /*spacer*/ " ");

  // We cannot copy the params returned as parameter into the destination
  // because we would loose the greatestMz/smallesMz values that we got from the
  // ms run data set upon creation of this window.

  // Create a local copy of the processing flow's default mz integration params.
  // Heap-allocated
  pappso::MzIntegrationParams *local_mz_integration_params_p =
    mp_processingFlow->getSensibleMzIntegrationParams();

  // Update some of the values with the params after change.
  local_mz_integration_params_p->setBinningType(
    mz_integration_params.getBinningType());
  local_mz_integration_params_p->setBinSizeModel(
    mz_integration_params.getBinSizeModel());
  local_mz_integration_params_p->setDecimalPlaces(
    mz_integration_params.getDecimalPlaces());
  local_mz_integration_params_p->setRemoveZeroValDataPoints(
    mz_integration_params.isRemoveZeroValDataPoints());

  // Now set the updated mz integration params back to the mp_processingFlow->

  mp_processingFlow->setDefaultMzIntegrationParams(
    *local_mz_integration_params_p);

  delete local_mz_integration_params_p;
}

void
MsRunDataSetTableViewWnd::ticIntensityValue(double intensity_value)
{
  statusBar()->showMessage(QString::number(intensity_value, 'g', 6), 2000);
}

void
MsRunDataSetTableViewWnd::keyPressEvent(QKeyEvent *event)
{
  // Select all the row in one key shortcut.
  if(event->key() == Qt::Key_A && event->modifiers() & Qt::ControlModifier)
    {
      mp_msRunDataSetTableView->selectAll();
    }

  // If Ctrl return, run the filter

  if(event->key() == Qt::Key_Return &&
     event->modifiers() & Qt::ControlModifier &&
     m_ui->filteringOptionsGroupBox->isChecked())
    {
      executeFilteringPushButtonClicked();
    }
}

void
MsRunDataSetTableViewWnd::createMainMenu()
{

  mp_mainMenu = new QMenu(this);

  const QIcon main_menu_icon =
    QIcon(":/images/svg/mobile-phone-like-menu-button.svg");
  mp_mainMenuPushButton = new QPushButton(main_menu_icon, QString(""), this);

  // When the main menu push button is clicked, the menu show up.
  connect(mp_mainMenuPushButton, &QPushButton::clicked, this, [&]() {
    mp_mainMenu->show();
  });

  QKeySequence key_sequence("Ctrl+O, I");
  QString action_label = QString("Open m/z integration parameters dialog");
  libXpertMassGui::ActionId action_id(
    "Global", "Integrations", "OpenMzIntegrationParams", action_label);

  ProgramWindow *program_window_p =
    static_cast<Application *>(qApp)->getProgramWindow();

  QAction *show_mz_integration_params_dlg_action_p =
    program_window_p->getActionManager()->installAction(action_id,
                                                        key_sequence);
  show_mz_integration_params_dlg_action_p->setStatusTip(action_label);
  connect(
    show_mz_integration_params_dlg_action_p, &QAction::triggered, this, [&]() {
      // We do not want that this action when triggered in another window
      // that is actually active, also triggers this one. So, we only
      // show the dialog window if this window is actually active.
      if(isActiveWindow())
        MsRunDataSetTableViewWnd::showMzIntegrationParamsDlg();
    });

  mp_mainMenu->addAction(show_mz_integration_params_dlg_action_p);

  // The main menu is under the control of a push button.
  mp_mainMenuPushButton->setMenu(mp_mainMenu);
}

bool
MsRunDataSetTableViewWnd::recordMsFragmentationSpec()
{

  MsFragmentationSpec ms_fragmentation_spec =
    mp_processingFlow->getDefaultMsFragmentationSpec();

  ms_fragmentation_spec.setMsLevel(m_ui->msLevelSpinBox->value());

  mp_processingFlow->setDefaultMsFragmentationSpec(ms_fragmentation_spec);

  return true;
}

void
MsRunDataSetTableViewWnd::recordQualifiedMassSpectraToIntegrate(
  std::vector<pappso::QualifiedMassSpectrumCstSPtr>
    &vector_of_qualified_mass_spectra,
  ProcessingStep &processing_step,
  const QString &integration_type) const
{
  // qDebug() << "integration_type:" << integration_type;

  // Start by getting the raw values.
  double begin_rt = -1;
  double end_rt   = -1;

  double begin_im_value = -1;
  double end_im_value   = -1;

  QItemSelectionModel *selection_model_p =
    mp_msRunDataSetTableView->selectionModel();

  QModelIndexList selected_rows_list = selection_model_p->selectedRows();
  // qDebug() << "The number of selected rows is:" << selected_rows_list.size();

  double smallest_rt_value = std::numeric_limits<double>::max();
  double greatest_rt_value = std::numeric_limits<double>::min();

  double smallest_im_value = std::numeric_limits<double>::max();
  double greatest_im_value = std::numeric_limits<double>::min();

  for(int iter = 0; iter < selected_rows_list.size(); ++iter)
    {
      QModelIndex index = selected_rows_list.at(iter);

      // Attention, when working with proxy filter models, the internal
      // pointer to the table view item is no more the one that points to the
      // initially created view item. We need to step up to the source model
      // to get the index to the "original" table view item. So, here we
      // convert that proxy index to a source model index

      QModelIndex source_index =
        mp_msRunDataSetTableViewProxyModel->mapToSource(index);

      // Try to get the table view item itself that contains interesting data,
      // like the pointer to the mass spectmrm.

      MsRunDataSetTableViewItem *view_item_p =
        static_cast<MsRunDataSetTableViewItem *>(
          source_index.internalPointer());

      if(view_item_p == nullptr)
        qFatal() << "Pointer cannot be nullptr.";

      pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
        view_item_p->getQualifiedMassSpectrum();

      if(qualified_mass_spectrum_csp == nullptr ||
         qualified_mass_spectrum_csp.get() == nullptr)
        qFatal() << "Pointer cannot be nullptr";

      // Try to get the data.

      QModelIndex sibling_index = source_index.siblingAtColumn(
        static_cast<int>(MassSpecDataViewColumns::COLUMN_SPECTRUM_INDEX));
      QString spectrum_index_string = sibling_index.data().toString();

      double rt_value = qualified_mass_spectrum_csp->getRtInMinutes();

      smallest_rt_value = std::min(smallest_rt_value, rt_value);
      greatest_rt_value = std::max(greatest_rt_value, rt_value);

      // Handle the mobility data, either drift time or 1/K0 depending
      // on the file type.
      double ion_mobility_value = -1;

      // Get the format of the file, because that will condition the
      // way we provide mobility values.

      pappso::Enums::MsDataFormat file_format =
        qualified_mass_spectrum_csp->getMassSpectrumId()
          .getMsRunIdCstSPtr()
          ->getMsDataFormat();

      if(file_format == pappso::Enums::MsDataFormat::brukerTims)
        {
          QVariant ion_mobility_variant_value =
            qualified_mass_spectrum_csp->getParameterValue(
              pappso::QualifiedMassSpectrumParameter::IonMobOneOverK0);

          if(ion_mobility_variant_value.isValid())
            {
              bool ok            = false;
              ion_mobility_value = ion_mobility_variant_value.toDouble(&ok);
              if(!ok)
                {
                  qFatal()
                    << "Failed to convert QVariant 1/K0 value to double.";
                }
            }
        }
      else
        {
          ion_mobility_value =
            qualified_mass_spectrum_csp->getDtInMilliSeconds();
        }

      if(ion_mobility_value > -1)
        {
          smallest_im_value = std::min(smallest_im_value, ion_mobility_value);
          greatest_im_value = std::max(greatest_im_value, ion_mobility_value);
        }

      // qDebug() << qSetRealNumberPrecision(10) << "From the table view: "
      //<< "rt_value: " << rt_value << "dt_value: " << dt_value;

      // Finally store the pointer to the qualified mass spectrum.
      vector_of_qualified_mass_spectra.push_back(qualified_mass_spectrum_csp);
    }

  if(!vector_of_qualified_mass_spectra.size())
    return;

  // At this point, we know the rt range and potentially also the dt range.

  begin_rt = smallest_rt_value;
  // qDebug() << "begin_rt:" << begin_rt;

  end_rt = greatest_rt_value;
  // qDebug() << "end_rt:" << end_rt;

  // Note that if the user selects either only one mass spectrum of a given RT
  // or only drift spectra of a given RT, then, begin_rt == end_rt and we cannot
  // craft a polygon. But we need to, because if DT data are included, the
  // selection is 2-dimensional.

  if(begin_rt == end_rt)
    {
      // qDebug()
      //   << "There is only one RT value, crafting an artificial RT range.";

      end_rt = pappso::Utils::nearestGreater(end_rt);
    }

  if(smallest_im_value != std::numeric_limits<double>::max() &&
     greatest_im_value != std::numeric_limits<double>::min())
    {
      begin_im_value = smallest_im_value;
      // qDebug() << "begin_im_value:" << begin_im_value;

      end_im_value = greatest_im_value;
      // qDebug() << "end_im_value:" << end_im_value;

      if(begin_im_value == end_im_value)
        {
          // qDebug()
          //   << "There is only one DT value, crafting an artificial DT
          //   range.";

          end_im_value = pappso::Utils::nearestGreater(end_im_value);
        }
    }
  else
    {
      begin_im_value = -1;
      end_im_value   = -1;
    }

  // At this point, craft the integration scope, that will be mono-dimensional
  // if there is no ion mobility or two-dimensional if there is ion mobility.

  // pappso::SelectionPolygon selection_polygon;
  pappso::IntegrationScopeBaseCstSPtr integration_scope_base_csp = nullptr;

  if(begin_im_value == -1 || end_im_value == -1)
    {
      // Ion mobility is not a criterion here. Craft a 1D polygon with the RT
      // data only.

      // selection_polygon.set1D(begin_rt, end_rt);
      pappso::IntegrationScopeSPtr integration_scope_sp =
        std::make_shared<pappso::IntegrationScope>(QPointF(begin_rt, 0),
                                                   fabs(end_rt - begin_rt));
      integration_scope_base_csp =
        std::static_pointer_cast<const pappso::IntegrationScopeBase>(
          integration_scope_sp);
    }
  else
    {
      // that is, if(begin_im_value > -1 && end_im_value > -1)

      // The data set contains ion mobility data.

      // Craft a 2D polygon with x=DT and y=RT (alphabetical sorting of the
      // axes).

      // qDebug() << qSetRealNumberPrecision(50)
      //<< "rt difference:" << end_rt - begin_rt
      //<< "dt difference:" << end_im_value - begin_im_value;

      // selection_polygon.set2D(QPointF(begin_im_value, end_rt),
      //                         QPointF(end_im_value, end_rt),
      //                         QPointF(end_im_value, begin_rt),
      //                         QPointF(begin_im_value, begin_rt));

      pappso::IntegrationScopeRectSPtr integration_scope_rect_sp =
        std::make_shared<pappso::IntegrationScopeRect>(
          QPointF(begin_im_value, begin_rt),
          fabs(end_im_value - begin_im_value),
          fabs(end_rt - begin_rt));

      integration_scope_base_csp =
        std::static_pointer_cast<const pappso::IntegrationScopeBase>(
          integration_scope_rect_sp);
    }

  processing_step.setIntegrationScope(integration_scope_base_csp);

  // Note that if the user selects either only one mass spectrum of a given RT
  // or only drift spectra of a given RT, then, begin_rt == end_rt and we cannot
  // craft a polygon. But we need to, because if DT data are included, the
  // selection is 2-dimensional.

  // We need to document the ProcessingType.

  // bool is_selection_polygon_2D = selection_polygon.is2D();
  // if(is_selection_polygon_2D)
  if(integration_scope_base_csp->is2D())
    {
      // If the selection polygon is bi-dimensional, then that means that
      // there are DT values. So we set dt for the x axis and rt for the y axis
      // (remember we set the axes according to the processing type source name
      // (here DT_RT).

      // qDebug() << "The integration scope is 2D.";

      processing_step.setDataKind(pappso::Enums::Axis::x,
                                  pappso::Enums::DataKind::dt);
      processing_step.setDataKind(pappso::Enums::Axis::y,
                                  pappso::Enums::DataKind::rt);

      processing_step.setSrcProcessingType(pappso::Enums::Axis::x,
                                           "DATA_TABLE_VIEW_DT");
      processing_step.setSrcProcessingType(pappso::Enums::Axis::y,
                                           "DATA_TABLE_VIEW_RT");
    }
  else
    {
      // If the integration scope is mono-dimensional, then that means that
      // there are no DT values. So we set rt for the x axis.

      // qDebug() << "The integration scope is 1D.";

      processing_step.setDataKind(pappso::Enums::Axis::x,
                                  pappso::Enums::DataKind::rt);
      processing_step.setDataKind(pappso::Enums::Axis::y,
                                  pappso::Enums::DataKind::unset);

      processing_step.setSrcProcessingType(pappso::Enums::Axis::x,
                                           "DATA_TABLE_VIEW_RT");
      processing_step.setSrcProcessingType(pappso::Enums::Axis::y, "NOT_SET");
    }

  // And now configure the destination type.

  if(integration_type == "TO_DT")
    processing_step.setDestProcessingType("DT");
  else if(integration_type == "TO_MZ")
    processing_step.setDestProcessingType("MZ");
  else if(integration_type == "TO_RT")
    processing_step.setDestProcessingType("RT");
  else if(integration_type == "TO_INT")
    processing_step.setDestProcessingType("INT");
  else if(integration_type == "TO_DT_MZ")
    processing_step.setDestProcessingType("DT_MZ");
  else if(integration_type == "TO_DT_RT")
    processing_step.setDestProcessingType("DT_RT");
  else if(integration_type == "TO_MZ_RT")
    processing_step.setDestProcessingType("MZ_RT");
  else
    qFatal() << "Programming error.";

  // qDebug() << "Finally configured step:" << processing_step.toString();
}

void
MsRunDataSetTableViewWnd::integrate(const QString &integration_type)
{
  // qDebug() << "Integration type:" << integration_type;

  // We need to craft a brand new processing flow because we do not want to
  // pollute the member datum.

  recordMsFragmentationSpec();

  ProcessingFlow local_processing_flow;
  local_processing_flow.initialize(*mp_processingFlow);

  // Immediately set the mz integration params to the step. These are located in
  // the processing flow as the default mz integartion params.

  // Create the processing step to document the processing. We cannot set the
  // ProcessingType because that will depend on the kind of data we use for the
  // integration below.

  ProcessingStepSPtr step_sp = std::make_shared<ProcessingStep>();

  // Takes ownership
  step_sp->setMzIntegrationParams(
    local_processing_flow.getSensibleMzIntegrationParams());

  // At this point, make sure we document in the processing flow the other
  // critical parameters: the RT and DT values along with the MS fragmentation
  // spec data. These elements are not taken into the account in the integration
  // itself, because the integration works by going through the list of
  // currently selected items in the table view, but they will be important in
  // subsequent integrations are performed starting from the trace generated by
  // the present integration. Otherwise we loose the fundamental concept of
  // "virtually restricting" the data set to the innermost ranges used all along
  // the integrations.

  typedef std::vector<pappso::QualifiedMassSpectrumCstSPtr>
    QualifiedMassSpectraVector;
  typedef std::shared_ptr<QualifiedMassSpectraVector>
    QualifiedMassSpectraVectorSPtr;

  QualifiedMassSpectraVector vector_of_qualified_mass_spectra;

  recordQualifiedMassSpectraToIntegrate(
    vector_of_qualified_mass_spectra, *step_sp, integration_type);

  if(!vector_of_qualified_mass_spectra.size())
    return;

  // qDebug() << "The number of spectra recorded for integration:"
  //          << vector_of_qualified_mass_spectra.size();

  // We have finished documenting the flow's step, append it to the processing
  // flow.

  // Should perform automatically the conversion from non-const to const.
  local_processing_flow.getStepsRef().push_back(step_sp);

  // qDebug().noquote() << "Right before integrating, processing flow:"
  //                    << local_processing_flow_p->toString();

  // Finally make a shared pointer with the vector of qualified mass spectra.

  QualifiedMassSpectraVectorSPtr vector_of_qualified_mass_spectra_sp =
    std::make_shared<QualifiedMassSpectraVector>(
      vector_of_qualified_mass_spectra);

  // We can now select the right integration.

  if(integration_type == "TO_DT")
    mp_programWindow->integrateToDt(
      nullptr, vector_of_qualified_mass_spectra_sp, local_processing_flow);
  else if(integration_type == "TO_MZ")
    mp_programWindow->integrateToMz(
      nullptr, vector_of_qualified_mass_spectra_sp, local_processing_flow);
  else if(integration_type == "TO_RT")
    mp_programWindow->integrateToRt(
      nullptr, vector_of_qualified_mass_spectra_sp, local_processing_flow);
  else if(integration_type == "TO_INT")
    mp_programWindow->integrateToTicIntensity(
      nullptr, vector_of_qualified_mass_spectra_sp, local_processing_flow);
  else if(integration_type == "TO_DT_MZ")
    mp_programWindow->integrateToDtMz(
      nullptr, vector_of_qualified_mass_spectra_sp, local_processing_flow);
  else if(integration_type == "TO_DT_RT")
    mp_programWindow->integrateToDtRt(
      nullptr, vector_of_qualified_mass_spectra_sp, local_processing_flow);
  else if(integration_type == "TO_MZ_RT")
    mp_programWindow->integrateToMzRt(
      nullptr, vector_of_qualified_mass_spectra_sp, local_processing_flow);
  else
    qFatal() << "Programming error.";
}


} // namespace MineXpert

} // namespace MsXpS
