/* 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
#include <cstdio>
#include <ctime>


/////////////////////// Qt includes
#include <QWidget>
#include <QDebug>
#include <QDir>
#include <QMessageBox>
#include <QFileDialog>
#include <QMenuBar>
#include <QMenu>
#include <QKeySequence>
#include <QSettings>
#include <QCloseEvent>
#include <QThread>
#include <QDateTime>
#include <QTimer>
#include <QStringLiteral>
#include <QPluginLoader>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/msfile/msfileaccessor.h>
#include <pappsomspp/core/js_qml/jsclassregistrar.h>
#include <pappsomspp/core/processing/cbor/mzcbor/mzcborreaderbase.h>


/////////////////////// libXpertMassCore
#include <MsXpS/libXpertMassCore/MassDataCborBaseHandler.hpp>
#include <MsXpS/libXpertMassCore/MassDataClient.hpp>
#include <MsXpS/libXpertMassCore/MassDataServer.hpp>


/////////////////////// libXpertMassGui
#include <MsXpS/libXpertMassGui/IsotopicClusterGeneratorDlg.hpp>
#include <MsXpS/libXpertMassGui/IsotopicClusterShaperDlg.hpp>
#include <MsXpS/libXpertMassGui/LabelledCheckBoxAction.hpp>
#include <MsXpS/libXpertMassGui/WindowGeometryManager.hpp>
#include <MsXpS/libXpertMassGui/TaskMonitorCompositeWidget.hpp>


/////////////////////// Local includes
#include "Application.hpp"
#include "DataPlottableNode.hpp"
#include "MassDataIntegratorTask.hpp"
#include "MsRunProbeInfoViewerDlg.hpp"
#include "MsRunProbeTask.hpp"
#include "MsRunReadTask.hpp"
#include "MsRunSelectorDlg.hpp"
#include "ProgramWindow.hpp"
#include "XyMsRunLoader.hpp"
#include "vendor/BrukerBafAsciiMsRunLoader.hpp"
#include "vendor/BrukerTimsTofMsRunLoader.hpp"
#include "vendor/MzCborMsRunLoader.hpp"
#include "vendor/PwizLiteMsRunLoader.hpp"
#include "ApplicationPreferencesWnd.hpp"
#include "plugins/common/MineXpertPluginInterface.hpp"

namespace MsXpS
{

namespace MineXpert
{

ProgramWindow::ProgramWindow(QWidget *parent,
                             const QString &application_name,
                             const QString &description)
  : QMainWindow(parent),
    m_applicationName(application_name),
    m_windowDescription(description)
{

  // if(static_cast<Application *>(qApp)->getProgramWindow() != nullptr)
  //   {
  //     qFatal() << "One instance of ProgramWindow exists already.";
  //   }

  dynamic_cast<QWidget *>(this)->setWindowTitle(m_applicationName);

  m_lastUsedDirectory = QDir::home().absolutePath();

  // This function is called by the user of the program window
  // because we need to return immediately so that Application can store
  // the pointer to the constructed program window. That pointer is
  // used throughout the program and also when setting up the window.
  // setupWindow();
}

ProgramWindow::~ProgramWindow()
{
  if(mpa_analysisFile != nullptr)
    delete mpa_analysisFile;

  mp_actionManager->saveActionData();
  // The action manager will be deleted because it was parented to this
  // Application instance.

  writeSettings();
}

//! Handler of the close event signal.
void
ProgramWindow::closeEvent(QCloseEvent *event)
{
  // qDebug() << "Now closing...";
  writeSettings();
  event->accept();
}

const QString &
ProgramWindow::applicationName() const
{
  return m_applicationName;
}

void
ProgramWindow::setMaxThreadUseCount(int count)
{
  if(!count || count > QThread::idealThreadCount())
    m_maxThreadUseCount = QThread::idealThreadCount();

  m_maxThreadUseCount = count;
}

int
ProgramWindow::getMaxThreadUseCount()
{
  if(!m_maxThreadUseCount)
    m_maxThreadUseCount = QThread::idealThreadCount();

  return m_maxThreadUseCount;
}

void
ProgramWindow::writeSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  // qDebug() << "Saving window geometry.";

  libXpertMassGui::WindowGeometryManager::save(
    dynamic_cast<const QWidget *>(this), "ProgramWindow", settings);

  settings.beginGroup("ProgramWindow");

  settings.setValue("recentFiles", m_recentFilesHandler.makeDataForSettings());

  settings.endGroup();
}

void
ProgramWindow::readSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

  qDebug() << "Restoring window geometry.";

  libXpertMassGui::WindowGeometryManager::restore(this, "ProgramWindow", settings);

  settings.beginGroup("ProgramWindow");

  QByteArray data = settings.value("recentFiles").toByteArray();
  m_recentFilesHandler.makeFromSettingsData(data);

  // qDebug().noquote() << "Most recent files:"
  //                    <<
  //                    m_recentFilesHandler.allItemsSortedByTime().join("\n");

  settings.endGroup();
}

void
ProgramWindow::initializeAllWindows()
{
  mp_ticXicChromPlotWnd = new TicXicChromTracePlotWnd(
    this, "TIC/XIC chromatograms", "TicXicChromTracePlotWnd");
  mp_ticXicChromPlotWnd->setWindowIcon(qApp->windowIcon());
  // mp_ticXicChromPlotWnd->show();

  mp_massSpecPlotWnd =
    new MassSpecTracePlotWnd(this, "Mass spectra", "MassSpecTracePlotWnd");
  mp_massSpecPlotWnd->setWindowIcon(qApp->windowIcon());
  // mp_massSpecPlotWnd->show();

  connect(mp_massSpecPlotWnd,
          &BasePlotWnd::massDataToBeServedSignal,
          this,
          &ProgramWindow::massDataToBeServed);

  mp_driftSpecPlotWnd =
    new DriftSpecTracePlotWnd(this, "Drift spectra", "DriftSpecTracePlotWnd");
  mp_driftSpecPlotWnd->setWindowIcon(qApp->windowIcon());
  // mp_driftSpecPlotWnd->show();

  mp_ticXicChromMassSpecColorMapWnd =
    new TicXicChromMassSpecColorMapWnd(this,
                                       "TIC/XIC chrom. / Mass spec. color maps",
                                       "TicXicChromMassSpecColorMapWnd");
  mp_ticXicChromMassSpecColorMapWnd->setWindowIcon(qApp->windowIcon());
  // mp_ticXicChromMassSpecColorMapWnd->show();

  mp_ticXicChromDriftSpecColorMapWnd = new TicXicChromDriftSpecColorMapWnd(
    this,
    "TIC/XIC chrom. / Drift spec. color maps",
    "TicXicChromDriftSpecColorMapWnd");
  mp_ticXicChromDriftSpecColorMapWnd->setWindowIcon(qApp->windowIcon());
  // mp_ticXicChromDriftSpecColorMapWnd->show();

  mp_driftSpecMassSpecColorMapWnd =
    new DriftSpecMassSpecColorMapWnd(this,
                                     "Drift spec. / Mass spec. color maps",
                                     "DriftSpecMassSpecColorMapWnd");
  mp_driftSpecMassSpecColorMapWnd->setWindowIcon(qApp->windowIcon());
  // mp_driftSpecMassSpecColorMapWnd->show();

  mp_xicExtractionWnd = new XicExtractionWnd(
    this, "XIC chromatogram extraction", "XicExtractionWnd");
  mp_xicExtractionWnd->setWindowIcon(qApp->windowIcon());

  mp_consoleWnd = new ConsoleWnd(this, m_applicationName);
  mp_consoleWnd->setWindowIcon(qApp->windowIcon());
  // mp_consoleWnd->show();

  mp_scriptingWnd =
    new libXpertMassGui::JavaScriptingWnd(m_applicationName, this);
  mp_scriptingWnd->setWindowIcon(qApp->windowIcon());

  mp_applicationPreferencesWnd = new ApplicationPreferencesWnd(
    m_applicationName, "Application preferences", this);
  mp_applicationPreferencesWnd->setWindowIcon(qApp->windowIcon());
  initializeApplicationPreferencesWnd();

  mp_openMsRunDataSetsDlg = new OpenMsRunDataSetsDlg(this);
  mp_openMsRunDataSetsDlg->setWindowIcon(qApp->windowIcon());
  // Trying to fix the odd behaviour with this dialog window that does not
  // conform (all the others do) to the instructions about placing at
  // given virtual desktop or activity. But these two lines below
  // did not fix the problem. In the end, not setting this dialog windows
  // as the first allocated widget in this function fixed the problem.
  // mp_openMsRunDataSetsDlg->setObjectName("OpenMsRunDataSetsDlg");
  // mp_openMsRunDataSetsDlg->setWindowRole("open_ms_run_data_sets_dlg");
  // mp_openMsRunDataSetsDlg->show();

  mp_taskMonitorWnd = new libXpertMassGui::TaskMonitorWnd(
    this,
    /*title*/ "MineXpert - Task Monitor Window",
    /*settings config title*/ "TaskMonitorWnd",
    /*description*/ "Monitor the running tasks");
  mp_taskMonitorWnd->setWindowIcon(qApp->windowIcon());
  showTaskMonitorWnd();

  // NO, this one is "on demand".
  // mp_isotopicClusterGeneratorDlg = new IsoSpecDlg(this, m_applicationName);

  // NO, this one is "on demand".
  // mp_isotopicClusterShaperDlg = new MassPeakShaperDlg(this,
  // m_applicationName);

  // At this point, try to check if we should remind the user to
  // cite the paper.
}

libXpertMassGui::ActionManager *
ProgramWindow::getActionManager()
{
  return mp_actionManager;
}

void
ProgramWindow::initializeApplicationPreferencesWnd()
{
}

