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


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


/////////////////////// Qt includes
#include <QDebug>
#include <QObject>
#include <QDialog>
#include <QAction>
#include <QSpinBox>
#include <QSettings>
#include <QMainWindow>
#include <QSvgWidget>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QFormLayout>
#include <QtCore/QVariant>
#include <QtWidgets/QScrollArea>
#include <QThread>
#include <QFileDialog>
#include <QMessageBox>
#include <QGraphicsView>
#include <QGraphicsScene>


/////////////////////// pappsomspp includes


/////////////////////// libXpertMassCore includes
#include "MsXpS/libXpertMassCore/Utils.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp"


/////////////////////// libXpertMassGui includes
#include <MsXpS/libXpertMassGui/ActionManagerTableWidget.hpp>
#include <MsXpS/libXpertMassGui/ToleranceWidget.hpp>
#include <MsXpS/libXpertMassGui/WindowGeometryManager.hpp>
#include <MsXpS/libXpertMassGui/ColorSelector.hpp>


/////////////////////// Local includes
#include "ApplicationPreferencesWnd.hpp"
#include "Application.hpp"
#include "ProgramWindow.hpp"

#include "ui_ApplicationPreferencesWnd.h"
#include "ui_ApplicationPreferencesWndGeneralPageWidget.h"

#include "ui_ApplicationPreferencesWndDiscoveriesPageWidget.h"
#include "ui_ApplicationPreferencesWndCentroidationPageWidget.h"
#include "ui_ApplicationPreferencesWndLowMassDeconvolverPageWidget.h"
#include "ui_ApplicationPreferencesWndMassPeakShaperPageWidget.h"
#include "ui_ApplicationPreferencesWndAnalyteIdentificationPageWidget.h"
#include "ui_DecimalPlacesPreferencesWidget.h"
#include "ui_ApplicationPreferencesWndPluginsPageWidget.h"
#include "ui_PeakPickingPreferencesWidget.h"
#include "ui_MaxThreadCountPreferencesWidget.h"
#include "ui_LowMassDeconvolverPreferencesWidget.h"
#include "ui_AnalyteIdentificationPreferencesWidget.h"


