/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 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
 */

/////////////////////// Std lib includes
#include <set>
#include <cassert>


/////////////////////// Qt includes
#include <QDebug>
#include <QFileDialog>
#include <QSettings>
#include <QAbstractItemView>
#include <QHeaderView>
#include <QMessageBox>
#include <QTimer>

/////////////////////// IsoSpec
#include <IsoSpec++/isoSpec++.h>
#include <IsoSpec++/element_tables.h>


// extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double
// elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const int
// elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double
// elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];


/////////////////////// PAPPSO includes


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/Isotope.hpp"
#include "MsXpS/libXpertMassCore/IsotopicData.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataBaseHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataLibraryHandler.hpp"
#include "MsXpS/libXpertMassCore/MassDataCborBaseHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataManualConfigHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicClusterGenerator.hpp"
#include "MsXpS/libXpertMassCore/MassDataCborBaseHandler.hpp"

#include "MsXpS/libXpertMassGui/IsotopicDataTableViewModel.hpp"
#include "MsXpS/libXpertMassGui/IsotopicClusterGeneratorDlg.hpp"
#include "MsXpS/libXpertMassGui/ColorSelector.hpp"
#include "MsXpS/libXpertMassGui/IsotopicClusterGeneratorDlg.hpp"

#include "ui_IsotopicClusterGeneratorDlg.h"

