/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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
 */


/////////////////////// Qt includes
#include <libXpertMassGui/MassDataClientServerConfigDlg.hpp>
#include <QSysInfo>
#include <QString>
#include <QtGui>
#include <QtXml>
#include <QtGlobal>
#include <QAction>
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QList>
#include <QApplication>
#include <QDebug>
#include <QtNetwork>


/////////////////////// Local includes

#include <libXpertMass/PolChemDef.hpp>
#include <libXpertMass/PolChemDefEntity.hpp>
#include <libXpertMass/PolChemDefSpec.hpp>
#include <libXpertMass/MassDataClient.hpp>
#include <libXpertMass/MassDataServer.hpp>
#include <libXpertMass/MassDataCborBaseHandler.hpp>
#include <libXpertMass/MassDataCborMassSpectrumHandler.hpp>
#include <pappsomspp/trace/trace.h>

// For the install directory defines
#include "config.h"

#include "ProgramWindow.hpp"

#include "ConfigSettingsDlg.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(const QString &applicationName,
                             const QString &description)
  : m_applicationName(applicationName), m_windowDescription(description)
{
  if(m_applicationName.isEmpty())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Allocate the object that will store all the system/user config/data files.
  mpa_configSettings = new ConfigSettings(m_applicationName);

  setupConfigSettings();
  setupWindow();

  // libXpertMass::MassDataServer *mass_data_server_p =
  // new libXpertMass::MassDataServer(this);

  // if(!mass_data_server_p->listen())
  // qFatal("Could not start the server.");

  // qDebug() << "Successfully started the server.";

  //// Set up the IP address as the localhost.

  // QString ip_address = QHostAddress(QHostAddress::LocalHost).toString();

  // int port_number = mass_data_server_p->serverPort();
  // qDebug() << "The server is running on IP:" << ip_address
  //<< "at port:" << port_number;

  // qDebug() << "Now starting the client.";

  // libXpertMass::MassDataClient *mass_data_client_p =
  // new libXpertMass::MassDataClient(ip_address, port_number, this);

  // qDebug() << "The client is:" << mass_data_client_p->getStatus();
}


ProgramWindow::~ProgramWindow()
{
  // qDebug() << __FILE__ << __LINE__
  // << "Enter ~ProgramWindow.";

  // qDebug() << __FILE__ << __LINE__
  // << "Start deletion of m_polChemDefCatList items.";

  // List of all the polymer chemistry definition specifications that
  // were found in the different catalogue files on the system.
  while(!m_polChemDefCatList.isEmpty())
    delete m_polChemDefCatList.takeFirst();

  // qDebug() << __FILE__ << __LINE__
  // << "End deletion of m_polChemDefCatList items.";

  delete mpa_configSettings;

  // qDebug() << __FILE__ << __LINE__
  // << "Exit ~ProgramWindow.";
}


void
ProgramWindow::readSettings()
{
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup("ProgramWindow");
  restoreGeometry(settings.value("geometry").toByteArray());
  settings.endGroup();
}