namespace MsXpS
{
namespace MineXpert
{

ApplicationPreferencesWnd::ApplicationPreferencesWnd(
  const QString &application_name,
  const QString &description,
  QWidget *parent_p)
  : QMainWindow(parent_p),
    mp_ui(new Ui::ApplicationPreferencesWnd),
    mp_generalPage_ui(new Ui::ApplicationPreferencesWndGeneralPageWidget),
    mp_discoveriesPage_ui(
      new Ui::ApplicationPreferencesWndDiscoveriesPageWidget),
    mp_centroidationPage_ui(
      new Ui::ApplicationPreferencesWndCentroidationPageWidget),
    mp_lowMassDeconvolverPage_ui(
      new Ui::ApplicationPreferencesWndLowMassDeconvolverPageWidget),
    mp_massPeakShaperConfigPage_ui(
      new Ui::ApplicationPreferencesWndMassPeakShaperPageWidget),
    mp_analyteIdentificationPage_ui(
      new Ui::ApplicationPreferencesWndAnalyteIdentificationPageWidget),
    // mp_pluginsPage_ui(new Ui::ApplicationPreferencesWndPluginsPageWidget),
    mp_decimalPlacesPreferencesWidget_ui(
      new Ui::DecimalPlacesPreferencesWidget),
    mp_maxThreadCountPreferencesWidget_ui(
      new Ui::MaxThreadCountPreferencesWidget),
    mp_peakPickingPreferencesWidget_ui(new Ui::PeakPickingPreferencesWidget),
    mp_lowMassDeconvolverPreferencesWidget_ui(
      new Ui::LowMassDeconvolverPreferencesWidget),
    mp_analyteIdentificationPreferencesWidget_ui(
      new Ui::AnalyteIdentificationPreferencesWidget),
    m_applicationName(application_name),
    m_windowDescription(description)
{
  if(parent_p == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  mp_ui->setupUi(this);
  // The general page widget is preformed and we'll need to set
  // the corresponding widget as the first section.
  mp_generalPage_ui->setupUi(this);
  // The discoveries page widget is preformed.
  mp_discoveriesPage_ui->setupUi(this);
  // The centroidation page widget is preformed.
  mp_centroidationPage_ui->setupUi(this);
  // The low mass deconvolver page widget is preformed.
  mp_lowMassDeconvolverPage_ui->setupUi(this);
  // The mass peak shaper config page widget is preformed.
  mp_massPeakShaperConfigPage_ui->setupUi(this);
  // The analyte identification page widget is preformed.
  mp_analyteIdentificationPage_ui->setupUi(this);
  // The plugins preferences page widget is preformed.
  // mp_pluginsPage_ui->setupUi(this);

  // The decimal places widget that we use the group box from
  // to stuff in the generalPage.
  mp_decimalPlacesPreferencesWidget_ui->setupUi(this);
  // The maximum thread count widget -> generalPage.
  mp_maxThreadCountPreferencesWidget_ui->setupUi(this);

  // The peak picking preferences
  mp_peakPickingPreferencesWidget_ui->setupUi(this);
  // The low mass deconvolver preferences
  mp_lowMassDeconvolverPreferencesWidget_ui->setupUi(this);
  // The analyte identification preferences
  mp_analyteIdentificationPreferencesWidget_ui->setupUi(this);

  mp_ui->mainTitleLabel->setText(
    QString("Preferences for %1").arg(m_applicationName));

  connect(mp_ui->sectionsListWidget,
          &QListWidget::currentRowChanged,
          mp_ui->sectionsStackedWidget,
          &QStackedWidget::setCurrentIndex);

  connect(mp_ui->applyPushButton,
          &QPushButton::clicked,
          this,
          &ApplicationPreferencesWnd::apply);
  connect(mp_ui->closePushButton,
          &QPushButton::clicked,
          this,
          &ApplicationPreferencesWnd::hide);

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

  mp_ui->sectionsListWidget->setViewMode(QListView::IconMode);
  mp_ui->sectionsListWidget->setFlow(
    QListView::TopToBottom);                     // keeps a vertical layout
  mp_ui->sectionsListWidget->setWrapping(false); // one item per row
  mp_ui->sectionsListWidget->setResizeMode(QListView::Adjust);
  mp_ui->sectionsListWidget->setMaximumWidth(85);
  mp_ui->sectionsListWidget->setIconSize(QSize(80, 80)); // adjust as needed
  mp_ui->sectionsListWidget->setGridSize(
    QSize(80, 100)); // must be large enough for icon + text
  mp_ui->sectionsListWidget->setWordWrap(true);
  mp_ui->sectionsListWidget->setTextElideMode(Qt::ElideNone);

  readSettings();

  setupWindow();
}

ApplicationPreferencesWnd::~ApplicationPreferencesWnd()
{
  // qDebug() << "Is visible:" << isVisible();
  writeSettings();
}

/*!
\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
ApplicationPreferencesWnd::writeSettings() const
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

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

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

  settings.beginGroup("ApplicationPreferencesWnd");

  // Sanity check. Works.
  // int current_stacked_widget_page_index =
  //   mp_ui->sectionsStackedWidget->currentIndex();
  // qDebug() << "Current page index:" << current_stacked_widget_page_index;

  int current_list_widget_page_index =
    mp_ui->sectionsListWidget->currentIndex().row();
  // qDebug() << "Current page index:" << current_list_widget_page_index;

  settings.setValue("currentPageIndex", current_list_widget_page_index);
  settings.setValue("isVisible", isVisible());

  settings.endGroup();

  settings.sync();

  // qDebug() << "Done writing the application preferences wnd settings to"
  //          << settings_file_path;
}

/*!
\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
ApplicationPreferencesWnd::readSettings()
{
  QString settings_file_path =
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath();

  QSettings settings(settings_file_path, QSettings::IniFormat);

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

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

  settings.beginGroup("ApplicationPreferencesWnd");

  // We'll set the stored current page index later.

  setVisible(settings.value("isVisible").toBool());

  // bool ok = false;

  // double width  = settings.value("windowWidth").toDouble(&ok);
  // double height = settings.value("windowHeight").toDouble(&ok);

  // resize(width, height);

  settings.endGroup();
}

void
ApplicationPreferencesWnd::setupWindow()
{
  setupGeneralPage();
  setupShortcutsPage();
  setupDiscoveriesPage();
  setupCentroidationPage();
  setupLowMassDeconvolverPage();
  setupMassPeakShaperPage();
  setupAnalyteIdentificationPage();

  // setupPluginsPage();

  QSettings settings(libXpertMassCore::Utils::configSettingsFilePath,
                     QSettings::IniFormat);

  settings.beginGroup("ApplicationPreferencesWnd");

  bool ok;
  int current_page_index = settings.value("currentPageIndex", 0).toInt(&ok);
  // qDebug() << "Current page index:" << current_page_index;
  if(!ok)
    qFatal() << "Programming error.";

  mp_ui->sectionsListWidget->setCurrentRow(current_page_index);

  settings.endGroup();
}

void
ApplicationPreferencesWnd::setupGeneralPage()
{
  // The page is available as a widget (see *this constructor)
  // as mp_generalPage_ui->scrollArea in which we need to pack
  // all the widgets we need in order. Each configuration bit
  // needs to be in a QGroupBox.

  // The general page has widgets that can be configured here.
  setupMaxThreadCountWidget();
  setupDecimalPlacesPreferencesWidget();

  // We want to push all the group boxes to the top.

  insertSectionListItem("General",
                        QIcon(":images/icons/svg/MineXpert3.svg"),
                        mp_generalPage_ui->scrollArea,
                        Pages::GENERAL);
}

void
ApplicationPreferencesWnd::setupMaxThreadCountWidget()
{
  // This is the main page scroll area widget contents layout.
  // We will end up packing into this layout (towards the bottom) each newly
  // created QGroupBox widget.
  QVBoxLayout *layout_p = mp_generalPage_ui->mainWidgetContentsLayout;

  // The group box widget that will contain all we need to provide the user
  // with for them to configure the max count of threads.
  QGroupBox *group_box_p =
    mp_maxThreadCountPreferencesWidget_ui->maxThreadCountPreferencesGroupBox;

  // We need to setup the icon in the corresponding frame.
  QFrame *icon_frame_p = mp_maxThreadCountPreferencesWidget_ui->iconFrame;
  icon_frame_p->setFrameShape(QFrame::StyledPanel);
  icon_frame_p->setFrameShadow(QFrame::Raised);
  QHBoxLayout *icon_frame_layout_p = new QHBoxLayout(icon_frame_p);
  icon_frame_layout_p->setContentsMargins(4, 4, 4, 4);
  auto *svg =
    new QSvgWidget(":/images/svg/thread-count-slider.svg", icon_frame_p);
  svg->setFixedSize(48, 48); // choose size explicitly
  icon_frame_layout_p->addWidget(svg);

  std::size_t ideal_thread_count = QThread::idealThreadCount();
  QSpinBox *max_thread_count_spin_box_p =
    mp_maxThreadCountPreferencesWidget_ui->maxThreadCountSpinBox;
  max_thread_count_spin_box_p->setRange(1, ideal_thread_count);
  max_thread_count_spin_box_p->setValue(ideal_thread_count);

  connect(max_thread_count_spin_box_p,
          &QSpinBox::valueChanged,
          this,
          [&](const int &value) {
            static_cast<Application *>(qApp)
              ->getProgramWindow()
              ->setMaxThreadUseCount(value);
          });

  layout_p->addWidget(group_box_p);
}

void
ApplicationPreferencesWnd::setupDecimalPlacesPreferencesWidget()
{
  // This is the main page scroll area widget contents layout.
  // We will end up packing into this layout (towards the bottom) each newly
  // created QGroupBox widget.
  QVBoxLayout *layout_p = mp_generalPage_ui->mainWidgetContentsLayout;

  // The group box widget that will contain all we need to provide the user
  // with for them to configure the decimal places preferences.
  QGroupBox *group_box_p =
    mp_decimalPlacesPreferencesWidget_ui->decimalPlacesPreferencesGroupBox;

  layout_p->addWidget(group_box_p);

  // Now read the settings and update the values in the widgets.
  readDecimalPlacesPreferences();
}

void
ApplicationPreferencesWnd::setupShortcutsPage()
{
  // The management of the actions, via the libXpertMassGui::ActionManager and
  // via the libXpertMassGui::ActionManagerWidget is performed in Application.

  libXpertMassGui::ActionManagerTableWidget *action_manager_table_widget_p =
    new libXpertMassGui::ActionManagerTableWidget(this);

  QIcon svg_icon(":/images/svg/keyboard-keys.svg");

  insertSectionListItem(
    "Shortcuts", svg_icon, action_manager_table_widget_p, Pages::SHORTCUTS);

  // qDebug() << "Done, inserting the section list item.";

  // We now have to fill in the table view.
  // ProgramWindow *program_window_p =
  //   static_cast<Application *>(qApp)->getProgramWindow();
  // if(program_window_p == nullptr)
  //   qFatal() << "The program window is nullptr !!!";

  libXpertMassGui::ActionManager *action_manager_p =
    static_cast<Application *>(qApp)->getProgramWindow()->getActionManager();
  if(action_manager_p == nullptr)
    qFatal() << "The action manager is nullptr !!!";

  action_manager_table_widget_p->setActionManager(action_manager_p);

  action_manager_table_widget_p->fillInTableWithData();

  // int row_count = action_manager_table_widget_p->fillInTableWithData();
  // qDebug() << "Added" << row_count
  //          << "rows in the action manager table widget.";

  // At this point we have filled the table.
}

void
ApplicationPreferencesWnd::setupDiscoveriesPage()
{
  // The widget we want to add as a new preferences section is
  QIcon svg_icon(":/images/svg/discoveries-preferences.svg");
  insertSectionListItem("Discoveries",
                        svg_icon,
                        mp_discoveriesPage_ui->scrollArea,
                        Pages ::DISCOVERIES);

  QSharedPointer<DiscoveriesPreferences> discoveries_preferences_qsp =
    QSharedPointer<DiscoveriesPreferences>::create();

  // qDebug() << "The discoveries_preferences_qsp:"
  //          << discoveries_preferences_qsp.get();

  QSharedPointer<QList<QPlainTextEdit *>> plain_text_edit_list_qsp =
    QSharedPointer<QList<QPlainTextEdit *>>::create();

  mp_discoveriesPage_ui->scrollArea->setProperty(
    "discoveries_preferences_qsp",
    QVariant::fromValue(discoveries_preferences_qsp));
  mp_discoveriesPage_ui->scrollArea->setProperty(
    "plain_text_edit_list_qsp", QVariant::fromValue(plain_text_edit_list_qsp));

  // Create the format tabs, using the data in the
  // m_dataFormatStringSpecifHash member of discoveries_preferences:

  // qDebug() << "There are " <<
  // mp_discoveriesPage_ui->formatsTabWidget->count()
  //          << "tabs in the widget.";


  for(int iter = 0; iter < FormatType::LAST; ++iter)
    {
      // qDebug() << "Preparing widgets for the format type" << iter;

      DataFormatStringSpecif *specif =
        discoveries_preferences_qsp->m_dataFormatStringSpecifHash.value(iter);

      if(specif == nullptr)
        {
          qFatal() << "Programming error.";
          return;
        }

      // First allocate a new plain text edit widget that we'll set to the
      // tab when we create it below. We want to store a pointer to the text
      // edit widget for later use.
      QPlainTextEdit *newEdit =
        new QPlainTextEdit(mp_discoveriesPage_ui->formatsTabWidget);
      plain_text_edit_list_qsp->append(newEdit);

      // qDebug() << "Now inserting new tab for iter" << iter
      //          << "with label:" << specif->m_label;

      mp_discoveriesPage_ui->formatsTabWidget->insertTab(
        iter, newEdit, specif->m_label);

      // qDebug() << "There are "
      //          << mp_discoveriesPage_ui->formatsTabWidget->count()
      //          << "tabs in the widget.";
    }

  // Read the settings, if any.
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);

  settings.beginGroup("DiscoveriesPreferences");

  QStringList historyList = settings.value("history").toStringList();
  mp_discoveriesPage_ui->historyComboBox->insertItems(0, historyList);

  mp_discoveriesPage_ui->destSplitter->restoreState(
    settings.value("destSplitter").toByteArray());
  mp_discoveriesPage_ui->formatSplitter->restoreState(
    settings.value("formatSplitter").toByteArray());

  // Now the various format strings for the tic, ms, dt tab widgets, if any.

  for(int iter = 0; iter < FormatType::LAST; ++iter)
    {
      const DataFormatStringSpecif *specif_p =
        discoveries_preferences_qsp->m_dataFormatStringSpecifHash.value(iter);

      if(specif_p == nullptr)
        qFatal() << "Programming error.";

      QString tabString =
        mp_discoveriesPage_ui->formatsTabWidget->tabText(iter);
      QString settingsKey = QString("%1_formatString").arg(tabString);

      QString settingsValue = settings.value(settingsKey).toString();

      QPlainTextEdit *textEdit = static_cast<QPlainTextEdit *>(
        mp_discoveriesPage_ui->formatsTabWidget->widget(iter));

      textEdit->setPlainText(settingsValue);

      // qDebug() << "Reading setting key/value for iter"
      //<< iter << settingsKey << settingsValue;
    }

  settings.endGroup();


  // At this point we need to display the help text that shows the various
  // possibilities for the format string.

  QString helpText;

  helpText +=
    "The formatting of the text describing the exploration session "
    "discovery is described below. It uses specific format patterns, like '%f' "
    "that will be replaced with corresponding value, like the filename, for "
    "example.\n\n";

  helpText +=
    "The numeric values that can be used to describe a discovery might "
    "be of differnt types: "
    "  - floats with decimals (m/z value)"
    "  - integers without decimals (charge z)\n\n";

  helpText +=
    "When float numeric values require a specific number of decimals, "
    "that can be specified using the decimals specifier of the form '.3', for "
    "example "
    "if the float number needs to be printed out with three decimas.\n\n";

  helpText +=
    "Float values for which no decimal specifier is set are printed "
    "out with 2 decimals by default.\n\n";

  helpText +=
    "If a float value is requied without decimal, use the '.0' decimal "
    "specifier.\n\n";

  helpText += "These are the available format strings:\n\n";

  helpText += "%f: mass spectrometry data file name\n";
  helpText += "%s: sample name\n";

  helpText += "%X (or %X.3): value on the X axis (no unit)\n";
  helpText += "%Y (or %Y.1): value on the Y axis at cursor point(no unit)\n";
  helpText += "%C (or %C.1): value on the Y axis on the curve (no unit)\n";

  helpText +=
    "%x (or %x.6): delta value on the X axis (when appropriate, no unit)\n";
  helpText +=
    "%y (or %y.1): delta value on the Y axis (when appropriate, no unit)\n";
  helpText +=
    "%c (or %c.1): delta value on the Y axis on the curve (no "
    "unit)\n";

  helpText +=
    "%I (or %I.1): TIC intensity after TIC integration over a graph range\n";

  helpText += "%b (or %b.3): X axis range begin value (where applicable)\n";
  helpText += "%e (or %e.3): X axis range end value (where applicable)\n";

  helpText += "\nMass spec plot widgets:\n";
  helpText += "\t%z : charge\n";
  helpText +=
    "\t%M (or %M.5 for small mass ions; %M.2 for medium-mass ions): Mr\n";

  mp_discoveriesPage_ui->formatDescriptionPlainTextEdit->appendPlainText(
    helpText);

  // Make the connections

  connect(mp_discoveriesPage_ui->analysisFilePushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            QString fileName =
              QFileDialog::getSaveFileName(this,
                                           tr("Select the data analysis file"),
                                           QDir::home().absolutePath(),
                                           tr("Any file (*)"),
                                           0,
                                           QFileDialog::DontConfirmOverwrite);

            mp_discoveriesPage_ui->analysisFileLineEdit->setText(fileName);
            mp_discoveriesPage_ui->analysisFileLineEdit->setToolTip(fileName);
          });

  connect(mp_discoveriesPage_ui->addToHistoryPushButton,
          &QPushButton::clicked,
          this,
          [this, plain_text_edit_list_qsp, discoveries_preferences_qsp]() {
            int idx = mp_discoveriesPage_ui->formatsTabWidget->currentIndex();
            QString text = plain_text_edit_list_qsp->at(idx)->toPlainText();

            DataFormatStringSpecif *specif =
              discoveries_preferences_qsp->m_dataFormatStringSpecifHash.value(
                idx);
            if(specif == nullptr)
              {
                qFatal() << "Programming error.";
                return;
              }

            QString historyText = QString("%1:%2").arg(specif->m_label, text);

            mp_discoveriesPage_ui->historyComboBox->addItem(historyText,
                                                            Qt::DisplayRole);
          });


  connect(mp_discoveriesPage_ui->removeFromHistoryPushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            int idx = mp_discoveriesPage_ui->historyComboBox->currentIndex();

            qDebug() << "The current index:" << idx;

            mp_discoveriesPage_ui->historyComboBox->removeItem(idx);
          });

  connect(
    mp_discoveriesPage_ui->historyComboBox,
    static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
    this,
    [&](int index) {
      // qDebug() << "The activated index:" << index;

      QString historyText =
        mp_discoveriesPage_ui->historyComboBox->itemText(index);

      // qDebug() << "The activated item's text:" << historyText;

      // Extract from the top widget of the discoveries page the two data we
      // need.
      QSharedPointer<DiscoveriesPreferences> discoveries_preferences_qsp =
        nullptr;
      QSharedPointer<QList<QPlainTextEdit *>> plain_text_edit_list_qsp =
        nullptr;

      QVariant variant = mp_discoveriesPage_ui->scrollArea->property(
        "discoveries_preferences_qsp");
      if(variant.canConvert<QSharedPointer<DiscoveriesPreferences>>())
        {
          discoveries_preferences_qsp =
            variant.value<QSharedPointer<DiscoveriesPreferences>>();

          if(discoveries_preferences_qsp.isNull())
            {
              qFatal() << "Programming error.";
              return;
            }
        }
      else
        {
          qFatal() << "Programming error.";
          return;
        }
      variant =
        mp_discoveriesPage_ui->scrollArea->property("plain_text_edit_list_qsp");
      if(variant.canConvert<QSharedPointer<QList<QPlainTextEdit *>>>())
        {
          plain_text_edit_list_qsp =
            variant.value<QSharedPointer<QList<QPlainTextEdit *>>>();

          if(plain_text_edit_list_qsp.isNull())
            {
              qFatal() << "Programming error.";
              return;
            }
        }
      else
        {
          qFatal() << "Programming error.";
          return;
        }

      // What is the label at the beginning of the text that states what
      // is the specialized format string (TIC chrom or Mass spec or Drift
      // spec).

      for(int iter = 0; iter < FormatType::LAST; ++iter)
        {
          // qDebug() << "The discoveries_preferences_qsp:"
          //          << discoveries_preferences_qsp.get();

          // Get the DataFormatStringSpecif instance for iter
          const DataFormatStringSpecif *specif_p =
            discoveries_preferences_qsp->m_dataFormatStringSpecifHash.value(
              iter);

          QString label = specif_p->m_label;

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


          if(historyText.startsWith(label))
            {
              mp_discoveriesPage_ui->formatsTabWidget->setCurrentIndex(iter);

              // Get a pointer to the proper tab plain text editor
              QPlainTextEdit *currentTextEdit =
                plain_text_edit_list_qsp->at(iter);

              // But before setting the text that we got from the combo
              // box, we need to remvove from it the label and the
              // separator that we used in the first place to tag that
              // format string in the history combo box !
              static QRegularExpression regExp(QString("^%1:").arg(label));
              historyText = historyText.remove(regExp);

              currentTextEdit->setPlainText(historyText);

              return;
            }
        }
    });
}

void
ApplicationPreferencesWnd::setupCentroidationPage()
{
  QVBoxLayout *layout_p = mp_centroidationPage_ui->mainWidgetContentsLayout;

  layout_p->addWidget(
    mp_peakPickingPreferencesWidget_ui->peakPickingPreferencesGroupBox);

  // The group box widget that will contain all we need to provide the user
  // with for them to configure the peak picking algorithm.
  QGroupBox *group_box_p =
    mp_peakPickingPreferencesWidget_ui->peakPickingPreferencesGroupBox;

  QString tool_tip_text;

  tool_tip_text =
    "Set the way the maximum admissible intensity value is determined.\n"
    "This is useful to avoid using outlier data points in the mass spectrum.";
  mp_peakPickingPreferencesWidget_ui->maxIntensityDefinitionGroupBox
    ->setToolTip(tool_tip_text);

  tool_tip_text = "Enter an absolute value.";
  mp_peakPickingPreferencesWidget_ui->manualMaxIntensitySpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Enter the standard deviation multiplication factor.\n"
    "Signal with an intensity greater than mean + (this value times the "
    "standard deviation) will be skipped.";
  mp_peakPickingPreferencesWidget_ui->stdDevFactorMaxIntensitySpinBox
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "Enter the proportion of the mass spectrum's data points that is used as "
    "follows:\n"
    "First create a histogram of the intensity population (bin size = max "
    "spectrum "
    "intensity / 100).\n"
    "Determine the number of data points that correspond to (percentile * mass "
    "spectrum size / 100), stored as 'percent_data_points'.\n"
    "Iterate in the bins of the histogram and sum up the number of data points "
    "in each bin until\n"
    "that sum number equals the 'percent_data_points' value above.\n"
    "The bin at which that 'percent_data_points' value was reached is stored "
    "as value 'bin'.\n"
    "Read the max intensity value as the value corresponding to histogram bin "
    "'bin', which is equivalent to reading 'the maximum intensity corresponds\n"
    "to the intensity bin value for which 95% of the mass data points have "
    "been "
    "accounted for.";

  mp_peakPickingPreferencesWidget_ui->percentileMaxIntensitySpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Number of data points to be used to craft a local mass spectrum window.\n"
    "That window is used to compute the signal-to-noise ratio (50).";
  mp_peakPickingPreferencesWidget_ui->rollingWindowSizeSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Minimum number of data points to be found inside the rolling window to "
    "perform the signal-to-noise ratio determination (10).";
  mp_peakPickingPreferencesWidget_ui->mininumRequiredElementsSpinBox
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "Number of bins to be used to represent the intensity values population.\n"
    "Must be greater than 0 (30).";
  mp_peakPickingPreferencesWidget_ui->histogramBinCountSpinBox->setToolTip(
    tool_tip_text);
  mp_peakPickingPreferencesWidget_ui->histogramBinCountSpinBox->setMinimum(1);

  tool_tip_text = "Mininum intensity for the mass signal to be considered.";
  mp_peakPickingPreferencesWidget_ui->minIntensityDoubleSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Signal-to-noise ratio below which a DataPoint will not be considered as "
    "centroid (1.0 for neat traces).";
  mp_peakPickingPreferencesWidget_ui->signalToNoiseRatioDoubleSpinBox
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "Number of peaks allowed to miss in the calculation of the centroid "
    "model.\n"
    "Modelling a peak generally requires 5 data points, the center data "
    "point,\n"
    "two data points left of it and two data points right of it.\n"
    "Strictest value: 0, sloppiest value: 2.";
  mp_peakPickingPreferencesWidget_ui->missingPeaksCountSpinBox->setToolTip(
    tool_tip_text);

  // Read the peak picking preferences and set the values to the widgets.
  readPeakPickingPreferences();

  layout_p->addWidget(group_box_p);

  // The widget we want to add as a new preferences section is
  QIcon svg_icon(":/images/svg/centroidation-preferences.svg");
  insertSectionListItem("Centroidation",
                        svg_icon,
                        mp_centroidationPage_ui->scrollArea,
                        Pages ::CENTROIDATION);
}

void
ApplicationPreferencesWnd::setupLowMassDeconvolverPage()
{
  QVBoxLayout *layout_p =
    mp_lowMassDeconvolverPage_ui->mainWidgetContentsLayout;

  // The group box widget that will contain all we need to provide the user
  // with for them to configure the low mass deconvolver preferences.
  QGroupBox *group_box_p = mp_lowMassDeconvolverPreferencesWidget_ui
                             ->lowMassDeconvolverPreferencesGroupBox;

  connect(mp_lowMassDeconvolverPreferencesWidget_ui->massToleranceDoubleSpinBox,
          &QDoubleSpinBox::valueChanged,
          this,
          [&](double new_value) {
            qDebug() << "PPM Tolerance changed:" << new_value;
          });

  connect(
    mp_lowMassDeconvolverPreferencesWidget_ui->selectIsotopicDataFilePushButton,
    &QPushButton::clicked,
    this,
    [&]() {
      QString file_name =
        QFileDialog::getOpenFileName(this, "Open isotopic data file");

      if(!file_name.isEmpty())
        {
          mp_lowMassDeconvolverPreferencesWidget_ui->isotopicDataFileLineEdit
            ->setText(file_name);
        }
    });

  connect(mp_lowMassDeconvolverPreferencesWidget_ui
            ->selectProcessingDetailsLogFilePushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            QString file_name = QFileDialog::getSaveFileName(
              this, "Open processing details log file for writing");

            if(!file_name.isEmpty())
              {
                mp_lowMassDeconvolverPreferencesWidget_ui
                  ->writeProcessingDetailsFileNameLineEdit->setText(file_name);
              }
          });

  connect(mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox,
          &QComboBox::currentTextChanged,
          this,
          [&](const QString &current_text) {
            populateLowMassDeconvolverPreferencesForHistoryItem(current_text);
          });

  connect(mp_lowMassDeconvolverPreferencesWidget_ui->addToHistoryPushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            addLowMassDeconvolverPreferencesToHistory();
          });

  connect(
    mp_lowMassDeconvolverPreferencesWidget_ui->removeFromHistoryPushButton,
    &QPushButton::clicked,
    this,
    [&]() {
      removeLowMassDeconvolverPreferencesFromHistory();
    });

  connect(mp_lowMassDeconvolverPreferencesWidget_ui->saveHistoryItem,
          &QPushButton::clicked,
          this,
          [&]() {
            updateLowMassDeconvolverPreferencesCurrentHistoryItem();
          });

  connect(
    mp_lowMassDeconvolverPreferencesWidget_ui->configureMassPeakShaperGroupBox,
    &QGroupBox::toggled,
    this,
    [&](bool checked) {
      if(checked)
        {
          qDebug() << "The group box is checked.";
          if(mp_lowMassDeconvolverPreferencesWidget_ui
               ->syntheticMassSpectrumTitleLineEdit->text()
               .isEmpty())
            {
              mp_lowMassDeconvolverPreferencesWidget_ui
                ->syntheticMassSpectrumTitleLineEdit->setText(
                  mp_lowMassDeconvolverPreferencesWidget_ui
                    ->historyItemTitleComboBox->currentText());
            }

          mp_ui->sectionsListWidget->setCurrentRow(Pages::MASS_PEAK_SHAPER);

          double min_mz =
            mp_lowMassDeconvolverPreferencesWidget_ui->minMassSpinBox->value();
          double max_mz =
            mp_lowMassDeconvolverPreferencesWidget_ui->maxMassSpinBox->value();

          mp_massPeakShaperConfigWidget_ui->setReferencePeakMz(
            abs(max_mz - min_mz) / 2);
        }
      else
        {
          qDebug() << "The button is unchecked.";
        }
    });


  connect(mp_lowMassDeconvolverPreferencesWidget_ui
            ->configureAnalyteIdentificationGroupBox,
          &QGroupBox::toggled,
          this,
          [&](bool checked) {
            if(checked)
              {
                mp_ui->sectionsListWidget->setCurrentRow(
                  Pages::ANALYTE_IDENTIFICATION);
              }
            else
              {
                qDebug() << "The button is unchecked.";
              }
          });

  // Setup the tooltips for the various widgets that need thorough explanations.

  QString tool_tip_text;

  tool_tip_text = "Store in the current settings group the new values.";
  mp_lowMassDeconvolverPreferencesWidget_ui->saveHistoryItem->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Add the current settings as a new settings group in the history to be "
    "saved using the button 'Apply'.";
  mp_lowMassDeconvolverPreferencesWidget_ui->addToHistoryPushButton->setToolTip(
    tool_tip_text);


  tool_tip_text = "Remove the current settings group from history.";
  mp_lowMassDeconvolverPreferencesWidget_ui->removeFromHistoryPushButton
    ->setToolTip(tool_tip_text);

  tool_tip_text = "Minimum charge that the analytes might bear.";
  mp_lowMassDeconvolverPreferencesWidget_ui->minChargeSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text = "Maximum charge that the analytes might bear.";
  mp_lowMassDeconvolverPreferencesWidget_ui->maxChargeSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Tolerance (ppm) with which m/z values are compared.\n"
    "This value is typically 20 ppm.";

  tool_tip_text = "Minimum mass of the analytes.";
  mp_lowMassDeconvolverPreferencesWidget_ui->minMassSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text = "Maximum mass of the analytes.";
  mp_lowMassDeconvolverPreferencesWidget_ui->maxMassSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text = "Minimum intensity of the ion signal.";
  mp_lowMassDeconvolverPreferencesWidget_ui->minIntensityDoubleSpinBox
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "Minimum number of centroid matches between mass spectrum and theoretical "
    "isotopic cluster.";
  mp_lowMassDeconvolverPreferencesWidget_ui->minClusterMatchSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Maximum number of centroid matches between mass spectrum and theoretical "
    "isotopic cluster.";
  mp_lowMassDeconvolverPreferencesWidget_ui->maxClusterMatchSpinBox->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "Number of centroids to be used for the isotopic cluster shape quality "
    "control.";
  mp_lowMassDeconvolverPreferencesWidget_ui->clusterShapeCentroidCountSpinBox
    ->setToolTip(tool_tip_text);

  mp_lowMassDeconvolverPreferencesWidget_ui->massToleranceDoubleSpinBox
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "Once the matches between the centroids in the theoretical isotopic "
    "cluster\n"
    "and those in the input centroided mass spectrum have been performed,\n"
    "the reliability of these matches is checked like so:\n"
    "Select the monoisotopic centroid and its contiguous centroid in both the\n"
    "theoretical isotopic cluster and in the matched centroided mass "
    "spectrum.\n"
    "Compute the ratio between the intensities of these centroids.\n"
    "That ratio is used to normalize the theoretical centroids intensities\n"
    "so they can be compared to the matched input mass spectrum centroid "
    "intensities.\n"
    "Compute the sum of the matched centroids intensities for all the matches "
    "\n"
    "(input mass spectrum vs theoretical).\n"
    "Compute abs(sum of input intensity - sum of theoretical intensities)\n"
    "Divide that difference the sum of theoretical intensities.\n"
    "This ratio indicates the proportion of discrepancy between the theoretica"
    "cluster summed intensities and that of the input cluster.\n"
    "If that ratio is below the value set here, the matches are considered "
    "reliable.\n";

  mp_lowMassDeconvolverPreferencesWidget_ui
    ->totalClusterIntensityToleranceDoubleSpinBox->setToolTip(tool_tip_text);

  tool_tip_text =
    "Once the matches between the centroids in the theoretical isotopic "
    "cluster\n"
    "and those in the input centroided mass spectrum have been performed,\n"
    "the reliability of these matches is checked like so:\n"
    "Get the monoisotopic (highest probability centroid, isotopologue 0) "
    "match.\n"
    "Get the isotopologue 1 match.\n"
    "For each one of these two matches compute the ratio of the intensities \n"
    "for the theoretical isotopic cluster data and for the input centroided "
    "mass spectrum\n"
    "(observed data)"
    "This yields the following intensity ratios:\n"
    "theor_ratio -> theor. intensity iso 1 / theor. intensity iso 0\n"
    "obser_ratio -> obser. intensity iso 1 / obser. intensity iso 0\n"
    "Compute the ratio abs(obser_ratio - theor_ratio) / theor_ratio\n"
    "which amounts to a 'normalized difference'.\n"
    "If the ratio above is smaller than this setting, then the matches\n"
    "are considered to be reliable.\n";

  mp_lowMassDeconvolverPreferencesWidget_ui->clusterShapeToleranceDoubleSpinBox
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "Once the matches between the centroids in the theoretical isotopic "
    "cluster\n"
    "and those in the input centroided mass spectrum have been performed,\n"
    "the reliability of these matches is checked like so:\n"
    "A Pearson correlation factor is computed to check that the\n"
    "observed and theoretical isotopic cluster centroids are correlated \n"
    "by their intensity.\n"
    "If the obtained correlation factor is greater or equal to this setting, \n"
    "then the matches are considered to be reliable.\n";

  mp_lowMassDeconvolverPreferencesWidget_ui
    ->minPearsonCorrelationScoreDoubleSpinBox->setToolTip(tool_tip_text);

  tool_tip_text = "Formula of the Averagine for the kind of analyte studied.";

  mp_lowMassDeconvolverPreferencesWidget_ui->averagineLineEdit->setToolTip(
    tool_tip_text);

  tool_tip_text =
    "The file that contains the isotopic data to be used for the "
    "Averagine-based theoretical isotopic cluster modelling.";
  mp_lowMassDeconvolverPreferencesWidget_ui->isotopicDataFileLineEdit
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "Check this check box to indicate that the deconvolution process must use "
    "heavy-isotope composition for the analytes.";
  mp_lowMassDeconvolverPreferencesWidget_ui->heavyIsotopeLabellingCheckBox
    ->setToolTip(tool_tip_text);

  tool_tip_text =
    "If a file path name is provided, all the processing details will be "
    "stored to that file.\n"
    "If the file exists already, a new file with a time-stamped file name will "
    "be created\n"
    "so as not to overwrite the existing file.";
  mp_lowMassDeconvolverPreferencesWidget_ui
    ->writeProcessingDetailsFileNameLineEdit->setToolTip(tool_tip_text);

  // Read the peak picking preferences and set the values to the widgets.
  readLowMassDeconvolverPreferences();

  layout_p->addWidget(group_box_p);

  // The widget we want to add as a new preferences section is
  QIcon svg_icon(":/images/svg/deconvolution-preferences.svg");
  insertSectionListItem("Low Mass Deconv",
                        svg_icon,
                        mp_lowMassDeconvolverPage_ui->scrollArea,
                        Pages::LOW_MASS_DECONVOLVER);

  // qDebug() << "Done.";
}

void
ApplicationPreferencesWnd::setupMassPeakShaperPage()
{
  QVBoxLayout *layout_p =
    mp_massPeakShaperConfigPage_ui->mainWidgetContentsLayout;

  // Now that we have setup all the widgets, we can pack the
  // mass peak shaper config widget in its final location.
  // Note that the constructor reads the settings and updates
  // the widgets.
  mp_massPeakShaperConfigWidget_ui =
    new libXpertMassGui::MassPeakShaperConfigWidget(
      m_massPeakShaperConfig, mp_massPeakShaperConfigPage_ui->scrollArea);
  layout_p->addWidget(mp_massPeakShaperConfigWidget_ui);

  // The widget we want to add as a new preferences section is
  QIcon svg_icon(":/images/svg/mass-peak-shaper-preferences.svg");
  insertSectionListItem("Peak shaper",
                        svg_icon,
                        mp_massPeakShaperConfigWidget_ui,
                        Pages ::MASS_PEAK_SHAPER);
}

void
ApplicationPreferencesWnd::setupAnalyteIdentificationPage()
{
  QVBoxLayout *layout_p =
    mp_analyteIdentificationPage_ui->mainWidgetContentsLayout;

  QGroupBox *group_box_p = mp_analyteIdentificationPreferencesWidget_ui
                             ->analyteIdentificationPreferencesGroupBox;
  layout_p->addWidget(group_box_p);

  QStringList *dictionaryFilePathHistoryItems_p = new QStringList;
  mp_analyteIdentificationPreferencesWidget_ui->dictionaryFilePathComboBox
    ->setProperty("dictionaryFilePathHistoryItems_p",
                  QVariant::fromValue(dictionaryFilePathHistoryItems_p));

  connect(mp_analyteIdentificationPreferencesWidget_ui
            ->addToDictionaryFilePathHistoryPushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            qDebug();

            QVariant variant = mp_analyteIdentificationPreferencesWidget_ui
                                 ->dictionaryFilePathComboBox->property(
                                   "dictionaryFilePathHistoryItems_p");

            if(variant.canConvert<QStringList *>())
              {
                QString current_text =
                  mp_analyteIdentificationPreferencesWidget_ui
                    ->dictionaryFilePathComboBox->currentText();

                QStringList *dictionaryFilePathHistoryItems_p =
                  variant.value<QStringList *>();

                // Only append if not already in the list.
                if(!dictionaryFilePathHistoryItems_p->contains(current_text))
                  {
                    dictionaryFilePathHistoryItems_p->append(current_text);
                  }

                qDebug().noquote()
                  << "The analyte identification dictionary file path history "
                     "is now:"
                  << dictionaryFilePathHistoryItems_p->join("\n");
              }
            else
              {
                qDebug() << "The variant failed to convert...";
              }
          });

  connect(mp_analyteIdentificationPreferencesWidget_ui
            ->removeFromDictionaryFilePathHistoryPushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            qDebug();

            // We assume that the user wants to remove the item
            // from the combo box, logically, if they want it removed
            // from the history.
            int current_index = mp_analyteIdentificationPreferencesWidget_ui
                                  ->dictionaryFilePathComboBox->currentIndex();
            QString current_text =
              mp_analyteIdentificationPreferencesWidget_ui
                ->dictionaryFilePathComboBox->currentText();

            QVariant variant = mp_analyteIdentificationPreferencesWidget_ui
                                 ->dictionaryFilePathComboBox->property(
                                   "dictionaryFilePathHistoryItems_p");

            if(variant.canConvert<QStringList *>())
              {
                QStringList *dictionaryFilePathHistoryItems_p =
                  variant.value<QStringList *>();
                dictionaryFilePathHistoryItems_p->removeAll(
                  mp_analyteIdentificationPreferencesWidget_ui
                    ->dictionaryFilePathComboBox->currentText());

                // We assume that the user wants to remove the item
                // from the combo box, logically, if they want it removed
                // from the history.
                mp_analyteIdentificationPreferencesWidget_ui
                  ->dictionaryFilePathComboBox->removeItem(current_index);

                qDebug().noquote()
                  << "The analyte identification dictionary file path history "
                     "is now:"
                  << dictionaryFilePathHistoryItems_p->join("\n");
              }
            else
              {
                qDebug() << "The variant failed to convert...";
              }
          });


  QStringList *isotopicDataFilePathHistoryItems_p = new QStringList;
  mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePathComboBox
    ->setProperty("isotopicDataFilePathHistoryItems_p",
                  QVariant::fromValue(isotopicDataFilePathHistoryItems_p));

  connect(mp_analyteIdentificationPreferencesWidget_ui
            ->addToIsotopicDataFilePathHistoryPushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            qDebug();
            QVariant variant = mp_analyteIdentificationPreferencesWidget_ui
                                 ->isotopicDataFilePathComboBox->property(
                                   "isotopicDataFilePathHistoryItems_p");

            if(variant.canConvert<QStringList *>())
              {
                QString current_text =
                  mp_analyteIdentificationPreferencesWidget_ui
                    ->isotopicDataFilePathComboBox->currentText();

                QStringList *isotopicDataFilePathHistoryItems_p =
                  variant.value<QStringList *>();

                // Only append if not already in the list.
                if(!isotopicDataFilePathHistoryItems_p->contains(current_text))
                  {
                    isotopicDataFilePathHistoryItems_p->append(current_text);
                  }

                qDebug().noquote()
                  << "The analyte identification isotopic data file path "
                     "history "
                     "is now:"
                  << isotopicDataFilePathHistoryItems_p->join("\n");
              }
            else
              {
                qDebug() << "The variant failed to convert...";
              }
          });

  connect(mp_analyteIdentificationPreferencesWidget_ui
            ->removeFromIsotopicDataFilePathHistoryPushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            qDebug();

            int current_index =
              mp_analyteIdentificationPreferencesWidget_ui
                ->isotopicDataFilePathComboBox->currentIndex();
            QString current_text =
              mp_analyteIdentificationPreferencesWidget_ui
                ->isotopicDataFilePathComboBox->currentText();

            QVariant variant = mp_analyteIdentificationPreferencesWidget_ui
                                 ->isotopicDataFilePathComboBox->property(
                                   "isotopicDataFilePathHistoryItems_p");

            if(variant.canConvert<QStringList *>())
              {
                QStringList *isotopicDataFilePathHistoryItems_p =
                  variant.value<QStringList *>();
                isotopicDataFilePathHistoryItems_p->removeAll(current_text);

                // We assume that the user wants to remove the item
                // from the combo box, logically, if they want it removed
                // from the history.
                mp_analyteIdentificationPreferencesWidget_ui
                  ->isotopicDataFilePathComboBox->removeItem(current_index);

                qDebug().noquote()
                  << "The analyte identification isotopic data file path "
                     "history "
                     "is now:"
                  << isotopicDataFilePathHistoryItems_p->join("\n");
              }
            else
              {
                qDebug() << "The variant failed to convert...";
              }
          });

  connect(
    mp_analyteIdentificationPreferencesWidget_ui->dictionaryFilePushButton,
    &QPushButton::clicked,
    this,
    [&]() {
      QString file_name =
        QFileDialog::getOpenFileName(this, "Select dictionary file");

      if(!file_name.isEmpty())
        {
          mp_analyteIdentificationPreferencesWidget_ui
            ->dictionaryFilePathComboBox->addItem(file_name);
          mp_analyteIdentificationPreferencesWidget_ui
            ->dictionaryFilePathComboBox->setCurrentText(file_name);
        }
    });

  connect(
    mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePushButton,
    &QPushButton::clicked,
    this,
    [&]() {
      QString file_name =
        QFileDialog::getOpenFileName(this, "Select isotopic data file");

      if(!file_name.isEmpty())
        {
          mp_analyteIdentificationPreferencesWidget_ui
            ->isotopicDataFilePathComboBox->addItem(file_name);
          mp_analyteIdentificationPreferencesWidget_ui
            ->isotopicDataFilePathComboBox->setCurrentText(file_name);
        }
    });

  connect(mp_analyteIdentificationPreferencesWidget_ui
            ->selectProcessingDetailsLogFilePushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            QString file_name = QFileDialog::getSaveFileName(
              this, "Open processing details log file for writing");

            if(!file_name.isEmpty())
              {
                mp_analyteIdentificationPreferencesWidget_ui
                  ->writeProcessingDetailsFileNameLineEdit->setText(file_name);
              }
          });

  // Set the button color to grey as the default label color
  // if the user does not proactively select one.
  QPushButton *colored_push_button =
    mp_analyteIdentificationPreferencesWidget_ui->labelColorPushButton;
  QPalette palette = colored_push_button->palette();
  QColor color("#959595");
  // QColor color("Grey");
  palette.setColor(QPalette::Button, color);
  colored_push_button->setAutoFillBackground(true);
  colored_push_button->setPalette(palette);
  colored_push_button->update();

  connect(
    mp_analyteIdentificationPreferencesWidget_ui->labelColorPushButton,
    &QPushButton::clicked,
    this,
    [&]() {
      // Allow (true) the user to select a color that has been chosen
      // already.
      QColor color = libXpertMassGui::ColorSelector::chooseColor(true);

      if(color.isValid())
        {
          QPushButton *colored_push_button =
            mp_analyteIdentificationPreferencesWidget_ui->labelColorPushButton;
          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();
          // QColor set_color = colored_push_button->palette ().color ();
        }
    });

  connect(mp_analyteIdentificationPreferencesWidget_ui->selectFontPushButton,
          &QPushButton::clicked,
          this,
          [&]() {
            QFont font;
            QFont *font_p = getLabelFont();
            if(font_p)
              {
                font = *font_p;
                delete font_p;
              }

            bool ok = false;
            font    = QFontDialog::getFont(
              &ok, font, this, "Select font for the labels");

            if(!ok)
              return;

            font_p = new QFont(font);

            mp_analyteIdentificationPreferencesWidget_ui->selectFontPushButton
              ->setProperty("LabelFont", QVariant::fromValue(font_p));
          });

  // The widget we want to add as a new preferences section is
  QIcon svg_icon(":/images/svg/analyte-identification-preferences.svg");
  insertSectionListItem("Analyte Identification",
                        svg_icon,
                        group_box_p,
                        Pages ::ANALYTE_IDENTIFICATION);

  readAnalyteIdentificationPreferences();
  qDebug() << "Done reading the AnalyteIdentificationPreferences.";
}

void
ApplicationPreferencesWnd::setupPluginsPage()
{
#if 0
  // The widget we want to add as a new preferences section is
  QIcon svg_icon(":/images/svg/plugins-preferences.svg");
  insertSectionListItem(
    "Plugins", svg_icon, mp_pluginsPage_ui->scrollArea, Pages ::PLUGINS);

  readPluginsPreferences();
#endif
}

int
ApplicationPreferencesWnd::insertSectionListItem(const QString &label,
                                                 const QIcon &icon,
                                                 QWidget *section_widget_p,
                                                 int index)
{
  // qDebug() << "Inserting section list item" << label << "at index" << index;
  QListWidgetItem *new_item_p =
    new QListWidgetItem(icon, label, mp_ui->sectionsListWidget);
  // In case the label is a bit too long, we can show it as a tooltip.
  new_item_p->setToolTip(label);
  mp_ui->sectionsListWidget->insertItem(index, new_item_p);

  // Returns the index of the inserted widget.
  return mp_ui->sectionsStackedWidget->insertWidget(index, section_widget_p);
}

void
ApplicationPreferencesWnd::apply()
{
  // The very first thing to do is check what is the currently active section.

  int current_index = mp_ui->sectionsStackedWidget->currentIndex();

  switch(current_index)
    {
      case Pages::GENERAL:
        applyGeneralPage();
        break;
      case Pages::SHORTCUTS:
        applyShortcutsPage();
        break;
      case Pages::DISCOVERIES:
        applyDiscoveriesPage();
        break;
      case Pages::CENTROIDATION:
        applyCentroidationPage();
        break;
      case Pages::LOW_MASS_DECONVOLVER:
        applyLowMassDeconvolver();
        break;
      case Pages::MASS_PEAK_SHAPER:
        applyMassPeakShaperPage();
        break;
      case Pages::ANALYTE_IDENTIFICATION:
        applyAnalyteIdentificationPage();
        break;
      // case Pages::PLUGINS:
      //   applyPluginsPage();
      //   break;
      default:
        qFatal() << "Programming error.";
    }
}

void
ApplicationPreferencesWnd::applyGeneralPage()
{
  // qDebug() << "The General page is currently displayed.";

  // The decimal places settings
  libXpertMassCore::MZ_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->mzSpinBox->value();
  libXpertMassCore::DELTA_MZ_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->deltaMzSpinBox->value();
  libXpertMassCore::RT_MINUTES_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->retentionTimeMinutesSpinBox->value();
  libXpertMassCore::RT_SECONDS_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->retentionTimeSecondsSpinBox->value();
  libXpertMassCore::INTENSITY_DEC_PLACES =
    mp_decimalPlacesPreferencesWidget_ui->intensitySpinBox->value();

  writeDecimalPlacesPreferences();
}

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("DecimalPlacesPreferences");

  settings.setValue("MzDecPlaces",
                    mp_decimalPlacesPreferencesWidget_ui->mzSpinBox->value());
  settings.setValue(
    "IntensityDecPlaces",
    mp_decimalPlacesPreferencesWidget_ui->intensitySpinBox->value());
  settings.setValue(
    "DeltaMzDecPlaces",
    mp_decimalPlacesPreferencesWidget_ui->deltaMzSpinBox->value());
  settings.setValue(
    "RtMinDecPlaces",
    mp_decimalPlacesPreferencesWidget_ui->retentionTimeMinutesSpinBox->value());
  settings.setValue(
    "RtSecDecPlaces",
    mp_decimalPlacesPreferencesWidget_ui->retentionTimeSecondsSpinBox->value());

  settings.endGroup();
}

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  bool ok   = false;
  int value = 0;

  settings.beginGroup("DecimalPlacesPreferences");

  // libXpertMassCore::MZ_DEC_PLACES
  value = settings.value("MzDecPlaces", 3).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->mzSpinBox->setValue(value);

  // libXpertMassCore::INTENSITY_DEC_PLACES
  value = settings.value("IntensityDecPlaces", 0).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->intensitySpinBox->setValue(value);

  // libXpertMassCore::DELTA_MZ_DEC_PLACES
  value = settings.value("DeltaMzDecPlaces", 4).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->deltaMzSpinBox->setValue(value);

  // libXpertMassCore::RT_MINUTES_DEC_PLACES
  value = settings.value("RtMinDecPlaces", 2).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->retentionTimeMinutesSpinBox->setValue(
    value);

  // libXpertMassCore::RT_SECONDS_DEC_PLACES
  value = settings.value("RtSecDecPlaces", 0).toInt(&ok);
  Q_ASSERT(ok);
  mp_decimalPlacesPreferencesWidget_ui->retentionTimeSecondsSpinBox->setValue(
    value);

  settings.endGroup();
}

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("PluginsPreferences");

  QString plugins_paths_as_string =
    mp_pluginsPage_ui->pluginsPathsPlainTextEdit->toPlainText();

  settings.setValue("PluginsPaths", plugins_paths_as_string);

  settings.endGroup();
}

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("PluginsPreferences");

  QString plugins_paths_as_string =
    settings.value("PluginsPaths", "NOT_SET").toString();

  settings.endGroup();

  if(plugins_paths_as_string == "NOT_SET")
    {
      mp_pluginsPage_ui->pluginsPathsPlainTextEdit->setPlaceholderText(
        "Add the plugins absolute paths, one line per path");
    }
  else
    {
      mp_pluginsPage_ui->pluginsPathsPlainTextEdit->setPlainText(
        plugins_paths_as_string);
    }
}

QStringList
ApplicationPreferencesWnd::getPluginsPaths()
{
  QStringList plugins_paths_as_string_list;
  QString plugins_paths_as_string =
    mp_pluginsPage_ui->pluginsPathsPlainTextEdit->toPlainText();

  plugins_paths_as_string_list = plugins_paths_as_string.split("\n");

  // Check if the paths actually exist.

  for(int iter = 0; iter < plugins_paths_as_string_list.size(); ++iter)
    {
      QDir dir(plugins_paths_as_string_list.at(iter));

      if(!dir.exists())
        {
          plugins_paths_as_string_list.remove(iter);
          --iter;
        }
    }

  return plugins_paths_as_string_list;
}


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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("PeakPickingPreferences");

  // FIXME:
  //   set a minimal intensity value if that works in pappsomspp.

  if(mp_peakPickingPreferencesWidget_ui->manualMaxIntensityRadioButton
       ->isChecked())
    {
      settings.setValue("NoiseEstimatorMaxIntensityMode", "MANUAL");
      settings.setValue(
        "MaximumIntensityParameter",
        mp_peakPickingPreferencesWidget_ui->manualMaxIntensitySpinBox->value());
    }
  else if(mp_peakPickingPreferencesWidget_ui
            ->stdDevFactorMaxIntensityRadioButton->isChecked())
  {
    settings.setValue("NoiseEstimatorMaxIntensityMode", "STD_DEV");
    settings.setValue("MaximumIntensityParameter",
                      mp_peakPickingPreferencesWidget_ui
                        ->stdDevFactorMaxIntensitySpinBox->value());
  }
  else if(mp_peakPickingPreferencesWidget_ui->percentileMaxIntensityRadioButton
            ->isChecked())
  {
    settings.setValue("NoiseEstimatorMaxIntensityMode", "PERCENTILE");
    settings.setValue("MaximumIntensityParameter",
                      mp_peakPickingPreferencesWidget_ui
                        ->percentileMaxIntensitySpinBox->value());
  }

  settings.setValue(
    "NoiseEstimatorRollingWindowSize",
    mp_peakPickingPreferencesWidget_ui->rollingWindowSizeSpinBox->value());

  settings.setValue("NoiseEstimatorMinimumRequiredElements",
                    mp_peakPickingPreferencesWidget_ui
                      ->mininumRequiredElementsSpinBox->value());

  settings.setValue(
    "NoiseEstimatorHistogramBinCount",
    mp_peakPickingPreferencesWidget_ui->histogramBinCountSpinBox->value());

  settings.setValue(
    "PeakPickerMinimalIntensity",
    mp_peakPickingPreferencesWidget_ui->minIntensityDoubleSpinBox->value());

  settings.setValue("PeakPickerSignalToNoiseRatio",
                    mp_peakPickingPreferencesWidget_ui
                      ->signalToNoiseRatioDoubleSpinBox->value());
  settings.setValue(
    "PeakPickerAllowedMissingPeaks",
    mp_peakPickingPreferencesWidget_ui->missingPeaksCountSpinBox->value());

  settings.sync();

  settings.endGroup();

  qDebug() << "Done writing the peak picking preferences to "
           << settings_file_path;
}

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  bool ok = false;
  double double_value = 0;

  settings.beginGroup("PeakPickingPreferences");

  QString manual_max_intensity_mode =
    settings.value("NoiseEstimatorMaxIntensityMode", "STD_DEV").toString();
  if(manual_max_intensity_mode == "MANUAL")
    {
      mp_peakPickingPreferencesWidget_ui->manualMaxIntensityRadioButton
        ->setChecked(true);
      double_value =
        settings.value("MaximumIntensityParameter", 0).toDouble(&ok);
      mp_peakPickingPreferencesWidget_ui->manualMaxIntensitySpinBox->setValue(
        double_value);
    }
  else if(manual_max_intensity_mode == "STD_DEV")
    {
      mp_peakPickingPreferencesWidget_ui->stdDevFactorMaxIntensityRadioButton
        ->setChecked(true);

      double_value =
        settings.value("MaximumIntensityParameter", 3).toDouble(&ok);
      mp_peakPickingPreferencesWidget_ui->stdDevFactorMaxIntensitySpinBox
        ->setValue(double_value);
    }
  else if(manual_max_intensity_mode == "PERCENTILE")
    {
      mp_peakPickingPreferencesWidget_ui->percentileMaxIntensityRadioButton
        ->setChecked(true);

      double_value =
        settings.value("MaximumIntensityParameter", 95).toDouble(&ok);
      mp_peakPickingPreferencesWidget_ui->percentileMaxIntensitySpinBox
        ->setValue(double_value);
    }
  else
    qFatal() << "Programming error.";

  int value = settings.value("NoiseEstimatorRollingWindowSize", 50).toInt(&ok);
  mp_peakPickingPreferencesWidget_ui->rollingWindowSizeSpinBox->setValue(value);

  value =
    settings.value("NoiseEstimatorMinimumRequiredElements", 10).toInt(&ok);
  mp_peakPickingPreferencesWidget_ui->mininumRequiredElementsSpinBox->setValue(
    value);

  value = settings.value("NoiseEstimatorHistogramBinCount", 30).toInt(&ok);
  mp_peakPickingPreferencesWidget_ui->histogramBinCountSpinBox->setValue(value);

  value = settings.value("PeakPickerMinimalIntensity", 0.0).toDouble(&ok);
  mp_peakPickingPreferencesWidget_ui->minIntensityDoubleSpinBox->setValue(
    value);

  value = settings.value("PeakPickerSignalToNoiseRatio", 0.0).toInt(&ok);
  mp_peakPickingPreferencesWidget_ui->signalToNoiseRatioDoubleSpinBox->setValue(
    value);

  value = settings.value("PeakPickerAllowedMissingPeaks", 0).toInt(&ok);
  mp_peakPickingPreferencesWidget_ui->missingPeaksCountSpinBox->setValue(value);

  settings.endGroup();
}

std::tuple<pappso::LocalSignalToNoiseEstimator::Parameters,
           pappso::HighResPeakPicker::Parameters>
ApplicationPreferencesWnd::getPeakPickingPreferences(QString &message) const
{
  QString msg =
    "Getting peak picking preferences from application preferences:\n";

  pappso::LocalSignalToNoiseEstimator::Parameters noise_estimator_params;

  if(mp_peakPickingPreferencesWidget_ui->manualMaxIntensityRadioButton
       ->isChecked())
    {
      noise_estimator_params.maxIntMode =
        pappso::LocalSignalToNoiseEstimator::MANUAL_BY_VALUE;
      noise_estimator_params.maxIntensity =
        mp_peakPickingPreferencesWidget_ui->manualMaxIntensitySpinBox->value();

      msg += QString("Max intensity determination: manual by value: %1\n")
               .arg(noise_estimator_params.maxIntensity);
    }
  else if(mp_peakPickingPreferencesWidget_ui
            ->stdDevFactorMaxIntensityRadioButton->isChecked())
    {
      noise_estimator_params.maxIntMode =
        pappso::LocalSignalToNoiseEstimator::AUTO_MAX_BY_STDEV;
      noise_estimator_params.maxIntStdDevFactor =
        mp_peakPickingPreferencesWidget_ui->stdDevFactorMaxIntensitySpinBox
          ->value();

      msg +=
        QString(
          "Max intensity determination: by standard deviation factor: %1\n")
          .arg(noise_estimator_params.maxIntStdDevFactor);
    }
  else if(mp_peakPickingPreferencesWidget_ui
            ->stdDevFactorMaxIntensityRadioButton->isChecked())
    {
      noise_estimator_params.maxIntMode =
        pappso::LocalSignalToNoiseEstimator::AUTO_MAX_BY_PERCENTILE;
      noise_estimator_params.maxIntPercentile =
        mp_peakPickingPreferencesWidget_ui->percentileMaxIntensitySpinBox
          ->value();

      msg += QString("Max intensity determination: by percentile: %1\n")
               .arg(noise_estimator_params.maxIntPercentile);
    }
  else
    qFatal() << "Programming error.";

  noise_estimator_params.windowSize =
    mp_peakPickingPreferencesWidget_ui->rollingWindowSizeSpinBox->value();

  msg +=
    QString("Rolling window size: %1\n").arg(noise_estimator_params.windowSize);

  noise_estimator_params.binCount =
    mp_peakPickingPreferencesWidget_ui->histogramBinCountSpinBox->value();

  msg +=
    QString("Histogram bin count: %1\n").arg(noise_estimator_params.binCount);

  noise_estimator_params.minRequiredElements =
    mp_peakPickingPreferencesWidget_ui->mininumRequiredElementsSpinBox->value();

  msg += QString("Minimum required elements: %1\n")
           .arg(noise_estimator_params.minRequiredElements);

  pappso::HighResPeakPicker::Parameters peak_picker_params;

  peak_picker_params.minimumIntensity =
    mp_peakPickingPreferencesWidget_ui->minIntensityDoubleSpinBox->value();

  msg += QString("Minimal mass spectral intensity: %1\n")
           .arg(peak_picker_params.minimumIntensity);

  peak_picker_params.signalToNoise =
    mp_peakPickingPreferencesWidget_ui->signalToNoiseRatioDoubleSpinBox
      ->value();

  msg += QString("Signal to noise ratio: %1\n")
           .arg(peak_picker_params.signalToNoise);

  peak_picker_params.missingPeakCount =
    mp_peakPickingPreferencesWidget_ui->missingPeaksCountSpinBox->value();

  msg += QString("Allowed missing peaks: %1\n")
           .arg(peak_picker_params.missingPeakCount);

  qDebug() << msg;

  message = msg;

  return std::tuple<pappso::LocalSignalToNoiseEstimator::Parameters,
                    pappso::HighResPeakPicker::Parameters>(
    noise_estimator_params, peak_picker_params);
}

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.remove("LowMassDeconvolverPreferences");

  settings.beginGroup("LowMassDeconvolverPreferences");

  settings.setValue("CurrentHistoryGroupIndex",
                    mp_lowMassDeconvolverPreferencesWidget_ui
                      ->historyItemTitleComboBox->currentIndex());

  // qDebug() << "Set the value for CurrentHistoryGroupIndex to "
  //          << mp_lowMassDeconvolverPreferencesWidget_ui
  //               ->historyItemTitleComboBox->currentIndex();

  foreach(const QString &params_title,
          m_lowMassDeconvolverParameterPairs.keys())
    {
      settings.beginGroup(params_title);

      QSharedPointer<libXpertMassCore::LowMassDeconvolverParameters>
        params_sp = m_lowMassDeconvolverParameterPairs[params_title];

      settings.setValue("MinCharge", params_sp->minCharge);
      settings.setValue("MaxCharge", params_sp->maxCharge);
      settings.setValue("PpmMassOrMzTolerance", params_sp->ppmMassTolerance);
      settings.setValue("MinMass", params_sp->minMass);
      settings.setValue("MaxMass", params_sp->maxMass);
      settings.setValue("MinIntensity", params_sp->minIntensity);
      settings.setValue("MinClusterCentroidMatches",
                        params_sp->minIsotopicClusterMatches);
      settings.setValue("MaxClusterCentroidMatches",
                        params_sp->maxIsotopicClusterMatches);
      settings.setValue("ClusterShapeCentroidCount",
                        params_sp->clusterShapeCentroidCount);
      settings.setValue(
        "AveragineFormula",
        params_sp->averagine.getFormulaCstRef().getActionFormula(
          true /*with_title*/));
      settings.setValue("IsotopicDataFilePath",
                        params_sp->averagine.getIsotopicData()->getFilePath());
      settings.setValue("FullHeavyIsotopeLabelling",
                        params_sp->fullHeavyIsotopeLabelling);
      settings.setValue("TotalClusterIntensityTolerance",
                        params_sp->totalClusterIntensityTolerance);

