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

/////////////////////// stdlib includes


/////////////////////// Qt includes
#include <QSysInfo>
#include <QString>
#include <QAction>
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QList>
#include <QApplication>
#include <QDebug>
#include <QtNetwork>
#include <QSvgRenderer>


/////////////////////// pappsomspp includes
#include <pappsomspp/core/trace/trace.h>


/////////////////////// libXpertMass includes
#include <MsXpS/libXpertMassCore/PolChemDef.hpp>
#include <MsXpS/libXpertMassCore/PolChemDefSpec.hpp>
#include <MsXpS/libXpertMassCore/MassDataClient.hpp>
#include <MsXpS/libXpertMassCore/MassDataServer.hpp>
#include <MsXpS/libXpertMassCore/Utils.hpp>
#include <MsXpS/libXpertMassCore/MassDataCborBaseHandler.hpp>
#include <MsXpS/libXpertMassCore/MassDataCborMassSpectrumHandler.hpp>


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/MassDataClientServerConfigDlg.hpp>
#include <MsXpS/libXpertMassGui/JavaScriptingWnd.hpp>

/////////////////////// Local includes
// For the install directory defines
#include "ApplicationPreferencesWnd.hpp"
#include "config.h"
#include "ProgramWindow.hpp"
#include "Application.hpp"
#include "../nongui/PolChemDefCatParser.hpp"
#include "PolChemDefWnd.hpp"
#include "CalculatorWnd.hpp"
#include "SequenceEditorWnd.hpp"
// #include "MzLabWnd.hpp"
#include "MassListSorterDlg.hpp"
#include "SeqToolsDlg.hpp"
#include "../nongui/ConfigSetting.hpp"


namespace MsXpS
{
namespace MassXpert
{


ProgramWindow::ProgramWindow(QWidget *parent,
                             const QString &application_name,
                             const QString &description)
  : QMainWindow(parent),
    m_applicationName(application_name),
    m_windowDescription(description)
{
  if(m_applicationName.isEmpty())
    qFatal() << "Programming error. The application name must not be empty.";

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

  displayArticleCitationHelpPage();
}


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


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

  settings.beginGroup("ProgramWindow");

  settings.setValue("geometry", saveGeometry());

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

  settings.endGroup();
}

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

  settings.beginGroup("ProgramWindow");

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

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

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

  settings.endGroup();
}


void
ProgramWindow::closeEvent(QCloseEvent *event)
{
  emit aboutToCloseSignal();

  writeSettings();

  QList<AbstractMainTaskWindow *> childList =
    dynamic_cast<QObject *>(this)->findChildren<AbstractMainTaskWindow *>();

  for(int iter = 0; iter < childList.size(); ++iter)
    {
      childList.at(iter)->close();
    }

  event->accept();

  return;
}

void
ProgramWindow::setupWindow()
{
  mp_lastFocusedSeqEdWnd = nullptr;

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

  // QDirIterator it(":/", QDirIterator::Subdirectories);
  // while (it.hasNext()) {
  //   qDebug() << it.next();
  // }

  // QSvgRenderer renderer(QString(":/images/icons/svg/MassXpert3.svg"));
  // if (renderer.isValid())
  //   qDebug() << "SVG loaded successfully!";
  // else
  //   qDebug() << "Failed to load SVG — check Qt SVG module.";

  // QPixmap pm(":/images/icons/svg/MassXpert3.svg");
  // qDebug() << "Pixmap loaded?" << !pm.isNull();
  // pm.save("/tmp/test_icon.png");

  // QFile f(":/images/icons/svg/MassXpert3.svg");
  // if (f.open(QIODevice::ReadOnly)) {
  //   QByteArray data = f.readAll();
  //   qDebug() << "SVG size:" << data.size();
  //   // Optionally dump first few bytes
  //   qDebug() << data.left(100);
  // }

  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.";

  createActions();
  createMenus();
  createStatusBar();

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

  initializeScripting();

  // Now that the application preferences window has been initialized,
  // we can access stuff in there.
  bool init_succeeded = setupConfigSettings();

  mp_applicationPreferencesWnd =
    new ApplicationPreferencesWnd(m_applicationName, m_windowDescription, this);
  mp_applicationPreferencesWnd->setWindowIcon(qApp->windowIcon());

  if(!init_succeeded)
    {
      qDebug() << "Failed to setup file system hierarchy settings.";
      mp_applicationPreferencesWnd->activateWindow();
      mp_applicationPreferencesWnd->show();
      mp_applicationPreferencesWnd->showSectionListItem(
        ApplicationPreferencesWnd::Pages::FILESYSTEM_HIERARCHY);

      QMessageBox::warning(
        mp_applicationPreferencesWnd,
        "MassXpert3 - Filesystem hierarchy preferences",
        "The initialization of the file system hierarchy failed,\n please "
        "check the application preferences window.",
        QMessageBox::Ok);
    }
  else
    qDebug() << "Succeeded to setup file system hierarchy settings.";
}


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

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

bool
ProgramWindow::setupConfigSettings()
{
  msp_configSettings = std::make_shared<ConfigSettings>(m_applicationName);

  // Initialize the user specifications.
  //
  // On UNIX-like: /home/<user>
  // On MS Window: /Users/<username>
  m_userSpec.setUserName();

  // Where are the configuration data stored? Application did the work.
  qDebug()
    << "User's config settings file path: "
    << dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath();

  // Initialize the configuration directories. The configurations are of two
  // kinds: system and user. The system configuration comprises all the
  // directories and files that are shipped with the software distribution. The
  // user configuration comprises all the directories and files that the user
  // has created in his $HOME config/data directories.

  bool init_succeded = initializeSystemConfig();
  if(!init_succeded)
    {
      // We need to open the application preferences window to the right
      // section. We ask that the file system hierarchy preferences section be
      // setup on demand below:
      qDebug()
        << "Failed initializing the system filesystem hierarchy configuration.";
    }

  // Always returns true.
  initializeUserConfig();

  PolChemDefCatParser parser;
  parser.setConfigAddMode(ContainerItemAddMode::APPEND);

  if(parser.parseFiles(msp_configSettings, m_polChemDefSpecs, UserType::BOTH) ==
     -1)
    {
      QMessageBox::critical(
        0,
        QString("%1 - Configuration Settings").arg(m_applicationName),
        tr("%1@%2\n"
           "Failed to parse the polymer chemistry "
           "definition catalogues.\n"
           "The software will not work as intended.")
          .arg(__FILE__)
          .arg(__LINE__),
        QMessageBox::Ok);
    }

  return init_succeded;
}