namespace MsXpS
{
namespace libXpertMassGui
{

/*!
\class MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg
\inmodule libXpertMassGui
\ingroup XpertMassGuiMassCalculations
\inheaderfile IsotopicClusterGeneratorDlg.hpp

\brief The IsotopicClusterGeneratorDlg class provides a graphical user interface
for the generation of an isotopic cluster.

The isotopic cluster is generated starting from a Formula.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::mp_ui

\brief The graphical user interface definition.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_applicationName

\brief The name of the application.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_windowDescription

\brief The text to be used for the window title.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_formula

\brief The formula that is used as the starting point for the calculations.

The formula holds the elemental composition of the analyte for which the
calculation should be performed. It must account for the chemical agent
responsible for its ionization.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::mp_programWindow

\brief The main program window.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_normalizeIntensity

\brief The intensity value that the most intense peak in the cluster should
have.

All the other peaks intensities are normalized against this value.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_maxSummedProbability

\brief The sum of all the intensities of the cluster peaks should not exceed
this value.

By default, the value is 0.95.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_charge

\brief The charge of the analyte for which the calculations are performed.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_sortType

\brief The type of sorting to use to sort the obtained isotopic cluster's peak
centroids.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_sortOrder

\brief The order for the sorting (if any) of the obtained isotopic cluster's
peak centroids.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::msp_isotopicDataLibrary

\brief The isotopic data from the IsoSpec library tables.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::msp_isotopicDataUserConfig

\brief The isotopic data from the IsoSpec-like library tables as modified by
the user.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::msp_isotopicDataUserManualConfig

\brief The isotopic data from the user's manual configuration.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::mpa_libraryStaticTableViewModel

\brief The table view model that manages the library static IsoSpec standard
data.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::mpa_userStaticTableViewModel

\brief The table view model that manages the library user-modified IsoSpec
standard data.
*/

/*!
\variable MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_colorByteArray

\brief The color that should be used for displaying the mass spectrum when the
isotopic cluster is later shaped into a mass spectrum.
*/

/*!
\variable
MsXpS::libXpertMassGui::IsotopicClusterGeneratorDlg::m_clusterGenerator

\brief The isotopic cluster generator that will be used for the computations.
*/


/*!
\brief Constructs a IsotopicClusterGeneratorDlg instance.

\list
\li \a program_window_p: the program's main window.
\li \a applicationName: the name of the application, typically massXpert2 or
mineXpert2, for example.
\li \a description: the string describing what this dialog window is for (used
for the window title).
\endlist
*/
IsotopicClusterGeneratorDlg::IsotopicClusterGeneratorDlg(
  QWidget *program_window_p,
  const QString &applicationName,
  const QString &description)
  : QDialog(program_window_p),
    mp_ui(new Ui::IsotopicClusterGeneratorDlg),
    m_applicationName{applicationName},
    m_windowDescription(description),
    mp_programWindow(program_window_p)
{
  if(!program_window_p)
    qFatal("Programming error.");

  mp_ui->setupUi(this);

  // We want to destroy the dialog when it is closed.
  setAttribute(Qt::WA_DeleteOnClose);

  // Right on creation, we need to allocate the isotopic data objects. We will
  // use the proper isotopic data handler depending on the context (library,
  // user config or manual config).

  msp_isotopicDataLibrary = std::make_shared<libXpertMassCore::IsotopicData>();
  msp_isotopicDataUserConfig =
    std::make_shared<libXpertMassCore::IsotopicData>();
  msp_isotopicDataUserManualConfig =
    std::make_shared<libXpertMassCore::IsotopicData>();

  setupDialog();

  // Update the window title because the window title element in mp_ui->might be
  // either erroneous or empty.
  setWindowTitle(QString("%1 - %2").arg(applicationName).arg(description));
}

/*!
\brief Upon closing of the dialog window (unused \a event), writes the settings
to the application configuration file.
*/
void
IsotopicClusterGeneratorDlg::closeEvent([[maybe_unused]] QCloseEvent *event)
{
  writeSettings(
    MsXpS::libXpertMassCore::Utils::configSettingsFilePath);
}

/*!
\brief Destructs this IsotopicClusterGeneratorDlg instance.
*/
IsotopicClusterGeneratorDlg::~IsotopicClusterGeneratorDlg()
{
  writeSettings(
    MsXpS::libXpertMassCore::Utils::configSettingsFilePath);
  delete mp_ui;
}

/*!
\brief Saves the settings of this dialog window to the application
configuration file (\a config_settings_file_path).

The saved configuration is read from the file to set back the dialog window in
the same status.
*/
void
IsotopicClusterGeneratorDlg::writeSettings(
  const QString &config_settings_file_path)
{
  QSettings settings(config_settings_file_path, QSettings::IniFormat);

  settings.beginGroup("IsotopicClusterGeneratorDlg");

  settings.setValue("geometry", saveGeometry());
  settings.setValue("splitter", mp_ui->splitter->saveState());

  // Write all the formulas that sit in the combo box
  settings.beginWriteArray("libraryformulas");

  int count = mp_ui->libraryFormulaComboBox->count();

  for(int iter = 0; iter < count; ++iter)
    {
      settings.setArrayIndex(iter);

      QString index;
      index.setNum(iter);

      settings.setValue(index, mp_ui->libraryFormulaComboBox->itemText(iter));
    }

  settings.endArray();
  // Done writing all the formulas.

  // Write all the formulas that sit in the combo box
  settings.beginWriteArray("userconfigformulas");

  count = mp_ui->userConfigFormulaComboBox->count();

  for(int iter = 0; iter < count; ++iter)
    {
      settings.setArrayIndex(iter);

      QString index;
      index.setNum(iter);

      settings.setValue(index,
                        mp_ui->userConfigFormulaComboBox->itemText(iter));
    }

  settings.endArray();
  // Done writing all the formulas.

  settings.endGroup();
}

/*!
\brief Reads the settings of this dialog window from the application
configuration file (\a config_settings_file_path).

The configuration is read from the file to set back the dialog window in
the same status.
*/
void
IsotopicClusterGeneratorDlg::readSettings(
  const QString &config_settings_file_path)
{
  QSettings settings(config_settings_file_path, QSettings::IniFormat);

  settings.beginGroup("IsotopicClusterGeneratorDlg");

  restoreGeometry(settings.value("geometry").toByteArray());
  mp_ui->splitter->restoreState(settings.value("splitter").toByteArray());


  int count = settings.beginReadArray("libraryformulas");

  for(int iter = 0; iter < count; ++iter)
    {
      settings.setArrayIndex(iter);

      QString index;
      index.setNum(iter);

      QString value = settings.value(index).toString();

      mp_ui->libraryFormulaComboBox->insertItem(iter, value);
    }

  settings.endArray();

  count = settings.beginReadArray("userconfigformulas");

  for(int iter = 0; iter < count; ++iter)
    {
      settings.setArrayIndex(iter);

      QString index;
      index.setNum(iter);

      QString value = settings.value(index).toString();

      mp_ui->userConfigFormulaComboBox->insertItem(iter, value);
    }

  settings.endArray();

  settings.endGroup();
}

/*!
\brief Sets up the dialog window.

Returns true upon success, or false if the IsoSpec-based isotopic data could not
be loaded.
*/
bool
IsotopicClusterGeneratorDlg::setupDialog()
{
  // We allow analytes to be neutral!
  mp_ui->ionChargeSpinBox->setRange(0, 10000);
  mp_ui->ionChargeSpinBox->setValue(1);

  readSettings(
    MsXpS::libXpertMassCore::Utils::configSettingsFilePath);

  ///////////////// Connections for the different contexts /////////////////

  /////////////////// Library isotopic data
  /////////////////// Library isotopic data

  // There is no load function because that is automatically performed when
  // setting up the dialog.

  connect(mp_ui->saveLibraryPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::saveLibrary);

  connect(mp_ui->runLibraryPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::runLibrary);

  connect(mp_ui->addLibraryFormulaPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::addLibraryFormula);

  connect(mp_ui->removeLibraryFormulaPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::removeLibraryFormula);

  connect(mp_ui->addUserConfigFormulaPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::addUserConfigFormula);

  connect(mp_ui->removeUserConfigFormulaPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::removeUserConfigFormula);


  /////////////////// User config isotopic data
  /////////////////// User config isotopic data

  connect(mp_ui->loadUserConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::loadUserConfig);

  connect(mp_ui->saveUserConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::saveUserConfig);

  connect(mp_ui->runUserConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::runUserConfig);


  /////////////////// User manual config isotopic data
  /////////////////// User manual config isotopic data

  connect(mp_ui->loadUserManualConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::loadUserManualConfig);


  connect(mp_ui->saveUserManualConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::saveUserManualConfig);

  connect(mp_ui->runUserManualConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::runUserManualConfig);


  //////////////////////  Peak shaping features /////////////////////

  connect(mp_ui->toIsotopicClusterShaperPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::toIsotopicClusterShaper);

  /////////////////// Manual config isotopic data helpers ////////////////////

  connect(mp_ui->addElementPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::addElementGroupBox);

  // The color button
  connect(mp_ui->colorSelectorPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::traceColorPushButtonClicked);

  // We systematically load the library isotopic data using the dedicated
  // handler.

  libXpertMassCore::IsotopicDataLibraryHandler isotopic_data_handler(
    msp_isotopicDataLibrary);

  qsizetype non_isotope_skipped_items = 0;

  qsizetype count = isotopic_data_handler.loadData(non_isotope_skipped_items);

  if(!count)
    {
      message("Failed to load IsoSpec entities from the C++ headers", 5000);
      qDebug() << "Failed to load IsoSpec entities from the C++ headers.";

      return false;
    }

  // qDebug() << "Loaded isotopic data with" << msp_isotopicDataLibrary->size()
  //<< "isotopes.";

  // At dialog creation time, we only load the IsoSpec standard static
  // element/isotope configuration from the library headers. While loading
  // these data we populate the model and thus the table view.
  setupIsoSpecStandardStaticTableView();

  return true;
}

/*!
\brief Sets up the IsoSpec tables model and table view.
*/
void
IsotopicClusterGeneratorDlg::setupIsoSpecStandardStaticTableView()
{
  // qDebug();

  // Now we can link the data to the table view model.

  mpa_libraryStaticTableViewModel =
    new IsotopicDataTableViewModel(this, msp_isotopicDataLibrary);

  mp_ui->libraryStaticIsotopicDataTableView->setModel(
    mpa_libraryStaticTableViewModel);

  mp_ui->libraryStaticIsotopicDataTableView->setIsotopicData(
    msp_isotopicDataLibrary);

  mp_ui->libraryStaticIsotopicDataTableView->setParent(this);

  mpa_libraryStaticTableViewModel->setTableView(
    mp_ui->libraryStaticIsotopicDataTableView);
}

/*!
\brief Sets up the user-configured IsoSpec-like tables model and table view.
*/
void
IsotopicClusterGeneratorDlg::setupIsoSpecStandardUserTableView()
{
  // qDebug();

  // Now we can link the data to the table view model.

  mpa_userStaticTableViewModel =
    new IsotopicDataTableViewModel(this, msp_isotopicDataUserConfig);

  mp_ui->userStaticIsotopicDataTableView->setModel(
    mpa_userStaticTableViewModel);

  mp_ui->userStaticIsotopicDataTableView->setIsotopicData(
    msp_isotopicDataUserConfig);

  mp_ui->userStaticIsotopicDataTableView->setParent(this);

  mpa_userStaticTableViewModel->setTableView(
    mp_ui->userStaticIsotopicDataTableView);
}

/*!
\brief Loads the user-configured IsoSpec-like data.

Returns true if successful, false otherwise.
*/
bool
IsotopicClusterGeneratorDlg::loadUserConfig()
{

  QString file_name = QFileDialog::getOpenFileName(
    this, tr("Load User IsoSpec table"), QDir::home().absolutePath());

  if(file_name.isEmpty())
    {
      message("The file name to read from is empty!", 5000);
      return false;
    }

  QFile file(file_name);
  if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
      message("Failed to open file for reading.", 5000);
      return false;
    }

  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler(
    msp_isotopicDataUserConfig);

  if(!isotopic_data_handler.loadData(file_name))
    {
      message("Failed to load the isotopic data from the file.", 5000);
      file.close();
      return false;
    }

  // Also setup the isospec standard *user* table view, even if that the
  // beginning it is empty. The user might load user tables from file.
  setupIsoSpecStandardUserTableView();

  file.close();