      settings.setValue("ClusterShapeTolerance",
                        params_sp->clusterShapeTolerance);
      // qDebug() << "Writing ClusterShapeTolerance:"
      //          << params_sp->clusterShapeTolerance;

      settings.setValue("MinPearsonCorrelationScore",
                        params_sp->minPearsonCorrelationScore);

      settings.setValue("WriteProcessingDetailsFileName",
                        params_sp->processingDetailsLogFilePath);
      // qDebug() << "Writing WriteProcessingDetailsFileName:"
      //          << params.processingDetailsLogFilePath;

      settings.endGroup();
    }

  settings.sync();

  settings.endGroup();

  qDebug() << "Done writing the low mass deconvolver preferences to "
           << settings_file_path;
}

void
ApplicationPreferencesWnd::readLowMassDeconvolverPreferences()
{
  QSettings settings(static_cast<Application *>(QCoreApplication::instance())
                       ->getUserConfigSettingsFilePath(),
                     QSettings::IniFormat);

  bool ok = false;

  int history_group_index = -1;

  settings.beginGroup("LowMassDeconvolverPreferences");

  QStringList sub_groups = settings.childGroups();

  qDebug() << "There are" << sub_groups.size() << "sub groups.";

  for(const QString &sub_group : sub_groups)
    {
      qDebug() << "Found sub-group:" << sub_group;

      settings.beginGroup(sub_group);

      int minCharge = settings.value("MinCharge", 1).toInt(&ok);
      Q_ASSERT(ok);
      int maxCharge = settings.value("MaxCharge", 1).toInt(&ok);
      Q_ASSERT(ok);

      double minMass = settings.value("MinMass", 150).toDouble(&ok);
      Q_ASSERT(ok);
      double maxMass = settings.value("MaxMass", 5000).toDouble(&ok);
      Q_ASSERT(ok);

      double ppmMassTolerance =
        settings.value("PpmMassOrMzTolerance", 10).toDouble(&ok);
      Q_ASSERT(ok);

      double minIntensity = settings.value("MinIntensity", 0.05).toDouble(&ok);
      Q_ASSERT(ok);

      int minIsotopicClusterMatches =
        settings.value("MinClusterCentroidMatches", 3).toInt(&ok);
      Q_ASSERT(ok);

      int maxIsotopicClusterMatches =
        settings.value("MaxClusterCentroidMatches", 3).toInt(&ok);
      Q_ASSERT(ok);

      int clusterShapeCentroidCount =
        settings.value("ClusterShapeCentroidCount", 3).toInt(&ok);
      Q_ASSERT(ok);

      QString isotopicDataFilePath =
        settings.value("IsotopicDataFilePath", "").toString();

      QFileInfo file_info(isotopicDataFilePath);

      if(!file_info.exists())
        {
          QString msg = QString(
                          "Please, select an existing isotopic data file for "
                          "settings group: %1")
                          .arg(sub_group);

          isotopicDataFilePath = QFileDialog::getOpenFileName(this, msg);

          if(isotopicDataFilePath.isEmpty())
            {
              // Skip the whole settings altogether.
              continue;
            }
        }

      qDebug() << "Isotopic data file path:" << isotopicDataFilePath;

      // Since we provide the user with the ability to load a specific
      // isotopic data file, that needs to be IsotopicDataUserConfigHandler
      // because otherwise (IsotopicDataLibraryHandler), the file
      // name would be of no use: the data would be loaded from the IsoSpec
      // library material (natural abundances for all the isotopes).
      libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler;

      qsizetype loaded_isotope_count =
        isotopic_data_handler.loadData(isotopicDataFilePath);

      if(!loaded_isotope_count)
        qFatal() << "Failed to load any isotope.";

      qDebug() << "Loaded isotopes:" << loaded_isotope_count;

      bool fullHeavyIsotopeLabelling =
        settings.value("FullHeavyIsotopeLabelling", false).toBool();

      QString averagineFormula =
        settings.value("AveragineFormula", "").toString();

      if(averagineFormula.isEmpty())
        {
          bool ok;
          QString msg =
            QString(
              "Please enter a valid Averagine formula for settings group: %1")
              .arg(sub_group);

          averagineFormula = QInputDialog::getText(
            this, msg, "Averagine:", QLineEdit::Normal, "", &ok);
          if(!ok)
            {
              // Skip the whole settings altogether.
              continue;
            }
        }

      qDebug() << "Read Averagine formula:" << averagineFormula;

      double totalClusterIntensityTolerance =
        settings.value("TotalClusterIntensityTolerance", 0.25).toDouble(&ok);
      Q_ASSERT(ok);

      double clusterShapeTolerance =
        settings.value("ClusterShapeTolerance", 0.5).toDouble(&ok);
      Q_ASSERT(ok);

      qDebug() << "Read ClusterShapeTolerance from settings:"
               << clusterShapeTolerance;
      Q_ASSERT(ok);

      double minPearsonCorrelationScore =
        settings.value("MinPearsonCorrelationScore", 0.85).toDouble(&ok);
      Q_ASSERT(ok);

      QString processingDetailsLogFilePath =
        settings.value("WriteProcessingDetailsFileName").toString();

      // At this point we should have all the info required to construct and
      // Averagine instance.
      libXpertMassCore::Averagine averagine(
        libXpertMassCore::Formula(averagineFormula),
        isotopic_data_handler.getIsotopicData(),
        nullptr);
      // averagine.setIsotopicData(isotopic_data_handler.getIsotopicData());

      if(averagine.getIsotopicData() == nullptr ||
         averagine.getIsotopicData().get() == nullptr)
        {
          QString msg =
            QString("Failed to load the isotopic data for settings %1")
              .arg(sub_group);

          QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                               "Low mass deconvolver preference settings",
                               msg,
                               QMessageBox::Ok);
          continue;
        }

      libXpertMassCore::ErrorList error_list;

      if(!averagine.validate(&error_list))
        {
          QString msg =
            QString(
              "Failed to validate successfully the Averagine for settings %1")
              .arg(sub_group);

          QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                               "Low mass deconvolver preference settings",
                               msg,
                               QMessageBox::Ok);
          continue;
        }

      qDebug()
        << "The Averagine instance could validate successfully with mono mass:"
        << averagine.getMonoMass();

      libXpertMassCore::LowMassDeconvolverParameters params(
        minCharge,
        maxCharge,
        minMass,
        maxMass,
        ppmMassTolerance,
        minIntensity,
        minIsotopicClusterMatches,
        maxIsotopicClusterMatches,
        clusterShapeCentroidCount,
        averagine,
        fullHeavyIsotopeLabelling,
        processingDetailsLogFilePath,
        totalClusterIntensityTolerance,
        clusterShapeTolerance,
        minPearsonCorrelationScore);

      qDebug() << "Now updating settings for sub group:" << sub_group
               << "with params:" << params.toString() << "with isotopic data: "
               << params.averagine.getIsotopicData().get();

      m_lowMassDeconvolverParameterPairs[sub_group] =
        QSharedPointer<libXpertMassCore::LowMassDeconvolverParameters>::create(
          params);
      ;

      // qDebug() << "Done setting the parameters to the pairs sub_group item.";

      settings.endGroup();
    }
  // End of
  // for(const QString &sub_group : sub_groups)

  qDebug() << "Done iterating in the" << sub_groups.size()
           << "settings sub-groups.";

  history_group_index =
    settings.value("CurrentHistoryGroupIndex", -1).toInt(&ok);

  Q_ASSERT(ok);

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

  // Now close the main group ("LowMassDeconvolverPreferences")
  settings.endGroup();

  // We now have the configuration(s) if any in the
  // m_lowMassDeconvolverParameterPairs member. Use that to fill in the combo
  // box items (the titles of the configurations).
  populateLowMassDeconvolverPreferencesCombobox();

  // Be cautious about absence of any configuration from the settings file!
  if(history_group_index >= 0 && sub_groups.size() &&
     m_lowMassDeconvolverParameterPairs.size())
    {
      if(history_group_index < m_lowMassDeconvolverParameterPairs.keys().size())
        {
          populateLowMassDeconvolverPreferencesForHistoryItem(
            m_lowMassDeconvolverParameterPairs.keys().at(history_group_index));
        }
      else
        {
          populateLowMassDeconvolverPreferencesForHistoryItem(
            m_lowMassDeconvolverParameterPairs.keys().last());
        }
    }
}