bool
ProgramWindow::initializeSystemConfig()
{
  // First off, some general concepts.

  // When the software is built, a configuration process will
  // provide values for a number of #defined variables, depending on
  // the platform on which the software is built.

  // For example on MS-Windows, the following variables are defined:

  /*
   * #define PROJECT_INSTALL_BIN_DIR C:/Program Files/MassXpert3
   * #define PROJECT_INSTALL_CHEMDATA_DIR C:/Program Files/MassXpert3/data
   * #define PROJECT_INSTALL_DOC_DIR C:/Program Files/MassXpert3/doc
   *
   * This means that for the massxpert module, the data will be copied as
   * C:/Program Files/massxpert3/data.
   */

  // On GNU/Linux, instead, the following variables are defined:

  /*
   * #define PROJECT_INSTALL_BIN_DIR /usr/local/bin
   * #define PROJECT_INSTALL_CHEMDATA_DIR /usr/local/share/massxpert3/data
   * #define PROJECT_INSTALL_DOC_DIR: /usr/local/share/doc/massxpert3/doc
   *
   * This means that for the massxpert module, the data will be copied as
   * /usr/local/share/massxpert3/data.
   */

  // Note that if the program detects that the APPDIR environment variable
  // is set, that means that all the Linux  directories above need to be
  // prepended with $APPDIR:
  // if (const char* appdir = std::getenv("APPDIR"))
  //    PROJECT_INSTALL_CHEMDATA_DIR =
  //          std::string(appdir) + PROJECT_INSTALL_CHEMDATA_DIR;

  // Now, it might be of interest for the user to install the
  // software in another location than the canonical one. In this
  // case, the program should still be able to access the data.

  // This is what this function is for. This function will try to
  // establish if the data that were built along the software
  // package are actually located in the canonical places listed
  // above. If not, this function returns false. The caller might
  // then take actions to let the user manually instruct the program
  // of where the data are located.

  // All this procedure is setup so as to let the user install the
  // software wherever she wants.

  // Settings are set according to a well-defined manner:
  //
  // 1. The QSettings-based configuration is tested first because the user
  // might have moved the data away from the original locations, and then
  // set the new location in the QSettings configuration system.
  //
  // 2. If the above solution fails, then we test the true config.h-based
  // configuration, since this is where the data are installed initially when
  // issuing make install or when using the distribution means (deb or exe).

  // Handy variables
  QDir testDir;
  QDir finalDir;
  QString dirString;

  // Let's store the number of errors, so that we can return true or false.
  int errorCount = 0;

  // See ProgramWindow::setupConfigSettings().

  QString config_settings_dir_path =
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsDirPath();

  QString config_settings_file_path =
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath();

  /////////////////// DATA_DIR ///////////////////
  /////////////////// DATA_DIR ///////////////////
  /////////////////// DATA_DIR ///////////////////

  qDebug() << "Querying the user's config settings file path:"
           << config_settings_file_path
           << "for the system data config settings.";

  QSettings settings(config_settings_file_path, QSettings::IniFormat);

  settings.beginGroup("SystemFileSystemHierarchyPreferences");

  // The main system data directory ////////////////////////////////

  // From the QSettings config file.

  dirString = settings.value("dataDir").toString();
  testDir.setPath(dirString);

  if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
    {
      // Ah ah, the user configured a new directory. Fine, let's go
      // with it.
      finalDir = testDir;

      qDebug() << "The user has configured the dataDir:"
               << finalDir.absolutePath();
    }
  else
    {
      // qDebug() << "The user has not configured the dataDir. Trying to use the
      // " "PROJECT_INSTALL_CHEMDATA_DIR variable from config.h.";

      /////// From the config.h file

      // On UNIX, that would be
      // /usr/local/share/massxpert3/data
      // On Window, that would be
      // C:\ProgramFiles\MassXpert3\data

      QString dirString = QString("%1/%2")
                            .arg(PROJECT_INSTALL_PREFIX)
                            .arg(PROJECT_INSTALL_CHEMDATA_DIR);

      QString app_dir = qgetenv("APPDIR");
      if(!app_dir.isEmpty())
        {
          // If APPDIR is set, we are running this program from a AppImage
          // mount.
          dirString = QString("%1/%2").arg(app_dir).arg(dirString);
        }

      qDebug() << "Setting testDir to" << dirString;

      testDir.setPath(dirString);

      // qDebug() << "From the config.h file:" << testDir.absolutePath();

      // The path must be absolute and must exist.

      if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
        {
          // Fantastic, the initial installation directory exists and that is
          // fine.
          finalDir = testDir;
        }
      else
        {
          // Well, even that failed, we need to craft a dummy ConfigSetting
          // instance with the initial install directory to later let the user
          // configure that directory location anew.

          // We'll tell the caller that at least one directory was definitely
          // not found.
          ++errorCount;

          finalDir = testDir;
        }
    }

  // And now craft the ConfigSetting instance:

  ConfigSettingSPtr config_setting_sp =
    std::make_shared<ConfigSetting>(m_applicationName);
  config_setting_sp->m_userType = UserType::SYSTEM;
  config_setting_sp->m_key      = "dataDir";
  config_setting_sp->m_value    = finalDir.absolutePath();
  config_setting_sp->m_title    = "System's main data directory";
  config_setting_sp->m_comment  = "The system's main data directory.";
  msp_configSettings->getConfigSettingsRef().push_back(config_setting_sp);


  // The polymer chemistry definitions directory where sits the polymer
  // chemistry definition catalogue ////////////////////////////////

  // From the QSettings config file.

  dirString = settings.value("polChemDefsDir").toString();
  testDir.setPath(dirString);

  if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
    {
      // Ah ah, the user already configured a new directory. Fine, let's go
      // with it.
      finalDir = testDir;

      qDebug() << "The user has configured the polChemDefsDir:"
               << finalDir.absolutePath();
    }
  else
    {
      // qDebug()
      //<< "The user has not configured the polChemDefsDir. Trying to use the "
      //"PROJECT_INSTALL_CHEMDATA_DIR variable from config.h.";

      /////// From the config.h file

      // On UNIX, that would be
      // /usr/local/share/massxpert3/data/polChemDefsDir
      // On Window, that would be
      // C:\ProgramFiles\MassXpert3\data\polChemDefsDir

      dirString = QString("%1/%2/%3")
                    .arg(PROJECT_INSTALL_PREFIX)
                    .arg(PROJECT_INSTALL_CHEMDATA_DIR)
                    .arg("polChemDefs");

      QString app_dir = qgetenv("APPDIR");
      if(!app_dir.isEmpty())
        {
          // If APPDIR is set, we are running this program from a AppImage
          // mount.
          dirString = QString("%1/%2").arg(app_dir).arg(dirString);
        }

      qDebug() << "Setting testDir to" << dirString;

      testDir.setPath(dirString);

      // qDebug() << "From the config.h file:" << testDir.absolutePath();

      // The path must be absolute and must exist.

      if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
        {
          // Fantastic, the initial installation directory exists and that is
          // fine.
          finalDir = testDir;
        }
      else
        {
          // Well, even that failed, we need to craft a dummy ConfigSetting
          // instance with the initial install directory to later let the user
          // configure that directory location anew.

          // We'll tell the caller that at least one directory was definitely
          // not found.
          ++errorCount;
          finalDir = testDir;
        }
    }

  // And now craft the ConfigSetting instance:

  config_setting_sp = std::make_shared<ConfigSetting>(m_applicationName);
  config_setting_sp->m_userType = UserType::SYSTEM;
  config_setting_sp->m_key      = "polChemDefsDir";
  config_setting_sp->m_value    = finalDir.absolutePath();
  config_setting_sp->m_title =
    "System's main polymer chemistry definitions directory";
  config_setting_sp->m_comment =
    "The system's main polymer chemistry definitions directory.";
  msp_configSettings->getConfigSettingsRef().push_back(config_setting_sp);


  // The polymer sequences directory ////////////////////////////////

  // From the QSettings config file.

  dirString = settings.value("polSeqsDir").toString();
  testDir.setPath(dirString);

  if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
    {
      // Ah ah, the user already configured a new directory. Fine, let's go
      // with it.
      finalDir = testDir;

      qDebug() << "The user has configured the polSeqsDir:"
               << finalDir.absolutePath();
    }
  else
    {
      // qDebug()
      //<< "The user has not configured the polSeqsDir. Trying to use the "
      //"DATA_DIR variable from config.h.";

      /////// From the config.h file

      // On UNIX, that would be
      // /usr/local/share/massxpert3/data/polSeqs
      // On Window, that would be
      // C:\ProgramFiles\MassXpert3\data\polSeqs

      dirString = QString("%1/%2/%3")
                    .arg(PROJECT_INSTALL_PREFIX)
                    .arg(PROJECT_INSTALL_CHEMDATA_DIR)
                    .arg("polSeqs");

      QString app_dir = qgetenv("APPDIR");
      if(!app_dir.isEmpty())
        {
          // If APPDIR is set, we are running this program from a AppImage
          // mount.
          dirString = QString("%1/%2").arg(app_dir).arg(dirString);
        }

      qDebug() << "Setting testDir to" << dirString;

      testDir.setPath(dirString);

      // qDebug() << "From the config.h file:" << testDir.absolutePath();

      // The path must be absolute and must exist.

      if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
        {
          // Fantastic, the initial installation directory exists and that is
          // fine.
          finalDir = testDir;
        }
      else
        {
          // Well, even that failed, we need to craft a dummy ConfigSetting
          // instance with the initial install directory to later let the user
          // configure that directory location anew.

          // We'll tell the caller that at least one directory was definitely
          // not found.
          ++errorCount;
          finalDir = testDir;
        }
    }

  // QMessageBox::information(
  // this, "dataDir/polSeqs path", finalDir.absolutePath(), QMessageBox::Ok);

  // And now craft the ConfigSetting instance:

  config_setting_sp = std::make_shared<ConfigSetting>(m_applicationName);
  config_setting_sp->m_userType = UserType::SYSTEM;
  config_setting_sp->m_key      = "polSeqsDir";
  config_setting_sp->m_value    = finalDir.absolutePath();
  config_setting_sp->m_title    = "System's main polymer sequences directory";
  config_setting_sp->m_comment =
    "The system's main polymer sequences directory.";
  msp_configSettings->getConfigSettingsRef().push_back(config_setting_sp);


  // The documentation directory ////////////////////////////////

  // From the QSettings config file.

  dirString = settings.value("docDir").toString();
  testDir.setPath(dirString);

  if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
    {
      // Ah ah, the user already configured a new directory. Fine, let's go
      // with it.
      finalDir = testDir;

      qDebug() << "The user has configured the docDir:"
               << finalDir.absolutePath();
    }
  else
    {
      // qDebug() << "The user has not configured the docDir. Trying to use the
      // " "DATA_DIR variable from config.h.";

      /////// From the config.h file

      // That would be /usr/local/share/doc/massxpert3

      // On UNIX, that would be
      // /usr/local/share/doc/massxpert3
      // On Window, that would be
      // C:\ProgramFiles\MassXpert3\doc

      dirString = QString("%1/%2")
                    .arg(PROJECT_INSTALL_PREFIX)
                    .arg(PROJECT_INSTALL_DOC_DIR);

      QString app_dir = qgetenv("APPDIR");
      if(!app_dir.isEmpty())
        {
          // If APPDIR is set, we are running this program from a AppImage
          // mount.
          dirString = QString("%1/%2").arg(app_dir).arg(dirString);
        }

      qDebug() << "Setting testDir to" << dirString;

      testDir.setPath(dirString);

      // qDebug() << "From the config.h file:" << testDir.absolutePath();

      // The path must be absolute and must exist.

      if(!dirString.isEmpty() && testDir.exists() && testDir.isAbsolute())
        {
          // Fantastic, the initial installation directory exists and that is
          // fine.
          finalDir = testDir;
        }
      else
        {
          // Well, even that failed, we need to craft a dummy ConfigSetting
          // instance with the initial install directory to later let the user
          // configure that directory location anew.

          // We'll tell the caller that at least one directory was definitely
          // not found.
          ++errorCount;

          finalDir = testDir;
        }
    }

  // And now craft the ConfigSetting instance:


  config_setting_sp = std::make_shared<ConfigSetting>(m_applicationName);
  config_setting_sp->m_userType = UserType::SYSTEM;
  config_setting_sp->m_key      = "docDir";
  config_setting_sp->m_value    = finalDir.absolutePath();
  config_setting_sp->m_title    = "System's main documentation directory";
  config_setting_sp->m_comment  = "The system's main documentation directory.";
  msp_configSettings->getConfigSettingsRef().push_back(config_setting_sp);

  // Finally we can close the group.
  settings.endGroup();

  return (!errorCount ? true : false);
}