  return true;
}

/*!
\brief Returns true if the IsoSpec isotopic data could be written to file, false
otherwise.
*/
bool
IsotopicClusterGeneratorDlg::saveLibrary()
{
  QFileDialog fileDlg(this);
  fileDlg.setFileMode(QFileDialog::AnyFile);
  fileDlg.setAcceptMode(QFileDialog::AcceptSave);

  QString file_name = fileDlg.getSaveFileName(
    this, tr("Save table"), QDir::home().absolutePath());

  if(file_name.isEmpty())
    {
      message("Please provide a file name.", 5000);
      return false;
    }

  libXpertMassCore::IsotopicDataLibraryHandler isotopic_data_handler(
    msp_isotopicDataLibrary);
  return isotopic_data_handler.writeData(file_name);
}

/*!
\brief Returns true if the IsoSpec-like user-modified isotopic data could be
written to file, false otherwise.
*/
bool
IsotopicClusterGeneratorDlg::saveUserConfig()
{
  QFileDialog fileDlg(this);
  fileDlg.setFileMode(QFileDialog::AnyFile);
  fileDlg.setAcceptMode(QFileDialog::AcceptSave);

  QString file_name = fileDlg.getSaveFileName(
    this, tr("Save table"), QDir::home().absolutePath());

  if(file_name.isEmpty())
    {
      message("Please provide a file name.", 5000);
      return false;
    }

  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler(
    msp_isotopicDataUserConfig);
  return isotopic_data_handler.writeData(file_name);
}

/*!
\brief Reacts to a click onto the push button.

Allows selecting a color for the mass spectrum trace to be created later on the
basis of the isotopic cluster calculated here.
*/
void
IsotopicClusterGeneratorDlg::traceColorPushButtonClicked()
{

  QPushButton *colored_push_button = mp_ui->colorSelectorPushButton;

  // Allow (true) the user to select a color that has been chosen already.
  QColor color = ColorSelector::chooseColor(true);

  if(color.isValid())
    {
      QPalette palette = colored_push_button->palette();
      palette.setColor(QPalette::Button, color);
      colored_push_button->setAutoFillBackground(true);
      colored_push_button->setPalette(palette);
      colored_push_button->update();

      // Now prepare the color in the form of a QByteArray

      QDataStream stream(&m_colorByteArray, QIODevice::WriteOnly);

      stream << color;
    }
}

/*!
\brief Copies the results of the computation to an \l IsotopicClusterShaperDlg
instance.
*/
void
IsotopicClusterGeneratorDlg::toIsotopicClusterShaper()
{
  // The calculation result is set into a vector that might contain more than
  // one pair (charge,cluster). In the present case, the dialog window allows
  // for only one elemental composition to perform the calculation, so we just
  // need to take the first (and in fact there must be only one) charge,cluster
  // pair from the vector. We get the trace and we send that to the peak shaper
  // dialog. We also want to set the color that might have been set by the user
  // for plotting the trace.

  const std::vector<libXpertMassCore::IsotopicClusterChargePair>
    &isotopic_cluster_charge_pairs =
      m_clusterGenerator.getIsotopicClusterChargePairs();

  if(!isotopic_cluster_charge_pairs.size())
    return;

  // In the dialog window, only one formula is handled at a time.
  Q_ASSERT(isotopic_cluster_charge_pairs.size() == 1);

  // Get the isotopic cluster as a Trace out of the pair.
  pappso::TraceCstSPtr isotopic_cluster_sp =
    isotopic_cluster_charge_pairs.front().first;


  // The user might want to reuse the same isotopic cluster shaper dialog
  // window, in which case the reuseIsotopicClusterShaperDlgCheckBox
  // is checked.

  IsotopicClusterShaperDlg *isotopic_cluster_shaper_dlg_p = nullptr;

  bool reuse_isotopic_cluster_shaper_dlg =
    mp_ui->reuseIsotopicClusterShaperDlgCheckBox->isChecked();

  // Make sure we pass the ProgramWindow pointer as the parent object of the
  // PeakShaperDlg, otherwise crash when using ConsoleWnd.

  if(reuse_isotopic_cluster_shaper_dlg &&
     mp_isotopicClusterShaperDlg != nullptr)
    {
      isotopic_cluster_shaper_dlg_p = mp_isotopicClusterShaperDlg;
      isotopic_cluster_shaper_dlg_p->setIsotopiCluster(isotopic_cluster_sp);
    }
  else
    {
      mp_isotopicClusterShaperDlg =
        new IsotopicClusterShaperDlg(mp_programWindow,
                                     m_applicationName,
                                     "Isotopic cluster shaper",
                                     isotopic_cluster_sp,
                                     m_normalizeIntensity);

      isotopic_cluster_shaper_dlg_p = mp_isotopicClusterShaperDlg;

      // In that shaper dialog, when the computation has finished, the user may
      // ask to display the spectrum. So the dialog window emits the signal
      // below. We want to relay that signal to the other listeners, typically
      // the program window, so that they can take action. If the program window
      // is of massXpert, they'll have to craft a CBOR-encoded packet and serve
      // it so that any listening program can display it (typically mineXpert).
      // If that program window is of mineXpert, then simply display the mass
      // spectrum in the mass spectra window...

      // Note also that this connection must be made once per dialog! Which
      // is why it is made only right after allocation of a new dialog window.
      connect(isotopic_cluster_shaper_dlg_p,
              &IsotopicClusterShaperDlg::displayMassSpectrumSignal,
              [this](const QString &title,
                     const QByteArray &color_byte_array,
                     pappso::TraceCstSPtr trace) {
                emit displayMassSpectrumSignal(title, color_byte_array, trace);
              });
    }

  isotopic_cluster_shaper_dlg_p->setColorByteArray(m_colorByteArray);
  isotopic_cluster_shaper_dlg_p->setMassSpectrumTitle(
    mp_ui->massSpectrumTitleLineEdit->text());

  isotopic_cluster_shaper_dlg_p->showTabWidgetInputDataPage();
  isotopic_cluster_shaper_dlg_p->show();
  isotopic_cluster_shaper_dlg_p->raise();
  isotopic_cluster_shaper_dlg_p->activateWindow();
}

/*!
\brief Validates the user manual isotopic data configuration using the \a
config_handler configuration handler.

The manual configuration is performed by entering by hand the chemical
elements (as a symbol) and their isotope (mass,prob) pairs. The element is
defined using the symbol. The count of the element in the formula is set in
a spin box.

Validating the manual configuration means iterating in all the widgets that
have been created by the user, extracting from them all the isotopic data.

Returns the count of elements in the configuration.
*/
std::size_t
IsotopicClusterGeneratorDlg::validateManualConfig(
  libXpertMassCore::IsotopicDataManualConfigHandler &config_handler)
{

  // Make clean room.
  msp_isotopicDataUserManualConfig->clear();

  // Each element (symbol, count, isotopes) is packed in a group box.  First
  // get the list of all the QGroupBox *elementGroupBox; that are packed in the
  // mp_ui->scrollAreaWidgetContents

  std::size_t element_count;

  QList<QGroupBox *> elementGroupBoxList =
    mp_ui->scrollAreaWidgetContents->findChildren<QGroupBox *>(
      "elementGroupBox");

  element_count = elementGroupBoxList.size();

  // qDebug() << "The number of elements manually configured is:" <<
  // element_count;

  if(!element_count)
    {
      message("There is currently not a single element defined.", 5000);
      return 0;
    }

  // This loop iterates through the element group boxes, each containing all the
  // other widgets that are required to define:
  // - the element symbol
  // - the count of such elements in the final formula
  // - the isotopes (listing mass/prob for each isotope)

  // Vector to collect the isotopes for a given element symbol. Will be cleared
  // at each new element-symbol/isotopes addition to the isotopic data.
  QList<libXpertMassCore::IsotopeQSPtr> element_isotopes;

  // This set is to ensure that we do not have twice the same element frame
  // (that is, with the same symbol).

  std::set<QString> symbol_set;

  // This loop iterates through all the element frames that contain the element
  // symbol and count pair.
  for(std::size_t iter = 0; iter < element_count; ++iter)
    {
      // qDebug() << "Iterating in element group box index" << iter;

      // Now starting a new element group box (symbol, count, istopes).
      QGroupBox *currentGroupBox = elementGroupBoxList.at(iter);

      QLineEdit *symbolLineEdit =
        currentGroupBox->findChild<QLineEdit *>("symbolLineEdit");

      if(symbolLineEdit == nullptr)
        qFatal("Programming error.");

      QString symbol = symbolLineEdit->text();
      // qDebug() << "The current symbol is" << symbol;

      // Now check if that symbol was encountered already, which would be an
      // error.
      auto res = symbol_set.insert(symbol);
      if(!res.second)
        {
          // We did not insert the symbol because one already existed. That is
          // an error.

          message(QString("An element by symbol %1 has already been processed: "
                          "not permitted.")
                    .arg(symbol),
                  5000);

          return 0;
        }

      // Get the atom count (the index of an element in a formula)
      int count =
        currentGroupBox->findChild<QSpinBox *>("atomCountSpinBox")->value();

      if(!count)
        {
          message(QString("The element by symbol %1 has a count of 0: "
                          "not permitted.")
                    .arg(symbol),
                  5000);

          return 0;
        }

      // Now handle the isotopic mass/abundance specifications.
      //
      // The (mass,prob) isotope pairs are packed in individual isotopeFrame
      // widgets.
      // Let's iterate in these frame widgets to extract in turn all the
      // (mass,prob) pairs for the current element.
      QList<QFrame *> isotopeFrameList =
        currentGroupBox->findChildren<QFrame *>("isotopeFrame");

      // qDebug() << "The number of isotopes configured for current element is:"
      //<< isotopeFrameList.size();

      if(!isotopeFrameList.size())
        {
          message(QString("The element by symbol %1 has no isotope defined: "
                          "not permitted.")
                    .arg(symbol),
                  4000);

          return 0;
        }

      // This loop iterates through all the frames that contain isotope
      // specifications for a given element symbol-count pair.
      for(int jter = 0; jter < isotopeFrameList.size(); ++jter)
        {
          QFrame *currentFrame = isotopeFrameList.at(jter);

          QDoubleSpinBox *massSpinBox =
            currentFrame->findChild<QDoubleSpinBox *>(
              "isotopeMassDoubleSpinBox");

          if(massSpinBox == nullptr)
            qFatal("Programming error.");

          double mass = massSpinBox->value();

          QDoubleSpinBox *probSpinBox =
            currentFrame->findChild<QDoubleSpinBox *>(
              "isotopeProbDoubleSpinBox");

          if(probSpinBox == nullptr)
            qFatal("Programming error.");

          double prob = probSpinBox->value();

          if(!prob)
            {
              message(
                QString(
                  "The element by symbol %1 has a naught-probability isotope: "
                  "not permitted.")
                  .arg(symbol),
                5000);

              return 0;
            }

          // qDebug() << "Iterated in isotope:" << mass << "/" << prob;

          // At this point create a brand new Isotope with the relevant data.

          // Isotope::Isotope(int id,
          // QString element,
          // QString symbol,
          // int atomicNo,
          // double mass,
          // int massNo,
          // int extraNeutrons,
          // double probability,
          // double logProbability,
          // bool radioactive)

          // There are a number of fields that are left to value 0 but this is
          // of no worries. Store the isotope in the vector of isotopes that
          // will be added to the isotopic data later when finishing the parsing
          // of the element frame widget.

          element_isotopes.push_back(
            QSharedPointer<libXpertMassCore::Isotope>::create(
              symbol, symbol, mass, prob));
        }
      // End of iterating in the isotope frame list for a given element symbol.

      // At this point that we know that for symbol the isotopes were all
      // correct, we can record all that information to the isotopic data via
      // the handler. Note that the handler will also record the
      // element-symbol-count pair for later use.

      config_handler.newChemicalSet(symbol, count, element_isotopes);

      // At this point we can reset variables for a new loop iteration.
      element_isotopes.clear();
      symbol = "";
    }
  // End of iterating in the element group box list.

  // Sanity check: there must be in the symbol/count map member of the isotopic
  // handler as many different symbols as we had element frames during manual
  // config data validation.

  if(element_count != config_handler.getSymbolCountMap().size())
    qFatal(
      "Programming error. The number of stored symbols does not match the "
      "user's data.");

  // At this point, make sure we update the maps in the IsotopicData.
  msp_isotopicDataUserManualConfig->updateMassMaps();

  return element_count;
}

/*!
\brief Adds a frame for the definition of an isotope.

Returns a pair of spin box widgets, the first for the isotope mass and the
second for the probability (abundance).
*/
std::pair<QDoubleSpinBox *, QDoubleSpinBox *>
IsotopicClusterGeneratorDlg::addIsotopeFrame()
{
  // qDebug();

  // We need to get the QPushButton object that sent the signal.

  QPushButton *sender = static_cast<QPushButton *>(QObject::sender());
  // qDebug() "sender:" << sender;

  // The + button was created with the isotopeFrame as its parent.
  QFrame *isotopeFrame = static_cast<QFrame *>(sender->parent());

  // The parent of the isotope frame is the elementGroupBox
  QGroupBox *elementGroupBox = static_cast<QGroupBox *>(isotopeFrame->parent());

  // Now get the elementGroupBox's layout where we'll pack the new
  // isotopeFrame.
  QVBoxLayout *elementGroupVBoxLayout =
    static_cast<QVBoxLayout *>(elementGroupBox->layout());

  // Now create the new isotope frame.
  QFrame *newIsotopeFrame = new QFrame(elementGroupBox);
  newIsotopeFrame->setObjectName(QStringLiteral("isotopeFrame"));
  newIsotopeFrame->setFrameShape(QFrame::NoFrame);
  newIsotopeFrame->setFrameShadow(QFrame::Plain);
  QGridLayout *newIsotopeFrameGridLayout = new QGridLayout(newIsotopeFrame);
  newIsotopeFrameGridLayout->setObjectName(
    QStringLiteral("isotopeFrameGridLayout"));
  QDoubleSpinBox *newIsotopeMassDoubleSpinBox =
    new QDoubleSpinBox(newIsotopeFrame);
  newIsotopeMassDoubleSpinBox->setObjectName(
    QStringLiteral("isotopeMassDoubleSpinBox"));

  // if(mass != nullptr)
  // newIsotopeMassDoubleSpinBox->setValue(*mass);

  newIsotopeMassDoubleSpinBox->setDecimals(60);
  newIsotopeMassDoubleSpinBox->setMinimum(0);
  newIsotopeMassDoubleSpinBox->setMaximum(1000);
  newIsotopeMassDoubleSpinBox->setToolTip(
    "Enter the mass of the isotope (like 12.0000 for 12[C])");

  newIsotopeFrameGridLayout->addWidget(newIsotopeMassDoubleSpinBox, 0, 0, 1, 1);

  QDoubleSpinBox *newIsotopeProbDoubleSpinBox =
    new QDoubleSpinBox(newIsotopeFrame);
  newIsotopeProbDoubleSpinBox->setObjectName(
    QStringLiteral("isotopeProbDoubleSpinBox"));

  // if(prob != nullptr)
  // newIsotopeProbDoubleSpinBox->setValue(*mass);

  newIsotopeProbDoubleSpinBox->setDecimals(60);
  newIsotopeProbDoubleSpinBox->setMinimum(0);
  newIsotopeProbDoubleSpinBox->setMaximum(1);
  newIsotopeProbDoubleSpinBox->setToolTip(
    "Enter the abundance of the isotope (that is, a probability <= 1)");

  newIsotopeFrameGridLayout->addWidget(newIsotopeProbDoubleSpinBox, 0, 1, 1, 1);

  QPushButton *addIsotopePushButton = new QPushButton(newIsotopeFrame);
  addIsotopePushButton->setObjectName(QStringLiteral("addIsotopePushButton"));

  connect(addIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::addIsotopeFrame);

  QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  sizePolicy.setHorizontalStretch(0);
  sizePolicy.setVerticalStretch(0);

  sizePolicy.setHeightForWidth(
    addIsotopePushButton->sizePolicy().hasHeightForWidth());
  addIsotopePushButton->setSizePolicy(sizePolicy);
  addIsotopePushButton->setText("Add isotope");

  newIsotopeFrameGridLayout->addWidget(addIsotopePushButton, 0, 2, 1, 1);

  QPushButton *removeIsotopePushButton = new QPushButton(isotopeFrame);
  removeIsotopePushButton->setObjectName(
    QStringLiteral("removeIsotopePushButton"));

  connect(removeIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::removeIsotopeFrame);

  sizePolicy.setHeightForWidth(
    removeIsotopePushButton->sizePolicy().hasHeightForWidth());
  removeIsotopePushButton->setSizePolicy(sizePolicy);
  removeIsotopePushButton->setText("Remove isotope");

  newIsotopeFrameGridLayout->addWidget(removeIsotopePushButton, 0, 3, 1, 1);

  elementGroupVBoxLayout->addWidget(newIsotopeFrame);

  return std::pair<QDoubleSpinBox *, QDoubleSpinBox *>(
    newIsotopeMassDoubleSpinBox, newIsotopeProbDoubleSpinBox);
}

/*!
\brief Removes an isotope frame.

The isotope frame to be removed is determined from the identity of the push
button that triggered this function.
*/
void
IsotopicClusterGeneratorDlg::removeIsotopeFrame()
{
  // qDebug();

  // We need to get the QPushButton object that sent the signal.

  QPushButton *sender = static_cast<QPushButton *>(QObject::sender());
  // qDebug() "sender:" << sender;

  // The - button was created with the isotopeFrame as its parent.
  QFrame *isotopeFrame = static_cast<QFrame *>(sender->parent());

  // The isotope frame was created with the element group  box as its parent.
  QGroupBox *elementGroupBox = static_cast<QGroupBox *>(isotopeFrame->parent());

  QList<QFrame *> childrenList =
    elementGroupBox->findChildren<QFrame *>("isotopeFrame");

  // We do not want to remove *all* the isotope frames, one must always be
  // there,
  // otherwise the element group box is not more usable.
  if(childrenList.size() < 2)
    {
      message("Cannot remove last isotope widget set.");
      return;
    }

  delete isotopeFrame;
}

/*!
\brief Creates a group box that will enshrine a chemical element definition.

The group box is populated with a line edit widget for the element symbol and
with a spin box for the count of the atoms in the final elemental composition
defined here.

The isotope-related widgets are not created.

\sa addElementGroupBox()
*/
QGroupBox *
IsotopicClusterGeneratorDlg::addElementSkeletonGroupBox()
{
  QGroupBox *elementGroupBox;
  elementGroupBox = new QGroupBox(mp_ui->scrollAreaWidgetContents);
  elementGroupBox->setObjectName(QStringLiteral("elementGroupBox"));

  QVBoxLayout *elementGroupVBoxLayout;
  elementGroupVBoxLayout = new QVBoxLayout(elementGroupBox);
  elementGroupVBoxLayout->setObjectName(
    QStringLiteral("elementGroupVBoxLayout"));

  QHBoxLayout *symbolLineEditHBoxLayout = new QHBoxLayout();

  QLineEdit *symbolLineEdit;
  symbolLineEdit = new QLineEdit(elementGroupBox);
  symbolLineEdit->setObjectName(QStringLiteral("symbolLineEdit"));
  symbolLineEdit->setPlaceholderText("Symbol");
  symbolLineEdit->setToolTip("Enter the symbol of the chemical element");
  symbolLineEditHBoxLayout->addWidget(symbolLineEdit);

  QSpinBox *atomCountSpinBox;
  atomCountSpinBox = new QSpinBox(elementGroupBox);
  atomCountSpinBox->setObjectName(QStringLiteral("atomCountSpinBox"));
  atomCountSpinBox->setRange(1, 100000000);
  atomCountSpinBox->setToolTip(
    "Enter the count of the chemical element in the formula");
  symbolLineEditHBoxLayout->addWidget(atomCountSpinBox);

  QSpacerItem *symbolLineEditHBoxLayoutHorizontalSpacer;
  symbolLineEditHBoxLayoutHorizontalSpacer =
    new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
  symbolLineEditHBoxLayout->addItem(symbolLineEditHBoxLayoutHorizontalSpacer);

  QPushButton *minusElementPushButton;
  minusElementPushButton = new QPushButton(elementGroupBox);
  minusElementPushButton->setObjectName(
    QStringLiteral("minusElementPushButton"));

  connect(minusElementPushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::removeElementGroupBox);

  QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  sizePolicy.setHorizontalStretch(0);
  sizePolicy.setVerticalStretch(0);
  sizePolicy.setHeightForWidth(
    minusElementPushButton->sizePolicy().hasHeightForWidth());
  minusElementPushButton->setSizePolicy(sizePolicy);
  minusElementPushButton->setText("Remove element");

  symbolLineEditHBoxLayout->addWidget(minusElementPushButton);

  elementGroupVBoxLayout->addLayout(symbolLineEditHBoxLayout);

  // Setting SetFixedSize helps maintaining the widgets most compact without
  // having to resort to vertical spacers.
  mp_ui->scrollAreaWidgetContentsVerticalLayout->setSizeConstraint(
    QLayout::SetFixedSize);
  mp_ui->scrollAreaWidgetContentsVerticalLayout->addWidget(elementGroupBox);

  return elementGroupBox;
}

/*!
\brief Returns a newly created frame inside \a elementGroupBox that will
enshrine all the specifications about the isotopes being configured.
*/
QFrame *
IsotopicClusterGeneratorDlg::createIsotopeFrame(QGroupBox *elementGroupBox)
{
  QFrame *isotopeFrame;

  // If elementGroupBox is nullptr, the frame is not parented. It is then up
  // to
  // the caller to parent it.

  isotopeFrame = new QFrame(elementGroupBox);

  isotopeFrame->setObjectName(QStringLiteral("isotopeFrame"));
  isotopeFrame->setFrameShape(QFrame::NoFrame);
  isotopeFrame->setFrameShadow(QFrame::Plain);

  QGridLayout *isotopeFrameGridLayout;
  isotopeFrameGridLayout = new QGridLayout(isotopeFrame);
  isotopeFrameGridLayout->setObjectName(
    QStringLiteral("isotopeFrameGridLayout"));

  QDoubleSpinBox *isotopeMassDoubleSpinBox;
  isotopeMassDoubleSpinBox = new QDoubleSpinBox(isotopeFrame);
  isotopeMassDoubleSpinBox->setObjectName(
    QStringLiteral("isotopeMassDoubleSpinBox"));
  isotopeMassDoubleSpinBox->setDecimals(60);
  isotopeMassDoubleSpinBox->setMinimum(0);
  isotopeMassDoubleSpinBox->setMaximum(1000);
  isotopeMassDoubleSpinBox->setToolTip(
    "Enter the mass of the isotope (like 12.0000 for 12[C])");

  isotopeFrameGridLayout->addWidget(isotopeMassDoubleSpinBox, 0, 0, 1, 1);

  QDoubleSpinBox *isotopeProbDoubleSpinBox;
  isotopeProbDoubleSpinBox = new QDoubleSpinBox(isotopeFrame);
  isotopeProbDoubleSpinBox->setObjectName(
    QStringLiteral("isotopeProbDoubleSpinBox"));
  isotopeProbDoubleSpinBox->setDecimals(60);
  isotopeProbDoubleSpinBox->setMinimum(0);
  isotopeProbDoubleSpinBox->setMaximum(1);
  isotopeProbDoubleSpinBox->setToolTip(
    "Enter the abundance of the isotope (that is, a probability <= 1)");

  isotopeFrameGridLayout->addWidget(isotopeProbDoubleSpinBox, 0, 1, 1, 1);

  QPushButton *addIsotopePushButton;
  addIsotopePushButton = new QPushButton(isotopeFrame);
  addIsotopePushButton->setObjectName(QStringLiteral("addIsotopePushButton"));

  connect(addIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::addIsotopeFrame);

  QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  sizePolicy.setHorizontalStretch(0);
  sizePolicy.setVerticalStretch(0);
  sizePolicy.setHeightForWidth(
    addIsotopePushButton->sizePolicy().hasHeightForWidth());
  addIsotopePushButton->setSizePolicy(sizePolicy);
  addIsotopePushButton->setText("Add isotope");

  isotopeFrameGridLayout->addWidget(addIsotopePushButton, 0, 2, 1, 1);

  QPushButton *removeIsotopePushButton;
  removeIsotopePushButton = new QPushButton(isotopeFrame);
  removeIsotopePushButton->setObjectName(
    QStringLiteral("removeIsotopePushButton"));

  connect(removeIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsotopicClusterGeneratorDlg::removeIsotopeFrame);

  sizePolicy.setHeightForWidth(
    removeIsotopePushButton->sizePolicy().hasHeightForWidth());
  removeIsotopePushButton->setSizePolicy(sizePolicy);
  removeIsotopePushButton->setText("Remove isotope");

  isotopeFrameGridLayout->addWidget(removeIsotopePushButton, 0, 3, 1, 1);

  QVBoxLayout *elementGroupVBoxLayout =
    static_cast<QVBoxLayout *>(elementGroupBox->layout());
  elementGroupVBoxLayout->addWidget(isotopeFrame);

  return isotopeFrame;
}

/*!
\brief Creates a group box that will enshrine a chemical element definition.

The group box is populated with a line edit widget for the element symbol and
with a spin box for the count of the atoms in the final elemental composition
defined here.

The isotope-related frame is then created inside the newly created group box.

\sa addElementSkeletonGroupBox()
*/
QGroupBox *
IsotopicClusterGeneratorDlg::addElementGroupBox()
{
  QGroupBox *elementGroupBox = addElementSkeletonGroupBox();

  // And now add the first isotope frame

  createIsotopeFrame(elementGroupBox);

  return elementGroupBox;
}

/*!
\brief Removes an element group box.

The group box to be removed is determined from the identity of the push button
that triggered this function.
*/
void
IsotopicClusterGeneratorDlg::removeElementGroupBox()
{
  // qDebug();

  // We need to get the QPushButton object that sent the signal.

  QPushButton *sender = static_cast<QPushButton *>(QObject::sender());
  // qDebug() "sender:" << sender;

  // The - button was created with the element group box as its parent.
  QGroupBox *elementGroupBox = static_cast<QGroupBox *>(sender->parent());

  delete elementGroupBox;
}

/*!
\brief Prints \a message in the message line edit widget, that will be erased
after the \a timeout.
*/
void
IsotopicClusterGeneratorDlg::message(const QString &message, int timeout)
{
  mp_ui->messageLineEdit->setText(message);

  QTimer::singleShot(timeout, [this]() {
    mp_ui->messageLineEdit->setText("");
  });
}

/*!
\brief Returns true if all the parameters are correct and consistent, false
otherwise.
*/
bool
IsotopicClusterGeneratorDlg::checkParameters()
{
  // We need to check a number of parameters before running the computation.

  // Let's get the summed probs value configure by the user
  m_maxSummedProbability = mp_ui->probabilityDoubleSpinBox->value();

  m_charge = mp_ui->ionChargeSpinBox->value();
  if(m_charge < 0)
    qFatal("The charge cannot be negative.");

  double gaussian_apex_intensity = 0;

  if(mp_ui->intensityGroupBox->isChecked())
    {
      bool ok = false;

      gaussian_apex_intensity =
        mp_ui->gaussianIntensityValueLineEdit->text().toDouble(&ok);

      if(!ok)
        {
          message(
            "The Gaussian apex intensity value failed to convert to double. "
            "Please, fix it.",
            5000);

          return false;
        }

      if(gaussian_apex_intensity < 0)
        {
          message(
            "The Gaussian apex intensity value is negative, converted to "
            "positive. ",
            5000);

          gaussian_apex_intensity = -gaussian_apex_intensity;
        }

      if(!gaussian_apex_intensity)
        {
          message(
            "The Gaussian apex intensity value is naught. Please, fix "
            "it.",
            5000);

          return false;
        }

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

      // Now that we know that the value is reliable, we can set it to the
      // member datum.
      m_normalizeIntensity = gaussian_apex_intensity;
      // qDebug() << "Should perform normalization.";
    }
  else
    {
      m_normalizeIntensity = std::numeric_limits<int>::min();
    }

  bool should_sort = !mp_ui->noSortRadioButton->isChecked();

  if(!should_sort)
    m_sortType = pappso::Enums::SortType::none;
  else
    {
      if(mp_ui->sortByMzRadioButton->isChecked())
        m_sortType = pappso::Enums::SortType::x;
      else
        m_sortType = pappso::Enums::SortType::y;

      // At this point the sort order.
      if(mp_ui->ascendingSortRadioButton->isChecked())
        m_sortOrder = pappso::Enums::SortOrder::ascending;
      else
        m_sortOrder = pappso::Enums::SortOrder::descending;
    }

  return true;
}

/*!
\brief Runs the computation using the IsoSpec-based isotopic data.

Returns true upon success, false otherwise.
*/
bool
IsotopicClusterGeneratorDlg::runLibrary()
{
  // qDebug();

  // This is the configuration of elements/isotopes that is loaded from the
  // IsoSpec library headers, that is, the configuration is static. The isotopic
  // data were loaded at setupDialog() time.

  assert(msp_isotopicDataLibrary->size());

  if(!checkParameters())
    {
      message("The parameters failed to check.", 6000);
      return false;
    }

  // Get the formula for which the isotopic computation is to be performed.
  QString formula_text = mp_ui->libraryFormulaComboBox->currentText();

  if(formula_text.isEmpty())
    {
      message(
        "The formula cannot be empty. Make sure it has the H2O1 syntax (note "
        "the 1 index for element O",
        5000);

      return false;
    }

  // Now configure the generator.
  m_clusterGenerator.setIsotopicData(msp_isotopicDataLibrary);
  m_clusterGenerator.setIsotopicDataType(
    libXpertMassCore::IsotopicDataType::LIBRARY_CONFIG);

  // Create the pair that we'll feed to the generator.
  std::pair<QString, int> formula_charge_pair(formula_text, m_charge);
  m_clusterGenerator.setFormulaChargePair(formula_charge_pair);

  m_clusterGenerator.setMaxSummedProbability(m_maxSummedProbability);
  m_clusterGenerator.setNormalizationIntensity(m_normalizeIntensity);
  m_clusterGenerator.setSortType(m_sortType);
  m_clusterGenerator.setSortOrder(m_sortOrder);

  if(m_clusterGenerator.run())
    {
      reportResults();
      return true;
    }
  else
    message("Failed to perform the computation.", 6000);

  return false;
}

/*!
\brief Runs the computation using the IsoSpec-like user-modified isotopic data.

Returns true upon success, false otherwise.
*/
bool
IsotopicClusterGeneratorDlg::runUserConfig()
{
  // qDebug();

  // When running this function, the user must have a correct atom/isotope
  // configuration in the tableview and a formula that matches the available
  // chemical elements.

  // The correct isotopic data handler is used to perform the preparation of the
  // arrays needed by IsoSpec.

  if(!msp_isotopicDataUserConfig->size())
    {
      QMessageBox::warning(
        this,
        QString("%1 - %2").arg(m_applicationName).arg(m_windowDescription),
        "Please, load a user-defined isotopic data table.\n"
        "Alternatively, use the static data table view (other tab)\n"
        "and modify it in place, then save it to disk.\n"
        "Finally load these isotopic data from file into this table view.",
        QMessageBox::Ok);

      return false;
    }

  if(!checkParameters())
    {
      message("The parameters failed to check.", 6000);
      return false;
    }

  // Get the formula for which the isotopic computation is to be performed.
  QString formula_text = mp_ui->userConfigFormulaComboBox->currentText();

  if(formula_text.isEmpty())
    {
      message(
        "The formula cannot be empty. Make sure it has the H2O1 syntax (note "
        "the 1 index for element O",
        5000);

      return false;
    }

  // Now configure the generator.
  m_clusterGenerator.setIsotopicData(msp_isotopicDataUserConfig);
  m_clusterGenerator.setIsotopicDataType(
    libXpertMassCore::IsotopicDataType::USER_CONFIG);

  // Create the pair that we'll feed to the m_clusterGenerator.
  std::pair<QString, int> formula_charge_pair(formula_text, m_charge);
  m_clusterGenerator.setFormulaChargePair(formula_charge_pair);

  m_clusterGenerator.setMaxSummedProbability(m_maxSummedProbability);
  m_clusterGenerator.setNormalizationIntensity(m_normalizeIntensity);
  m_clusterGenerator.setSortType(m_sortType);
  m_clusterGenerator.setSortOrder(m_sortOrder);

  if(m_clusterGenerator.run())
    {
      reportResults();
      return true;
    }
  else
    message("Failed to perform the computation.", 6000);

  return false;
}

/*!
\brief Runs the computation using the user manually-configured isotopic data.

Returns true upon success, false otherwise.
*/
bool
IsotopicClusterGeneratorDlg::runUserManualConfig()
{
  // qDebug();

  // First validate the configuration. Errors are fatal in the IsoSpec
  // library. In order to validate the configuration and keep interesting data
  // in it, we first instantiate a specific handler that we'll pass to the
  // validation function.

  // Make clean room.
  msp_isotopicDataUserManualConfig->clear();

  // Here, we do not have a formula to make the computation with because the
  // user has manually configured the isotopes and for each symbol as also told
  // how many such atoms are the the compound. The determination of the
  // symbol/count pairs is done in the validateManualConfig() call below.

  libXpertMassCore::IsotopicDataManualConfigHandler isotopic_data_handler(
    msp_isotopicDataUserManualConfig);

  std::size_t element_count = validateManualConfig(isotopic_data_handler);

  if(!element_count)
    {
      // No messages because the validation function does the job.
      return false;
    }

  QString formula_text = isotopic_data_handler.craftFormula();

  assert(msp_isotopicDataUserManualConfig->size());

  if(!checkParameters())
    {
      message("The parameters failed to check.", 6000);
      return false;
    }

  // Now configure the generator.
  m_clusterGenerator.setIsotopicData(msp_isotopicDataUserManualConfig);
  m_clusterGenerator.setIsotopicDataType(
    libXpertMassCore::IsotopicDataType::MANUAL_CONFIG);

  // Create the pair that we'll feed to the m_clusterGenerator.
  std::pair<QString, int> formula_charge_pair(formula_text, m_charge);
  m_clusterGenerator.setFormulaChargePair(formula_charge_pair);

  m_clusterGenerator.setMaxSummedProbability(m_maxSummedProbability);
  m_clusterGenerator.setNormalizationIntensity(m_normalizeIntensity);
  m_clusterGenerator.setSortType(m_sortType);
  m_clusterGenerator.setSortOrder(m_sortOrder);

  if(m_clusterGenerator.run())
    {
      reportResults();
      return true;
    }
  else
    message("Failed to perform the computation.", 6000);

  return false;
}

/*!
\brief Loads the user manually-configured isotopic data configuration.

All the widgets required to actually display the user manual configuration are
created and configured automatically.

Returns true upon success, false otherwise.
*/
bool
IsotopicClusterGeneratorDlg::loadUserManualConfig()
{
  // File format:
  //
  //[Element]
  // symbol C count 100
  //[Isotopes] 2
  // mass 12.000 prob 0.9899
  // mass 13.000 prob 0.010

  QString file_name = QFileDialog::getOpenFileName(
    this, tr("Load configuration"), QDir::home().absolutePath());

  if(file_name.isEmpty())
    {
      message("The file name to read from is empty!", 5000);
      return false;
    }

  QFile file(file_name);
  if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
      message("Failed to open file for reading.", 5000);
      return false;
    }