bool
ApplicationPreferencesWnd::getLowMassDeconvolverPreferences(
  libXpertMassCore::LowMassDeconvolverParameters &params) const
{
  QString file_path =
    mp_lowMassDeconvolverPreferencesWidget_ui->isotopicDataFileLineEdit->text();
  QFileInfo file_info(file_path);
  if(!file_info.exists())
    {
      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           "Please select an isotopic data file",
                           QMessageBox::Ok);
      return false;
    }

  // Since we provide the user with the ability to load a specific
  // isotopic data file, that needs to be IsotopicDataUserConfigHandler
  // because otherwise (IsotopicDataLibraryHandler), the file
  // name would be of no use: the data would be loaded from the IsoSpec
  // library material (natural abundances for all the isotopes).
  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler;

  qsizetype loaded_isotope_count = isotopic_data_handler.loadData(file_path);

  if(!loaded_isotope_count)
    qFatal() << "Failed to load any isotope.";

  params.averagine.setIsotopicData(isotopic_data_handler.getIsotopicData());
  if(params.averagine.getIsotopicData() == nullptr ||
     params.averagine.getIsotopicData().get() == nullptr)
    qFatal() << "The isotopic data pointer is nullptr.";

  // qDebug() << "The low mass deconvolver was initialized with"
  //          << params.averagine.getIsotopicData ()->size() << "isotopes.";

  QString averagine_formula_string =
    mp_lowMassDeconvolverPreferencesWidget_ui->averagineLineEdit->text();
  if(averagine_formula_string.isEmpty())
    {
      QMessageBox::warning(
        const_cast<ApplicationPreferencesWnd *>(this),
        "Low mass deconvolver preference settings",
        "Please fill in a correct neutral Averagine formula\n"
        "like C4.29H6.86N0.86O2.00.",
        QMessageBox::Ok);

      return false;
    }

  params.averagine.setFormula(
    libXpertMassCore::Formula(averagine_formula_string));
  // qDebug() << "Initializing averagine formula with:"
  //          << params.averagine.getFormulaCstRef ().getActionFormula(false
  //          /*withTitle*/);

  libXpertMassCore::ErrorList error_list;

  if(!params.averagine.validate(&error_list))
    {
      QString msg =
        QString("Failed to validate the Averagine instance, with errors: %1")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list));

      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           msg,
                           QMessageBox::Ok);

      return false;
    }

  params.processingDetailsLogFilePath =
    mp_lowMassDeconvolverPreferencesWidget_ui
      ->writeProcessingDetailsFileNameLineEdit->text();

  params.fullHeavyIsotopeLabelling =
    mp_lowMassDeconvolverPreferencesWidget_ui->heavyIsotopeLabellingCheckBox
      ->isChecked();

  params.minCharge =
    mp_lowMassDeconvolverPreferencesWidget_ui->minChargeSpinBox->value();

  params.maxCharge =
    mp_lowMassDeconvolverPreferencesWidget_ui->maxChargeSpinBox->value();

  params.minMass =
    mp_lowMassDeconvolverPreferencesWidget_ui->minMassSpinBox->value();

  params.maxMass =
    mp_lowMassDeconvolverPreferencesWidget_ui->maxMassSpinBox->value();

  params.minIntensity = mp_lowMassDeconvolverPreferencesWidget_ui
                          ->minIntensityDoubleSpinBox->value();

  params.minIsotopicClusterMatches =
    mp_lowMassDeconvolverPreferencesWidget_ui->minClusterMatchSpinBox->value();

  params.maxIsotopicClusterMatches =
    mp_lowMassDeconvolverPreferencesWidget_ui->maxClusterMatchSpinBox->value();

  params.clusterShapeCentroidCount =
    mp_lowMassDeconvolverPreferencesWidget_ui->clusterShapeCentroidCountSpinBox
      ->value();

  params.ppmMassTolerance = mp_lowMassDeconvolverPreferencesWidget_ui
                              ->massToleranceDoubleSpinBox->value();

  params.totalClusterIntensityTolerance =
    mp_lowMassDeconvolverPreferencesWidget_ui
      ->totalClusterIntensityToleranceDoubleSpinBox->value();

  params.clusterShapeTolerance =
    mp_lowMassDeconvolverPreferencesWidget_ui
      ->clusterShapeToleranceDoubleSpinBox->value();

  qDebug() << "Got params.clusterShapeTolerance:"
           << params.clusterShapeTolerance;

  params.minPearsonCorrelationScore =
    mp_lowMassDeconvolverPreferencesWidget_ui
      ->minPearsonCorrelationScoreDoubleSpinBox->value();

  return true;
}