bool
ProgramWindow::initializeUserConfig()
{

  // First off, some general concepts.
  //
  // MassXpert should let the user define any polymer chemistry of their
  // requirement, without needing the root priviledges for installation of these
  // new data.
  //
  // This is why MassXpert allows for personal data directories modelled on the
  // same filesystem scheme as for the system data directory that are installed
  // with the distribution of the MassXpert3 software package.
  //
  // On GNU/Linux, the configuration directory for the user is located at
  // $HOME/.config (QStandardPaths::ConfigLocation). The Application class of
  // this program has determined if that location is writable (exists) and has
  // thus set the configuration file to be
  // $HOME/.config/<application_name>/configSettings.ini.
  //
  // If that .config file is not available, then the other option is
  // $HOME (QStandardPaths::HomeLocation) and the configuration file to be
  // $HOME/.<application_name>/configSettings.ini.

  // We'll now write all the necessary config/data files.

  // Handy variables
  QDir testDir;
  QDir finalDir;

  /////////////////// $HOME/.config/MassXpert3/ ///////////////////

  // See ProgramWindow::setupConfigSettings().

  QString config_settings_dir_path =
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsDirPath();

  QString config_settings_file_path =
    dynamic_cast<Application *>(qApp)->getUserConfigSettingsFilePath();


  QSettings settings(config_settings_file_path, QSettings::IniFormat);
  settings.beginGroup("UserFileSystemHierarchyPreferences");

  // We could start by checking if the canonical
  // $HOME/.config/<application_name> directory is there, but in fact it might
  // be there but not used, with the user having configured already a new
  // location within the config settings file. So we start by checking that
  // first.

  testDir.setPath(settings.value("dataDir").toString());

  if(!testDir.path().isEmpty() && testDir.exists())
    {
      // Ah ah, the user already configured a new directory. Fine, let's go
      // with it.

      // qDebug() << "The user has configured the dataDir:"
      //<< testDir.absolutePath();

      finalDir = testDir;
    }
  else
    {
      // No, the user has not configured an alternative directory, so let's
      // look if the canonical one is there:

      testDir.setPath(config_settings_dir_path + QDir::separator() + "data");

      qDebug() << "Testing the existence of " << testDir.absolutePath();

      if(testDir.exists())
        {
          qDebug() << "The user data directory has been found:"
                   << testDir.absolutePath();

          finalDir = testDir;
        }
      else
        {
          if(!testDir.mkpath(testDir.absolutePath()))
            {
              qFatal(
                "Directory %s did not exist and its creation failed."
                "Program aborted.",
                testDir.absolutePath().toLatin1().data());
            }
          else
            {
              // At last, we could get a working directory...

              // qDebug() << "Finally, we created the directory:" << testDir;

              finalDir = testDir;
            }
        }
    }

  // And now craft the ConfigSetting instance:

  ConfigSettingSPtr config_setting_sp =
    std::make_shared<ConfigSetting>(m_applicationName);
  config_setting_sp->m_userType = UserType::USER;
  config_setting_sp->m_key      = "dataDir";
  config_setting_sp->m_value    = finalDir.absolutePath();
  config_setting_sp->m_title    = "Users's main data directory";
  config_setting_sp->m_comment =
    "The user's main data directory should be the one set.";
  msp_configSettings->getConfigSettingsRef().push_back(config_setting_sp);


  // The polymer chemistry definitions directory where sits the polymer
  // chemistry definition catalogue ////////////////////////////////

  testDir.setPath(settings.value("polChemDefsDir").toString());

  if(!testDir.path().isEmpty() && testDir.exists())
    {
      // Ah ah, the user already configured a new directory. Fine, let's go
      // with it.

      // qDebug() << "The user has configured the polChemDefsDir:"
      // << testDir.absolutePath();

      finalDir = testDir;
    }
  else
    {
      // No, the user has not configured an alternative directory, so let's
      // look if the canonical one is there:

      testDir.setPath(config_settings_dir_path + QDir::separator() + "data" +
                      QDir::separator() + "polChemDefs");

      // qDebug() << "Testing the existence of " << testDir.absolutePath();

      if(testDir.exists())
        {
          qDebug() << "The user polChemDefs directory has been found:"
                   << testDir.absolutePath();

          finalDir = testDir;
        }
      else
        {
          if(!testDir.mkpath(testDir.absolutePath()))
            {
              qFatal(
                "Directory %s did not exist and its creation failed."
                "Program aborted.",
                testDir.absolutePath().toLatin1().data());
            }
          else
            {
              // At last, we could get a working directory...

              // qDebug() << "Finally, we created the directory:" << testDir;

              finalDir = testDir;
            }
        }
    }

  // And now craft the ConfigSetting instance:

  config_setting_sp = std::make_shared<ConfigSetting>(m_applicationName);
  config_setting_sp->m_userType = UserType::USER;
  config_setting_sp->m_key      = "polChemDefsDir";
  config_setting_sp->m_value    = finalDir.absolutePath();
  config_setting_sp->m_title =
    "User's main polymer chemistry definitions directory";
  config_setting_sp->m_comment =
    "The user's main polymer chemistry definitions directory should be the "
    "one "
    "set.";
  msp_configSettings->getConfigSettingsRef().push_back(config_setting_sp);


  // The polymer sequences directory ////////////////////////////////

  testDir.setPath(settings.value("polSeqsDir").toString());

  if(!testDir.path().isEmpty() && testDir.exists())
    {
      // Ah ah, the user already configured a new directory. Fine, let's go
      // with it.

      // qDebug() << "The user has configured the polSeqsDir:"
      //<< testDir.absolutePath();

      finalDir = testDir;
    }
  else
    {
      // No, the user has not configured an alternative directory, so let's
      // look if the canonical one is there:

      // The data are installed by default to the user's home directory
      // that would be /home/<username>/.config/MassXpert3/data/polChemDefs.

      testDir.setPath(config_settings_dir_path + QDir::separator() + "data" +
                      QDir::separator() + "polSeqs");

      // qDebug() << "Testing the existence of " << testDir.absolutePath();

      if(testDir.exists())
        {
          qDebug() << "The user polSeqs directory has been found:"
                   << testDir.absolutePath();

          finalDir = testDir;
        }
      else
        {
          if(!testDir.mkpath(testDir.absolutePath()))
            {
              qFatal(
                "Directory %s did not exist and its creation failed."
                "Program aborted.",
                testDir.absolutePath().toLatin1().data());
            }
          else
            {
              // At last, we could get a working directory...

              // qDebug() << "Finally, we created the directory:" << testDir;

              finalDir = testDir;
            }
        }
    }

  // And now craft the ConfigSetting instance:

  config_setting_sp = std::make_shared<ConfigSetting>(m_applicationName);
  config_setting_sp->m_userType = UserType::USER;
  config_setting_sp->m_key      = "polSeqsDir";
  config_setting_sp->m_value    = finalDir.absolutePath();
  config_setting_sp->m_title    = "User's polymer sequences directory";
  config_setting_sp->m_comment =
    "The user's polymer sequences directory should be the one set.";
  msp_configSettings->getConfigSettingsRef().push_back(config_setting_sp);

  // Finally we can close the settings group.
  settings.endGroup();

  return true;
}