void
ProgramWindow::writeSettings()
{
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup("ProgramWindow");
  settings.setValue("geometry", saveGeometry());
  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 = 0;

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

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

  setWindowIcon(QApplication::windowIcon());

  readSettings();

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

  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);
  settings.beginGroup("Globals");

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

  if(runNumber == 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("runNumber", 1);
    }
  else
    {
      settings.setValue("runNumber", ++runNumber);
    }

  settings.endGroup();
}


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

  dlg->show();
}


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

  // Where are the configuration data stored? We have Qt support for this.

  m_configSettingsFilePath = QDir::homePath() + QDir::separator() + "." +
                             m_applicationName + QDir::separator() +
                             "configSettings.ini";

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

  // 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 result = false;

  if(!initializeSystemConfig())
    {
      result = setConfigSettingsManually();

      if(!result)
        {
          QMessageBox::critical(
            0,
            QString("%1 - Configuration Settings").arg(m_applicationName),
            QString("%1@%2\n"
                    "Failed to configure %3.\n"
                    "The software will not work as "
                    "intended.")
              .arg(__FILE__)
              .arg(__LINE__)
              .arg(m_applicationName),
            QMessageBox::Ok);
        }
    }

  if(!initializeUserConfig())
    {
      qDebug() << "User configuration settings did not succeed.";
    }

  initializeDecimalPlacesOptions();

  PolChemDefCatParser parser;
  parser.setConfigSysUser(POLCHEMDEF_CAT_PARSE_BOTH_CONFIG);
  parser.setPendingMode(POLCHEMDEF_CAT_PARSE_APPEND_CONFIG);

  if(parser.parseFiles(mpa_configSettings, &m_polChemDefCatList) == -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 true;
}


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 BIN_DIR C:/Program Files/massXpert2
   * #define DATA_DIR C:/Program Files/massXpert2/data
   * #define DOC_DIR C:/Program Files/massXpert2/doc
   *
   * This means that for the massxpert module, the data will be copied as
   * C:/Program Files/massxpert2/data.
   */

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

  /*
   * #define BIN_DIR /usr/local/bin
   * #define DATA_DIR /usr/local/share/massxpert2/data
   * #define DOC_DIR: /usr/local/share/doc/massxpert2/doc
   *
   * This means that for the massxpert module, the data will be copied as
   * /usr/local/share/massxpert2/data.
   */

  // 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;
  ConfigSetting *configSetting = nullptr;

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


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

  // qDebug() << "Querying m_configSettingsFilePath:" <<
  // m_configSettingsFilePath
  //<< "for the system data config settings.";

  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);
  settings.beginGroup("systemDataConfigSettings");

  // 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
      // " "DATA_DIR variable from config.h.";

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

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

      dirString = QString("%1").arg(DATA_DIR);
      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:

  configSetting             = new ConfigSetting(m_applicationName);
  configSetting->m_userType = UserType::USER_TYPE_SYSTEM;
  configSetting->m_key      = "dataDir";
  configSetting->m_value    = finalDir.absolutePath();
  configSetting->m_title    = "System's main data directory";
  configSetting->m_comment  = "The system's main data directory.";
  mpa_configSettings->append(configSetting);


  // 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 "
      //"DATA_DIR variable from config.h.";

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

      // That would be
      // /usr/local/share/massXpert2/data/polChemDefs
      dirString = QString("%1/%2").arg(DATA_DIR).arg("polChemDefs");
      testDir.setPath(dirString);

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

      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:

  configSetting             = new ConfigSetting(m_applicationName);
  configSetting->m_userType = UserType::USER_TYPE_SYSTEM;
  configSetting->m_key      = "polChemDefsDir";
  configSetting->m_value    = finalDir.absolutePath();
  configSetting->m_title =
    "System's main polymer chemistry definitions directory";
  configSetting->m_comment =
    "The system's main polymer chemistry definitions directory.";
  mpa_configSettings->append(configSetting);


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

      // That would be /usr/local/share/massXpert2/data/polSeqs
      dirString = QString("%1/%2").arg(DATA_DIR).arg("polSeqs");
      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:

  configSetting             = new ConfigSetting(m_applicationName);
  configSetting->m_userType = UserType::USER_TYPE_SYSTEM;
  configSetting->m_key      = "polSeqsDir";
  configSetting->m_value    = finalDir.absolutePath();
  configSetting->m_title    = "System's main polymer sequences directory";
  configSetting->m_comment  = "The system's main polymer sequences directory.";
  mpa_configSettings->append(configSetting);


  // 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/msxpertsuite
      // that is, /usr/local/share/doc/msxpertsuite, for example.
      dirString = QString("%1").arg(DOC_DIR);
      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:

  configSetting             = new ConfigSetting(m_applicationName);
  configSetting->m_userType = UserType::USER_TYPE_SYSTEM;
  configSetting->m_key      = "docDir";
  configSetting->m_value    = finalDir.absolutePath();
  configSetting->m_title    = "System's main documentation directory";
  configSetting->m_comment  = "The system's main documentation directory.";
  mpa_configSettings->append(configSetting);

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

  if(errorCount > 0)
    return false;

  return true;
}


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 massXpert2 software package.
  //
  // The Qt software has one key directory location that will be used in
  // crafting all the subdirectories needed to store the user's configuration
  // and data directories: QDir::homePath().
  //
  // That variable is /home/<user> on UNIX-like systems and C:\Users\<username>
  // on MS-Windows.
  //
  // From there, we'll craft the following:
  //
  // /home/<user>/.massXpert2
  //
  // in which we'll write all the necessary config/data files.

  // Handy variables
  QDir testDir;
  QDir finalDir;
  ConfigSetting *configSetting = nullptr;

  /////////////////// $HOME/.massXpert2/ ///////////////////

  // See ProgramWindow::setupConfigSettings().
  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);
  settings.beginGroup("userDataConfigSettings");

  // We could start by checking if the canonical $HOME/.config/<applicationName>
  // 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(QDir::homePath() + QDir::separator() + "." +
                      m_applicationName + 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:

  configSetting             = new ConfigSetting(m_applicationName);
  configSetting->m_userType = UserType::USER_TYPE_USER;
  configSetting->m_key      = "dataDir";
  configSetting->m_value    = finalDir.absolutePath();
  configSetting->m_title    = "Users's main data directory";
  configSetting->m_comment =
    "The user's main data directory should be the one set.";
  mpa_configSettings->append(configSetting);


  // 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(QDir::homePath() + QDir::separator() + "." +
                      m_applicationName + 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:

  configSetting             = new ConfigSetting(m_applicationName);
  configSetting->m_userType = UserType::USER_TYPE_USER;
  configSetting->m_key      = "polChemDefsDir";
  configSetting->m_value    = finalDir.absolutePath();
  configSetting->m_title =
    "User's main polymer chemistry definitions directory";
  configSetting->m_comment =
    "The user's main polymer chemistry definitions directory should be the "
    "one "
    "set.";
  mpa_configSettings->append(configSetting);


  // 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>/.massxpert/polChemDefs.

      testDir.setPath(QDir::homePath() + QDir::separator() + "." +
                      m_applicationName + 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:

  configSetting             = new ConfigSetting(m_applicationName);
  configSetting->m_userType = UserType::USER_TYPE_USER;
  configSetting->m_key      = "polSeqsDir";
  configSetting->m_value    = finalDir.absolutePath();
  configSetting->m_title    = "User's polymer sequences directory";
  configSetting->m_comment =
    "The user's polymer sequences directory should be the one set.";
  mpa_configSettings->append(configSetting);

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

  return true;
}