int
ApplicationPreferencesWnd::populateLowMassDeconvolverPreferencesCombobox()
{
  if(!m_lowMassDeconvolverParameterPairs.size())
    return 0;

  int count = 0;
  foreach(const QString &params_title,
          m_lowMassDeconvolverParameterPairs.keys())
    {
      mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox
        ->addItem(params_title);
      ++count;
    }

  qDebug() << "Effectively added" << count << "history items.";

  return count;
}

bool
ApplicationPreferencesWnd::populateLowMassDeconvolverPreferencesForHistoryItem(
  const QString &params_title)
{
  if(!m_lowMassDeconvolverParameterPairs.contains(params_title))
    return false;

  mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox
    ->setCurrentText(params_title);

  QSharedPointer<libXpertMassCore::LowMassDeconvolverParameters>
    params_sp = m_lowMassDeconvolverParameterPairs.value(params_title);

  mp_lowMassDeconvolverPreferencesWidget_ui->minChargeSpinBox->setValue(
    params_sp->minCharge);

  mp_lowMassDeconvolverPreferencesWidget_ui->maxChargeSpinBox->setValue(
    params_sp->maxCharge);

  mp_lowMassDeconvolverPreferencesWidget_ui->minMassSpinBox->setValue(
    params_sp->minMass);

  mp_lowMassDeconvolverPreferencesWidget_ui->maxMassSpinBox->setValue(
    params_sp->maxMass);

  mp_lowMassDeconvolverPreferencesWidget_ui->minIntensityDoubleSpinBox
    ->setValue(params_sp->minIntensity);

  mp_lowMassDeconvolverPreferencesWidget_ui->minClusterMatchSpinBox->setValue(
    params_sp->minIsotopicClusterMatches);

  mp_lowMassDeconvolverPreferencesWidget_ui->maxClusterMatchSpinBox->setValue(
    params_sp->maxIsotopicClusterMatches);

  mp_lowMassDeconvolverPreferencesWidget_ui->clusterShapeCentroidCountSpinBox
    ->setValue(params_sp->clusterShapeCentroidCount);

  mp_lowMassDeconvolverPreferencesWidget_ui->massToleranceDoubleSpinBox
    ->setValue(params_sp->ppmMassTolerance);

  mp_lowMassDeconvolverPreferencesWidget_ui->isotopicDataFileLineEdit->setText(
    params_sp->averagine.getIsotopicData()->getFilePath());

  mp_lowMassDeconvolverPreferencesWidget_ui->heavyIsotopeLabellingCheckBox
    ->setCheckState(params_sp->fullHeavyIsotopeLabelling ? Qt::Checked
                                                         : Qt::Unchecked);

  mp_lowMassDeconvolverPreferencesWidget_ui->averagineLineEdit->setText(
    params_sp->averagine.getFormulaCstRef().getActionFormula(
      true /*with_title*/));

  mp_lowMassDeconvolverPreferencesWidget_ui
    ->totalClusterIntensityToleranceDoubleSpinBox->setValue(
      params_sp->totalClusterIntensityTolerance);

  mp_lowMassDeconvolverPreferencesWidget_ui->clusterShapeToleranceDoubleSpinBox
    ->setValue(params_sp->clusterShapeTolerance);

  mp_lowMassDeconvolverPreferencesWidget_ui
    ->minPearsonCorrelationScoreDoubleSpinBox->setValue(
      params_sp->minPearsonCorrelationScore);

  return true;
}