  // Make sure we clear the room.

  msp_isotopicDataUserManualConfig->clear();

  // Instantiate the proper handler!

  libXpertMassCore::IsotopicDataManualConfigHandler isotopic_data_handler(
    msp_isotopicDataUserManualConfig);

  qsizetype isotope_count = isotopic_data_handler.loadData(file_name);

  if(!isotope_count)
    {
      qDebug() << "Failed to load any isotope from the configuration file.";
      return false;
    }

  // At this point, we have all the required data to start creating the
  // widgets!

  initializeUserManualConfigurationWidgets(isotopic_data_handler);

  return true;
}

/*!
\brief Saves the user manually-entered isotopic data configuration to file.

The user is provided with a file selection dialog window.

Returns true upon succes, false if the manual isotopic data configuration does
not validate successfully.
*/
bool
IsotopicClusterGeneratorDlg::saveUserManualConfig()
{
  // We need to iterate into the various widgets, exactly as done for the
  // run of the manual configuration.

  // Make clean room.
  msp_isotopicDataUserManualConfig->clear();

  // Here, we do not have a formula to make the computation with because the
  // user has manually configured the isotopes and for each symbol as also told
  // how many such atoms are the the compound. The determination of the
  // symbol/count pairs is done in the validateManualConfig() call below.

  libXpertMassCore::IsotopicDataManualConfigHandler isotopic_data_handler(
    msp_isotopicDataUserManualConfig);

  std::size_t element_count = validateManualConfig(isotopic_data_handler);

  if(!element_count)
    {
      // No messages because the validation function does the job.
      return false;
    }

  // At this point all the isotopic data have been parsed and are available for
  // writing. We ask the isotopic data handler to write to the file.
  // At this point let the user choose a file for that config.

  QFileDialog fileDlg(this);
  fileDlg.setFileMode(QFileDialog::AnyFile);
  fileDlg.setAcceptMode(QFileDialog::AcceptSave);

  QString file_name = fileDlg.getSaveFileName(
    this, tr("Save configuration"), QDir::home().absolutePath());

  if(file_name.isEmpty())
    {
      message("Please provide a file name.", 5000);
      return false;
    }

  isotopic_data_handler.writeData(file_name);

  message(QString("Configuration saved in %1").arg(file_name), 5000);

  return true;
}