QString
ProgramWindow::configSettingsFilePath() const
{
  return m_configSettingsFilePath;
}


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


const ConfigSettings *
ProgramWindow::configSettings() const
{
  return mpa_configSettings;
}


bool
ProgramWindow::setConfigSettingsManually()
{

  // qDebug() << "Starting manual configuration.";

  // Apparently, the system configuration directory was not found to
  // be of a canonical structure. This might be due to the
  // relocation of the package. Let the user tell where the package
  // is located.
  ConfigSettingsDlg dlg(
    m_applicationName, mpa_configSettings, m_configSettingsFilePath);

  int result = dlg.exec();

  if(result)
    return true;
  else
    return false;
}


void
ProgramWindow::initializeDecimalPlacesOptions()
{
  // The decimal places should be read from the configuration
  // settings.

  QSettings settings(m_configSettingsFilePath, QSettings::IniFormat);

  settings.beginGroup("decimalPlacesOptions");

  libXpertMass::ATOM_DEC_PLACES = settings.value("ATOM_DEC_PLACES", 10).toInt();
  // qDebug() << __FILE__ << __LINE__
  // << "ATOM_DEC_PLACES" << ATOM_DEC_PLACES;

  libXpertMass::OLIGOMER_DEC_PLACES =
    settings.value("OLIGOMER_DEC_PLACES", 5).toInt();
  // qDebug() << __FILE__ << __LINE__
  // << "OLIGOMER_DEC_PLACES" << OLIGOMER_DEC_PLACES;

  libXpertMass::POLYMER_DEC_PLACES = settings.value("POLYMER_DEC_PLACES", 3).toInt();
  // qDebug() << __FILE__ << __LINE__
  // << "POLYMER_DEC_PLACES" << POLYMER_DEC_PLACES;

  libXpertMass::PH_PKA_DEC_PLACES = settings.value("PH_PKA_DEC_PLACES", 2).toInt();
  // qDebug() << __FILE__ << __LINE__
  // << "PH_PKA_DEC_PLACES" << PH_PKA_DEC_PLACES;

  settings.endGroup();
}


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


QList<libXpertMass::PolChemDefSpec *> *
ProgramWindow::polChemDefCatList()
{
  return &m_polChemDefCatList;
}


std::vector<libXpertMass::PolChemDefCstSPtr>
ProgramWindow::polChemDefList()
{
  return m_polChemDefList;
}


libXpertMass::PolChemDefSpec *
ProgramWindow::polChemDefSpecName(const QString &name)
{
  libXpertMass::PolChemDefSpec *polChemDefSpec = 0;

  for(int iter = 0; iter < m_polChemDefCatList.size(); ++iter)
    {
      polChemDefSpec = m_polChemDefCatList.at(iter);

      if(polChemDefSpec->name() == name)
        return polChemDefSpec;
    }

  return 0;
}