bool
ApplicationPreferencesWnd::
  updateLowMassDeconvolverPreferencesCurrentHistoryItem()
{
  QString current_text = mp_lowMassDeconvolverPreferencesWidget_ui
                           ->historyItemTitleComboBox->currentText();

  if(current_text.isEmpty())
    {
      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           "Please enter a name for the current parameter set",
                           QMessageBox::Ok);
      return false;
    }

  if(!m_lowMassDeconvolverParameterPairs.contains(current_text))
    {
      QMessageBox::warning(
        const_cast<ApplicationPreferencesWnd *>(this),
        "Low mass deconvolver preference settings",
        "First add the settings to history by clicking the '+' button.",
        QMessageBox::Ok);
      return false;
    }

  QSharedPointer<libXpertMassCore::LowMassDeconvolverParameters>
    params_sp = m_lowMassDeconvolverParameterPairs[current_text];

  QString file_path =
    mp_lowMassDeconvolverPreferencesWidget_ui->isotopicDataFileLineEdit->text();
  QFileInfo file_info(file_path);
  if(!file_info.exists())
    {
      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           "Please select an isotopic data file.",
                           QMessageBox::Ok);
      return false;
    }

  // Since we provide the user with the ability to load a specific
  // isotopic data file, that needs to be IsotopicDataUserConfigHandler
  // because otherwise (IsotopicDataLibraryHandler), the file
  // name would be of no use: the data would be loaded from the IsoSpec
  // library material (natural abundances for all the isotopes).
  libXpertMassCore::IsotopicDataUserConfigHandler isotopic_data_handler;

  qsizetype loaded_isotope_count = isotopic_data_handler.loadData(file_path);

  if(!loaded_isotope_count)
    {
      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           "Failed to load any isotope.",
                           QMessageBox::Ok);
      return false;
    }

  params_sp->averagine.setIsotopicData(isotopic_data_handler.getIsotopicData());
  if(params_sp->averagine.getIsotopicData() == nullptr ||
     params_sp->averagine.getIsotopicData().get() == nullptr)
    qFatal() << "The isotopic data pointer is nullptr.";

  // qDebug() << "The low mass deconvolver was initialized with"
  //          << params.averagine.getIsotopicData ()->size() << "isotopes.";

  params_sp->fullHeavyIsotopeLabelling =
    mp_lowMassDeconvolverPreferencesWidget_ui->heavyIsotopeLabellingCheckBox
      ->isChecked();

  QString averagine_formula_string =
    mp_lowMassDeconvolverPreferencesWidget_ui->averagineLineEdit->text();
  if(averagine_formula_string.isEmpty())
    {
      QMessageBox::warning(
        const_cast<ApplicationPreferencesWnd *>(this),
        "Low mass deconvolver preference settings",
        "Please fill in a correct neutral Averagine formula\n"
        "like C4.29H6.86N0.86O2.00",
        QMessageBox::Ok);
      return false;
    }
  params_sp->averagine.setFormula(
    libXpertMassCore::Formula(averagine_formula_string));
  // qDebug() << "Initializing averagine formula with:"
  //          << params.averagine.getFormulaCstRef ().getActionFormula(false
  //          /*withTitle*/);

  libXpertMassCore::ErrorList error_list;

  if(!params_sp->averagine.validate(&error_list))
    {
      QString msg =
        QString("Failed to validate the Averagine instance, with errors: %1")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list));

      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           msg,
                           QMessageBox::Ok);

      return false;
    }

  params_sp->minCharge =
    mp_lowMassDeconvolverPreferencesWidget_ui->minChargeSpinBox->value();

  params_sp->maxCharge =
    mp_lowMassDeconvolverPreferencesWidget_ui->maxChargeSpinBox->value();

  params_sp->minMass =
    mp_lowMassDeconvolverPreferencesWidget_ui->minMassSpinBox->value();

  params_sp->maxMass =
    mp_lowMassDeconvolverPreferencesWidget_ui->maxMassSpinBox->value();

  params_sp->minIntensity = mp_lowMassDeconvolverPreferencesWidget_ui
                              ->minIntensityDoubleSpinBox->value();

  params_sp->minIsotopicClusterMatches =
    mp_lowMassDeconvolverPreferencesWidget_ui->minClusterMatchSpinBox->value();

  params_sp->maxIsotopicClusterMatches =
    mp_lowMassDeconvolverPreferencesWidget_ui->maxClusterMatchSpinBox->value();

  params_sp->clusterShapeCentroidCount =
    mp_lowMassDeconvolverPreferencesWidget_ui->clusterShapeCentroidCountSpinBox
      ->value();

  params_sp->ppmMassTolerance = mp_lowMassDeconvolverPreferencesWidget_ui
                                  ->massToleranceDoubleSpinBox->value();

  params_sp->totalClusterIntensityTolerance =
    mp_lowMassDeconvolverPreferencesWidget_ui
      ->totalClusterIntensityToleranceDoubleSpinBox->value();

  params_sp->clusterShapeTolerance =
    mp_lowMassDeconvolverPreferencesWidget_ui
      ->clusterShapeToleranceDoubleSpinBox->value();

  params_sp->minPearsonCorrelationScore =
    mp_lowMassDeconvolverPreferencesWidget_ui
      ->minPearsonCorrelationScoreDoubleSpinBox->value();

  return true;
}