/*!
\brief Initializes all the widgets required to host the user manually-configured
isotopic data.

This function should be called right after having loaded a user manually-defined
isotopic data configuration. \a config_handler is the isotopic data
configuration handler that had previously loaded the data.

Returns true upon success, false if the user manual configuration is empty.

\sa loadUserManualConfig()
*/
bool
IsotopicClusterGeneratorDlg::initializeUserManualConfigurationWidgets(
  const libXpertMassCore::IsotopicDataManualConfigHandler &config_handler)
{

  // This function must be called right after having loaded a manual
  // configuration file because during that file reading, all the atomistic info
  // have been set and the handler holds some more of use.

  if(!msp_isotopicDataUserManualConfig->size())
    {
      qDebug() << "There are no data to display in the widgets.";
      return false;
    }

  // Now iterate in all the data and create widgets to display them.

  using SymbolCountMap      = std::map<QString, int>;
  const SymbolCountMap &map = config_handler.getSymbolCountMap();

  // qDebug() << "The symbol/count map has this count of items:" << map.size();

  // Get all the unqiue isotope symbols that were set the isotopic data object.
  // Remember that in the manual config context, only isotopes from the user's
  // manual configuration have been set in the IsotopicData object (which is not
  // true for library/user isotopic data.
  std::vector<QString> symbols =
    msp_isotopicDataUserManualConfig->getUniqueSymbolsInOriginalOrder();

  // We can now iterate in each symbol and start getting the data.

  for(const QString &symbol : symbols)
    {
      // This call throws if the symbol is not there.
      int symbol_count = map.at(symbol);

      // qDebug() << "For symbol" << symbol << "the count is:" << symbol_count;

      // This is where we contruct a new elementGroupBox
      QGroupBox *elementGroupBox = addElementSkeletonGroupBox();

      // Now the symbol line edit widget
      QLineEdit *symbolLineEdit =
        elementGroupBox->findChild<QLineEdit *>("symbolLineEdit");
      symbolLineEdit->setText(symbol);

      // Now the symbol count spin box widget
      QSpinBox *atomCountSpinBox =
        elementGroupBox->findChild<QSpinBox *>("atomCountSpinBox");
      atomCountSpinBox->setValue(symbol_count);

      // Now get iterators to the range of isotopes by that symbol in the
      // isotopic data.
      libXpertMassCore::IsotopeListCstIteratorPair iter_pair =
        msp_isotopicDataUserManualConfig->getIsotopesBySymbol(symbol);

      // We can now iterate in the range and extract the isotopes.

      while(iter_pair.first != iter_pair.second)
        {
          libXpertMassCore::IsotopeQSPtr isotope_qsp = *iter_pair.first;

          // The frame is automatically parented to the skeleton
          // elementGroupBox that we created above.

          QFrame *isotopeFrame = createIsotopeFrame(elementGroupBox);

          QDoubleSpinBox *massSpinBox =
            isotopeFrame->findChild<QDoubleSpinBox *>(
              "isotopeMassDoubleSpinBox");

          massSpinBox->setValue(isotope_qsp->getMass());

          QDoubleSpinBox *probSpinBox =
            isotopeFrame->findChild<QDoubleSpinBox *>(
              "isotopeProbDoubleSpinBox");

          probSpinBox->setValue(isotope_qsp->getProbability());

          ++iter_pair.first;
        }
    }
  // End
  // iterating in the unique symbols in their original order.

  return true;
}