libXpertMass::PolChemDefSpec *
ProgramWindow::polChemDefSpecFilePath(const QString &filePath)
{
  libXpertMass::PolChemDefSpec *polChemDefSpec = 0;

  for(int iter = 0; iter < m_polChemDefCatList.size(); ++iter)
    {
      polChemDefSpec = m_polChemDefCatList.at(iter);

      if(polChemDefSpec->getFilePath() == filePath)
        return polChemDefSpec;
    }

  return 0;
}


libXpertMass::PolChemDefCstSPtr
ProgramWindow::polChemDefByName(const QString &name)
{
  libXpertMass::PolChemDefCstSPtr polChemDefCstSPtr;

  std::vector<libXpertMass::PolChemDefCstSPtr>::iterator it = find_if(
    m_polChemDefList.begin(),
    m_polChemDefList.end(),
    [name](libXpertMass::PolChemDefCstSPtr pcdsp) { return pcdsp->name() == name; });

  if(it != m_polChemDefList.end())
    return *it;

  return nullptr;
}


void
ProgramWindow::polChemDefCatStringList(QStringList &stringList)
{
  libXpertMass::PolChemDefSpec *polChemDefSpec = 0;

  for(int iter = 0; iter < m_polChemDefCatList.size(); ++iter)
    {
      polChemDefSpec = m_polChemDefCatList.at(iter);

      stringList << polChemDefSpec->getFilePath();
    }
}


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.
  QStringList stringList;
  QString filePath;
  bool ok;

  // Get the polchemdef catalogue contents.
  polChemDefCatStringList(stringList);

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

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

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

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

  new PolChemDefWnd(
    this, filePath, 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()
{
  QStringList stringList;
  QString filePath;
  bool ok;

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

  // Get the polchemdef catalogue contents.
  polChemDefCatStringList(stringList);

  // Ask the user to select one file or to click Cancel to browse.
  filePath = QInputDialog::getItem(this,
                                   tr("Select a polymer chemistry definition "
                                      "or click Cancel to browse"),
                                   tr("Polymer chemistry definition:"),
                                   stringList,
                                   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)"));

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

      if(!QFile::exists(filePath))
        {
          QMessageBox::warning(this,
                               m_applicationName,
                               tr("File(%1) not found.").arg(filePath),
                               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, filePath, m_applicationName, "Calculator", QString(), QString());
}


void
ProgramWindow::openSequence(const QString &fileName)
{
  qDebug();

  QString filePath;
  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 system data reside:
      const ConfigSetting *configSetting =
        mpa_configSettings->value("polSeqsDir", UserType::USER_TYPE_USER);
      QDir polSeqsDir(configSetting->m_value.toString());

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

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

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

      return;
    }

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

  if(!sequenceEditorWnd->openSequence(filePath))
    {
      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;
    }

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

  connect(sequenceEditorWnd,
          &SequenceEditorWnd::displayMassSpectrumSignal,
          [this](const QString &title,
                 const QByteArray &color_byte_array,
                 pappso::TraceCstSPtr trace) {
            qDebug() << "Received a mass spectrum to be displayed.";
            libXpertMass::MassDataCborMassSpectrumHandler cbor_handler(this, *trace);
            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);
          });

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


void
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.

  QStringList stringList;
  QString filePath;
  QString name;

  // Get the directory where the system data reside:
  const ConfigSetting *configSetting =
    mpa_configSettings->value("polSeqsDir", UserType::USER_TYPE_SYSTEM);

  QDir polSeqsDir(configSetting->m_value.toString());

  QStringList filters;
  filters << "*.mxp"
          << "*.MXP";

  polSeqsDir.setNameFilters(filters);

  QFileInfoList infoList =
    polSeqsDir.entryInfoList(filters, QDir::Files | QDir::Readable);

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

  bool ok = false;

  // Ask the user to select one file or to click Cancel to browse.
  filePath = QInputDialog::getItem(this,
                                   tr("Select a sample sequence file "
                                      "or click Cancel to browse"),
                                   tr("Sample sequence file:"),
                                   stringList,
                                   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 sequence file"),
                                              QDir::homePath(),
                                              tr("mxp files(*.mxp *.MXP)"
                                                 ";; All files(*.*)"));
    }

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

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

      return;
    }

  return openSequence(filePath);
}


void
ProgramWindow::newSequence()
{
  QStringList stringList;

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

  polChemDefCatStringList(stringList);

  // 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 definition:"),
                          stringList,
                          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;

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

          return;
        }
    }

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

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


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