void
ApplicationPreferencesWnd::addLowMassDeconvolverPreferencesToHistory()
{
  libXpertMassCore::LowMassDeconvolverParameters params;

  bool ok = getLowMassDeconvolverPreferences(params);

  if(!ok)
    {
      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           "Failed to get the preferences",
                           QMessageBox::Ok);
      return;
    }

  QString current_text = mp_lowMassDeconvolverPreferencesWidget_ui
                           ->historyItemTitleComboBox->currentText();
  if(current_text.isEmpty())
    current_text = QDateTime::currentDateTime().toString();

  // If the current item already exists, we are not adding it,
  // we just simply update into the history item the data
  // currently set in the various widgets.
  if(m_lowMassDeconvolverParameterPairs.contains(current_text))
    {
      updateLowMassDeconvolverPreferencesCurrentHistoryItem();
      return;
    }

  mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox->addItem(
    current_text);

  m_lowMassDeconvolverParameterPairs[current_text] = QSharedPointer<
    libXpertMassCore::LowMassDeconvolverParameters>::create(params);

  writeLowMassDeconvolverPreferences();
}

void
ApplicationPreferencesWnd::removeLowMassDeconvolverPreferencesFromHistory()
{

  Q_ASSERT(mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox
             ->count() == m_lowMassDeconvolverParameterPairs.size());

  if(!mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox
        ->count())
    return;

  QString current_text = mp_lowMassDeconvolverPreferencesWidget_ui
                           ->historyItemTitleComboBox->currentText();

  mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox
    ->removeItem(mp_lowMassDeconvolverPreferencesWidget_ui
                   ->historyItemTitleComboBox->currentIndex());

  qDebug() << "Before removing, items:"
           << m_lowMassDeconvolverParameterPairs.size();

  m_lowMassDeconvolverParameterPairs.remove(current_text);

  qDebug() << "After removing, items:"
           << m_lowMassDeconvolverParameterPairs.size();

  Q_ASSERT(mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox
             ->count() == m_lowMassDeconvolverParameterPairs.size());

  writeLowMassDeconvolverPreferences();

  if(!mp_lowMassDeconvolverPreferencesWidget_ui->historyItemTitleComboBox
        ->count())
    return;

  current_text = mp_lowMassDeconvolverPreferencesWidget_ui
                   ->historyItemTitleComboBox->currentText();

  if(!populateLowMassDeconvolverPreferencesForHistoryItem(current_text))
    {
      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Low mass deconvolver preference settings",
                           "Failed to update the preferences",
                           QMessageBox::Ok);
    }
}

void
ApplicationPreferencesWnd::writeMassPeakShaperConfigPreferences()
{
  mp_massPeakShaperConfigWidget_ui->writeSettings(
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath());
}

void
ApplicationPreferencesWnd::readMassPeakShaperConfigPreferences()
{
  mp_massPeakShaperConfigWidget_ui->readSettings(
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath());
}

const libXpertMassGui::MassPeakShaperConfigWidget *
ApplicationPreferencesWnd::getMassPeakShaperConfigWidget() const
{
  return mp_massPeakShaperConfigWidget_ui;
}

bool
ApplicationPreferencesWnd::getMassPeakShaperConfigPreferences(
  libXpertMassCore::MassPeakShaperConfig &mass_peak_shaper_config) const
{
  libXpertMassCore::ErrorList error_list;

  if(!mp_massPeakShaperConfigWidget_ui->checkParameters(&error_list))
    {
      QString message =
        QString("Failed to get the preferences, with errors:\n%1\n")
          .arg(libXpertMassCore::Utils::joinErrorList(error_list, "\n"));

      QMessageBox::warning(const_cast<ApplicationPreferencesWnd *>(this),
                           "Mass peak shaper configuration preference settings",
                           message,
                           QMessageBox::Ok);

      return false;
    }

  mass_peak_shaper_config.initialize(
    *(mp_massPeakShaperConfigWidget_ui->getConfig()));

  // qDebug() << "The gotten mass peak shaper config preference settings:"
  //          << mass_peak_shaper_config.toString();

  return true;
}

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

  int current_combo_box_item_index = -1;

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("AnalyteIdentificationPreferences");

  QVariant variant =
    mp_analyteIdentificationPreferencesWidget_ui->dictionaryFilePathComboBox
      ->property("dictionaryFilePathHistoryItems_p");

  if(variant.canConvert<QStringList *>())
    {
      QStringList *dictionaryFilePathHistoryItems_p =
        variant.value<QStringList *>();
      settings.setValue("DictionaryFilePathHistory",
                        *dictionaryFilePathHistoryItems_p);

      qDebug() << "Set DictionaryFilePathHistory:"
               << *dictionaryFilePathHistoryItems_p;
    }

  current_combo_box_item_index = mp_analyteIdentificationPreferencesWidget_ui
                                   ->dictionaryFilePathComboBox->currentIndex();
  settings.setValue("DictionaryFilePathHistoryCurrentItemIndex",
                    current_combo_box_item_index);
  qDebug() << "Set DictionaryFilePathHistoryCurrentItemIndex to:"
           << current_combo_box_item_index;


  variant =
    mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePathComboBox
      ->property("isotopicDataFilePathHistoryItems_p");

  if(variant.canConvert<QStringList *>())
    {
      QStringList *isotopicDataFilePathHistoryItems_p =
        variant.value<QStringList *>();
      settings.setValue("IsotopicDataFilePathHistory",
                        *isotopicDataFilePathHistoryItems_p);

      qDebug() << "Set IsotopicDataFilePathHistory:"
               << *isotopicDataFilePathHistoryItems_p;
    }

  current_combo_box_item_index =
    mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePathComboBox
      ->currentIndex();
  settings.setValue("IsotopicDataFilePathHistoryCurrentItemIndex",
                    current_combo_box_item_index);
  qDebug() << "Set IsotopicDataFilePathHistoryCurrentItemIndex to:"
           << current_combo_box_item_index;

  settings.setValue("MassTolerancePpm",
                    mp_analyteIdentificationPreferencesWidget_ui
                      ->massToleranceDoubleSpinBox->value());

  settings.setValue("MassType",
                    mp_analyteIdentificationPreferencesWidget_ui
                        ->monoMassRadioButton->isChecked()
                      ? "MONO"
                      : "AVG");

  settings.setValue("ProcessingDetailsFileNameLineEdit",
                    mp_analyteIdentificationPreferencesWidget_ui
                      ->writeProcessingDetailsFileNameLineEdit->text());

  // The font that might have been set for the labels.
  QVariant label_font_variant = mp_analyteIdentificationPreferencesWidget_ui
                                  ->selectFontPushButton->property("LabelFont");

  if(label_font_variant.canConvert<QFont *>())
    {
      // A font was set.

      QFont *font_p = label_font_variant.value<QFont *>();
      settings.setValue("LabelFont", *font_p);
    }

  settings.endGroup();
}

void
ApplicationPreferencesWnd::readAnalyteIdentificationPreferences()
{
  qDebug() << "";

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("AnalyteIdentificationPreferences");

  QStringList file_paths_string_list;

  // Get the list of strings that are file paths to the dictionary files
  // used for the analyte identification process.

  // Note how, if no settings is found by that name, then a single string
  // is returned, but empty.
  file_paths_string_list =
    settings.value("DictionaryFilePathHistory", "").toStringList();

  qDebug() << "Got DictionaryFilePathHistory from settings:"
           << file_paths_string_list;

  // We do not need the single empty string that is returned if no settings
  // by the requested key were found.
  if(file_paths_string_list.size() == 1 && file_paths_string_list.first() == "")
    file_paths_string_list.clear();

  if(file_paths_string_list.size())
    {
      // If there were strings, set them to the combo box.
      mp_analyteIdentificationPreferencesWidget_ui->dictionaryFilePathComboBox
        ->insertItems(0, file_paths_string_list);

      QString text;
      for(qsizetype iter = 0;
          iter < mp_analyteIdentificationPreferencesWidget_ui
                   ->dictionaryFilePathComboBox->count();
          ++iter)
        text += mp_analyteIdentificationPreferencesWidget_ui
                  ->dictionaryFilePathComboBox->itemText(iter);

      qDebug() << "The combo box items:" << text;

      // Set the current text item in the combo box to the index 0.
      bool ok = false;
      int history_current_item_index =
        settings.value("DictionaryFilePathHistoryCurrentItemIndex", 0)
          .toInt(&ok);

      Q_ASSERT(ok);

      mp_analyteIdentificationPreferencesWidget_ui->dictionaryFilePathComboBox
        ->setCurrentIndex(history_current_item_index);

      // Now set the history to the combo box as a property because
      // this is what will be looked for when writing the settings.
      QStringList *dictionaryFilePathHistoryItems_p = nullptr;

      // Ask if the combo box already has a property by the name.
      // It should be cause that was set upon setting up of the
      // analyte identification preferences widget.
      QVariant history_string_list_pointer_variant =
        mp_analyteIdentificationPreferencesWidget_ui->dictionaryFilePathComboBox
          ->property("dictionaryFilePathHistoryItems_p");

      if(!history_string_list_pointer_variant.canConvert<QStringList *>())
        {
          // No such property, so allocate a new string list.
          dictionaryFilePathHistoryItems_p =
            new QStringList(file_paths_string_list);
        }
      else
        {
          dictionaryFilePathHistoryItems_p =
            history_string_list_pointer_variant.value<QStringList *>();
        }

      // Now clear the items in the string list (even if it was allocated
      // anew, that does no harm) and append the string list that we
      // have read from the settings.
      dictionaryFilePathHistoryItems_p->clear();
      dictionaryFilePathHistoryItems_p->append(file_paths_string_list);

      // At this point we have to set the string list allocated on the
      // heap as a property to the combo box.
      mp_analyteIdentificationPreferencesWidget_ui->dictionaryFilePathComboBox
        ->setProperty("dictionaryFilePathHistoryItems_p",
                      QVariant::fromValue(dictionaryFilePathHistoryItems_p));

      qDebug() << "The dictionary file path history "
                  "items:"
               << dictionaryFilePathHistoryItems_p->join(";");
    }

  // Now do the same for the strings that are paths to the isotopic
  // data files. Refer to comments above for explanations.

  file_paths_string_list =
    settings.value("IsotopicDataFilePathHistory", "").toStringList();

  qDebug() << "IsotopicDataFilePathHistory read from settings:"
           << file_paths_string_list;


  if(file_paths_string_list.size() == 1 && file_paths_string_list.first() == "")
    file_paths_string_list.clear();

  if(file_paths_string_list.size())
    {
      // If there were strings, set them to the combo box.
      mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePathComboBox
        ->insertItems(0, file_paths_string_list);

      QString text;
      for(qsizetype iter = 0;
          iter < mp_analyteIdentificationPreferencesWidget_ui
                   ->isotopicDataFilePathComboBox->count();
          ++iter)
        text += mp_analyteIdentificationPreferencesWidget_ui
                  ->isotopicDataFilePathComboBox->itemText(iter);

      qDebug() << "The combo box items:" << text;

      // Set the current text item in the combo box to the index 0.bool ok =
      // false;
      bool ok = false;
      mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePathComboBox
        ->setCurrentIndex(
          settings.value("IsotopicDataFilePathHistoryCurrentItemIndex", 0)
            .toUInt(&ok));
      Q_ASSERT(ok);

      // Now set the history to the combo box as a property because
      // this is what will be looked for when writing the settings.
      QStringList *isotopicDataFilePathHistoryItems_p = nullptr;

      // Ask if the combo box already has a property by the name.
      // It should be cause that was set upon setting up of the
      // analyte identification preferences widget.
      QVariant history_string_list_pointer_variant =
        mp_analyteIdentificationPreferencesWidget_ui
          ->isotopicDataFilePathComboBox->property(
            "isotopicDataFilePathHistoryItems_p");

      if(!history_string_list_pointer_variant.canConvert<QStringList *>())
        {
          // No such property, so allocate a new string list.
          isotopicDataFilePathHistoryItems_p =
            new QStringList(file_paths_string_list);
        }
      else
        {
          isotopicDataFilePathHistoryItems_p =
            history_string_list_pointer_variant.value<QStringList *>();
        }

      // Now clear the items in the string list (even if it was allocated
      // anew, that does no harm) and append the string list that we
      // have read from the settings.
      isotopicDataFilePathHistoryItems_p->clear();
      isotopicDataFilePathHistoryItems_p->append(file_paths_string_list);

      // At this point we have to set the string list allocated on the
      // heap as a property to the combo box.
      mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePathComboBox
        ->setProperty("isotopicDataFilePathHistoryItems_p",
                      QVariant::fromValue(isotopicDataFilePathHistoryItems_p));

      qDebug() << "The dictionary file path history "
                  "items:"
               << isotopicDataFilePathHistoryItems_p->join(";");
    }

  // The font that might have been set for the labels.
  QFont default_font = QApplication::font();
  QFont font         = settings.value("LabelFont", default_font).value<QFont>();

  if(font != default_font)
    {
      // One font was actually stored as setting.

      QVariant label_font_variant =
        mp_analyteIdentificationPreferencesWidget_ui->selectFontPushButton
          ->property("LabelFont");

      if(label_font_variant.canConvert<QFont *>())
        {
          // A font was set.

          QFont *font_p = label_font_variant.value<QFont *>();
          *font_p       = font;
        }
      else
        {
          // Allocate a new font on the heap;
          QFont *font_p = new QFont(font);
          mp_analyteIdentificationPreferencesWidget_ui->selectFontPushButton
            ->setProperty("LabelFont", QVariant::fromValue(font_p));
        }
    }

  bool ok = false;
  mp_analyteIdentificationPreferencesWidget_ui->massToleranceDoubleSpinBox
    ->setValue(settings.value("MassTolerancePpm", 20).toDouble(&ok));

  mp_analyteIdentificationPreferencesWidget_ui->monoMassRadioButton->setChecked(
    settings.value("MassType", "MONO").toString() == "MONO" ? true : false);

  mp_analyteIdentificationPreferencesWidget_ui
    ->writeProcessingDetailsFileNameLineEdit->setText(
      settings.value("ProcessingDetailsFileNameLineEdit", "").toString());

  settings.endGroup();
}