/*!
\brief Adds the currently displayed formula for the IsoSpec-based table view to
the corresponding combo box widget.
*/
void
IsotopicClusterGeneratorDlg::addLibraryFormula()
{
  QString text = mp_ui->libraryFormulaComboBox->currentText();

  if(text.isEmpty())
    return;

  mp_ui->libraryFormulaComboBox->addItem(text);
}

/*!
\brief Removes the currently displayed formula for the IsoSpec-based table view
from the corresponding combo box widget.
*/
void
IsotopicClusterGeneratorDlg::removeLibraryFormula()
{
  int currentIndex = mp_ui->libraryFormulaComboBox->currentIndex();

  if(currentIndex != -1)
    mp_ui->libraryFormulaComboBox->removeItem(currentIndex);
}

/*!
\brief Adds the currently displayed formula for the IsoSpec-like user-modified
table view to the corresponding combo box widget.
*/
void
IsotopicClusterGeneratorDlg::addUserConfigFormula()
{
  QString text = mp_ui->userConfigFormulaComboBox->currentText();

  if(text.isEmpty())
    return;

  mp_ui->userConfigFormulaComboBox->addItem(text);
}

/*!
\brief Removes the currently displayed formula for the IsoSpec-like
user-modified table view from the corresponding combo box widget.
*/
void
IsotopicClusterGeneratorDlg::removeUserConfigFormula()
{
  int currentIndex = mp_ui->userConfigFormulaComboBox->currentIndex();

  if(currentIndex != -1)
    mp_ui->userConfigFormulaComboBox->removeItem(currentIndex);
}