void
ProgramWindow::updateRecentFilesMenu()
{
  if(mp_recentFilesMenu != nullptr)
    delete mp_recentFilesMenu;

  mp_recentFilesMenu = new QMenu("&Recent files...", mp_fileMenu);
  // We want to insert the menu (typically sfter updateRecentFilesMenu()
  // is called right before the separator.)
  mp_fileMenu->insertMenu(mp_recentFilesMenuSeparator, mp_recentFilesMenu);

  // The first menu item is an item that the user may click
  // to erase the Recent files... list.

  QAction *erase_recent_files_list_action_p =
    new QAction("Erase the Recent files list", dynamic_cast<QObject *>(this));
  mp_recentFilesMenu->addAction(erase_recent_files_list_action_p);
  connect(erase_recent_files_list_action_p, &QAction::triggered, this, [&]() {
    m_recentFilesHandler.clear();
    updateRecentFilesMenu();
  });

  // The second menu item is a check box where the user will be
  // selecting if the the file load is to be performed as
  // full in memory or in stream mode.

  QWidgetAction *streamed_mode_widget_action_p =
    new libXpertMassGui::LabelledCheckBoxAction(
      "Check to load any file below in stream mode",
      Qt::CheckState::Unchecked,
      "" /*no text here*/,
      dynamic_cast<QObject *>(this));

  mp_recentFilesMenu->addAction(streamed_mode_widget_action_p);

  QStringList all_file_paths_reverse_sorted =
    m_recentFilesHandler.allItemsSortedByTime(/*most_recent_first*/ true);

  QAction *file_path_action_p = nullptr;

  foreach(QString path, all_file_paths_reverse_sorted)
    {
      // qDebug() << "Currently iterated path:" << path;

      // First the full in memory action/menu/signal/slot
      file_path_action_p = new QAction(path, dynamic_cast<QObject *>(this));
      file_path_action_p->setProperty("filePath", path);

      connect(file_path_action_p,
              &QAction::triggered,
              this,
              [streamed_mode_widget_action_p, file_path_action_p, this]() {
                QString file_path =
                  file_path_action_p->property("filePath").toString();

                bool streamed_mode =
                  dynamic_cast<libXpertMassGui::LabelledCheckBoxAction *>(
                    streamed_mode_widget_action_p)
                    ->isChecked();

                // qDebug() << "Now loading recent file:" << file_path
                //          << "in streamed mode:" << streamed_mode;

                openMassSpectrometryFile(file_path,
                                         /*full_in_memory*/ !streamed_mode);
              });

      mp_recentFilesMenu->addAction(file_path_action_p);
    }
}