//! Initialize the JavaScript scripting framework.
void
ProgramWindow::initializeScripting()
{
  mp_javaScriptingWnd =
    new libXpertMassGui::JavaScriptingWnd(m_applicationName, this);

  // Make available to the scripting environment all the entities that will
  // be useful to script MassXpert.

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

  // ProgramWindow. We expose this class instance to the QJSEngine as progWnd.
  if(!mp_javaScriptingWnd->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.";

  connect(mp_scriptingConsoleAct,
          &QAction::triggered,
          this,
          &ProgramWindow::showScriptingWnd);
}


const UserSpec &
ProgramWindow::getUserSpec() const
{
  return m_userSpec;
}

ConfigSettingsSPtr
ProgramWindow::getConfigSettings() const
{
  return msp_configSettings;
}

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

void
ProgramWindow::setLastFocusedSeqEdWnd(SequenceEditorWnd *wnd)
{
  mp_lastFocusedSeqEdWnd = wnd;
  // qDebug() << __FILE__<< __LINE__ << wnd;
}

std::vector<libXpertMassCore::PolChemDefSpecSPtr> &
ProgramWindow::getPolChemDefSpecsRef()
{
  return m_polChemDefSpecs;
}

const std::vector<libXpertMassCore::PolChemDefSpecSPtr> &
ProgramWindow::getPolChemDefSpecsCstRef() const
{
  return m_polChemDefSpecs;
}

std::vector<libXpertMassCore::PolChemDefCstSPtr> &
ProgramWindow::getPolChemDefsRef()
{
  return m_polChemDefs;
}

const std::vector<libXpertMassCore::PolChemDefCstSPtr> &
ProgramWindow::getPolChemDefsCstRef() const
{
  return m_polChemDefs;
}

libXpertMassCore::PolChemDefSpecSPtr
ProgramWindow::getPolChemDefSpecByName(const QString &name)
{
  std::vector<libXpertMassCore::PolChemDefSpecSPtr>::iterator the_iterator =
    std::find_if(
      m_polChemDefSpecs.begin(),
      m_polChemDefSpecs.end(),
      [name](libXpertMassCore::PolChemDefSpecSPtr &pol_chem_def_spec_sp) {
        return pol_chem_def_spec_sp->getName() == name;
      });

  if(the_iterator != m_polChemDefSpecs.end())
    return *the_iterator;

  return nullptr;
}

libXpertMassCore::PolChemDefSpecSPtr
ProgramWindow::getPolChemDefSpecByFilePath(const QString &file_path)
{
  std::vector<libXpertMassCore::PolChemDefSpecSPtr>::iterator the_iterator =
    std::find_if(
      m_polChemDefSpecs.begin(),
      m_polChemDefSpecs.end(),
      [file_path](libXpertMassCore::PolChemDefSpecSPtr &pol_chem_def_spec_sp) {
        return pol_chem_def_spec_sp->getFilePath() == file_path;
      });

  if(the_iterator != m_polChemDefSpecs.end())
    return *the_iterator;

  return nullptr;
}


libXpertMassCore::PolChemDefCstSPtr
ProgramWindow::getPolChemDefByName(const QString &name)
{
  libXpertMassCore::PolChemDefCstSPtr polChemDefCstSPtr;

  std::vector<libXpertMassCore::PolChemDefCstSPtr>::iterator the_iterator =
    find_if(m_polChemDefs.begin(),
            m_polChemDefs.end(),
            [name](libXpertMassCore::PolChemDefCstSPtr pol_chem_def_csp) {
              return pol_chem_def_csp->getName() == name;
            });

  if(the_iterator != m_polChemDefs.end())
    return *the_iterator;

  return nullptr;
}

QStringList
ProgramWindow::polChemDefSpecsAsStringList()
{
  QStringList string_list;

  for(const libXpertMassCore::PolChemDefSpecSPtr &pol_chem_def_spec_sp :
      m_polChemDefSpecs)
    string_list << pol_chem_def_spec_sp->getFilePath();

  return string_list;
}

bool
ProgramWindow::isSequenceEditorWnd(SequenceEditorWnd *seqEdWnd) const
{
  // Does this pointer correspond to a sequence editor window
  // currently opened?

  for(int iter = 0; iter < m_sequenceEditorWndList.size(); ++iter)
    {
      if(m_sequenceEditorWndList.at(iter) == seqEdWnd)
        return true;
    }

  return false;
}


void
ProgramWindow::openPolChemDef()
{
  // We are asked to open a polymer chemistry definition file, which
  // we'll do inside a PolChemDefWnd.
  QString file_path;
  bool ok;

  // Get the PolchemDefSpecs' file paths as the catalogue contents.
  QStringList string_list = polChemDefSpecsAsStringList();

  // Ask the user to select one file or to click Cancel to browse.
  file_path = QInputDialog::getItem(this,
                                    tr("Select a polymer chemistry definition "
                                       "or click Cancel to browse"),
                                    tr("Polymer chemistry definition:"),
                                    string_list,
                                    0,
                                    false,
                                    &ok);

  if(!ok || file_path.isEmpty())
    {
      file_path = QFileDialog::getOpenFileName(this,
                                               tr("Open definition file"),
                                               QDir::homePath(),
                                               tr("XML files(*.xml *.XML)"));

      if(file_path.isNull() || file_path.isEmpty())
        return;

      if(!QFile::exists(file_path))
        return;
    }

  new PolChemDefWnd(
    this, file_path, m_applicationName, "Polymer chemistry definition");
}


void
ProgramWindow::newPolChemDef()
{
  // We are asked to create a polymer chemistry definition file, which
  // we'll do inside a PolChemDefWnd.

  new PolChemDefWnd(
    this, "", m_applicationName, "Polymer chemistry definition");
}


void
ProgramWindow::newCalculator()
{
  QString file_path;
  bool ok;

  // Open a calculator window, making sure that a polymer chemistry
  // definition is offered to open to the caller.

  // Get the PolchemDefSpecs' file paths as the catalogue contents.
  QStringList file_path_list = polChemDefSpecsAsStringList();

  // Ask the user to select one file or to click Cancel to browse.
  file_path = QInputDialog::getItem(this,
                                    tr("Select a polymer chemistry definition "
                                       "or click Cancel to browse"),
                                    tr("Polymer chemistry definition:"),
                                    file_path_list,
                                    0,
                                    false,
                                    &ok);

  if(!ok || file_path.isEmpty())
    {
      // We could not get the file_path. Try by giving the user the
      // opportunity to select a file from the filesystem.
      file_path = QFileDialog::getOpenFileName(this,
                                               tr("Open definition file"),
                                               QDir::homePath(),
                                               tr("XML files(*.xml *.XML)"));

      if(file_path.isNull() || file_path.isEmpty())
        return;

      if(!QFile::exists(file_path))
        {
          QMessageBox::warning(this,
                               m_applicationName,
                               tr("File(%1) not found.").arg(file_path),
                               QMessageBox::Ok);

          return;
        }
    }

  // Open a calculator window without any polymer chemistry definition.

  // We do not need to seed the window with mono nor avg mass (QString() below).
  new CalculatorWnd(
    this, file_path, m_applicationName, "Calculator", QString(), QString());
}


SequenceEditorWnd *
ProgramWindow::openSequence(const QString &fileName)
{
  QString file_path;
  QString name;

  if(fileName.isEmpty())
    {
      // Has the user configured a path to the polymer sequences?
      // That is, we should check for userPolSeqsDir.

      // Get the directory where the user data reside:
      const ConfigSettingSPtr config_settings_sp =
        msp_configSettings->getConfigSetting("polSeqsDir", UserType::USER);

      if(config_settings_sp == nullptr)
        qFatal() << "Programming error. Pointer cannot be nullptr.";

      QDir pol_seqs_dir(config_settings_sp->m_value.toString());

      file_path =
        QFileDialog::getOpenFileName(this,
                                     tr("Open sequence file"),
                                     pol_seqs_dir.absolutePath(),
                                     tr("Sequence files(*.mxp *.mXp *.MXP)"
                                        ";; All files(*.*)"));
    }
  else
    file_path = fileName;

  if(file_path.isNull() || file_path.isEmpty())
    return nullptr;

  if(!QFile::exists(file_path))
    {
      QMessageBox::warning(this,
                           m_applicationName,
                           tr("File(%1) not found.").arg(file_path),
                           QMessageBox::Ok);

      return nullptr;
    }

  SequenceEditorWnd *sequenceEditorWnd =
    new SequenceEditorWnd(this, m_applicationName, "Polymer sequence editor");

  if(!sequenceEditorWnd->openSequence(file_path))
    {
      QMessageBox::warning(this,
                           m_applicationName,
                           tr("%1@%2\n"
                              "Failed to open sequence in the editor window.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      sequenceEditorWnd->close();

      return nullptr;
    }

  qDebug() << "Connecting sequence editor window:" << sequenceEditorWnd;

  connect(sequenceEditorWnd,
          &SequenceEditorWnd::displayMassSpectrumSignal,
          this,
          &ProgramWindow::massSpectrumToBeDisplayed);

  // At this time we have a polymer chemistry definition window that
  // is fully initialized and functional. We can add its pointer to
  // the list of such windows that is stored in the application
  // object.
  m_sequenceEditorWndList.append(sequenceEditorWnd);

  // Finally connect the about to close signal to a function
  // that removes the sequence editor window pointer from the list.
  connect(sequenceEditorWnd,
          &SequenceEditorWnd::polymerSequenceWndAboutToClose,
          this,
          &ProgramWindow::delistSequenceEditorWnd);

  // Placeholder to receive values from the called functions below, although at
  // the moment we do not use it.
  QJSValue returned_js_value;
  QString sequence_file_base_name = QFileInfo(file_path).baseName();

  QJSEngine *js_engine_p =
    mp_javaScriptingWnd->getScriptingEnvironment()->getJsEngine();

  // This sequence editor window must be exposed to the QJSEngine as progWnd.
  if(!mp_javaScriptingWnd->getScriptingEnvironment()->exposeQObject(
       js_engine_p,
       "seqEdWnd" /*name*/,
       sequence_file_base_name.replace(" ", "_").replace("-", "_") /*alias*/,
       QString("Sequence editor window of file %1")
         .arg(sequence_file_base_name),
       sequenceEditorWnd,
       this /*object_parent_p*/,
       returned_js_value,
       QJSEngine::CppOwnership))
    qFatal() << "Failed to expose QObject to provided QJSEngine.";

  // At this point we know the sequence file has been loaded, store the file
  // path for the Recent files... menu.

  m_recentFilesHandler.addItem(file_path);

  return sequenceEditorWnd;
}


SequenceEditorWnd *
ProgramWindow::openSampleSequence()
{
  // This menu is served to the user so that he can get immediate
  // access to the sample sequences even without knowing what the
  // structure of the data is on the filesystem. In particular, the
  // user might not be able to find the sample sequences on a UNIX
  // system or in a MacOSX bundle.

  // Get the directory where the system data reside:
  const ConfigSettingSPtr config_settings_sp =
    msp_configSettings->getConfigSetting("polSeqsDir", UserType::SYSTEM);

  if(config_settings_sp == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  QDir pol_seqs_dir(config_settings_sp->m_value.toString());
  QStringList file_filters;
  file_filters << "*.mxp"
               << "*.MXP";
  pol_seqs_dir.setNameFilters(file_filters);
  QFileInfoList infoList =
    pol_seqs_dir.entryInfoList(file_filters, QDir::Files | QDir::Readable);

  QStringList file_path_list;

  for(int iter = 0; iter < infoList.size(); ++iter)
    {
      QFileInfo fileInfo(infoList.at(iter));
      file_path_list << fileInfo.absoluteFilePath();
    }

  bool ok = false;

  // Ask the user to select one file or to click Cancel to browse.

  QString file_path = QInputDialog::getItem(this,
                                            tr("Select a sample sequence file "
                                               "or click Cancel to browse"),
                                            tr("Sample sequence file:"),
                                            file_path_list,
                                            0,
                                            false,
                                            &ok);

  if(!ok || file_path.isEmpty())
    {
      // We could not get the file_path. Try by giving the user the
      // opportunity to select a file from the filesystem.
      file_path = QFileDialog::getOpenFileName(this,
                                               tr("Open sequence file"),
                                               QDir::homePath(),
                                               tr("mxp files(*.mxp *.MXP)"
                                                  ";; All files(*.*)"));
    }

  if(file_path.isNull() || file_path.isEmpty())
    return nullptr;

  if(!QFile::exists(file_path))
    {
      QMessageBox::warning(this,
                           m_applicationName,
                           tr("File %1 not found.").arg(file_path),
                           QMessageBox::Ok);

      return nullptr;
    }

  return openSequence(file_path);
}

SequenceEditorWnd *
ProgramWindow::newSequence()
{
  // The user should first tell of what polymer chemistry definition
  // the sequence should be. Let him choose amongst the available
  // polymer chemistry definitions:
  // Get the polchemdef catalogue contents.
  QStringList file_path_list = polChemDefSpecsAsStringList();

  // Ask the user to select one file or to click Cancel to browse.
  bool ok;
  QString filePath =
    QInputDialog::getItem(this,
                          tr("Select a polymer chemistry definition "
                             "or click Cancel to browse"),
                          tr("Polymer chemistry definitions:"),
                          file_path_list,
                          0,
                          false,
                          &ok);

  if(!ok || filePath.isEmpty())
    {
      // We could not get the filePath. Try by giving the user the
      // opportunity to select a file from the filesystem.
      filePath = QFileDialog::getOpenFileName(this,
                                              tr("Open definition file"),
                                              QDir::homePath(),
                                              tr("XML files(*.xml *.XML)"
                                                 ";; All files(*.*)"));

      if(filePath.isNull() || filePath.isEmpty())
        return nullptr;

      if(!QFile::exists(filePath))
        {
          QMessageBox::warning(this,
                               m_applicationName,
                               tr("File(%1) not found.").arg(filePath),
                               QMessageBox::Ok);

          return nullptr;
        }
    }

  // At this stage we should have a proper polymer chemistry
  // definition filePath.

  SequenceEditorWnd *sequenceEditorWnd =
    new SequenceEditorWnd(this, m_applicationName, "Polymer sequence editor");

  if(!sequenceEditorWnd->newSequence(filePath))
    {
      QMessageBox::warning(this,
                           m_applicationName,
                           tr("%1@%2\n"
                              "Failed to create sequence in the editor window.")
                             .arg(__FILE__)
                             .arg(__LINE__),
                           QMessageBox::Ok);

      sequenceEditorWnd->close();

      return nullptr;
    }

  // At this time we have a polymer chemistry definition window that
  // is fully initialized and functional. We can add its pointer to
  // the list of such windows.
  m_sequenceEditorWndList.append(sequenceEditorWnd);

  return sequenceEditorWnd;
}


void
ProgramWindow::delistSequenceEditorWnd(SequenceEditorWnd *wnd)
{
  for(int iter = 0; iter < m_sequenceEditorWndList.size(); ++iter)
    {
      if(m_sequenceEditorWndList.at(iter) == wnd)
        m_sequenceEditorWndList.takeAt(iter);
    }
}

// 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)
    {
      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 = 0;

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

#if 0
// Not yet ported

void
ProgramWindow::mzLab()
{
  // Make sure that a polymer chemistry definition is offered to
  // open to the caller.

  // Get the polchemdef catalogue contents.
  QStringList file_path_list = polChemDefSpecsAsStringList();

  // Ask the user to select one file or to click Cancel to browse.

  bool ok;

  QString file_path =
    QInputDialog::getItem(this,
                          tr("Select a polymer chemistry definition "
                             "or click Cancel to browse"),
                          tr("Polymer chemistry definition:"),
                          file_path_list,
                          0,
                          false,
                          &ok);

  if(!ok || file_path.isEmpty())
    {
      // We could not get the file_path. Try by giving the user the
      // opportunity to select a file from the filesystem.
      file_path = QFileDialog::getOpenFileName(this,
                                               tr("Open definition file"),
                                               QDir::homePath(),
                                               tr("XML files(*.xml *.XML)"));

      if(file_path.isNull() || file_path.isEmpty())
        return;

      if(!QFile::exists(file_path))
        {
          QMessageBox::warning(this,
                               m_applicationName,
                               tr("File(%1) not found.").arg(file_path),
                               QMessageBox::Ok);

          return;
        }
    }

  MzLabWnd *mzLabWnd =
    new MzLabWnd(this, file_path, m_applicationName, "m/z lab");

  mzLabWnd->show();
}
#endif

void
ProgramWindow::massListSorter()
{
  MassListSorterDlg *dialog =
    new MassListSorterDlg(this, m_applicationName, "Mass list sorting");

  dialog->show();
}


void
ProgramWindow::seqManip()
{
  SeqToolsDlg *dialog =
    new SeqToolsDlg(this, m_applicationName, "Sequence manipulations");

  dialog->show();
}


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

  // When the isotopic cluster shaper has finished in the dialog window, the
  // user can click on the display mass spectrum button that sends a signal with
  // the mass spectral data and the title. Use these to craft a CBOR-encoded
  // byte array and serve that to any listening entity.

  // This is massXpert and we have no mass spectrum display functionality, so we
  // delegate that to some application who might be listening.
  connect(mp_isotopicClusterShaperDlg,
          &libXpertMassGui::IsotopicClusterShaperDlg::displayMassSpectrumSignal,
          [this](const QString &title,
                 const QByteArray &color_byte_array,
                 pappso::TraceCstSPtr trace) {
            qDebug() << "Received a mass spectrum to be displayed.";
            libXpertMassCore::MassDataCborMassSpectrumHandler cbor_handler(
              this, *trace);
            cbor_handler.setTitle(title);
            cbor_handler.setTraceColor(color_byte_array);
            QByteArray byte_array;
            cbor_handler.writeByteArray(byte_array);
            massDataToBeServed(byte_array);
          });

  // Here we need to somehow transfer the mass spectral data to a program
  // that can display that spectrum, typically minexpert2.

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


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

  // This is massXpert and we have no mass spectrum display functionality, so we
  // delegate that to some application who might be listening.
  connect(
    mp_isotopicClusterGeneratorDlg,
    &libXpertMassGui::IsotopicClusterGeneratorDlg::displayMassSpectrumSignal,
    [this](const QString &title,
           const QByteArray &color_byte_array,
           pappso::TraceCstSPtr trace) {
      qDebug() << "Received a mass spectrum to be displayed at"
               << QDateTime::currentDateTime();
      libXpertMassCore::MassDataCborMassSpectrumHandler cbor_handler(this,
                                                                     *trace);
      cbor_handler.setTitle(title);
      cbor_handler.setTraceColor(color_byte_array);
      QByteArray byte_array;
      cbor_handler.writeByteArray(byte_array);
      massDataToBeServed(byte_array);
    });

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


void
ProgramWindow::showScriptingWnd()
{

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

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

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


void
ProgramWindow::registerJsConstructor(QJSEngine *engine)

{
  if(!engine)
    {
      qWarning() << "Cannot register ProgramWindow class: engine is null";
      return;
    }

  // Register the meta object as a constructor

  QJSValue jsMetaObject =
    engine->newQMetaObject(&ProgramWindow::staticMetaObject);
  engine->globalObject().setProperty("ProgramWindow", jsMetaObject);
}


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

  dlg->show();
}


void
ProgramWindow::createActions()
{
  // File/Exit
  mp_exitAct = new QAction(tr("E&xit"), this);
  mp_exitAct->setShortcut(tr("Ctrl+Q"));
  mp_exitAct->setStatusTip(tr("Exit the application"));
  connect(mp_exitAct, SIGNAL(triggered()), this, SLOT(close()));

  // XpertDef Menu/Open PolChemDef
  mp_openPolChemDefAct =
    new QAction(QIcon(":/images/icons/new.png"), tr("&Open..."), this);
  mp_openPolChemDefAct->setShortcut(tr("Ctrl+D,O"));
  mp_openPolChemDefAct->setStatusTip(
    tr("Open an existing polymer chemistry definition file"));
  connect(
    mp_openPolChemDefAct, SIGNAL(triggered()), this, SLOT(openPolChemDef()));


  // XpertDef Menu/New PolChemDef
  mp_newPolChemDefAct =
    new QAction(QIcon(":/images/new.png"), tr("&New..."), this);
  mp_newPolChemDefAct->setShortcut(tr("Ctrl+D,N"));
  mp_newPolChemDefAct->setStatusTip(
    tr("Create a new polymer chemistry definition file"));
  connect(
    mp_newPolChemDefAct, SIGNAL(triggered()), this, SLOT(newPolChemDef()));


  // XpertCalc Menu/New Calculator
  mp_newCalculatorAct =
    new QAction(QIcon(":/images/new.png"), tr("&Open calculator"), this);
  mp_newCalculatorAct->setShortcut(tr("Ctrl+C,O"));
  mp_newCalculatorAct->setStatusTip(tr("Open a new calculator window"));
  connect(
    mp_newCalculatorAct, SIGNAL(triggered()), this, SLOT(newCalculator()));


  // XpertEdit Menu/Open Sample Sequence
  mp_openSampleSequenceAct =
    new QAction(QIcon(":/images/new.png"), tr("&Open sample sequence"), this);
  mp_openSampleSequenceAct->setShortcut(tr("Ctrl+E,S"));
  mp_openSampleSequenceAct->setStatusTip(
    tr("Open a sample polymer sequence file"));
  connect(mp_openSampleSequenceAct,
          SIGNAL(triggered()),
          this,
          SLOT(openSampleSequence()));

  mp_openSequenceAct =
    new QAction(QIcon(":/images/new.png"), tr("&Open sequence"), this);
  mp_openSequenceAct->setShortcut(tr("Ctrl+E,O"));
  mp_openSequenceAct->setStatusTip(
    tr("Open an existing polymer sequence file"));
  connect(mp_openSequenceAct, SIGNAL(triggered()), this, SLOT(openSequence()));

  // XpertEdit Menu/New Sequence
  mp_newSequenceAct =
    new QAction(QIcon(":/images/new.png"), tr("&New sequence"), this);
  mp_newSequenceAct->setShortcut(tr("Ctrl+E,N"));
  mp_newSequenceAct->setStatusTip(tr("Create a new polymer sequence file"));
  connect(mp_newSequenceAct, SIGNAL(triggered()), this, SLOT(newSequence()));

  // XpertMiner Menu/mz Lab
  mp_mzLabAct = new QAction(QIcon(":/images/new.png"), tr("&mz Lab"), this);
  mp_mzLabAct->setShortcut(tr("Ctrl+M,Z"));
  mp_mzLabAct->setStatusTip(tr("Open a new mz lab window"));
  // connect(mp_mzLabAct, SIGNAL(triggered()), this, SLOT(mzLab()));

  // XpertMiner Menu/mz Lab
  mp_scriptingConsoleAct =
    new QAction(QIcon(":/images/new.png"), tr("&Scripting Console"), this);
  mp_scriptingConsoleAct->setShortcut(tr("Ctrl+S,C"));
  mp_scriptingConsoleAct->setStatusTip(tr("Open the scripting console window"));
  connect(mp_scriptingConsoleAct,
          SIGNAL(triggered()),
          this,
          SLOT(showScriptingWnd()));

  // Utilities

  // 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).

  mp_clientServerConfigDlgAct =
    new QAction(QIcon(":/images/new.png"), "Client-Server configuration", this);
  mp_clientServerConfigDlgAct->setStatusTip(
    "Open the client-server configuration dialog window.");

  connect(mp_clientServerConfigDlgAct,
          &QAction::triggered,
          this,
          &ProgramWindow::configureNetworkConnection);

  mp_massListSorterAct =
    new QAction(QIcon(":/images/new.png"), tr("&Mass list sorter"), this);
  mp_massListSorterAct->setShortcut(tr("Ctrl+M,S"));
  mp_massListSorterAct->setStatusTip(tr("Sort masses in a list"));
  connect(
    mp_massListSorterAct, SIGNAL(triggered()), this, SLOT(massListSorter()));

  mp_seqManipAct =
    new QAction(QIcon(":/images/new.png"), tr("&Sequence manipulation"), this);
  mp_seqManipAct->setShortcut(tr("Ctrl+S,M"));
  mp_seqManipAct->setStatusTip(tr("Sequence manipulation"));
  connect(mp_seqManipAct, SIGNAL(triggered()), this, SLOT(seqManip()));

  mp_isotopicClusterGeneratorDlgAct = new QAction(
    QIcon(":/images/new.png"), tr("&Isotopic cluster generator"), this);
  mp_isotopicClusterGeneratorDlgAct->setShortcut(tr("Ctrl+I,C"));
  mp_isotopicClusterGeneratorDlgAct->setStatusTip(
    tr("Perform isotopic cluster simulations"));
  connect(mp_isotopicClusterGeneratorDlgAct,
          SIGNAL(triggered()),
          this,
          SLOT(showIsotopicClusterGeneratorDlg()));

  mp_isotopicClusterShaperDlgAct = new QAction(
    QIcon(":/images/new.png"), tr("&Isotopic cluster shaper"), this);
  mp_isotopicClusterShaperDlgAct->setShortcut(tr("Ctrl+I,G"));
  mp_isotopicClusterShaperDlgAct->setStatusTip(tr(
    "Generate a fully shaped isotopic cluster starting from the cluster's peak "
    "centroids"));
  connect(mp_isotopicClusterShaperDlgAct,
          SIGNAL(triggered()),
          this,
          SLOT(showIsotopicClusterShaperDlg()));

  // Help
  mp_aboutAct =
    new QAction(QIcon(":/images/icons/svg/help-information-icon.svg"),
                tr("&About"),
                dynamic_cast<QObject *>(this));
  mp_aboutAct->setStatusTip(tr("Show the application's About box"));
  mp_aboutAct->setShortcut(tr("Ctrl+H"));
  mp_aboutAct->setStatusTip(tr("Show the application's About box"));
  connect(mp_aboutAct, SIGNAL(triggered()), this, SLOT(showAboutDlg()));

  mp_aboutQtAct =
    new QAction(QIcon(":/images/icons/svg/help-qt-information-icon.svg"),
                tr("About &Qt"),
                dynamic_cast<QObject *>(this));
  mp_aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
  connect(mp_aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}


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

  mp_fileMenu->addAction(mp_exitAct);

  menuBar()->addSeparator();

  // XpertDef
  mp_xpertDefMenu = menuBar()->addMenu(tr("Xpert&Def"));
  mp_xpertDefMenu->addAction(mp_openPolChemDefAct);
  mp_xpertDefMenu->addAction(mp_newPolChemDefAct);

  menuBar()->addSeparator();

  // XpertCalc
  mp_xpertCalcMenu = menuBar()->addMenu(tr("Xpert&Calc"));
  mp_xpertCalcMenu->addAction(mp_newCalculatorAct);

  menuBar()->addSeparator();

  // XpertEdit
  mp_xpertEditMenu = menuBar()->addMenu(tr("Xpert&Edit"));

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

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

  mp_xpertEditMenu->addAction(mp_openSequenceAct);
  mp_xpertEditMenu->addAction(mp_openSampleSequenceAct);

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

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

  menuBar()->addSeparator();

  mp_xpertEditMenu->addAction(mp_newSequenceAct);

  // XpertMiner
  mp_xpertMinerMenu = menuBar()->addMenu(tr("Xpert&Miner"));
  mp_xpertMinerMenu->addAction(mp_mzLabAct);

  menuBar()->addSeparator();

  // XpertScript
  mp_xpertScriptMenu = menuBar()->addMenu(tr("Xpert&Script"));
  mp_xpertScriptMenu->addAction(mp_scriptingConsoleAct);

  menuBar()->addSeparator();

  // Isotopic clusters
  mp_isotopicClustersMenu = menuBar()->addMenu(tr("&Isotopic clusters"));
  mp_isotopicClustersMenu->addAction(mp_isotopicClusterGeneratorDlgAct);
  mp_isotopicClustersMenu->addAction(mp_isotopicClusterShaperDlgAct);

  menuBar()->addSeparator();

  // Utilities
  mp_utilitiesMenu = menuBar()->addMenu(tr("&Utilities"));
  mp_utilitiesMenu->addAction(mp_clientServerConfigDlgAct);
  mp_utilitiesMenu->addAction(mp_massListSorterAct);
  mp_utilitiesMenu->addAction(mp_seqManipAct);

  menuBar()->addSeparator();

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

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

  menuBar()->addSeparator();

  // help
  mp_helpMenu = menuBar()->addMenu(tr("&Help"));
  mp_helpMenu->addAction(mp_aboutAct);
  mp_helpMenu->addAction(mp_aboutQtAct);
}


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

  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, [file_path_action_p, this]() {
          QString file_path =
            file_path_action_p->property("filePath").toString();

          // qDebug() << "Now loading recent file:" << file_path;

          openSequence(file_path);
        });

      mp_recentFilesMenu->addAction(file_path_action_p);
    }
}

void
ProgramWindow::createStatusBar()
{
  statusBar()->showMessage(tr("Ready"));
}


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

  if(mp_massDataServer != nullptr)
    return true;

  mp_massDataServer = new libXpertMassCore::MassDataServer(this);

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

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

  configureNetworkConnection(true /*no_dlg_show*/);

#if 0
  ////// Specific block in case this function is called as a slot ////////
  {
    // The caller might not have been a signal/slot relation, this
    // function might be called from script, for example, in which
    // case there is no sender!

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

    if(sender_p != nullptr)
      sender_p->updateClientConfigurationData(
        mp_massDataServer->getIpAddress(), mp_massDataServer->getPortNumber());
  }
#endif

  return true;
}

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


bool
ProgramWindow::startMineXpertWithClient(const QString &ip_address,
                                        int port_number,
                                        const QByteArray &data)
{
  // bool is_minexpert3_running =
  //   libXpertMassCore::Utils::isProgramRunning("MineXpert3");
  //
  // if(is_minexpert3_running)
  //   {
  //     qDebug() << "MineXpert3 is already running !";
  //     return false;
  //   }

  Q_ASSERT(mp_massDataServer != nullptr);

  QProcess *process = new QProcess(this);

  QFileInfo file_info(
    "/home/rusconi/devel/minexpert/build-area/unix/src/MineXpert3");

  if(file_info.exists())
    process->setProgram(
      "/home/rusconi/devel/minexpert/build-area/unix/src/MineXpert3");
  else
    process->setProgram("MineXpert3");

  // QMessageBox::warning(
  //   this,
  //   "Starting process MineXpert3",
  //   "MineXpert3",
  //   QMessageBox::Ok);

  QStringList args;

  args << "--host" << ip_address << "--port" << QString::number(port_number);

  // qDebug() << "Starting MineXpert3 with args:" << args.join(" ");

  process->setArguments(args);

  qint64 pid = -1;

  process->startDetached(&pid);

  if(pid == -1)
    {
      qDebug() << "Failed to start MineXpert3:" << process->errorString();
      delete process;
      return false;
    }

  QObject::connect(process, &QProcess::readyReadStandardOutput, [process]() {
    qDebug() << "MineXpert3 output:" << process->readAllStandardOutput();
  });

  QObject::connect(process, &QProcess::readyReadStandardError, [process]() {
    qDebug() << "MineXpert3 error:" << process->readAllStandardError();
  });

  QObject::connect(
    process,
    QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
    [process](int exitCode, QProcess::ExitStatus exitStatus) {
      qDebug() << "MineXpert3 finished with exit code:" << exitCode
               << "and exit status:" << exitStatus;
      process->deleteLater();
    });

  QElapsedTimer timer;
  timer.start();
  const int timeout_milliseconds = 5000; // wait up to 5 seconds

  while(timer.elapsed() < timeout_milliseconds)
    {
      if(mp_massDataServer->hasReadyClient())
        {
          qDebug() << "MineXpert3 is connected and ready!";
          massDataToBeServed(data);
          break;
        }
      QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }

  if(!mp_massDataServer->hasReadyClient())
    {
      qWarning() << "MineXpert3 did not connect in time!";
    }

  return true;
}

// 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.
// Typically if MineXpert needs to send data to this MassXpert instance.
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;
    }

  // Lots of config (see same class::function in MineXpert3!)
  // missing here to be able to receive data for a server.

  configureNetworkConnection(true /*no_dlg_show*/);

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

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


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::UNSET";
    }
  else if(mass_data_type ==
          libXpertMassCore::Enums::MassDataType::MASS_SPECTRUM)
    {
      qDebug() << "The mass data type is "
                  "libXpertMassCore::Enums::MassDataType::MASS_SPECTRUM";

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


void
ProgramWindow::massSpectrumToBeDisplayed(const QString &title,
                                         const QByteArray &color_byte_array,
                                         pappso::TraceCstSPtr trace_csp)
{
  qDebug() << "Received a mass spectrum to be displayed.";

  qDebug() << "Current thread:" << QThread::currentThread()
           << "is main thread:" << QThread::isMainThread();

  libXpertMassCore::MassDataCborMassSpectrumHandler cbor_handler(this,
                                                                 *trace_csp);

  cbor_handler.setTitle(title);
  cbor_handler.setTraceColor(color_byte_array);

  QByteArray byte_array;
  cbor_handler.writeByteArray(byte_array);

  qDebug() << "On the verge of serving data.";

  massDataToBeServed(byte_array);
}

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

  if(mp_massDataServer == nullptr)
    {
      // Try to start the server
      if(!startServer())
        {
          QMessageBox::warning(
            this, "Mass data server", "Failed to start the mass data server");
          return;
        }
    }

  // MineXpert3 was not started already, try to start it with the
  // server IP config details.

  if(libXpertMassCore::Utils::isProgramRunning("MineXpert3"))
    {
      qDebug() << "MineXpert3 is already running !";

      if(mp_massDataServer->hasReadyClient())
        {
          qDebug() << "At least one connection is set.";

          mp_massDataServer->serveData(byte_array);
          return;
        }
      else
        {
          qDebug() << "MineXpert3 is running but is not connected.";

          QMessageBox msgBox;
          msgBox.setText("Client-server configuration with MineXpert3");

          QString msg =
            "MineXpert3 is running but is not listening to the server.\n\n";
          msg += "There are two options:\n";

          msg +=
            "* start a new MineXpert3 session with automatic client/server "
            "configuration.\n";

          msg += "* configure the running MineXpert3 client to listen to:\n";

          msg += QString("\t- IP address %1\n")
                   .arg(mp_massDataServer->getIpAddress());

          msg += QString("\t- Port number %2\n\n")
                   .arg(mp_massDataServer->getPortNumber());
          msg += QString(
            "Should a new MineXpert3 session automatically started?\n"
            "or\n"
            "Do you configure the running MineXpert3 client?\n");

          msgBox.setInformativeText(msg);

          msgBox.setIcon(QMessageBox::Question);

          // Add custom buttons with specific roles (optional but
          // recommended)
          QPushButton *start_auto_session_p =
            msgBox.addButton("Start MineXpert3 session", QMessageBox::YesRole);
          QPushButton *configure_client_p = msgBox.addButton(
            "Configure MineXpert3 client", QMessageBox::NoRole);
          QPushButton *cancel_p =
            msgBox.addButton("Cancel", QMessageBox::RejectRole);

          msgBox.exec();

          // Determine which button was clicked
          QAbstractButton *clicked_button_p = msgBox.clickedButton();

          if(clicked_button_p == start_auto_session_p)
            {
              qDebug() << "User chose to start new MineXpert3 session.";
              startMineXpertWithClient(mp_massDataServer->getIpAddress(),
                                       mp_massDataServer->getPortNumber(),
                                       byte_array);
              return;
            }
          else if(clicked_button_p == configure_client_p)
            {
              QMessageBox msgBox;
              msgBox.setText("Click OK when the client has been configured.");
              msgBox.exec();
            }
          else if(clicked_button_p == cancel_p)
            {
              qDebug() << "User chose to Cancel.";
              return;
            }
        }
    }
  else
    {
      // MineXpert3 is not running, just open it with the connection data
      // that will allow it to connect to this server host.
      startMineXpertWithClient(mp_massDataServer->getIpAddress(),
                               mp_massDataServer->getPortNumber(),
                               byte_array);
      return;
    }

  qDebug() << "At this point we can serve the data.";
  // At this point we should be able to serve the data.
  mp_massDataServer->serveData(byte_array);
}


} // namespace MassXpert
} // namespace MsXpS