void
ProgramWindow::mzLab()
{
  QStringList stringList;
  QString filePath;
  bool ok;

  // Make sure that a polymer chemistry definition is offered to
  // open to the caller.

  // Get the polchemdef catalogue contents.
  polChemDefCatStringList(stringList);

  // Ask the user to select one file or to click Cancel to browse.
  filePath = QInputDialog::getItem(this,
                                   tr("Select a polymer chemistry definition "
                                      "or click Cancel to browse"),
                                   tr("Polymer chemistry definition:"),
                                   stringList,
                                   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)"));

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

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

          return;
        }
    }

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

  mzLabWnd->show();
}


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.";
            libXpertMass::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.";
            libXpertMass::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::about()
{
  // The application name will be set automatically by default parameter
  // value.
  AboutDlg *dlg = new AboutDlg(this);

  dlg->show();
}


void
ProgramWindow::createActions()
{
  // File/Config settings
  mp_configSettingsAct = new QAction(tr("Config &settings"), this);
  mp_configSettingsAct->setShortcut(tr("Ctrl+S"));
  mp_configSettingsAct->setStatusTip(tr("Set the configuration"));
  connect(mp_configSettingsAct,
          &QAction::triggered,
          this,
          &ProgramWindow::setConfigSettingsManually);

  // 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/open.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 an 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()));

  // 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, [this]() {
    if(mp_clientServerConfigDlg == nullptr)
      {

        mp_clientServerConfigDlg =
          new libXpertMassGui::MassDataClientServerConfigDlg(
            this, m_applicationName, "Client-server configuration");

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

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

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


  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()
{
  mp_fileMenu = menuBar()->addMenu(tr("&File"));
  mp_fileMenu->addAction(mp_configSettingsAct);
  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"));
  mp_xpertEditMenu->addAction(mp_openSequenceAct);
  mp_xpertEditMenu->addAction(mp_openSampleSequenceAct);
  mp_xpertEditMenu->addAction(mp_newSequenceAct);

  menuBar()->addSeparator();

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

  menuBar()->addSeparator();

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

  menuBar()->addSeparator();

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


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


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

  if(mpa_massDataServer != nullptr)
    return true;

  mpa_massDataServer = new libXpertMass::MassDataServer(this);

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

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

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

  QString ip_address;
  QList<QHostAddress> ip_addressesList = QNetworkInterface::allAddresses();

  // Use the first non-localhost IPv4 address
  for(int i = 0; i < ip_addressesList.size(); ++i)
    {
      if(ip_addressesList.at(i) != QHostAddress::LocalHost &&
         ip_addressesList.at(i).toIPv4Address())
        {
          ip_address = ip_addressesList.at(i).toString();
          break;
        }
    }

  // If we did not find one, use IPv4 localhost
  if(ip_address.isEmpty())
    ip_address = QHostAddress(QHostAddress::LocalHost).toString();

  int port_number    = mpa_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(mpa_massDataServer != nullptr)
    {
      delete mpa_massDataServer;
      mpa_massDataServer = nullptr;
    }
}


bool
ProgramWindow::startClient(const QString &ip_address,
                           int port_number,
                           int connection_frequency)
{
  qDebug();

  if(mpa_massDataClient != nullptr)
    delete mpa_massDataClient;

  mpa_massDataClient = new libXpertMass::MassDataClient(
    ip_address, port_number, connection_frequency, this);

  if(mpa_massDataClient != nullptr)
    {
      // Now connect to the error feedback signal so that the server settings
      // dialog display the errors.

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

      connect(mpa_massDataClient,
              &libXpertMass::MassDataClient::newDataSignal,
              [&](const QByteArray &byte_array) {
                qDebug() << "Received new data of size:" << byte_array.size();

                dispatchReceivedData(byte_array);
              });

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

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

      return true;
    }

  return false;
}


void
ProgramWindow::stopClient()
{
  if(mpa_massDataClient != nullptr)
    {
      delete mpa_massDataClient;
      mpa_massDataClient = nullptr;
    }
}


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.

  libXpertMass::MassDataType mass_data_type =
    libXpertMass::MassDataCborBaseHandler::readMassDataType(byte_array);

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

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

      // FIXME
      //<sequence editor window>->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(mpa_massDataServer != nullptr)
    mpa_massDataServer->serveData(byte_array);
  else
    QMessageBox::warning(
      this, "Mass data server", "Please, start the mass data server");
}


} // namespace massxpert

} // namespace MsXpS