void
ProgramWindow::createMenusAndActions()
{
  // File menu
  mp_fileMenu = menuBar()->addMenu("&File");

  // We want to capture the moment the File menu is about to show
  // so that we can update the Recent files... submenu.

  connect(mp_fileMenu, &QMenu::aboutToShow, this, [&]() {
    // qDebug() << "File menu is about to show.";
    updateRecentFilesMenu();
  });

  QString action_label;
  libXpertMassGui::ActionId action_id;
  QAction *action_p = nullptr;

  // Handle the QAction-based actions centrally in the ActionManager.
  action_label = "Open mass spectrum file(s) fully in memory";
  action_id.initialize(
    "MainWindow", "File", "OpenMSFullInMemory", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+O, M"));
  mp_fileMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p, &QAction::triggered, this, [&]() {
    openMassSpectrometryFileDlg(m_lastUsedDirectory, true);
  });

  action_label = "Open mass spectrum file(s) in stream";
  action_id.initialize("MainWindow", "File", "OpenMSInStream", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+O, S"));
  mp_fileMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p, &QAction::triggered, this, [&]() {
    openMassSpectrometryFileDlg(m_lastUsedDirectory, false);
  });

  action_label = "Load mass spectrum from clipboard";
  action_id.initialize(
    "MainWindow", "File", "LoadMSFromClipboard", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+L, C"));
  mp_fileMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p, &QAction::triggered, this, [&]() {
    openMassSpectrometryFileFromClipBoard();
  });

  // Now handle the Recent files menus
  updateRecentFilesMenu();

  // This action below will serve us as a beacon to insert
  // the Recent files... submenu when it is updated.
  mp_recentFilesMenuSeparator = mp_fileMenu->addSeparator();

  mp_fileMenu->addSeparator();

  action_label = "Quit";
  action_id.initialize("MainWindow", "File", "Quit", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+Q"));
  mp_fileMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p, &QAction::triggered, this, &ProgramWindow::close);

  // Windows menu
  mp_windowsMenu = menuBar()->addMenu("&Windows");

  action_label = "Show TIC/XIC chrom. window";
  action_id.initialize(
    "MainWindow", "Windows", "ShowTicXicChromWnd", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+W, R"));
  mp_windowsMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showTicXicChromatogramsWnd);

  action_label = "Show mass spectra window";
  action_id.initialize("MainWindow", "Windows", "ShowMsWnd", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+W, M"));
  mp_windowsMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(
    action_p, &QAction::triggered, this, &ProgramWindow::showMassSpectraWnd);

  action_label = "Show ion mobility spectra window";
  action_id.initialize("MainWindow", "Windows", "ShowImWnd", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+W, D"));
  mp_windowsMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showIonMobilitySpectraWnd);

  action_label = "Show TIC/XIC chrom. vs mass spectra color maps window";
  action_id.initialize(
    "MainWindow", "Windows", "ShowTicXicMsWnd", action_label);
  action_p = getActionManager()->installAction(action_id,
                                               QKeySequence("Ctrl+W, Ctrl+M"));
  mp_windowsMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showTicXicChromMassSpecColorMapsWnd);

  action_label = "Show TIC/XIC chrom. vs ion mob. spectra color maps window";
  action_id.initialize(
    "MainWindow", "Windows", "ShowTicXicIonMobWnd", action_label);
  action_p = getActionManager()->installAction(action_id,
                                               QKeySequence("Ctrl+W, Ctrl+R"));
  mp_windowsMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showTicXicChromIonMobSpecColorMapsWnd);

  action_label = "Show ion mob. spec. vs mass spectra color maps window";
  action_id.initialize(
    "MainWindow", "Windows", "ShowTicXicIonMobWnd", action_label);
  action_p = getActionManager()->installAction(action_id,
                                               QKeySequence("Ctrl+W, Ctrl+D"));
  mp_windowsMenu->addAction(action_p);
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showIonMobSpecMassSpecColorMapsWnd);

  action_label = "Show task monitor window";
  action_id.initialize(
    "MainWindow", "Windows", "ShowTaskMonitorWnd", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+T, M"));
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  mp_windowsMenu->addAction(action_p);
  connect(
    action_p, &QAction::triggered, this, &ProgramWindow::showTaskMonitorWnd);

  action_label = "Show MS run data sets window";
  action_id.initialize(
    "MainWindow", "Windows", "ShowMsRunDataSetsWnd", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(
    action_p, &QAction::triggered, this, &ProgramWindow::showMsRunDataSetsWnd);
  mp_windowsMenu->addAction(action_p);

  action_label = "Show console window";
  action_id.initialize("MainWindow", "Windows", "ShowConsoleWnd", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(action_p, &QAction::triggered, this, &ProgramWindow::showConsoleWnd);
  mp_windowsMenu->addAction(action_p);

  action_label = "Show scripting window";
  action_id.initialize(
    "MainWindow", "Windows", "ShowScriptingWnd", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(
    action_p, &QAction::triggered, this, &ProgramWindow::showScriptingWnd);
  mp_windowsMenu->addAction(action_p);

  mp_windowsMenu->addSeparator();

  action_label = "Save workspace";
  action_id.initialize("MainWindow", "Windows", "Save workspace", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(action_p, &QAction::triggered, this, &ProgramWindow::saveWorkspace);
  mp_windowsMenu->addAction(action_p);

  // Utilities menu
  mp_utilitiesMenu = menuBar()->addMenu("&Utilities");

  action_label = "XIC extractions";
  action_id.initialize(
    "MainWindow", "Utilities", "XicExtractionsWnd", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(
    action_p, &QAction::triggered, this, &ProgramWindow::showXicExtractionsWnd);
  mp_utilitiesMenu->addAction(action_p);

  action_label = "Isotopic cluster calculations";
  action_id.initialize(
    "MainWindow", "Utilities", "IsotopicClusterCalculations", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showIsotopicClusterGeneratorDlg);
  mp_utilitiesMenu->addAction(action_p);

  action_label = "Mass peak shape calculations";
  action_id.initialize(
    "MainWindow", "Utilities", "MassPeakShapeCalculations", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showIsotopicClusterShaperDlg);
  mp_utilitiesMenu->addAction(action_p);

  action_label = "Scripting";
  action_id.initialize("MainWindow", "Utilities", "Scripting", action_label);
  action_p = getActionManager()->installAction(action_id);
  connect(
    action_p, &QAction::triggered, this, &ProgramWindow::showScriptingWnd);
  mp_utilitiesMenu->addAction(action_p);

  mp_utilitiesMenu->addSeparator();

  // Menu that pops up a dialog to start the server and also to configure the
  // client to connect to a server (id address : port number).

  action_label = "Client-Server configuration";
  action_id.initialize(
    "MainWindow", "Utilities", "ClientServerConfig", action_label);
  mp_clientServerConfigDlgAct = getActionManager()->installAction(action_id);
  connect(mp_clientServerConfigDlgAct,
          &QAction::triggered,
          this,
          &ProgramWindow::configureNetworkConnection);
  mp_utilitiesMenu->addAction(mp_clientServerConfigDlgAct);

  // Plugins menu
  mp_pluginsMenu = menuBar()->addMenu("Plugins");

  // Preferences menu
  mp_preferencesMenu = menuBar()->addMenu("&Preferences");

  action_label = "Edit preferences";
  action_id.initialize(
    "MainWindow", "Preferences", "EditPreferences", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+E, P"));
  // We have not to be on the main program window for this shortcut to work.
  action_p->setShortcutContext(Qt::ApplicationShortcut);
  mp_preferencesMenu->addAction(action_p);
  connect(action_p,
          &QAction::triggered,
          this,
          &ProgramWindow::showApplicationPreferencesWnd);

  // Help menu
  mp_helpMenu = menuBar()->addMenu("&Help");

  action_label = "About";
  action_id.initialize("MainWindow", "Help", "About", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+H, A"));
  action_p->setStatusTip(tr("Show this programs's About dialog window"));
  action_p->setIcon(QIcon(":/images/svg/help-information-icon.svg"));
  connect(action_p, &QAction::triggered, this, &ProgramWindow::showAboutDlg);
  mp_helpMenu->addAction(action_p);

  action_label = "About Qt";
  action_id.initialize("MainWindow", "Help", "AboutQt", action_label);
  action_p =
    getActionManager()->installAction(action_id, QKeySequence("Ctrl+H, Q"));
  action_p->setStatusTip(tr("Show the Qt library's About box"));
  action_p->setIcon(QIcon(":/images/svg/help-qt-information-icon.svg"));
  connect(action_p, &QAction::triggered, this, &Application::aboutQt);
  mp_helpMenu->addAction(action_p);
}

void
ProgramWindow::setupWindow()
{
  // qDebug();

  // This function must be called by the allocator of this instance.
  // At this time, this is in the main.cpp file and the pointer to the
  // ProgramWindow is set as soon as possible to the Application because
  // a great many objects need to be able to call
  // static_cast<Application *>(qApp)->getProgramWindow(), including
  // objects that are allocated in this function or functions called here.

  setWindowTitle(
    QString("%1 - %2").arg(m_applicationName, m_windowDescription));

  setWindowIcon(qApp->windowIcon());

  // This attribute make sure that the main window of the program is destroyed
  // when it is closed. Effectively stopping the program.
  setAttribute(Qt::WA_DeleteOnClose);

  statusBar()->setSizeGripEnabled(true);

  readSettings();

  // Allocate the ActionManager that we will need below for the creation
  // of the menus and actions of this window.
  mp_actionManager = new libXpertMassGui::ActionManager(this);
  // Because we already have initialized the libXpertMassCore library,
  // we can load all the shortcuts.
  mp_actionManager->loadActionData();

  // qDebug() << "Loaded" << mp_actionManager->actionCount() << "actions.";

  // Now create the menus and actions taking profit of the potentially
  // configured shortcuts loaded above.
  // qDebug() << "Start creating menus and actions";
  createMenusAndActions();
  // qDebug() << "Stop creating menus and actions";

  // qDebug() << "Start initializing all windows.";
  initializeAllWindows();
  // qDebug() << "Stop initializing all windows.";

  displayArticleCitationHelpPage();
  initializeScripting();
  // This feature is not yet used.
  // loadPlugins();
}

void
ProgramWindow::incrementLoadedFileCount()
{
  ++m_loadedFileCount;
}

bool
ProgramWindow::openMassSpectrometryFileDlg(const QString &dir_name,
                                           bool full_in_memory)
{
  // qDebug() << "The directory name is:" << dir_name;

  QFileDialog file_dialog(dynamic_cast<QWidget *>(this),
                          QString("%1 - Select mass spectrometry data file(s)")
                            .arg(m_applicationName));

  file_dialog.setFileMode(QFileDialog::ExistingFiles);
  file_dialog.setViewMode(QFileDialog::Detail);
  file_dialog.setAcceptMode(QFileDialog::AcceptOpen);

  // Open the dialog in the right directory.
  if(dir_name.isEmpty())
    file_dialog.setDirectory(m_lastUsedDirectory);
  else
    file_dialog.setDirectory(dir_name);

  QStringList file_names;

  if(file_dialog.exec())
    {
      file_names = file_dialog.selectedFiles();

      // Store the directory name whence the selected files come.

      if(file_names.size())
        {
          QString first_file_name = file_names.at(0);
          QFileInfo file_info     = QFileInfo(first_file_name);
          m_lastUsedDirectory     = file_info.absoluteDir().dirName();
        }
    }

  for(int iter = 0; iter < file_names.size(); ++iter)
    {
      QString cur_file_name = file_names.at(iter);

      openMassSpectrometryFile(cur_file_name, full_in_memory);
    }

  return true;
}

void
ProgramWindow::openMassSpectrometryFile(
  const QString &file_name,
  bool full_in_memory,
  [[maybe_unused]] const QString &sample_name)
{
  // qDebug() << "Opening mass spectrometry file:" << file_name
  //          << "with sample name:" << sample_name
  //          << "from thread:" << QThread::currentThread();

  if(file_name.isEmpty() || !QFileInfo::exists(file_name))
    {
      openMassSpectrometryFileDlg(QDir::homePath(), full_in_memory);
      return;
    }

  // At this point, we have a file name. Let's store the directory where the
  // file sits so that later the user can be directed there immediately.

  m_lastUsedDirectory = QFileInfo(file_name).dir().absolutePath();

  // Now let's start the file type identification work.

  // The MsFileAccessor will allow to get a list of MS run ids that can then
  // be used to request a MS run reader appropriate for the MS data file
  // format. Because there might be more than one ms run in a single mzML
  // file, we need to have a common prefix for all those ms run ids.
  pappso::MsFileAccessorSPtr ms_file_accessor_sp =
    std::make_shared<pappso::MsFileAccessor>(file_name, m_msRunIdPrefix);

  // qDebug() << "Could get MsFileAccessor.";

  // It is only when we ask for MS run ids that the file accessor actually
  // detects the format of the file. So start with this step.

  std::vector<pappso::MsRunIdCstSPtr> ms_run_ids =
    ms_file_accessor_sp->getMsRunIds();

  std::size_t run_count = ms_run_ids.size();

  // qDebug() << "The number of ms run ids:" << run_count;

  if(!run_count)
    {
      QMessageBox::information(
        dynamic_cast<QWidget *>(this),
        "Open mass spectrometry file",
        "This file does not contain any recognizable MS run.");
      return;
    }

  int run_index = 0;

  // qDebug() << "The number of ms runs in the file is:" << run_count;

  // If there are more than one run, let the user select the one they are
  // interested in.

  if(run_count > 1)
    {

      // If the user cancels the dialog, returns -1, otherwise returns the
      // index of the ms run id.

      run_index = selectMsRun(ms_run_ids);

      // qDebug() << "The run_index: " << run_index;

      if(run_index == -1)
        return;
    }

  // Get the run id of the vector at the index selected by the user.
  pappso::MsRunIdCstSPtr ms_run_id_csp = ms_run_ids.at(run_index);

  // Store in the MsFileAccessor the pointer to the selected MsRunId. This way,
  // we'll be able later to access the proper MsRunReader.
  ms_file_accessor_sp->setSelectedMsRunId(ms_run_id_csp);

  // qDebug() << "The file format was detected as:"
  //          << pappso::Utils::msDataFormatAsString(
  //               ms_file_accessor_sp->getFileFormat());

  if(ms_file_accessor_sp->getFileFormat() ==
     pappso::Enums::MsDataFormat::brukerTims)
    {
      //  Allocate a specific loader
      BrukerTimsTofMsRunLoader *ms_run_loader_p =
        new BrukerTimsTofMsRunLoader(this, ms_file_accessor_sp);

      ms_run_loader_p->loadMsRun(full_in_memory);

      //  Note that the loader is deleteLater() after its work.
    }
  // End of
  // if(ms_file_accessor.getFileFormat() ==
  // pappso::Enums::MsDataFormat::brukerTims)
  else if(ms_file_accessor_sp->getFileFormat() ==
            pappso::Enums::MsDataFormat::mzML ||
          ms_file_accessor_sp->getFileFormat() ==
            pappso::Enums::MsDataFormat::mzXML ||
          ms_file_accessor_sp->getFileFormat() ==
            pappso::Enums::MsDataFormat::MGF)
    {
      PwizLiteMsRunLoader *ms_run_loader_p =
        new PwizLiteMsRunLoader(this, ms_file_accessor_sp);
      ms_run_loader_p->loadMsRun(full_in_memory);

      //  Note that the loader is deleteLater() after its work.
    }
  else if(ms_file_accessor_sp->getFileFormat() ==
          pappso::Enums::MsDataFormat::brukerBafAscii)
    {
      // qDebug()
      //   << "Handling MS run file reading with MsDataFormat::brukerBafAscii";

      // The data are a file obtained by conversion of Bruker Baf data to ascii
      // by the Bruker DataAnalysis/Compass software. This file format is
      // extremely resource intensive and loading data in streamed mode is not
      // supporte.
      if(!full_in_memory)
        {
          QMessageBox msgBox;
          msgBox.setText(
            "Opening this kind of data file in streamed moded is not yet "
            "supported.");

          msgBox.exec();

          return;
        }

      BrukerBafAsciiMsRunLoader *ms_run_loader_p =
        new BrukerBafAsciiMsRunLoader(this, ms_file_accessor_sp);
      ms_run_loader_p->loadMsRun(full_in_memory);

      //  Note that the loader is deleteLater() after its work.
    }
  else if(ms_file_accessor_sp->getFileFormat() ==
          pappso::Enums::MsDataFormat::xy)
    {
      // qDebug() << "Handling MS run file reading with MsDataFormat::xy";

      XyMsRunLoader *ms_run_loader_p =
        new XyMsRunLoader(this, ms_file_accessor_sp);
      ms_run_loader_p->loadMsRun(full_in_memory);

      //  Note that the loader is deleteLater() after its work.
    }
  else if(ms_file_accessor_sp->getFileFormat() ==
          pappso::Enums::MsDataFormat::mzcbor)
    {
      MzCborMsRunLoader *ms_run_loader_p =
        new MzCborMsRunLoader(this, ms_file_accessor_sp);
      ms_run_loader_p->loadMsRun(full_in_memory);

      //  Note that the loader is deleteLater() after its work.
    }

  return;
}

void
ProgramWindow::openMassSpectrometryFileFromClipBoard()
{
  QClipboard *clipboard = QGuiApplication::clipboard();
  QString ms_data       = clipboard->text();

  QDateTime current_date_time = QDateTime::currentDateTime();

  QString time_as_string = current_date_time.toString("yyyyMMdd-HHmmss");

  openMassSpectrometryFileFromText(ms_data, "clipBoard-" + time_as_string);
  return;
}

void
ProgramWindow::openMassSpectrometryFileFromText(const QString &text,
                                                const QString &sample_name)
{

  qDebug() << "Opening mass spectrum from text with sample name:"
           << sample_name;

  if(text.isEmpty())
    {
      dynamic_cast<QMainWindow *>(this)->statusBar()->showMessage(
        QString("The trace is empty"), 4000);

      return;
    }

  // Now write the text to a temporary file and load it.

  // Get a directory suitable for temp files.

  QDir sys_tmp_dir = QDir::temp();

  QString temp_file_name = sample_name;

  if(temp_file_name.isEmpty())
    {
      // Craft the sample name with the date and time
      temp_file_name = m_applicationName;
    }

  temp_file_name += "-";

  temp_file_name.append(
    QDateTime::currentDateTime().toString("ddMMyyyy-HHmmss"));

  QFile file(sys_tmp_dir.absolutePath() + '/' + temp_file_name);

  // qDebug() << "Opening file: " << file.fileName();

  if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
    qFatal() << "Failed to open destination file.";

  QTextStream out(&file);

  out << text;

  out.flush();

  file.close();

  // qDebug() << "File " << file.fileName() << "has been closed now.";

  openMassSpectrometryFile(file.fileName(), true, sample_name);

  return;
}

void
ProgramWindow::updateDiscoveriesPreferences(
  const DiscoveriesPreferences &discovery_preferences)
{
  // qDebug() << "Updating DiscoveriesPreferences";

  // This function is called by the Discoveries page of the
  // ApplicationPreferences window.

  // Update the preferences with the application preferences window to *this.
  if(mqsp_discoveriesPreferences == nullptr)
    {
      mqsp_discoveriesPreferences =
        QSharedPointer<DiscoveriesPreferences>::create(discovery_preferences);
    }
  else
    {
      mqsp_discoveriesPreferences->initialize(discovery_preferences);
    }

  // Let's check if the recording should go a file, in which case we test
  // that we can open it properly.

  if((mqsp_discoveriesPreferences->m_recordTarget &
      RecordTarget::RECORD_TO_FILE) == RecordTarget::RECORD_TO_FILE)
    {
      // Now check that the analysis file could be opened.

      if(mpa_analysisFile != nullptr)
        {
          delete mpa_analysisFile;
          mpa_analysisFile = nullptr;
        }

      mpa_analysisFile = new QFile(mqsp_discoveriesPreferences->m_fileName);

      bool ok = false;

      if(mqsp_discoveriesPreferences->m_fileOpenMode == QIODevice::Truncate)

        // Interestingly, only truncate did not work. Since writeonly
        // implies truncate, that's fine, and it works.

        ok = mpa_analysisFile->open(QIODevice::WriteOnly);
      else
        ok = mpa_analysisFile->open(QIODevice::Append);

      if(!ok)
        {
          delete mpa_analysisFile;
          mpa_analysisFile = nullptr;

          QMessageBox msgBox;
          msgBox.setText(
            "Failed to open the analysis file, please fix "
            "the analysis preferences.");
          msgBox.exec();

          return;
        }
      else
        {
          // Close the file, which will be opened each time it is
          // necessary.
          mpa_analysisFile->close();

          // We can effectively start the work. The windows needing to
          // access the file will call getAnalysisFile() and make sure
          // that it is not nullptr.
        }
    }
}

void
ProgramWindow::msRunDataSetRemovalRequested(
  MsRunDataSetCstSPtr &ms_run_data_set_csp)
{
  // qDebug() << "Removal of ms run data set:" << ms_run_data_set_csp.get()
  //<< "requested";

  // The user asks that a given ms run data set be removed from the software.
  // We need to orderly remove all the shared pointer references so that the
  // object is destroyed. Shared pointers to the ms run data set are located
  // in the plottables created on the basis of the ms run data set. We need to
  // remove from all the plot widgets, all the plottables that have been
  // created using the ms run data set as the starting point of integrations.

  // All the plottables are registered in the tree that mimicks the structure
  // of all the plottable and plot widget in the program.

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // Find all the root nodes that match ms_run_data_set_csp non recursively.
  std::vector<DataPlottableNode *> found_nodes =
    m_dataPlottableTree.findNodes(ms_run_data_set_csp, false);

  // qDebug() << "Found nodes:" << found_nodes.size() << "nodes:";
  // for(std::size_t iter = 0; iter < found_nodes.size(); ++iter)
  // qDebug() << found_nodes.at(iter)->toString(0, true);

  // Since each node has both the plot widget and the plottable pointer, we
  // can ask for the recursive destruction of the plottable.

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  for(const DataPlottableNode *node : found_nodes)
    {
      // true: recursively
      // false: not destroying a widget.

      plottableDestructionRequested(
        node->getPlotWidget(), node->getPlottable(), true);
    }

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // Look for a tree view window that matches the ms
  // run data set.

  std::map<MsRunDataSetCstSPtr, MsRunDataSetTableViewWnd *>::iterator
    res_iterator = std::find_if(
      mp_msRunDataSetTableViewWndMap.begin(),
      mp_msRunDataSetTableViewWndMap.end(),
      [ms_run_data_set_csp](
        const std::pair<MsRunDataSetCstSPtr, MsRunDataSetTableViewWnd *> item) {
        return item.first == ms_run_data_set_csp;
      });

  if(res_iterator == mp_msRunDataSetTableViewWndMap.end())
    {
      // Nothing to do, the data set had not associated tree view.
    }
  else
    {
      delete res_iterator->second;

      mp_msRunDataSetTableViewWndMap.erase(ms_run_data_set_csp);
    }

  // Finally remove the ms run data set item from the list widget in the
  // open ms run data set dialog window.

  mp_openMsRunDataSetsDlg->removeMsRunDataSet(ms_run_data_set_csp);

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();
}

const DataPlottableTree &
ProgramWindow::getDataPlottableTree() const
{
  return m_dataPlottableTree;
}

std::vector<MsRunDataSetCstSPtr>
ProgramWindow::allSelectedOrUniqueMsRunDataSet()
{
  return mp_openMsRunDataSetsDlg->allSelectedOrUniqueMsRunDataSets();
}

//! Initialize the JavaScript scripting framework.
void
ProgramWindow::initializeScripting()
{
  // Make available to the scripting environment all the entities that will
  // be useful to script mineXpert.

  // We need this to call the exposeQOject() below, but we do not use it.
  QJSValue returned_js_value;

  // This is make this ProgramWindow object available in the QJSEngine as
  // property progWnd.
  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "progWnd" /*name*/,
       "" /*alias*/,
       "Main program window",
       this,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // Same for the console window.
  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "consoleWnd" /*name*/,
       "" /*alias*/,
       "Console window",
       mp_consoleWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // Same for the Tic/Xic chrom window.
  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "ticChromWnd" /*name*/,
       "" /*alias*/,
       "TIC/XIC chromatogram window",
       mp_ticXicChromPlotWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // Same for the mass spec window.
  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "mzPlotWnd" /*name*/,
       "" /*alias*/,
       "Mass spectra window",
       mp_massSpecPlotWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // Same for the drift spec window.
  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "dtPlotWnd" /*name*/,
       "" /*alias*/,
       "Drift spectra window",
       mp_driftSpecPlotWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // Color map plot windows
  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "dt_mzPlotWnd" /*name*/,
       "" /*alias*/,
       "DT vs MZ color map window",
       mp_driftSpecMassSpecColorMapWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "rt_mzPlotWnd" /*name*/,
       "" /*alias*/,
       "RT vs MZ color map window",
       mp_ticXicChromMassSpecColorMapWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "rt_dtPlotWnd" /*name*/,
       "" /*alias*/,
       "RT vs DT color map window",
       mp_ticXicChromDriftSpecColorMapWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "xicExtractionWnd" /*name*/,
       "" /*alias*/,
       "XIC extraction window",
       mp_xicExtractionWnd,
       nullptr /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // Open ms run data sets dialog window

  if(!mp_scriptingWnd->getScriptingEnvironment()->exposeQObject(
       "dataSetsWnd" /*name*/,
       "" /*alias*/,
       "Data sets window",
       mp_openMsRunDataSetsDlg,
       nullptr /* parent QObject */,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // Do not publish the scripting window as that makes calling the
  // destructor if this scripting window twice and that of the
  // program window also. Need to check this later. FIXME.
  // scriptingWnd

  // mp_scriptingWnd->publishQObject(
  //   mp_scriptingWnd,
  //   nullptr /* parent QObject */,
  //   "scriptingWnd",
  //   QString(),
  //   "Make available the scripting window under name \"scriptingWnd\".\n",
  //   "The scripting window is the central place where scripting occurs. The "
  //   "scripting window provides a number of features available to the
  //   scripter.", scriptValue, QJSEngine::CppOwnership);
}

int
ProgramWindow::loadPlugins()
{
  // This feature is not yet used. The framework was started at a time
  // when plugins were thought to be necessary.

#if 0
  // Since we get no directory path argument, that means we'll try
  // to load the plugins from all the available places as configured
  // in the application preferences window.

  int loaded_plugins_count = 0;

  QStringList plugins_paths = mp_applicationPreferencesWnd->getPluginsPaths();

  for(const QString &path : plugins_paths)
    {
      qDebug() << "Now trying to load plugins at path:" << path;

      loaded_plugins_count += loadPlugins(path);
    }

  return loaded_plugins_count;
#endif

  return 0;
}

int
ProgramWindow::loadPlugins(const QString &dir_path)
{
  QString sys_path_to_plugins = QString("%1/%2")
                                  .arg(PROJECT_INSTALL_PREFIX)
                                  .arg(PROJECT_INSTALL_LIB_PLUGINS_DIR);

  QString local_dir_path = dir_path;

  QDir dir(local_dir_path);
  if(!dir.exists())
    dir.setPath(sys_path_to_plugins);

  if(!dir.exists())
    dir.setPath(sys_path_to_plugins);

  dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
  dir.setNameFilters(QStringList() << "*.so" << "*.dll");

  int loaded_plugins_count = 0;

  for(const QFileInfo &fileInfo : dir.entryInfoList())
    {
      QPluginLoader loader(fileInfo.absoluteFilePath());
      if(loader.load())
        {
          MineXpertPluginInterface *plugin_p =
            qobject_cast<MineXpertPluginInterface *>(loader.instance());
          if(plugin_p)
            {
              qDebug() << "Loaded plugin:" << plugin_p->pluginName();
              plugin_p->initialize(nullptr);
              ++loaded_plugins_count;
            }
          else
            {
              qWarning() << "Failed to cast plugin:" << loader.errorString();
              loader.unload();
            }
        }
      else
        {
          qWarning() << "Failed to load plugin:" << loader.errorString();
        }
    }

  return loaded_plugins_count;
}


BaseTracePlotWnd *
ProgramWindow::getPlotWndPtr(const QString &type_name)
{
  if(type_name != "mass spectrum" && type_name != "tic xic chromatogram" &&
     type_name != "drift spectrum")
    qFatal() << "Error with the plot window type name.";

  if(type_name == "mass spectrum")
    return mp_massSpecPlotWnd;
  else if(type_name == "tic xic chromatogram")
    return mp_ticXicChromPlotWnd;
  else if(type_name == "drift spectrum")
    return mp_driftSpecPlotWnd;

  qFatal() << "Should never encounter this point.";

  return nullptr;
}

libXpertMassGui::TaskMonitorWnd *
ProgramWindow::getTaskMonitorWnd() const
{
  return mp_taskMonitorWnd;
}

void
ProgramWindow::integrateToDt(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<QualifiedMassSpectraVector> qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  // Each trace plot window handles the integration requests that produce
  // their own type of data, so we delegate the integration to the
  // relevant window.

  mp_driftSpecPlotWnd->integrateToDt(
    parent_plottable_p, qualified_mass_spectra_sp, processing_flow);
}

void
ProgramWindow::integrateToMz(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<QualifiedMassSpectraVector> qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  // Each trace plot window handles the integration requests that produce
  // their own type of data, so we delegate the integration to the
  // relevant window.

  // qDebug().noquote() << "Integrating to mz with processing flow:"
  //<< processing_flow.toString();

  mp_massSpecPlotWnd->integrateToMz(
    parent_plottable_p, qualified_mass_spectra_sp, processing_flow);
}

void
ProgramWindow::integrateToRt(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<QualifiedMassSpectraVector> qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  // Each trace plot window handles the integration requests that produce
  // their own type of data, so we delegate the integration to the
  // relevant window.

  // qDebug().noquote() << "Integrating to rt with processing flow:"
  // << processing_flow.toString();

  mp_ticXicChromPlotWnd->integrateToRt(
    parent_plottable_p, qualified_mass_spectra_sp, processing_flow);
}

void
ProgramWindow::integrateToTicIntensity(
  [[maybe_unused]] QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<QualifiedMassSpectraVector> qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{

  // Get the ms run data set that for the graph we are going to base the
  // integration on.
  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();
  if(ms_run_data_set_csp == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  //  qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

  // Allocate the mass data integrator that is speciaized in the computation
  // of TIC chromaotograms, starting from a vector of qualified mass spectrum
  // pointers.

  QualifiedMassSpectrumVectorMassDataIntegratorToTicInt
    *mass_data_integrator_p =
      new QualifiedMassSpectrumVectorMassDataIntegratorToTicInt(
        ms_run_data_set_csp, processing_flow, qualified_mass_spectra_sp);

  mass_data_integrator_p->setMaxThreadUseCount(m_maxThreadUseCount);

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(
    this,
    static_cast<void (ProgramWindow::*)(
      QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
      &ProgramWindow::integrateQualifiedMassSpectrumVectorToTicIntensitySignal),
    mass_data_integrator_task_p,
    static_cast<void (MassDataIntegratorTask::*)(
      QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
      &MassDataIntegratorTask::integrateToTicIntensity),
    // Fundamental for signals that travel across QThread instances...
    Qt::QueuedConnection);

  // When the task finishes, it sends a signal that we trap to go on with
  // the plot widget creation stuff.

  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          this,
          static_cast<void (ProgramWindow::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &ProgramWindow::
              finishedIntegratingQualifiedMassSpectrumVectorToTicIntensity),
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // Since we allocated the QThread dynamically we need to be able to destroy
  // it later, so make the connection.
  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          this,
          [thread_p, mass_data_integrator_task_p]() {
            // Do not forget that we have to delete the MassDataIntegratorTask
            // allocated instance.
            mass_data_integrator_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
          });

  // Ensure the mass data integrator messages are used.

  connect(
    mass_data_integrator_p,
    &QualifiedMassSpectrumVectorMassDataIntegrator::logTextToConsoleSignal,
    this,
    &ProgramWindow::logTextToConsole);

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_taskMonitorWnd->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Integrating to TIC intensity from the table view");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections

  // When the MsRunReadTask instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some seconds,
  // the monitor widget.

  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(
            QualifiedMassSpectrumVectorMassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &QualifiedMassSpectrumVectorMassDataIntegrator::cancelOperation);

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mass_data_integrator_p);

  // task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
  //   mass_data_integrator_p);

  emit integrateQualifiedMassSpectrumVectorToTicIntensitySignal(
    mass_data_integrator_p);

  // Because the emitter is the same ProgramWindow object, we need to
  // disconnect the signal so that at next opening of mass data file, it will
  // not be called twice.
  disconnect(
    this,
    static_cast<void (ProgramWindow::*)(
      QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
      &ProgramWindow::integrateQualifiedMassSpectrumVectorToTicIntensitySignal),
    mass_data_integrator_task_p,
    static_cast<void (MassDataIntegratorTask::*)(
      QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *)>(
      &MassDataIntegratorTask::integrateToTicIntensity));
}

void
ProgramWindow::integrateToDtMz(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  mp_driftSpecMassSpecColorMapWnd->integrateToDtMz(
    parent_plottable_p, qualified_mass_spectra_sp, processing_flow);
}

void
ProgramWindow::integrateToDtRt(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  mp_ticXicChromDriftSpecColorMapWnd->integrateToDtRt(
    parent_plottable_p, qualified_mass_spectra_sp, processing_flow);
}

void
ProgramWindow::integrateToMzRt(
  QCPAbstractPlottable *parent_plottable_p,
  std::shared_ptr<std::vector<pappso::QualifiedMassSpectrumCstSPtr>>
    qualified_mass_spectra_sp,
  const ProcessingFlow &processing_flow)
{
  mp_ticXicChromMassSpecColorMapWnd->integrateToMzRt(
    parent_plottable_p, qualified_mass_spectra_sp, processing_flow);
}

void
ProgramWindow::xicIntegrationToRt(const ProcessingFlow &processing_flow)
{
  // qDebug().noquote() << "XIC integration to rt with processing flow:"
  //<< processing_flow.toString();

  // Sanity check:

  if(processing_flow.getMsRunDataSetCstSPtr() == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    processing_flow.getMsRunDataSetCstSPtr();

  // Pass the integrator the flow we got as param and that describes in its
  // most recent step the integration that it should perform.
  MsRunDataSetTreeMassDataIntegratorToRt *mass_data_integrator_p =
    new MsRunDataSetTreeMassDataIntegratorToRt(ms_run_data_set_csp,
                                               processing_flow);

  // Ensure the mass data integrator messages are used.

  connect(mass_data_integrator_p,
          &MassDataIntegrator::logTextToConsoleSignal,
          this,
          &ProgramWindow::logTextToConsole);

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // Allocate a mass data integrator to integrate the data.

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(
    this,
    // SIGNAL(integrateToRtSignal(MsRunDataSetTreeMassDataIntegratorToRt *)),
    static_cast<void (ProgramWindow::*)(
      MsRunDataSetTreeMassDataIntegratorToRt *)>(
      &ProgramWindow::integrateToRtSignal),
    mass_data_integrator_task_p,
    // SLOT(integrateToRt(MsRunDataSetTreeMassDataIntegratorToRt *)),
    static_cast<void (MassDataIntegratorTask::*)(
      MsRunDataSetTreeMassDataIntegratorToRt *)>(
      &MassDataIntegratorTask::integrateToRt),
    // Fundamental for signals that travel across QThread instances...
    Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // When the read task finishes, it sends a signal that we trap to go on with
  // the plot widget creation stuff.

  // Since we allocated the QThread dynamically we need to be able to destroy
  // it later, so make the connection.
  connect(
    mass_data_integrator_task_p,
    static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
      &MassDataIntegratorTask::finishedIntegratingDataSignal),
    this,
    [this, thread_p, mass_data_integrator_p, mass_data_integrator_task_p]() {
      // Do not forget that we have to delete the MassDataIntegratorTask
      // allocated instance.
      mass_data_integrator_task_p->deleteLater();
      // Once the task has been labelled to be deleted later, we can stop
      // the thread and ask for it to also be deleted later.
      thread_p->deleteLater(), thread_p->quit();
      thread_p->wait();
      this->finishedXicIntegrationToRt(mass_data_integrator_p);
    });


  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    getTaskMonitorWnd()->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_data_set_csp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Integrating to XIC chromatogram.");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections

  // When the integrator task instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some seconds,
  // the monitor widget.

  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &MassDataIntegrator::cancelTask);

  // We need to register the meta type for std::size_t because otherwise it
  // cannot be shipped though signals.

  qRegisterMetaType<std::size_t>("std::size_t");

  // Now make all the connections that will allow the integrator to provide
  // dynamic feedback to the user via the task monitor widget.
  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mass_data_integrator_p);
  // task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
  //   mass_data_integrator_p);

  // qDebug() << "going to emit integrateToRtSignal with mass "
  //"data integrator:"
  //<< mass_data_integrator_p;

  emit integrateToRtSignal(mass_data_integrator_p);

  // We do not want to make signal/slot calls more than once. This is because
  // one user might well trigger more than one integration from this window to
  // a mass spectrum. Thus we do not want that *this window be still connected
  // to the specific mass_data_integrator_task_p when a new integration is
  // triggered. We want the signal/slot pairs to be contained to specific
  // objects. Each MassSpecTracePlotWnd::integrateToMz() call must be
  // contained to a this/mass_data_integrator_task_p specific signal/slot
  // pair.
  disconnect(
    this,
    // SIGNAL(integrateToRtSignal(MsRunDataSetTreeMassDataIntegratorToRt *)),
    static_cast<void (ProgramWindow::*)(
      MsRunDataSetTreeMassDataIntegratorToRt *)>(
      &ProgramWindow::integrateToRtSignal),
    mass_data_integrator_task_p,
    // SLOT(integrateToRt(MsRunDataSetTreeMassDataIntegratorToRt *)));
    static_cast<void (MassDataIntegratorTask::*)(
      MsRunDataSetTreeMassDataIntegratorToRt *)>(
      &MassDataIntegratorTask::integrateToRt));
}

void
ProgramWindow::displayMassSpectralTrace(pappso::Trace trace,
                                        const ProcessingFlow &processing_flow,
                                        const QString &sample_name)
{
  // qDebug() << "The trace has size:" << trace.size()
  //<< "and sample name:" << sample_name;

  // There are two possibilities:
  //
  // 1. There is not a single MS run data set: we need to create one as a shim
  // 2. There is/are at least one MS run data set: ask the user if they want
  // to anchor the new to one of these by providing a selection list.

  // Check with the OpenMsRunDataSetsDlg if there is at least one MsRunDataSet
  // available.

  // Set the pointer to nullptr, that will help us later.
  MsRunDataSetCstSPtr ms_run_data_set_csp = nullptr;

  QColor color;

  bool ok = false;

  std::size_t opened_ms_run_data_sets =
    mp_openMsRunDataSetsDlg->msRunDataSetCount();

  if(opened_ms_run_data_sets)
    {
      // There is a least one MS run data set. Provide a list to the user and
      // let them choose one of these. There might also be a choice to create
      // a shim file.

      // Get a list of the MS run data sets.
      std::vector<MsRunDataSetCstSPtr> ms_run_data_sets =
        mp_openMsRunDataSetsDlg->allMsRunDataSets();

      // qDebug() << "MS run data sets count:" << ms_run_data_sets.size();

      // The list of items' sample names will start with an item
      // allowing the user to ask for the creation of a new MS run data set
      // for the trace to be displayed.
      QStringList item_texts = {"Create a new MS run data set"};

      // Extract a list of strings from that list of widgets.
      for(auto &&item : ms_run_data_sets)
        {
          QString iter_sample_name = item->getMsRunId()->getSampleName();
          // qDebug() << "iter_sample_name:" << iter_sample_name;

          item_texts.append(iter_sample_name);
        }

      // qDebug() << "All the sample names:" << item_texts;

      // Craft an input dialog.

      QInputDialog input_dialog(this);
      input_dialog.setComboBoxItems(item_texts);

      // Now as the user to select one item.

      QString item_text = QInputDialog::getItem(this,
                                                "Select a MS run data set",
                                                "MS run data set:",
                                                item_texts,
                                                0,
                                                false,
                                                &ok);
      if(!ok)
        {
          // The user pressed cancel. Just return.
          return;
        }

      if(item_text != "Create a new MS run data set")
        {
          // Now we know that the user wants the new trace to be anchored to a
          // given MsRunDataSet.

          bool got_ms_run_data_set_fine =
            mp_openMsRunDataSetsDlg->msRunDataSetFromSampleName(
              item_text, ms_run_data_set_csp, color);

          if(!got_ms_run_data_set_fine || ms_run_data_set_csp == nullptr)
            qFatal() << "Programming error.";

          // At this point we know we won't need to create a shim ms run data
          // set because ms_run_data_set_csp is no more nullptr and we also
          // know the color.
        }
      else
        {
          // qDebug() << "Creating a new MS run data set.";
        }
    }
  // End of
  // if(opened_ms_run_data_sets)

  // Now test if we need to create a shim ms run data set or not by checking
  // ms_run_data_set_csp


  if(ms_run_data_set_csp == nullptr)
    {
      // Craft a local sample name

      QString local_sample_name = sample_name;

      if(local_sample_name.isEmpty())
        local_sample_name = "mass-spectrum";

      openMassSpectrometryFileFromText(trace.toString(), local_sample_name);

      // At this point we should have a new file. When the new ms run data set
      // will be added to the open ms run data sets dlg list widget, it will
      // be selected.

      showMsRunDataSetsWnd();

      QMessageBox::information(
        this,
        "Select a MS run data set",
        "One shim mass data file was loaded.\n"
        "Please, integrate the pseuso TIC to a mass spectrum.");

      return;
    }

  // At this point we have what we needed, a proper ms run data set pointer
  // that we have to set to the ProcessingFlow, but because addTracePlot()
  // below passes the ProcessingFlow to later code that will in turn
  // reclone it, all we have to do is set the ProcessingFlow to
  // a temporay ProcessingFlow, that we'll delete immediately after.

  ProcessingFlow *temp_processing_flow_p = processing_flow.clone(nullptr);

  temp_processing_flow_p->setMsRunDataSetCstSPtr(ms_run_data_set_csp);

  // FIXME: we could get back the graph and publish the graph
  // to the JavaScript environment.
  mp_massSpecPlotWnd->addTracePlot(
    trace, ms_run_data_set_csp, *temp_processing_flow_p, color, nullptr);

  delete temp_processing_flow_p;
}

const BasePlotCompositeWidget *
ProgramWindow::getPlotWidget(
  [[maybe_unused]] MsRunDataSetCstSPtr ms_run_data_set_csp,
  QCPAbstractPlottable *plottable_p)
{
  return m_dataPlottableTree.getPlotWidget(plottable_p);
}

void
ProgramWindow::documentMsRunDataPlottableFiliation(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  QCPAbstractPlottable *new_plottable_p,
  QCPAbstractPlottable *parent_plottable_p,
  BasePlotCompositeWidget *plot_widget_p)
{
  // qDebug();

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // Now we need to document the filiation of the parent / child
  // plottable.

  // qDebug() << "Before documenting: data plottable tree has"
  //<< m_dataPlottableTree.depth() << "depth and"
  //<< m_dataPlottableTree.size() << "size";

  m_dataPlottableTree.addPlottable(
    ms_run_data_set_csp, new_plottable_p, parent_plottable_p, plot_widget_p);

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // qDebug() << "After documenting data plot plottable tree has"
  //<< m_dataPlottableTree.depth() << "depth and"
  //<< m_dataPlottableTree.size() << "size";
}

void
ProgramWindow::plottableDestructionRequested(
  BasePlotCompositeWidget *base_plot_composite_widget_p,
  QCPAbstractPlottable *plottable_p,
  bool recursively)
{
  // qDebug().noquote() << "Destruction of plottable:"
  //<< pappso::Utils::pointerToString(plottable_p)
  //<< "is being requested recursively:" << recursively;

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    base_plot_composite_widget_p->getMsRunDataSetCstSPtrForPlottable(
      plottable_p);

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // When one plottable is on the verge of being deleted, we need to first
  // check its node in the plottable data tree. This is because we need to
  // at least remove that node !

  // Further, there is another fact to keep in mind: if the user wanted to
  // remove all the children plottable, then we would need to do that.

  // Another complexity: if the plottable that is removed had children,
  // but the user does not want to remove them, we would need to reparent
  // them to the parent of the plottable being deleted !

  // How many children did that plottable have?

  // qDebug() << "Getting the right node for the plottable to be destroyed.";

  DataPlottableNode *plottable_node_p =
    m_dataPlottableTree.findNode(plottable_p);

  if(plottable_node_p == nullptr)
    qFatal(
      "Could not find a node for plottable . This is a programming "
      "error.");

  // qDebug() << "The plottable is in node:"
  //<< plottable_node_p->toString(0, true);

  std::size_t descendant_count = 0;
  plottable_node_p->size(descendant_count);

  // qDebug() << "The plottable node has" << descendant_count <<
  // "descendants";

  // bool res =
  m_dataPlottableTree.removeNodeAndPlottable(plottable_node_p, recursively);

  // qDebug() << "Removal of node(s) and plottable(s) is success:" << res;

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  //  qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();
}

void
ProgramWindow::plottableDestructionRequested(
  BasePlotCompositeWidget *base_plot_composite_widget_p,
  QCPAbstractPlottable *plottable_p,
  const pappso::BasePlotContext &context)
{
  bool recursively = context.m_keyboardModifiers & Qt::ControlModifier;

  // If a recursive plottable destruction is requested, the the plottable
  // and all its children, as documented by the main program's plottable
  // tree nodes are to be destroyed.

  // if(recursively)
  // qDebug() << "Destruction recursive of plottable:" << plottable_p
  //<< "is being requested.";
  // else
  // qDebug() << "Destruction of plottable:" << plottable_p
  //<< "is being requested.";

  return plottableDestructionRequested(
    base_plot_composite_widget_p, plottable_p, recursively);
}

void
ProgramWindow::plotCompositeWidgetDestructionRequested(
  BasePlotCompositeWidget *base_plot_composite_widget_p)
{
  // qDebug() << "Handling the destruction of the plot widget:"
  //<< base_plot_composite_widget_p;

  // One plot widget and all the plottables that it contains need to be
  // destroyed. We first need to get a list of all the data plot tree nodes
  // that match the plot widget. Then we non-recursively destroy all the
  // plottables and matching data tree nodes. Finally we destroy the widget.

  std::vector<DataPlottableNode *> plottable_nodes =
    m_dataPlottableTree.findNodes(base_plot_composite_widget_p, true);

  // qDebug() << "The number of nodes for plot widget:"
  //<< base_plot_composite_widget_p << "is:" << plottable_nodes.size();

  // We want an orderly destruction of the plottable(s).

  for(const DataPlottableNode *node_p : plottable_nodes)
    {

      plottableDestructionRequested(
        base_plot_composite_widget_p, node_p->getPlottable(), false);
    }

  // Once all the plottables have been destroyed, we need to destroy the plot
  // widget itself.

  delete base_plot_composite_widget_p;

  // qDebug() << "Done deleting the composite widget.";
}

void
ProgramWindow::seedInitialTicChromatogramAndMsRunDataSetStatistics(
  MsRunDataSetSPtr &ms_run_data_set_sp)
{
  if(ms_run_data_set_sp == nullptr)
    qFatal() << "Cannot be that pointer is nullptr";

  // If we are in this function, that means that loading the file
  // has succeeded. We can thus add the datetime and file path data
  // to the recent files handler.

  m_recentFilesHandler.addItem(ms_run_data_set_sp->getMsRunId()->getFileName());

  // qDebug() << "sample name:"
  //<< ms_run_data_set_sp->getMsRunId()->getSampleName();

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  //  qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

  MassDataIntegratorTask *mass_data_integrator_task_p =
    new MassDataIntegratorTask();

  // This signal starts the computation in the MassDataIntegratorTask object.
  connect(
    this,
    &ProgramWindow::seedInitialTicChromatogramAndMsRunDataSetStatisticsSignal,
    mass_data_integrator_task_p,
    &MassDataIntegratorTask::
      seedInitialTicChromatogramAndMsRunDataSetStatistics,
    // Fundamental for signals that travel across QThread instances...
    Qt::QueuedConnection);

  // When the task finishes, it sends a signal that we trap to go on with
  // the plot widget creation stuff.

  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          this,
          &ProgramWindow::
            finishedSeedingInitialTicChromatogramAndMsRunDataSetStatistics,
          // Fundamental for signals that travel across QThread instances...
          Qt::QueuedConnection);

  // Allocate the thread in which the integrator task will run.
  QThread *thread_p = new QThread;

  // qDebug() << "Moving the mass_data_integrator_task_p from thread"
  //          << QThread::currentThread() << "to thread" << thread_p;

  // Move the task to the matching thread.
  mass_data_integrator_task_p->moveToThread(thread_p);
  thread_p->start();

  // Since we allocated the QThread dynamically we need to be able to destroy
  // it later, so make the connection.
  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          this,
          [thread_p, mass_data_integrator_task_p]() {
            // Do not forget that we have to delete the MassDataIntegratorTask
            // allocated instance.
            mass_data_integrator_task_p->deleteLater();
            // Once the task has been labelled to be deleted later, we can
            // stop the thread and ask for it to also be deleted later.
            thread_p->deleteLater(), thread_p->quit();
            thread_p->wait();
          });

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  //  qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

  // No need to provide any processing flow object to the constructor, as we
  // have none yet, this is the very first integration.
  MsRunDataSetTreeMassDataIntegratorToRt *mass_data_integrator_p =
    new MsRunDataSetTreeMassDataIntegratorToRt(ms_run_data_set_sp, nullptr);

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_sp.use_count();

  // Ensure the mass data integrator messages are used.

  connect(mass_data_integrator_p,
          &MassDataIntegrator::logTextToConsoleSignal,
          this,
          &ProgramWindow::logTextToConsole);

  // qDebug() << "the integrator pointer:" << mass_data_integrator_p;

  // Allocate a new TaskMonitorCompositeWidget that will receive all the
  // integrator's signals and provide feedback to the user about the ongoing
  // integration.

  libXpertMassGui::TaskMonitorCompositeWidget *task_monitor_composite_widget_p =
    mp_taskMonitorWnd->addTaskMonitorWidget(Qt::red);

  // Initialize the monitor composite widget's widgets and make all the
  // connections mass data integrator <--> widget.

  task_monitor_composite_widget_p->taskSetMainIdentifierText(
    ms_run_data_set_sp->getMsRunId()->getSampleName());
  task_monitor_composite_widget_p->taskSetDescriptionText(
    "Integrating to initial TIC chromatogram and calculating MS run "
    "statistics");
  task_monitor_composite_widget_p->taskSetMinValue(0);

  // Make the connections

  // When the MsRunReadTask instance has finished working, it will send a
  // signal that we trap to finally destroy (after a time lag of some seconds,
  // the monitor widget.

  connect(mass_data_integrator_task_p,
          static_cast<void (MassDataIntegratorTask::*)(MassDataIntegrator *)>(
            &MassDataIntegratorTask::finishedIntegratingDataSignal),
          task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::taskFinished,
          Qt::QueuedConnection);

  // If the user clicks the cancel button, relay the signal to the loader.
  connect(task_monitor_composite_widget_p,
          &libXpertMassGui::TaskMonitorCompositeWidget::cancelTaskSignal,
          mass_data_integrator_p,
          &MassDataIntegrator::cancelTask);

  // We need to register the meta type for std::size_t because otherwise it
  // cannot be shipped though signals.

  qRegisterMetaType<std::size_t>("std::size_t");

  task_monitor_composite_widget_p->makeSignalSlotConnections(
    mass_data_integrator_p);

  // task_monitor_composite_widget_p->makeMassDataIntegratorConnections(
  //   mass_data_integrator_p);

  // qDebug() << "going to emit integrateToInitialTicChromatogramSignal with
  // mass " "data integrator:"
  //<< mass_data_integrator_p;

  emit seedInitialTicChromatogramAndMsRunDataSetStatisticsSignal(
    mass_data_integrator_p);

  // Because the emitter is the same ProgramWindow object, we need to
  // disconnect the signal so that at next opening of mass data file, it will
  // not be called twice.
  disconnect(
    this,
    &ProgramWindow::seedInitialTicChromatogramAndMsRunDataSetStatisticsSignal,
    mass_data_integrator_task_p,
    &MassDataIntegratorTask::
      seedInitialTicChromatogramAndMsRunDataSetStatistics);
}

void
ProgramWindow::finishedSeedingInitialTicChromatogramAndMsRunDataSetStatistics(
  MassDataIntegrator *mass_data_integrator_p)
{
  // In this calculation, the m_mapTrace object in the integrator is the TIC
  // chroamtogram.

  // The function below uses the mass_data_integrator_p to get to the TIC
  // chromatogram and create the TIC chrom widget.
  std::pair<MsRunDataSetCstSPtr, QColor> result_pair =
    mp_ticXicChromPlotWnd->finishedIntegratingToInitialTicChromatogram(
      static_cast<MsRunDataSetTreeMassDataIntegratorToRt *>(
        mass_data_integrator_p));

  // Make sure the tic chrom wnd is visible!
  showTicXicChromatogramsWnd();

  // qDebug() << "the sample name:"
  //<< result_pair.first->getMsRunId()->getSampleName();

  if(result_pair.first != nullptr)
    {
      // There were data in the data set ! Go on.

      mp_openMsRunDataSetsDlg->newOpenMsRunDataSet(result_pair.first,
                                                   result_pair.second);

      showMsRunDataSetsWnd();
    }

  // As for the statistics of the ms run data set, it has been set
  // to the data set already.
}

void
ProgramWindow::finishedIntegratingQualifiedMassSpectrumVectorToTicIntensity(
  QualifiedMassSpectrumVectorMassDataIntegrator *mass_data_integrator_p)
{
  //  qDebug();

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    mass_data_integrator_p->getMsRunDataSet();

  pappso::Trace trace = mass_data_integrator_p->getMapTrace().toTrace();

  // ProcessingFlow *processing_flow_p =
  //   mass_data_integrator_p->getProcessingFlow();
  // qDebug() << "The processing flow gotten from the mass data integrator:"
  //          << processing_flow_p->toString();

  // We now need to display the result into the status bar of the window.

  double tic_intensity =
    static_cast<QualifiedMassSpectrumVectorMassDataIntegratorToTicInt *>(
      mass_data_integrator_p)
      ->getTicIntensity();

  emit(ticIntensityValueSignal(tic_intensity));

  QString sample_name = ms_run_data_set_csp->getMsRunId()->getSampleName();

  QString msg =
    QString("%1: TIC intensity: %2\n").arg(sample_name).arg(tic_intensity);

  QColor color =
    mp_openMsRunDataSetsDlg->colorForMsRunDataSet(ms_run_data_set_csp);

  logColoredTextToConsole(msg, color);

  // Finally destroy the integrator!
  delete mass_data_integrator_p;
}

void
ProgramWindow::finishedXicIntegrationToRt(
  MassDataIntegrator *mass_data_integrator_p)
{
  // qDebug();

  // At this point we need to ask the TIC XIC chromatogram window to display
  // the result of the compuation.

  MsRunDataSetCstSPtr ms_run_data_set_csp =
    mass_data_integrator_p->getMsRunDataSet();

  pappso::Trace trace = mass_data_integrator_p->getMapTrace().toTrace();

  const ProcessingFlow *processing_flow_p =
    mass_data_integrator_p->getProcessingFlow();

  QColor color =
    mp_openMsRunDataSetsDlg->colorForMsRunDataSet(ms_run_data_set_csp);

  mp_ticXicChromPlotWnd->newTrace(
    trace, ms_run_data_set_csp, *processing_flow_p, color, nullptr);

  if(!mp_ticXicChromPlotWnd->isVisible())
    mp_ticXicChromPlotWnd->showWindow();

  // Finally destroy the integrator!
  delete mass_data_integrator_p;
}

// This function can be called as a slot to a program menu signal
// or as a normal function (see startClient()).
libXpertMassGui::MassDataClientServerConfigDlg *
ProgramWindow::configureNetworkConnection(bool no_dlg_show)
{
  if(mp_clientServerConfigDlg == nullptr)
    {
      // qDebug() << "The client server configuration dialog is not
      // instantiated.";

      mp_clientServerConfigDlg =
        new libXpertMassGui::MassDataClientServerConfigDlg(
          dynamic_cast<QWidget *>(this),
          m_applicationName,
          "Client-server configuration");

      if(mp_clientServerConfigDlg == nullptr)
        qFatal(
          "Programming error. Cannot be that the dialog window pointer is "
          "nullptr.");

      // At this point, make the connections with the signals provided by
      // the configuration dialog window.
      connect(
        mp_clientServerConfigDlg,
        &libXpertMassGui::MassDataClientServerConfigDlg::startServerSignal,
        this,
        &ProgramWindow::startServer);

      connect(mp_clientServerConfigDlg,
              &libXpertMassGui::MassDataClientServerConfigDlg::stopServerSignal,
              this,
              &ProgramWindow::stopServer);

      connect(
        mp_clientServerConfigDlg,
        &libXpertMassGui::MassDataClientServerConfigDlg::startClientSignal,
        this,
        &ProgramWindow::startClient);

      connect(mp_clientServerConfigDlg,
              &libXpertMassGui::MassDataClientServerConfigDlg::stopClientSignal,
              this,
              &ProgramWindow::stopClient);
    }

  // And now make sure we display relevant data.
  QString ip_address;
  int port_number;

  if(mp_massDataServer != nullptr)
    {
      ip_address  = mp_massDataServer->getIpAddress();
      port_number = mp_massDataServer->getPortNumber();

      mp_clientServerConfigDlg->updateServerIpData(
        std::pair<QString, int>(ip_address, port_number));
    }

  if(mp_massDataClient != nullptr)
    {
      ip_address  = mp_massDataClient->getIpAddress();
      port_number = mp_massDataClient->getPortNumber();

      mp_clientServerConfigDlg->updateClientIpData(
        std::pair<QString, int>(ip_address, port_number));
    }

  if(!no_dlg_show)
    {
      mp_clientServerConfigDlg->activateWindow();
      mp_clientServerConfigDlg->raise();
      mp_clientServerConfigDlg->show();
    }

  return mp_clientServerConfigDlg;
}

void
ProgramWindow::logTextToConsole(const QString &msg)
{
  mp_consoleWnd->logTextToConsole(msg);
}

void
ProgramWindow::logColoredTextToConsole(const QString &text, const QColor &color)
{
  mp_consoleWnd->logColoredTextToConsole(text, color);
}

QColor
ProgramWindow::getColorForMsRunDataSet(
  MsRunDataSetCstSPtr ms_run_data_set_csp) const
{
  return mp_openMsRunDataSetsDlg->colorForMsRunDataSet(ms_run_data_set_csp);
}

QFile *
ProgramWindow::getAnalysisFilePtr()
{
  return mpa_analysisFile;
}

QSharedPointer<DiscoveriesPreferences>
ProgramWindow::getDiscoveriesPreferences()
{
  return mqsp_discoveriesPreferences;
}

void
ProgramWindow::recordAnalysisStanza(const QString &stanza, const QColor &color)
{
  // Check that the user configured the analysis preferences.

  if(mqsp_discoveriesPreferences == nullptr)
    return;

  // The record might go to a file or to the console, to the clipboard...
  if((mqsp_discoveriesPreferences->m_recordTarget &
      RecordTarget::RECORD_TO_FILE) == RecordTarget::RECORD_TO_FILE)
    {
      if(mpa_analysisFile != nullptr)
        {

          // Write the stanza, that was crafted by the calling plot widget to
          // the file.

          if(!mpa_analysisFile->open(QIODevice::Append))
            {
              statusBar()->showMessage(
                QString("Could not record the step because "
                        "the file could not be opened."),
                4000);
            }
          else
            {
              mpa_analysisFile->write(stanza.toLatin1());

              // Force writing because we may want to have tail -f work fine
              // on the file, and see modifications live to change fiels in a
              // text editor.

              mpa_analysisFile->flush();
              mpa_analysisFile->close();
            }
        }
      else
        {
          // qDebug() << "The mpa_analysisFile pointer is nullptr.";

          statusBar()->showMessage(
            QString("Could not record the analysis step to file. "
                    "Please define a file to write the data to."),
            4000);
        }
    }

  // Also, if recording to the console is asked for, then do that also.
  if((mqsp_discoveriesPreferences->m_recordTarget &
      RecordTarget::RECORD_TO_CONSOLE) == RecordTarget::RECORD_TO_CONSOLE)
    {
      logColoredTextToConsole(stanza, color);
    }

  // Also, if recording to the clipboard is asked for, then do that also.
  if((mqsp_discoveriesPreferences->m_recordTarget &
      RecordTarget::RECORD_TO_CLIPBOARD) == RecordTarget::RECORD_TO_CLIPBOARD)
    {
      QClipboard *clipboard = QApplication::clipboard();
      clipboard->setText(clipboard->text() + stanza, QClipboard::Clipboard);
    }

  return;
}

int
ProgramWindow::selectMsRun(std::vector<pappso::MsRunIdCstSPtr> &ms_run_ids)
{
  MsRunSelectorDlg *dlg =
    new MsRunSelectorDlg(this, ms_run_ids, m_applicationName);

  return dlg->exec();
}

AboutDlg *
ProgramWindow::showAboutDlg()
{
  // The application name will be set automatically by default parameter
  // value.
  AboutDlg *dlg = new AboutDlg(this, m_applicationName);

  dlg->show();

  return dlg;
}

void
ProgramWindow::showMsRunDataSetsWnd()
{
  mp_openMsRunDataSetsDlg->activateWindow();
  mp_openMsRunDataSetsDlg->raise();
  mp_openMsRunDataSetsDlg->show();
}

const OpenMsRunDataSetsDlg *
ProgramWindow::getOpenMsRunDataSetsDlg() const
{
  return mp_openMsRunDataSetsDlg;
}

void
ProgramWindow::showTicXicChromatogramsWnd()
{
  mp_ticXicChromPlotWnd->showWindow();
}

void
ProgramWindow::showMassSpectraWnd()
{
  mp_massSpecPlotWnd->showWindow();
}

void
ProgramWindow::showIonMobilitySpectraWnd()
{
  mp_driftSpecPlotWnd->showWindow();
}

void
ProgramWindow::showTicXicChromMassSpecColorMapsWnd()
{
  mp_ticXicChromMassSpecColorMapWnd->showWindow();
}

void
ProgramWindow::showTicXicChromIonMobSpecColorMapsWnd()
{
  mp_ticXicChromDriftSpecColorMapWnd->showWindow();
}

void
ProgramWindow::showIonMobSpecMassSpecColorMapsWnd()
{
  mp_driftSpecMassSpecColorMapWnd->showWindow();
}

void
ProgramWindow::showXicExtractionsWnd()
{

  mp_xicExtractionWnd->showWindow();
}

void
ProgramWindow::showIsotopicClusterGeneratorDlg()
{
  mp_isotopicClusterGeneratorDlg =
    new libXpertMassGui::IsotopicClusterGeneratorDlg(
      this, m_applicationName, "Isotopic cluster generator");

  connect(
    mp_isotopicClusterGeneratorDlg,
    &libXpertMassGui::IsotopicClusterGeneratorDlg::displayMassSpectrumSignal,
    this,
    [&](const QString &title,
        const QString &color_name,
        pappso::TraceCstSPtr trace) {
      mp_massSpecPlotWnd->handleReceivedData(title, color_name, trace);
    });

  mp_isotopicClusterGeneratorDlg->activateWindow();
  mp_isotopicClusterGeneratorDlg->raise();
  mp_isotopicClusterGeneratorDlg->show();
}

void
ProgramWindow::showIsotopicClusterShaperDlg()
{
  mp_isotopicClusterShaperDlg = new libXpertMassGui::IsotopicClusterShaperDlg(
    this, m_applicationName, "Isotopic cluster shaper");

  connect(mp_isotopicClusterShaperDlg,
          &libXpertMassGui::IsotopicClusterShaperDlg::displayMassSpectrumSignal,
          this,
          [&](const QString &title,
              const QString &color_name,
              pappso::TraceCstSPtr trace) {
            mp_massSpecPlotWnd->handleReceivedData(title, color_name, trace);
          });

  mp_isotopicClusterShaperDlg->activateWindow();
  mp_isotopicClusterShaperDlg->raise();
  mp_isotopicClusterShaperDlg->show();
}

void
ProgramWindow::showTaskMonitorWnd()
{
  mp_taskMonitorWnd->activateWindow();
  mp_taskMonitorWnd->raise();
  mp_taskMonitorWnd->show();
}

void
ProgramWindow::showConsoleWnd()
{
  mp_consoleWnd->activateWindow();
  mp_consoleWnd->raise();
  mp_consoleWnd->show();
}

void
ProgramWindow::showScriptingWnd()
{
  mp_scriptingWnd->activateWindow();
  mp_scriptingWnd->raise();
  mp_scriptingWnd->show();
}

libXpertMassGui::JavaScriptingWnd *
ProgramWindow::getScriptingWnd()
{
  return mp_scriptingWnd;
}

void
ProgramWindow::showApplicationPreferencesWnd()
{
  mp_applicationPreferencesWnd->activateWindow();
  mp_applicationPreferencesWnd->raise();
  mp_applicationPreferencesWnd->show();
}

const ApplicationPreferencesWnd *
ProgramWindow::getApplicationPreferencesWnd()
{
  return mp_applicationPreferencesWnd;
}

void
ProgramWindow::saveWorkspace()
{
  writeSettings();
  mp_openMsRunDataSetsDlg->writeSettings();
  mp_taskMonitorWnd->writeSettings();
  mp_consoleWnd->writeSettings();

  mp_ticXicChromPlotWnd->writeSettings();
  mp_massSpecPlotWnd->writeSettings();
  mp_driftSpecPlotWnd->writeSettings();
  mp_ticXicChromMassSpecColorMapWnd->writeSettings();
  mp_ticXicChromDriftSpecColorMapWnd->writeSettings();
  mp_driftSpecMassSpecColorMapWnd->writeSettings();

  mp_scriptingWnd->writeSettings();

  mp_applicationPreferencesWnd->writeSettings();

  mp_xicExtractionWnd->writeSettings();
}

void
ProgramWindow::showMsRunDataSetTableViewWnd(
  MsRunDataSetCstSPtr ms_run_data_set_csp, const QColor &color)
{
  // qDebug();

  // Each ms run data set has the ability to show its data in the form of a
  // tree view in a specific window. This is what we want to do here.

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // First check if the window exists already.

  std::map<MsRunDataSetCstSPtr, MsRunDataSetTableViewWnd *>::iterator
    res_iterator = std::find_if(
      mp_msRunDataSetTableViewWndMap.begin(),
      mp_msRunDataSetTableViewWndMap.end(),
      [ms_run_data_set_csp](
        const std::pair<MsRunDataSetCstSPtr, MsRunDataSetTableViewWnd *> item) {
        return item.first == ms_run_data_set_csp;
      });

  MsRunDataSetTableViewWnd *tree_view_wnd_p = nullptr;

  if(res_iterator == mp_msRunDataSetTableViewWndMap.end())
    {
      // The tree view window for this ms run data set was never asked to be
      // shown. Thus, do the whole work.

      tree_view_wnd_p =
        new MsRunDataSetTableViewWnd(this, ms_run_data_set_csp, color, this);

      // qDebug() << "Creating new ms run data set tree view.";

      // Immediately map the ms run data set and the tree view.

      mp_msRunDataSetTableViewWndMap[ms_run_data_set_csp] = tree_view_wnd_p;
    }
  else
    {
      // qDebug() << "A map item was found;";

      tree_view_wnd_p = res_iterator->second;
    }

  tree_view_wnd_p->activateWindow();
  tree_view_wnd_p->raise();
  tree_view_wnd_p->show();
}

void
ProgramWindow::displayArticleCitationHelpPage()
{
  // At this point, try to check if we should remind the user to
  // cite the paper.

  QSettings settings(
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath(),
    QSettings::IniFormat);

  settings.beginGroup("Globals");

  int run_count = settings.value("run_count", 1).toInt();

  if(run_count == 15)
    {
      // The proper application name will be set by default value parameter.
      AboutDlg *dlg = new AboutDlg(static_cast<QWidget *>(this));

      dlg->showHowToCiteTab();

      dlg->show();

      settings.setValue("run_count", 1);
    }
  else
    {
      settings.setValue("run_count", ++run_count);
    }

  settings.endGroup();
}

bool
ProgramWindow::startServer()
{
  // qDebug();

  if(mp_massDataServer != nullptr)
    delete mp_massDataServer;

  mp_massDataServer = new libXpertMassCore::MassDataServer(this);

  if(mp_massDataServer != nullptr)
    {
      // Actually start the server.

      if(!mp_massDataServer->listen())
        {
          qCritical() << "Failed to start the server.";
          delete mp_massDataServer;
          return false;
        }
    }
  else
    {
      qCritical() << "Failed to allocate the server.";
      return false;
    }

  // At this point try to get to the details.

  QString ip_address = mp_massDataServer->serverAddress().toString();
  int port_number    = mp_massDataServer->serverPort();

  libXpertMassGui::MassDataClientServerConfigDlg *sender_p =
    static_cast<libXpertMassGui::MassDataClientServerConfigDlg *>(
      QObject::sender());

  sender_p->updateClientConfigurationData(ip_address, port_number);

  return true;
}

void
ProgramWindow::stopServer()
{
  if(mp_massDataServer != nullptr)
    {
      delete mp_massDataServer;
      mp_massDataServer = nullptr;
    }
}

// This function can be called as a slot of signal
// libXpertMassGui::MassDataClientServerConfigDlg::startClientSignal
// or directly from main.cpp if args correspond to ip address and port number.
bool
ProgramWindow::startClient(const QString &ip_address, int port_number)
{
  // qDebug() << "Starting client for connection to server ip:" << ip_address
  //          << "port number:" << port_number;

  if(mp_massDataClient != nullptr)
    delete mp_massDataClient;

  mp_massDataClient =
    new libXpertMassCore::MassDataClient(ip_address, port_number, this);

  if(mp_massDataClient == nullptr)
    {
      qCritical() << "Failed to start the client.";

      return false;
    }

  // Now connect to the error feedback signal so that the server settings
  // dialog display the errors.

  connect(mp_massDataClient,
          &libXpertMassCore::MassDataClient::reportErrorSignal,
          this,
          [&](const QString &error) {
            // qDebug();
            if(mp_clientServerConfigDlg)
              mp_clientServerConfigDlg->clientFailingFeedback(error);
          });

  connect(mp_massDataClient,
          &libXpertMassCore::MassDataClient::newDataSignal,
          this,
          [&](const QByteArray &byte_array) {
            qsizetype data_size = byte_array.size();

            QString msg =
              QString("Received %1 bytes of new data").arg(data_size);

            // qDebug() << msg;

            if(mp_clientServerConfigDlg)
              mp_clientServerConfigDlg->message(msg, 3000);

            dispatchReceivedData(byte_array);
          });

  connect(mp_massDataClient,
          &libXpertMassCore::MassDataClient::hostFoundSignal,
          this,
          [&]() {
            if(mp_clientServerConfigDlg)
              mp_clientServerConfigDlg->message("The remote host was found",
                                                6000);
          });

  connect(mp_massDataClient,
          &libXpertMassCore::MassDataClient::connectedSignal,
          this,
          [&]() {
            if(mp_clientServerConfigDlg)
              mp_clientServerConfigDlg->message("Connected to the remote host",
                                                6000);
          });

  connect(mp_massDataClient,
          &libXpertMassCore::MassDataClient::disconnectedSignal,
          this,
          [&]() {
            // qDebug() << "Handling disconnectedSignal,"
            //          << QDateTime::currentDateTime();
            if(mp_clientServerConfigDlg)
              {
                mp_clientServerConfigDlg->message(
                  "Disconnected from to the remote host", 10000);

                mp_clientServerConfigDlg->updateClientIpData(
                  std::pair<QString, int>("", 0));
              }
          });

  configureNetworkConnection(/*no_dlg_show*/ true);

  return true;
}

void
ProgramWindow::stopClient()
{
  // qDebug() << "Stopping client." << QDateTime::currentDateTime();

  if(mp_massDataClient != nullptr)
    {
      // This will delete the socket in the client.
      mp_massDataClient->forcefullyDisconnect();

      // qDebug() << "Actually deleting the mass data client."
      //          << QDateTime::currentDateTime();

      delete mp_massDataClient;
      mp_massDataClient = nullptr;

      if(mp_clientServerConfigDlg != nullptr)
        mp_clientServerConfigDlg->updateClientIpData(
          std::pair<QString, int>("", 0));
    }
}

void
ProgramWindow::dispatchReceivedData(const QByteArray &byte_array)
{
  // We receive the byte_array from the TCP socket and we need to actually
  // determine what kind of data it contains. Depending on the data type, the
  // data are dispatched to the proper window.

  libXpertMassCore::Enums::MassDataType mass_data_type =
    libXpertMassCore::MassDataCborBaseHandler::readMassDataType(byte_array);

  // qDebug() << "Mass data type:" << (int)mass_data_type;

  if(mass_data_type == libXpertMassCore::Enums::MassDataType::NOT_SET)
    {
      qDebug() << "The mass data type is"
                  " libXpertMassCore::Enums::MassDataType::NOT_SET ";
    }
  else if(mass_data_type ==
          libXpertMassCore::Enums::MassDataType::MASS_SPECTRUM)
    {
      qDebug() << "The mass data type is"
                  " libXpertMassCore::Enums::MassDataType::MASS_SPECTRUM ";

      mp_massSpecPlotWnd->handleReceivedData(byte_array);
    }
  else
    {
      // qDebug() << "The mass data type is not yet supported.";
    }
}

void
ProgramWindow::massDataToBeServed(const QByteArray &byte_array)
{
  // qDebug() << "Got signal that data had to be served; size:"
  // << byte_array.size();

  if(mp_massDataServer != nullptr)
    mp_massDataServer->serveData(byte_array);

  else
    QMessageBox::warning(
      this, "Mass data server", "Please, start the mass data server");
}

} // namespace MineXpert

} // namespace MsXpS