bool
ApplicationPreferencesWnd::getAnalyteIdentificationPreferences(
  libXpertMassCore::DeconvolutedMassIdMatcherParameters &parameters) const
{
  parameters.dictionaryFilePath = mp_analyteIdentificationPreferencesWidget_ui
                                    ->dictionaryFilePathComboBox->currentText();
  parameters.isotopicDataFilePath =
    mp_analyteIdentificationPreferencesWidget_ui->isotopicDataFilePathComboBox
      ->currentText();
  parameters.massTolerancePpm = mp_analyteIdentificationPreferencesWidget_ui
                                  ->massToleranceDoubleSpinBox->value();
  parameters.massType = mp_analyteIdentificationPreferencesWidget_ui
                            ->monoMassRadioButton->isChecked()
                          ? libXpertMassCore::Enums::MassType::MONO
                          : libXpertMassCore::Enums::MassType::AVG;
  parameters.processingDetailsLogFilePath =
    mp_analyteIdentificationPreferencesWidget_ui
      ->writeProcessingDetailsFileNameLineEdit->text();

  qDebug() << "Got the following parameters:" << parameters.toString();

  return true;
}

bool
ApplicationPreferencesWnd::shouldSynthesizeMassSpectrum() const
{
  return mp_lowMassDeconvolverPreferencesWidget_ui
    ->configureMassPeakShaperGroupBox->isChecked();
}

QString
ApplicationPreferencesWnd::syntheticMassSpectrumSampleName() const
{
  return mp_lowMassDeconvolverPreferencesWidget_ui
    ->syntheticMassSpectrumTitleLineEdit->text();
}

bool
ApplicationPreferencesWnd::shouldIdentifyAnalytes() const
{
  return mp_lowMassDeconvolverPreferencesWidget_ui
    ->configureAnalyteIdentificationGroupBox->isChecked();
}

bool
ApplicationPreferencesWnd::shouldLabelPeaksWithAnalyteId() const
{
  return mp_analyteIdentificationPreferencesWidget_ui->labelPeaksGroupBox
    ->isChecked();
}

QString
ApplicationPreferencesWnd::getLabelListName() const
{
  if(!shouldLabelPeaksWithAnalyteId())
    return QString();

  return mp_analyteIdentificationPreferencesWidget_ui->labelListNameLineEdit
    ->text();
}

bool
ApplicationPreferencesWnd::shouldAddNewLabels() const
{
  return mp_analyteIdentificationPreferencesWidget_ui->addLabelsCheckBox
    ->isChecked();
}

QColor
ApplicationPreferencesWnd::getLabelColor() const
{
  return mp_analyteIdentificationPreferencesWidget_ui->labelColorPushButton
    ->palette()
    .color(QPalette::ColorRole::Button);
}

QFont *
ApplicationPreferencesWnd::getLabelFont() const
{
  QVariant variant = mp_analyteIdentificationPreferencesWidget_ui
                       ->selectFontPushButton->property("LabelFont");

  if(variant.canConvert<QFont *>())
    {
      return variant.value<QFont *>();
    }

  return nullptr;
}

void
ApplicationPreferencesWnd::applyShortcutsPage()
{
  qDebug() << "The Shortcuts page is currently displayed.";

  // At this point, all we have to do is save the shortcuts.

  libXpertMassGui::ActionManager *action_manager_p =
    static_cast<Application *>(qApp)->getProgramWindow()->getActionManager();
  if(action_manager_p == nullptr)
    {
      qFatal() << "The action manager is nullptr !!!";
      return;
    }

  action_manager_p->saveActionData();
}

void
ApplicationPreferencesWnd::applyDiscoveriesPage()
{
  qDebug() << "The Discoveries page is currently displayed.";

  // Extract from the top widget of the discoveries page the two data we need.
  QSharedPointer<DiscoveriesPreferences> discoveries_preferences_qsp = nullptr;
  QSharedPointer<QList<QPlainTextEdit *>> plain_text_edit_list_qsp   = nullptr;

  QVariant variant =
    mp_discoveriesPage_ui->scrollArea->property("discoveries_preferences_qsp");

  if(variant.canConvert<QSharedPointer<DiscoveriesPreferences>>())
    {
      discoveries_preferences_qsp =
        variant.value<QSharedPointer<DiscoveriesPreferences>>();

      if(discoveries_preferences_qsp.isNull())
        {
          qFatal() << "Programming error.";
          return;
        }
    }
  else
    {
      qFatal() << "Programming error.";
      return;
    }

  variant =
    mp_discoveriesPage_ui->scrollArea->property("plain_text_edit_list_qsp");

  if(variant.canConvert<QSharedPointer<QList<QPlainTextEdit *>>>())
    {
      plain_text_edit_list_qsp =
        variant.value<QSharedPointer<QList<QPlainTextEdit *>>>();

      if(plain_text_edit_list_qsp.isNull())
        {
          qFatal() << "Programming error.";
          return;
        }
    }
  else
    {
      qFatal() << "Programming error.";
      return;
    }

  // Start by setting the value to a ground state.
  discoveries_preferences_qsp->m_recordTarget = RecordTarget::RECORD_NOTHING;

  if(mp_discoveriesPage_ui->toConsoleCheckBox->isChecked())
    discoveries_preferences_qsp->m_recordTarget |=
      RecordTarget::RECORD_TO_CONSOLE;

  if(mp_discoveriesPage_ui->toClipboardCheckBox->isChecked())
    {
      discoveries_preferences_qsp->m_recordTarget |=
        RecordTarget::RECORD_TO_CLIPBOARD;

      QMessageBox::StandardButton ret;
      QString msgBoxTitle =
        QString("%1 - Record to clipboard").arg(m_applicationName);
      ret = QMessageBox::warning(this,
                                 msgBoxTitle,
                                 tr("Initialize the clipboard by emptying it?"),
                                 QMessageBox::Yes | QMessageBox::No);

      if(ret == QMessageBox::Yes)
        {
          QClipboard *clipboard = QApplication::clipboard();
          clipboard->setText("", QClipboard::Clipboard);
        }
    }

  if(mp_discoveriesPage_ui->toFileGroupBox->isChecked())
    discoveries_preferences_qsp->m_recordTarget |= RecordTarget::RECORD_TO_FILE;

  // qDebug() << "Record target: " <<
  // discoveries_preferences_qsp->m_recordTarget ;

  // And now if the record should be directed to a file, make the
  // verifications that we can open the files in either write only or
  // append mode.

  if((discoveries_preferences_qsp->m_recordTarget &
      RecordTarget::RECORD_TO_FILE) == RecordTarget::RECORD_TO_FILE)
    {
      // qDebug() << "Record to file bit set.";

      discoveries_preferences_qsp->m_fileName =
        mp_discoveriesPage_ui->analysisFileLineEdit->text();
      QFile analysisFile(discoveries_preferences_qsp->m_fileName);

      if(mp_discoveriesPage_ui->overwriteRadioButton->isChecked())
        {
          // qDebug() << "overwrite checked";

          discoveries_preferences_qsp->m_fileOpenMode = QIODevice::Truncate;

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

          analysisFile.open(QIODevice::WriteOnly);

          analysisFile.close();
        }
      else
        {
          // qDebug() << "append checked";

          discoveries_preferences_qsp->m_fileOpenMode = QIODevice::Append;

          analysisFile.open(QIODevice::Append);
          analysisFile.close();
        }
    }

  // At this point we need to get all the format strings from the various
  // tab pages.

  for(int iter = 0; iter < FormatType::LAST; ++iter)
    {
      QPlainTextEdit *textEdit = plain_text_edit_list_qsp->at(iter);
      QString formatString     = textEdit->toPlainText();

      // We need to get the DataFormatStringSpecif instance for the current
      // iter. We assume that the member m_discoveriesPreferences instance has
      // been correctly setup.

      DataFormatStringSpecif *specif =
        discoveries_preferences_qsp->m_dataFormatStringSpecifHash.value(iter);

      // Now modify the format string.
      specif->m_format = formatString;

      // qDebug() << "Dealt with iter " << iter << "with format string:" <<
      // specif->m_format;
    }

  // Store the history to the settings, alongside the geometry of the window.

  QStringList historyList;

  for(int iter = 0; iter < mp_discoveriesPage_ui->historyComboBox->count();
      ++iter)
    historyList.append(mp_discoveriesPage_ui->historyComboBox->itemText(iter));

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

  QSettings settings(settings_file_path, QSettings::IniFormat);

  settings.beginGroup("DiscoveriesPreferences");

  settings.setValue("destSplitter",
                    mp_discoveriesPage_ui->destSplitter->saveState());
  settings.setValue("formatSplitter",
                    mp_discoveriesPage_ui->formatSplitter->saveState());

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

  // And now store the different format strings, if there are any, so that
  // upon reopening, it won't necessarily be required to set them again and
  // again.

  for(int iter = 0; iter < FormatType::LAST; ++iter)
    {
      const DataFormatStringSpecif *specif =
        discoveries_preferences_qsp->m_dataFormatStringSpecifHash.value(iter);

      if(specif == nullptr)
        {
          qFatal() << "Programming error.";
          return;
        }

      QString tabString   = specif->m_label;
      QString settingsKey = QString("%1_formatString").arg(tabString);

      QPlainTextEdit *textEdit = static_cast<QPlainTextEdit *>(
        mp_discoveriesPage_ui->formatsTabWidget->widget(iter));

      QString settingsValue = textEdit->toPlainText();

      settings.setValue(settingsKey, settingsValue);

      // qDebug() << "Writing setting key/value for iter"
      //<< iter << settingsKey << settingsValue;
    }

  settings.sync();

  settings.endGroup();

  // qDebug() << "Done writing discoveries preferences to " <<
  // settings_file_path;

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

  if(program_window_p == nullptr)
    qFatal() << "The program window is nullptr !!!";

  // Finally make sure we set these settings to the ProgramWindow, because this
  // is the location where these settings are asked for by the users of
  // this feature.
  program_window_p->updateDiscoveriesPreferences(*discoveries_preferences_qsp);
}

void
ApplicationPreferencesWnd::applyCentroidationPage()
{
  // qDebug() << "The Centroidation page is currently displayed.";
  writePeakPickingPreferences();
}

void
ApplicationPreferencesWnd::applyLowMassDeconvolver()
{
  writeLowMassDeconvolverPreferences();
}

void
ApplicationPreferencesWnd::applyMassPeakShaperPage()
{
  // qDebug() << "The MassPeakShaper page is currently displayed.";
  mp_massPeakShaperConfigWidget_ui->writeSettings(
    static_cast<Application *>(QCoreApplication::instance())
      ->getUserConfigSettingsFilePath());
}

void
ApplicationPreferencesWnd::applyAnalyteIdentificationPage()
{
  writeAnalyteIdentificationPreferences();
  qDebug() << "Done writing the AnalyteIdentificationPreferences.";
}

void
ApplicationPreferencesWnd::applyPluginsPage()
{
  // qDebug() << "The Plugins page is currently displayed.";

  writePluginsPreferences();
}

} // namespace MineXpert
} // namespace MsXpS