/*!
\brief Exports the computation results to a string that is set to the plain text
edit widget in the results tab widget tab.
*/
void
IsotopicClusterGeneratorDlg::reportResults()
{
  // Finally export the results as a string.
  // Note how we do export the relative intensity. If there was normalization,
  // that value was updated, otherwise it had been initialized identical to
  // the intensity upon creation of the PeakCentroid instances in the
  // vector.

  libXpertMassCore::IsotopicClusterChargePair isotopic_cluster_charge_pair =
    m_clusterGenerator.getIsotopicClusterChargePairs().front();

  QString results = m_clusterGenerator.clustersToString();

  // qDebug().noquote() << results;

  mp_ui->isoSpecTabWidget->setCurrentWidget(mp_ui->isoSpecTabWidgetResultsTab);
  mp_ui->isoSpecOutputDataPlainTextEdit->setPlainText(results);
}
} // namespace libXpertMassGui

} // namespace MsXpS


#if 0

Example from IsoSpec.

const int elementNumber = 2;
const int isotopeNumbers[2] = {2,3};

const int atomCounts[2] = {2,1};


const double hydrogen_masses[2] = {1.00782503207, 2.0141017778};
const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610};

const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses};

const double hydrogen_probs[2] = {0.5, 0.5};
const double oxygen_probs[3] = {0.5, 0.3, 0.2};

const double* probs[2] = {hydrogen_probs, oxygen_probs};

IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99);

#endif
