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


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

/////////////////////// IsoSpec
#include <IsoSpec++/isoSpec++.h>
#include <IsoSpec++/element_tables.h>
// extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double
// elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const int
// elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double
// elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];


/////////////////////// Local includes
#include "IsoSpecDlg.hpp"
#include "IsoSpecTableViewModel.hpp"
#include "PeakShaperDlg.hpp"

// For the element data groupbox widgets that are packed dynamically
#include "ui_ElementGroupBoxWidget.h"


namespace msXpSmineXpert
{


IsoSpecDlg::IsoSpecDlg(QWidget *parent, const QString &applicationName)
  : QDialog{parent}, m_applicationName{applicationName}
{
  if(!parent)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  mp_parentWnd = parent;
  setWindowTitle(QString("%1 - IsoSpec isotopic cluster calculations")
                   .arg(m_applicationName));

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

  m_ui.setupUi(this);

  setupDialog();
}


void
IsoSpecDlg::closeEvent(QCloseEvent *event)
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";
  // writeSettings();
}


IsoSpecDlg::~IsoSpecDlg()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  // Do not free m_isoSpecStandardStaticAtomList because for unknow reasons it
  // crashes... The vector clear will do since the QList and Vector hold the
  // same pointers. Same for the user variant.

  m_isoSpecStandardStaticAtomVector.clear();
  m_isoSpecStandardUserAtomVector.clear();

  writeSettings();
}


//! Save the settings to later restore the window in its same position.
void
IsoSpecDlg::writeSettings()
{
  QSettings settings;
  settings.beginGroup("IsoSpecDlg");
  settings.setValue("geometry", saveGeometry());
  settings.setValue("splitter", m_ui.splitter->saveState());
  settings.endGroup();
}


//! Read the settings to restore the window in its last position.
void
IsoSpecDlg::readSettings()
{
  QSettings settings;
  settings.beginGroup("IsoSpecDlg");
  restoreGeometry(settings.value("geometry").toByteArray());
  m_ui.splitter->restoreState(settings.value("splitter").toByteArray());
  settings.endGroup();
}


bool
IsoSpecDlg::setupDialog()
{
  m_ui.ionChargeSpinBox->setRange(1, 10000);
  m_ui.ionChargeSpinBox->setValue(1);

  setWindowTitle(QString("%1 - IsoSpec isotopic cluster calculations")
                   .arg(m_applicationName));

  readSettings();


  // Data loading functions that are tailored to the files to handle (either
  // IsoSpec standard library data files or user manual config files).

  connect(m_ui.saveIsoSpecStandardStaticTablesPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::saveIsoSpecStandardStaticTables);


  connect(m_ui.loadIsoSpecStandardUserTablesPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::loadIsoSpecStandardUserTables);

  connect(m_ui.saveIsoSpecStandardUserTablesPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::saveIsoSpecStandardUserTables);

  connect(m_ui.loadUserManualConfigurationPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::loadUserManualConfiguration);

  connect(m_ui.saveUserManualConfigurationPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::saveUserManualConfiguration);

  connect(m_ui.toPeakShaperPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::toPeakShaper);

  connect(m_ui.addElementPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::addElementGroupBox);

  // The run functions depending on the configuration that needs to be used.
  connect(m_ui.runIsoSpecStandardStaticConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::runIsoSpecStandardStaticConfig);

  connect(m_ui.runIsoSpecStandardUserConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::runIsoSpecStandardUserConfig);

  connect(m_ui.runIsoSpecManualConfigPushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::runIsoSpecManualConfig);


  // Load the element/isotope data from the IsoSpec header files. No changes
  // possible.
  if(!initializeIsoSpecStandardStaticEntityList())
    {
      message("Failed to load IsoSpec entities from the C++ headers", 5000);

      qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
               << "Failed to load IsoSpec entities from the C++ headers.";

      return false;
    }

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

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

  return true;
}


QString
IsoSpecDlg::formatLibIsoSpecAtomAsMassXpertXmlElement(const msXpSlibmass::Atom &atom)
{
  // This function is merely utilitary when we want to parse the IsoSpec
  // library's data into a massXpert xml format.

	// We do not indent the text, any editor will do.
	
  QString text = "<atom>\n";

  QString capitalized_name = atom.name().replace(0, 1, atom.name().at(0).toUpper());

  text += QString("<name>%2</name>\n").arg(capitalized_name);

  text += QString("<symbol>%2</symbol>\n").arg(atom.symbol());

  // At this point we have the different isotope(s) of the atom. We
  // delegate that to the appropriate function of the isotope class.

  for(int iter = 0; iter < atom.isotopeList().size(); ++iter)
    {
			msXpSlibmass::Isotope isotope(*atom.isotopeList().at(iter));

			text += "<isotope>\n";

      text += QString("<mass>%1</mass>\n").arg(QString::number(isotope.mass(), 'f', 10));
			// The libIsoSpec++ library stores abundance data in the form of
			// probabilities and not percentages. We thus need to * 100 the abundance.
      text += QString("<abundance>%1</abundance>\n").arg(QString::number(100 * isotope.abundance(), 'f', 60));

      text += QString("</isotope>\n");
    }

  text += "</atom>\n";

  return text;
}


bool
IsoSpecDlg::initializeIsoSpecStandardStaticEntityList()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  // We need to allocate one IsoSpecEntity instance for each element in the
  // various arrays in the IsoSpec++ source code header file.

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


  // This was used to export in the format of massXpert's polChemDef the
  // atoms parsed from the libIsoSpec++ lib. See below, same commented text.

	 QFile file("/tmp/atom_data_as_mxp_pol_chem_def_data");
	 file.open(QFile::WriteOnly | QFile::Truncate);
	 QTextStream textStream(&file);


  // Big sanity check, all the arrays must be the same length!
  std::size_t arrayLength = checkConsistencyIsoSpecTables();
  if(arrayLength < 1)
    return false;

  // At this point we know everything is fine. just iterate in the array.
  // Another last sanity check:
  // if(IsoSpec::ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES != arrayLength)
  //{
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Found corruption: the size of arrays is not like expected.";

  // return false;
  //}

  // We will feed the m_isoSpecAtomList with the data found in the IsoSpec
  // tables. This is run a single time at dialog creation time, so the
  // list/array are empty by definition;

  // To record the element that was read at previous iteration.
  QString lastElement;
  msXpSlibmass::Atom *atom = nullptr;

  for(std::size_t iter = 0; iter < arrayLength; ++iter)
    {
      msXpSlibmass::IsoSpecEntity *newIsoSpecEntity =
        new msXpSlibmass::IsoSpecEntity(
          QString(IsoSpec::elem_table_element[iter]),
          QString(IsoSpec::elem_table_symbol[iter]),
          IsoSpec::elem_table_atomicNo[iter],
          IsoSpec::elem_table_mass[iter],
          IsoSpec::elem_table_massNo[iter],
          IsoSpec::elem_table_extraNeutrons[iter],
          IsoSpec::elem_table_probability[iter],
          IsoSpec::elem_table_log_probability[iter],
          IsoSpec::elem_table_Radioactive[iter]);

      m_isoSpecStandardStaticEntityList.append(newIsoSpecEntity);

      // Now construct the list of msXpSlibmass::Atom instances that will be
      // required to work with the msXpSlibmass::Formula.

      QString currentElement = QString(IsoSpec::elem_table_element[iter]);
      QString currentSymbol  = QString(IsoSpec::elem_table_symbol[iter]);

      double mass      = IsoSpec::elem_table_mass[iter];
      double abundance = IsoSpec::elem_table_probability[iter];

      if(currentElement != lastElement)
        {
          // We are starting a new Atom. Either this is the very first atom or
          // one other atom had been prepared. In that case we need to store it
          // in the list.

          if(atom != nullptr)
            {
              // Sanity check, we cannot be at first iteration.
              if(iter == 0)
                qFatal(
                  "Fatal error at %s@%d -- %s(). "
                  "Programming error."
                  "Program aborted.",
                  __FILE__,
                  __LINE__,
                  __FUNCTION__);

              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
              //<< "Now appending the previously formed atom:"
              //<< atom->name();

              // This atom list is necessary because we work with
              // msXpSlibmass::Formula and it need it.
              m_isoSpecStandardStaticAtomList.append(atom);

              // This vector is the one that will actually free the Atom*
              // instances when closing the dialog.
              m_isoSpecStandardStaticAtomVector.push_back(atom);

              // This was used to export in the format of massXpert's polChemDef
              // the atoms parsed from the libIsoSpec++ lib.

							 textStream << formatLibIsoSpecAtomAsMassXpertXmlElement(*atom);
            }

          // Now start with the new atom.

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Now creating a new atom" << currentElement;

          atom = new msXpSlibmass::Atom(currentElement, currentSymbol);

          m_isoSpecStandardStaticAtomList.append(atom);
          m_isoSpecStandardStaticAtomVector.push_back(atom);

          // Now feed it with the very first isotope.
          msXpSlibmass::Isotope *isotope =
            new msXpSlibmass::Isotope(mass, abundance);

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Now appending isotope:" << isotope->massString()
          //<< "for atom" << atom->name();

          atom->appendIsotope(isotope);
        }
      else // same as if(currentElement == lastElement)
        {
          // We are still going on with the same alement, so add a new isotope.
          msXpSlibmass::Isotope *isotope =
            new msXpSlibmass::Isotope(mass, abundance);

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Now appending isotope:" << isotope->massString()
          //<< "for atom" << atom->name();

          atom->appendIsotope(isotope);
        }

      // Store the current element name so that at next iteration we can see if
      // we are still working on isotoes of the same element or starting a new
      // element.
      lastElement = currentElement;
    }

  // During that last iteration, we had constructed an atom that we must not
  // forget:
  if(atom != nullptr)
    {
      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "Now appending the very last previously formed atom:"
      //<< atom->name();

      m_isoSpecStandardStaticAtomList.append(atom);
      m_isoSpecStandardStaticAtomVector.push_back(atom);


      // This was used to export in the format of massXpert's polChemDef the
      // atoms parsed from the libIsoSpec++ lib.

			textStream << formatLibIsoSpecAtomAsMassXpertXmlElement(*atom);
    }

  // This was used to export in the format of massXpert's polChemDef the atoms
  // parsed from the libIsoSpec++ lib.
  // file.close();

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  ////<< "The list has" << m_isoSpecEntityList.size() << "items"
  ////<< "The atom list has" << m_isoSpecAtomList.size() << "atoms";
  //<< "This vector has" << m_isoSpecAtomVector.size() << "items";

  if(m_isoSpecStandardStaticAtomList.size())
    return true;
  else
    return false;
}


void
IsoSpecDlg::setupIsoSpecStandardStaticTableView()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  mpa_isoSpecStandardStaticTableViewModel =
    new IsoSpecTableViewModel(&m_isoSpecStandardStaticEntityList, this);

  m_ui.isoSpecStandardStaticConfigTableView->setModel(
    mpa_isoSpecStandardStaticTableViewModel);
  m_ui.isoSpecStandardStaticConfigTableView->setIsoSpecEntityList(
    &m_isoSpecStandardStaticEntityList);
  m_ui.isoSpecStandardStaticConfigTableView->setParentDlg(this);
  mpa_isoSpecStandardStaticTableViewModel->setTableView(
    m_ui.isoSpecStandardStaticConfigTableView);
}


void
IsoSpecDlg::setupIsoSpecStandardUserTableView()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

  mpa_isoSpecStandardUserTableViewModel =
    new IsoSpecTableViewModel(&m_isoSpecStandardUserEntityList, this);

  m_ui.isoSpecStandardUserConfigTableView->setModel(
    mpa_isoSpecStandardUserTableViewModel);
  m_ui.isoSpecStandardUserConfigTableView->setIsoSpecEntityList(
    &m_isoSpecStandardUserEntityList);
  m_ui.isoSpecStandardUserConfigTableView->setParentDlg(this);
  mpa_isoSpecStandardUserTableViewModel->setTableView(
    m_ui.isoSpecStandardUserConfigTableView);
}


bool
IsoSpecDlg::saveIsoSpecStandardStaticTables()
{

  // Iterate in the various lines of the tableView and write the selected ones
  // to a tsv file.

  QItemSelectionModel *selectionModel =
    m_ui.isoSpecStandardStaticConfigTableView->selectionModel();
  QModelIndexList selectedIndices = selectionModel->selectedRows();

  if(!selectedIndices.size())
    {
      message(
        "No entities are selected. Select the entities to export first (Ctrl A "
        "to select all)",
        5000);
      return false;
    }

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Number of selected rows:" << selectedIndices.size();

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

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

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

  QTextStream outStream(&file);
  outStream.setRealNumberPrecision(60);

  for(int iter = 0; iter < selectedIndices.size(); ++iter)
    {
      QModelIndex index = selectedIndices.at(iter);

      int row = index.row();

      msXpSlibmass::IsoSpecEntity *isoSpecEntity =
        m_isoSpecStandardStaticEntityList.at(row);

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "the entity:" << isoSpecEntity->asText();

      // Now send the line to the file.
      outStream << isoSpecEntity->getElement() << "\t"
                << isoSpecEntity->getSymbol() << "\t"
                << isoSpecEntity->getAtomicNo() << "\t";

      outStream.setRealNumberPrecision(60);

      outStream << isoSpecEntity->getMass() << "\t"
                << isoSpecEntity->getMassNo() << "\t"
                << isoSpecEntity->getExtraNeutrons() << "\t"
                << isoSpecEntity->getProbability() << "\t"
                << isoSpecEntity->getLogProbability() << "\t"
                << isoSpecEntity->getRadioactive() << "\n";

      outStream.flush();
    }

  file.close();

  return true;
}


bool
IsoSpecDlg::saveIsoSpecStandardUserTables()
{

  // Iterate in the various lines of the tableView and write the selected ones
  // to a tsv file.

  QItemSelectionModel *selectionModel =
    m_ui.isoSpecStandardUserConfigTableView->selectionModel();
  QModelIndexList selectedIndices = selectionModel->selectedRows();

  if(!selectedIndices.size())
    {
      message(
        "No entities are selected. Select the entities to export first (Ctrl A "
        "to select all)",
        5000);
      return false;
    }

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Number of selected rows:" << selectedIndices.size();

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

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

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

  QTextStream outStream(&file);
  outStream.setRealNumberPrecision(60);

  for(int iter = 0; iter < selectedIndices.size(); ++iter)
    {
      QModelIndex index = selectedIndices.at(iter);

      int row = index.row();

      msXpSlibmass::IsoSpecEntity *isoSpecEntity =
        m_isoSpecStandardUserEntityList.at(row);

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "the entity:" << isoSpecEntity->asText();

      // Now send the line to the file.
      outStream << isoSpecEntity->getElement() << "\t"
                << isoSpecEntity->getSymbol() << "\t"
                << isoSpecEntity->getAtomicNo() << "\t";

      outStream.setRealNumberPrecision(60);

      outStream << isoSpecEntity->getMass() << "\t"
                << isoSpecEntity->getMassNo() << "\t"
                << isoSpecEntity->getExtraNeutrons() << "\t"
                << isoSpecEntity->getProbability() << "\t"
                << isoSpecEntity->getLogProbability() << "\t"
                << isoSpecEntity->getRadioactive() << "\n";

      outStream.flush();
    }

  file.close();

  return true;
}


bool
IsoSpecDlg::loadIsoSpecStandardUserTables()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  // The format of the file from which data is to be loaded is mimicking the
  // tables that are defined as a number of C arrays in the libIsoSpec++ header
  // element_tables.c/h files.

  // When the dialog window is opened, these C arrays are parsed in the
  // IsoSpecDlg::initializeIsoSpecStandardStaticEntityList() function. We need
  // to mimick this function but reading data from text files. The text files
  // were initially created by the user with the
  // saveIsoSpecStandardStaticTables() function. These files are TSV
  // (tab-separated values).

  // This function is not like initializeIsoSpecStandardStaticEntityList,
  // because here we might already have some items in the model/table view. Thus
  // we first append all the parsed IsoSpecEntity instances to a local list,
  // then we make sure we remove all the items from the model and we append the
  // new ones there. The model has a pointer to *this dialog entity list.

  // Because we need to ensure that *this m_isoSpecStandardUserEntityList, the
  // model and the view are consistent, we delegate all removal/additions to the
  // model.

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

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

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

  QRegularExpression commentRegexp("^\\s+#");

  // Local lists and vector for storing the parsed data until the end of the
  // file. When everything was fine, then, move the data to the member
  // lists/vecotr.
  QList<msXpSlibmass::IsoSpecEntity *> isoSpecEntityList;
  QList<msXpSlibmass::Atom *> isoSpecAtomList;

  msXpSlibmass::Atom *atom = nullptr;

  QString lastElement;

  // Now start reading from file

  int iter = 0;

  QTextStream in(&file);
  while(!in.atEnd())
    {
      QString line = in.readLine();

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "line:" << line;

      // Ignore empty or comment lines
      if(line.length() < 1)
        continue;

      QRegularExpressionMatch match = commentRegexp.match(line);
      if(match.hasMatch())
        continue;

      QStringList dataStringList = line.split("\t", QString::SkipEmptyParts);

      if(dataStringList.size() <
         static_cast<int>(IsoSpecTableViewColumns::TOTAL_COLUMNS))
        {
          message("Failed to load data. Make sure the file is not corrupted.",
                  5000);
          return false;
        }

      // This is the order in which the data must be set in the file for each
      // line.

      QString element, symbol;
      int atomicNo, massNo, extraNeutrons;
      double mass, probability, logProbability;
      bool radioactive;

      bool ok    = false;
      int errors = 0;

      element =
        dataStringList.at(static_cast<int>(IsoSpecTableViewColumns::ELEMENT));

      symbol =
        dataStringList.at(static_cast<int>(IsoSpecTableViewColumns::SYMBOL));

      atomicNo =
        dataStringList.at(static_cast<int>(IsoSpecTableViewColumns::ATOMIC_NO))
          .toInt(&ok);
      if(!ok)
        ++errors;

      mass = dataStringList.at(static_cast<int>(IsoSpecTableViewColumns::MASS))
               .toDouble(&ok);
      if(!ok)
        ++errors;

      massNo =
        dataStringList.at(static_cast<int>(IsoSpecTableViewColumns::MASS_NO))
          .toInt(&ok);
      if(!ok)
        ++errors;

      extraNeutrons =
        dataStringList
          .at(static_cast<int>(IsoSpecTableViewColumns::EXTRA_NEUTRONS))
          .toInt(&ok);
      if(!ok)
        ++errors;

      probability =
        dataStringList
          .at(static_cast<int>(IsoSpecTableViewColumns::PROBABILITY))
          .toDouble(&ok);
      if(!probability || !ok)
        ++errors;

      logProbability =
        dataStringList
          .at(static_cast<int>(IsoSpecTableViewColumns::LOG_PROBABILITY))
          .toDouble(&ok);
      if(!ok)
        ++errors;

      radioactive =
        dataStringList
          .at(static_cast<int>(IsoSpecTableViewColumns::RADIOACTIVE))
          .toInt(&ok);
      if(!ok)
        ++errors;

      if(errors)
        {
          message(QString("Failed to read line: %1").arg(line), 5000);
          return false;
        }

      msXpSlibmass::IsoSpecEntity *newIsoSpecEntity =
        new msXpSlibmass::IsoSpecEntity(element,
                                        symbol,
                                        atomicNo,
                                        mass,
                                        massNo,
                                        extraNeutrons,
                                        probability,
                                        logProbability,
                                        radioactive ? true : false);

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "entity:" << newEntity->asText();

      isoSpecEntityList.append(newIsoSpecEntity);

      // Now construct the list of msXpSlibmass::Atom instances that will be
      // required to work with the msXpSlibmass::Formula.

      if(element != lastElement)
        {
          // We are starting a new Atom. Either this is the very first atom or
          // one other atom had been prepared. In that case we need to store it
          // in the list.

          if(atom != nullptr)
            {
              // Sanity check, we cannot be at first iteration.
              if(iter == 0)
                qFatal(
                  "Fatal error at %s@%d -- %s(). "
                  "Programming error."
                  "Program aborted.",
                  __FILE__,
                  __LINE__,
                  __FUNCTION__);

              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
              //<< "Now appending the previously formed atom:"
              //<< atom->name();

              // This atom list is necessary because we work with
              // msXpSlibmass::Formula and it need it.
              isoSpecAtomList.append(atom);
            }

          // Now start with the new atom.

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Now creating a new atom" << currentElement;

          atom = new msXpSlibmass::Atom(element, symbol);

          isoSpecAtomList.append(atom);

          // Now feed it with the very first isotope.
          msXpSlibmass::Isotope *isotope =
            new msXpSlibmass::Isotope(mass, probability);

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Now appending isotope:" << isotope->massString()
          //<< "for atom" << atom->name();

          atom->appendIsotope(isotope);
        }
      else // same as if(currentElement == lastElement)
        {
          // We are still going on with the same alement, so add a new isotope.
          msXpSlibmass::Isotope *isotope =
            new msXpSlibmass::Isotope(mass, probability);

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Now appending isotope:" << isotope->massString()
          //<< "for atom" << atom->name();

          atom->appendIsotope(isotope);
        }

      // Store the current element name so that at next iteration we can see if
      // we are still working on isotoes of the same element or starting a new
      // element.
      lastElement = element;

      // Store the iteration count because we need it for checks.
      ++iter;
    }
  // End of
  // while(!in.atEnd())

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Finished creating all the IsoSpecEntity instances.";

  file.close();

  // At this point, it seems that the loading went fine.

  // There might be old entities in the user table view/model, so first remove
  // everything and then add the new ones.

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Now removing the old entities";

  mpa_isoSpecStandardUserTableViewModel->removeIsoSpecEntities(0, -1);

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Now adding the new entities";

  mpa_isoSpecStandardUserTableViewModel->addIsoSpecEntities(isoSpecEntityList);

  // for(int iter = 0; iter < m_isoSpecEntityList.size(); ++iter)
  //{
  // msXpSlibmass::IsoSpecEntity *entity = m_isoSpecEntityList.at(iter);

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Entity:" << entity->asText();
  //}

  // Now that the entities stuff went flawlessly, let's do the same with the
  // Atom* instances.

  // But we first need to delete the preexisting Atom instances that might have
  // been created during a previous data load operation. Note that we do not
  // need to work on the entity list, because that it handled by the
  // removeIsoSpecEntities function in the model that was called above.

  // First clear (no delete) all the atoms form the list
  m_isoSpecStandardUserAtomList.clear();
  // Now clear them form the vector (will actuall free them)
  m_isoSpecStandardUserAtomVector.clear();

  // And now transfer the new Atom instances to the list/vector that we have
  // emptied at the moment.

  while(isoSpecAtomList.size())
    {
      msXpSlibmass::Atom *atom = isoSpecAtomList.takeFirst();
      m_isoSpecStandardUserAtomList.append(atom);
      m_isoSpecStandardUserAtomVector.push_back(atom);
    }

  return true;
}


void
IsoSpecDlg::toPeakShaper()
{
  // We need to get the text from the output text edit widget and send that to
  // the input widget of the PeakShaperDlg.

  QString results = m_ui.isoSpecOutputDataPlainTextEdit->toPlainText();

  // Make sure we pass the MainWindow pointer as the parent object of the
  // PeakShaperDlg, otherwise crash when using ConsoleWnd.
  PeakShaperDlg *peak_shaper_dlg_p =
    new PeakShaperDlg(dynamic_cast<QWidget *>(mp_parentWnd), m_applicationName);

  peak_shaper_dlg_p->show();
  peak_shaper_dlg_p->raise();
  peak_shaper_dlg_p->activateWindow();

  peak_shaper_dlg_p->setCentroidData(results);
}


QString
IsoSpecDlg::checkFormula(msXpSlibmass::Formula &formula,
                         const QList<msXpSlibmass::Atom *> &atomRefList)
{
  QString result;

  // Check the syntax of the formula, not that we need an obligatory count
  // index
  // even for elements that are present a single time (H2O1 note the 1).
  int errors = 0;

  // IsoSpec requires that even single-count element be qualified with an
  // index
  // (H2O1)

  formula.setForceCountIndex(true);
  if(!formula.checkSyntax())
    ++errors;

  // Now validate the formula. This step is also useful to count the elements
  // in the formula.
  if(!formula.validate(
       atomRefList, true /*store atom count*/, true /*reset counts*/))
    ++errors;

  if(errors)
    {
      return QString(
        "The formula entered is not valid. Ensure that each element has a "
        "count index (like H2O1). All the elements need to be defined.");
    }

  return QString();
}


std::size_t
IsoSpecDlg::validateManualConfig()
{

  // The manual configuration is performed by entering by hand the chemical
  // elements and their isotope (mass,prob) pairs. The element is defined
  // using
  // the symbol. The count of the element in the formula is set in a spin box.
  //
  // Validating the manual configuration means iterating in all the widgets
  // that
  // have been created by the user, extracting from them all the atomistic
  // data.

  // Make clean room.
  m_userManualAtomVector.clear();
  m_atomCountPairVector.clear();

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

  std::size_t elementNumber;

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

  elementNumber = elementGroupBoxList.size();

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "The number of elements manually configured is:" << elementNumber;

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

  for(std::size_t iter = 0; iter < elementNumber; ++iter)
    {
      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "Iterating in element group box index" << iter;

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

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

      if(symbolLineEdit == nullptr)
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "Programming error."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);

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

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

      // Store the (symbol,count) pair for later.
      m_atomCountPairVector.push_back(std::pair<QString, int>(symbol, count));

      // Instantiate new atom as the perfect box to enshrine all the isotopes.
      msXpSlibmass::Atom atom(symbol, symbol);

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

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

      if(!isotopeFrameList.size())
        {
          message("One of the elements has not isotope defined.", 4000);
          return 0;
        }

      for(int jter = 0; jter < isotopeFrameList.size(); ++jter)
        {
          QFrame *currentFrame = isotopeFrameList.at(jter);

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

          if(massSpinBox == nullptr)
            qFatal(
              "Fatal error at %s@%d -- %s(). "
              "Programming error."
              "Program aborted.",
              __FILE__,
              __LINE__,
              __FUNCTION__);

          double mass = massSpinBox->value();

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

          if(probSpinBox == nullptr)
            qFatal(
              "Fatal error at %s@%d -- %s(). "
              "Programming error."
              "Program aborted.",
              __FILE__,
              __LINE__,
              __FUNCTION__);

          double prob = probSpinBox->value();

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

              return 0;
            }

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Iterated in isotope:" << mass << "/" << prob;

          atom.appendIsotope(new msXpSlibmass::Isotope(mass, prob));
        }
      // End of iterating in the isotope frame list.

      // Store that atom for later use.
      m_userManualAtomVector.push_back(atom);
    }
  // End of iterating in the element group box list.

  // Sanity checks

  if(elementNumber != static_cast<std::size_t>(m_userManualAtomVector.size()))
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The parsing of the element data has failed."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(m_atomCountPairVector.size() != elementNumber)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The parsing of the element data has failed."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  return elementNumber;
}


std::pair<QDoubleSpinBox *, QDoubleSpinBox *>
IsoSpecDlg::addIsotopeFrame()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ <<
  // "()";

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

  QPushButton *sender = static_cast<QPushButton *>(QObject::sender());
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "sender:" << sender;

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

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

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

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

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

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

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

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

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

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

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

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

  connect(plusIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::addIsotopeFrame);

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

  sizePolicy.setHeightForWidth(
    plusIsotopePushButton->sizePolicy().hasHeightForWidth());
  plusIsotopePushButton->setSizePolicy(sizePolicy);
  QIcon icon1;
  icon1.addFile(
    QStringLiteral(":/images/plus.svg"), QSize(), QIcon::Normal, QIcon::Off);
  plusIsotopePushButton->setIcon(icon1);

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

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

  connect(minusIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::removeIsotopeFrame);

  sizePolicy.setHeightForWidth(
    minusIsotopePushButton->sizePolicy().hasHeightForWidth());
  minusIsotopePushButton->setSizePolicy(sizePolicy);
  QIcon icon;
  icon.addFile(
    QStringLiteral(":/images/minus.svg"), QSize(), QIcon::Normal, QIcon::Off);
  minusIsotopePushButton->setIcon(icon);

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

  elementGroupVBoxLayout->addWidget(newIsotopeFrame);

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

void
IsoSpecDlg::removeIsotopeFrame()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ <<
  // "()";

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

  QPushButton *sender = static_cast<QPushButton *>(QObject::sender());
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "sender:" << sender;

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

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

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

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

  delete isotopeFrame;
}


// std::pair<QLineEdit *, QSpinBox *>
QGroupBox *
IsoSpecDlg::addElementSkeletonGroupBox()
{
  QGroupBox *elementGroupBox;
  elementGroupBox = new QGroupBox(m_ui.scrollAreaWidgetContents);
  elementGroupBox->setObjectName(QStringLiteral("elementGroupBox"));

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

  QHBoxLayout *symbolLineEditHBoxLayout = new QHBoxLayout();

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

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

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

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

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

  QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  sizePolicy.setHorizontalStretch(0);
  sizePolicy.setVerticalStretch(0);
  sizePolicy.setHeightForWidth(
    minusElementPushButton->sizePolicy().hasHeightForWidth());
  minusElementPushButton->setSizePolicy(sizePolicy);
  QIcon icon;
  icon.addFile(
    QStringLiteral(":/images/minus.svg"), QSize(), QIcon::Normal, QIcon::Off);
  minusElementPushButton->setIcon(icon);

  symbolLineEditHBoxLayout->addWidget(minusElementPushButton);

  elementGroupVBoxLayout->addLayout(symbolLineEditHBoxLayout);

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

  return elementGroupBox;
}


QFrame *
IsoSpecDlg::createIsotopeFrame(QGroupBox *elementGroupBox)
{
  QFrame *isotopeFrame;

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

  isotopeFrame = new QFrame(elementGroupBox);

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

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

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

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

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

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

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

  connect(plusIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::addIsotopeFrame);

  QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  sizePolicy.setHorizontalStretch(0);
  sizePolicy.setVerticalStretch(0);
  sizePolicy.setHeightForWidth(
    plusIsotopePushButton->sizePolicy().hasHeightForWidth());
  plusIsotopePushButton->setSizePolicy(sizePolicy);
  QIcon icon1;
  icon1.addFile(
    QStringLiteral(":/images/plus.svg"), QSize(), QIcon::Normal, QIcon::Off);
  plusIsotopePushButton->setIcon(icon1);

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

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

  connect(minusIsotopePushButton,
          &QPushButton::clicked,
          this,
          &IsoSpecDlg::removeIsotopeFrame);

  sizePolicy.setHeightForWidth(
    minusIsotopePushButton->sizePolicy().hasHeightForWidth());
  minusIsotopePushButton->setSizePolicy(sizePolicy);
  QIcon icon;
  icon.addFile(
    QStringLiteral(":/images/minus.svg"), QSize(), QIcon::Normal, QIcon::Off);
  minusIsotopePushButton->setIcon(icon);

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

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

  return isotopeFrame;
}


QGroupBox *
IsoSpecDlg::addElementGroupBox()
{
  QGroupBox *elementGroupBox = addElementSkeletonGroupBox();

  // And now add the first isotope frame

  createIsotopeFrame(elementGroupBox);

  return elementGroupBox;
}

void
IsoSpecDlg::removeElementGroupBox()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ <<
  // "()";

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

  QPushButton *sender = static_cast<QPushButton *>(QObject::sender());
  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "sender:" << sender;

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

  delete elementGroupBox;
}


void
IsoSpecDlg::message(const QString &message, int timeout)
{
  m_ui.messageLineEdit->setText(message);

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


std::size_t
IsoSpecDlg::checkConsistencyIsoSpecTables()
{
  std::size_t arrayLength = sizeof(IsoSpec::elem_table_atomicNo) /
                            sizeof(IsoSpec::elem_table_atomicNo[0]);

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "The array length is:" << arrayLength;

  std::size_t currentLength = sizeof(IsoSpec::elem_table_probability) /
                              sizeof(IsoSpec::elem_table_probability[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  currentLength =
    sizeof(IsoSpec::elem_table_mass) / sizeof(IsoSpec::elem_table_mass[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  currentLength =
    sizeof(IsoSpec::elem_table_massNo) / sizeof(IsoSpec::elem_table_massNo[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  currentLength = sizeof(IsoSpec::elem_table_extraNeutrons) /
                  sizeof(IsoSpec::elem_table_extraNeutrons[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  currentLength = sizeof(IsoSpec::elem_table_element) /
                  sizeof(IsoSpec::elem_table_element[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  currentLength =
    sizeof(IsoSpec::elem_table_symbol) / sizeof(IsoSpec::elem_table_symbol[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  currentLength = sizeof(IsoSpec::elem_table_Radioactive) /
                  sizeof(IsoSpec::elem_table_Radioactive[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  currentLength = sizeof(IsoSpec::elem_table_log_probability) /
                  sizeof(IsoSpec::elem_table_log_probability[0]);
  if(currentLength != arrayLength)
    {
      qDebug()
        << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
        << "Found corruption: at least two arrays are not of the same length."
        << "currentLength:" << currentLength;

      return 0;
    }

  return currentLength;
}


bool
IsoSpecDlg::runIsoSpecStandardStaticConfig()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  // This is the configuration of elements/isotopes that is loaded from the
  // IsoSpec library headers, that is, the configuration is static.

  double summedProbs = m_ui.probabilityDoubleSpinBox->value();

  int charge = m_ui.ionChargeSpinBox->value();

  QString formulaText = m_ui.isoSpecStandardStaticConfigFormulaLineEdit->text();

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

      return false;
    }

  // We can create a Formula and check it.
  msXpSlibmass::Formula formula(formulaText);

  QString errors = checkFormula(formula, m_isoSpecStandardStaticAtomList);

  if(!errors.isEmpty())
    {
      message(errors, 4000);
    }
  else
    {
      message("The formula could be validated", 4000);
    }

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "probability:" << summedProbs << "formula:" << m_formula.text();

  IsoSpec::IsoLayeredGenerator iso(formulaText.toLatin1().data(), summedProbs);

  QString results;

  while(iso.advanceToNextConfiguration())
    {
      results += QString("%1 %2\n")
                   .arg(iso.mass() / charge, 0, 'f', 30)
                   .arg(iso.prob(), 0, 'f', 30);

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< results;
      // std::cout << iso.mass() << " " << iso.prob() << std::endl;
    }

  m_ui.isoSpecTabWidget->setCurrentWidget(m_ui.isoSpecTabWidgetResultsTab);
  m_ui.isoSpecOutputDataPlainTextEdit->setPlainText(results);

  return true;
}


bool
IsoSpecDlg::runIsoSpecStandardUserConfig()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

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

  // When the user has loaded the chemistry defintion from file, the following
  // data are set:

  // The lists that hold *User* IsoSpec standard entities
  // QList<msXpSlibmass::IsoSpecEntity *> m_isoSpecStandardUserEntityList;
  //
  // Helper lists for the static list of entities
  // QList<msXpSlibmass::Atom *> m_isoSpecStandardUserAtomList;
  // std::vector<msXpSlibmass::Atom *> m_isoSpecStandardUserAtomVector;

  // Whe the user clicks on run, they should have filled at least one formula:
  // the formula on the isoSpec standard tab or the formula on the manual config
  // tab.

  QString formulaText = m_ui.isoSpecStandardUserConfigFormulaLineEdit->text();

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

      return false;
    }

  // We can create a Formula and check it.
  msXpSlibmass::Formula formula(formulaText);

  QString errors = checkFormula(formula, m_isoSpecStandardUserAtomList);

  if(!errors.isEmpty())
    {
      message(errors, 5000);
      return false;
    }
  else
    {
      message("The formula could be validated", 2000);
    }

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "probability:" << summedProbs << "formula:" << m_formula.text();

  // At this point we need to create the arrays exactly as we do in the user
  // manual config. So we need to know how many different chemical elements we
  // have in the formula.

  // When we have checked the formula, we have also validated it and asked that
  // AtomCount instances be stored during the validation, so we can now ask for
  // the number of such AtomCount objects that reflect the number of chemical
  // elements in the formula.

  const QList<msXpSlibmass::AtomCount *> &atomCountList =
    formula.atomCountList();

  std::size_t elementNumber = atomCountList.size();

  // This array lists the number of isotopes that each element has
  int isotopeNumbers[elementNumber];

  // This array list the count of atoms of each element
  int atomCounts[elementNumber];

  // These are arrays of arrays! Each array contains the isotope masses (or
  // probs) for each element. The "sub-arrays" are allocated in the loop
  // below.
  double *isotope_masses[elementNumber];
  double *isotope_probs[elementNumber];

  for(std::size_t iter = 0; iter < elementNumber; ++iter)
    {
      msXpSlibmass::AtomCount *curAtomCount = atomCountList.at(iter);

      QString symbol = curAtomCount->symbol();
      int atomCount  = curAtomCount->count();

      // Get the matching Atom from the user-configured atom list.

      bool atomFound = false;

      for(std::vector<msXpSlibmass::Atom *>::iterator jt =
            m_isoSpecStandardUserAtomVector.begin();
          jt != m_isoSpecStandardUserAtomVector.end();
          ++jt)
        {
          msXpSlibmass::Atom *atom = *jt;

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Iterating in atom" << atom.symbol();

          if(atom->symbol() == symbol)
            {
              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()"
              //<< "Found the right atom" << atom.symbol();

              int isotopeCount = atom->isotopeList().size();

              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()"
              //<< "The number of isotopes for current atom"
              //<< atom.symbol() << "is:" << isotopeCount;

              // We can now fill-in the isotope count for symbol
              isotopeNumbers[iter] = isotopeCount;
              atomCounts[iter]     = atomCount;

              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()"
              //<< "Filled-in for currentSymbolIndex"
              //<< currentSymbolIndex
              //<< "isotopeNumbers[currentSymbolIndex]"
              //<< isotopeNumbers[currentSymbolIndex]
              //<< "atomCounts[currentSymbolIndex]"
              //<< atomCounts[currentSymbolIndex];

              // Now fill-in the isotopic stuff

              // Allocate a double array os the isotopeCount size
              double *masses = new double[isotopeCount];

              // Allocate a double array os the isotopeCount size
              double *probs = new double[isotopeCount];

              for(int jter = 0; jter < isotopeCount; ++jter)
                {
                  msXpSlibmass::Isotope *isotope = atom->isotopeList().at(jter);

                  masses[jter] = isotope->mass();
                  probs[jter]  = isotope->abundance();

                  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__
                  //<< "()"
                  //<< "Filled-in for currentSymbolIndex"
                  //<< currentSymbolIndex << "and isotope at index"
                  //<< jter << "masses[jter]" << masses[jter]
                  //<< "probs[jter]" << probs[jter];
                }

              isotope_masses[iter] = masses;
              isotope_probs[iter]  = probs;

              // qDebug()
              //<< __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
              //<< "Set the masses and probs sub-arrary to the main arrays.";

              atomFound = true;
            }
          else
            continue;
        }
      // Finished searching for atom symbol in the list of user manual
      // configuration atom vector.

      if(!atomFound)
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "Programming error."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);
    }
  // End of
  // for(int iter = 0; iter < elementNumber; ++iter)

  // At this point we have all the arrays needed to work.

  // Let's the summed probs value configure by the user
  double summedProbs = m_ui.probabilityDoubleSpinBox->value();

  IsoSpec::IsoLayeredGenerator iso(
    IsoSpec::Iso(
      elementNumber, isotopeNumbers, atomCounts, isotope_masses, isotope_probs),
    summedProbs);

  int charge = m_ui.ionChargeSpinBox->value();

  QString results;

  while(iso.advanceToNextConfiguration())
    {
      results += QString("%1 %2\n")
                   .arg(iso.mass() / charge, 0, 'f', 30)
                   .arg(iso.prob(), 0, 'f', 30);

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< results;
      // std::cout << iso.mass() << " " << iso.prob() << std::endl;
    }

  m_ui.isoSpecTabWidget->setCurrentWidget(m_ui.isoSpecTabWidgetResultsTab);
  m_ui.isoSpecOutputDataPlainTextEdit->setPlainText(results);

  return true;
}


// This function works on the configuration that the user has created (either
// from scratch or by loading a config file) in the manual configuration tab
// of
// the dialog window.
bool
IsoSpecDlg::runIsoSpecManualConfig()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ <<
  // "()";

  // First validate the configuration. Errors are fatal in the IsoSpec
  // library.

  std::size_t elementNumber = validateManualConfig();

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

  // At this point, all the elements defined by the user have been completed
  // and
  // the user atom vector will be now used to create the static arrays that
  // are
  // needed by IsoSpec.

  // We now need to construct the C arrays for IsoSpec. The arrays need to be
  // filled-in very accurately.

  // This array lists the number of isotopes that each element has
  int isotopeNumbers[elementNumber];

  // This array list the count of atoms of each element
  int atomCounts[elementNumber];

  // These are arrays of arrays! Each array contains the isotope masses (or
  // probs) for each element. The "sub-arrays" are allocated in the loop
  // below.
  double *isotope_masses[elementNumber];
  double *isotope_probs[elementNumber];

  // Iterate in the m_atomCountPairVector and for each element symbol work out
  // the various C arrays.

  std::size_t currentSymbolIndex = 0;

  for(std::vector<std::pair<QString, int>>::iterator it =
        m_atomCountPairVector.begin();
      it != m_atomCountPairVector.end();
      ++it)
    {
      std::pair<QString, int> atomSymbolCount = *it;

      QString symbol = it->first;
      int atomCount  = it->second;

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "Iterating in atom symbol count pair:"
      //<< "(" << symbol << "," << atomCount << ")";

      // Get the matching Atom from the user-configured atom list.

      bool atomFound = false;

      for(std::vector<msXpSlibmass::Atom>::iterator jt =
            m_userManualAtomVector.begin();
          jt != m_userManualAtomVector.end();
          ++jt)
        {
          msXpSlibmass::Atom atom = *jt;

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Iterating in atom" << atom.symbol();

          if(atom.symbol() == symbol)
            {
              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()"
              //<< "Found the right atom" << atom.symbol();

              int isotopeCount = atom.isotopeList().size();

              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()"
              //<< "The number of isotopes for current atom"
              //<< atom.symbol() << "is:" << isotopeCount;

              // We can now fill-in the isotope count for symbol
              isotopeNumbers[currentSymbolIndex] = isotopeCount;
              atomCounts[currentSymbolIndex]     = atomCount;

              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()"
              //<< "Filled-in for currentSymbolIndex"
              //<< currentSymbolIndex
              //<< "isotopeNumbers[currentSymbolIndex]"
              //<< isotopeNumbers[currentSymbolIndex]
              //<< "atomCounts[currentSymbolIndex]"
              //<< atomCounts[currentSymbolIndex];

              // Now fill-in the isotopic stuff

              // Allocate a double array os the isotopeCount size
              double *masses = new double[isotopeCount];

              // Allocate a double array os the isotopeCount size
              double *probs = new double[isotopeCount];

              for(int jter = 0; jter < isotopeCount; ++jter)
                {
                  msXpSlibmass::Isotope *isotope = atom.isotopeList().at(jter);

                  masses[jter] = isotope->mass();
                  probs[jter]  = isotope->abundance();

                  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__
                  //<< "()"
                  //<< "Filled-in for currentSymbolIndex"
                  //<< currentSymbolIndex << "and isotope at index"
                  //<< jter << "masses[jter]" << masses[jter]
                  //<< "probs[jter]" << probs[jter];
                }

              isotope_masses[currentSymbolIndex] = masses;
              isotope_probs[currentSymbolIndex]  = probs;

              // qDebug()
              //<< __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
              //<< "Set the masses and probs sub-arrary to the main arrays.";

              atomFound = true;
            }
          else
            continue;
        }
      // Finished searching for atom symbol in the list of user manual
      // configuration atom vector.

      if(!atomFound)
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "Programming error."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);

      // Increment the index that allows filling the various C array slots.
      ++currentSymbolIndex;
    }
  // Finished iterating in the vector of all the symbols found in the formula
  // submitted by the user.

  // At this point we have all the array needed to work.

  // Let's the summed probs value configure by the user
  double summedProbs = m_ui.probabilityDoubleSpinBox->value();

  IsoSpec::IsoLayeredGenerator iso(
    IsoSpec::Iso(
      elementNumber, isotopeNumbers, atomCounts, isotope_masses, isotope_probs),
    summedProbs);

  int charge = m_ui.ionChargeSpinBox->value();

  QString results;

  while(iso.advanceToNextConfiguration())
    {
      results += QString("%1 %2\n")
                   .arg(iso.mass() / charge, 0, 'f', 30)
                   .arg(iso.prob(), 0, 'f', 30);

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< results;
      // std::cout << iso.mass() << " " << iso.prob() << std::endl;
    }

  m_ui.isoSpecTabWidget->setCurrentWidget(m_ui.isoSpecTabWidgetResultsTab);
  m_ui.isoSpecOutputDataPlainTextEdit->setPlainText(results);

  // To avoid a memory leak, we need to delete the mass and prob
  // heap-allocated
  // arrays.

  for(std::size_t iter = 0; iter < elementNumber; ++iter)
    {
      delete[] isotope_masses[iter];
      delete[] isotope_probs[iter];
    }

  return true;
}


// The manual configuration that was saved can be reloaded and all the widgets
// will be recreated as necessary.
bool
IsoSpecDlg::loadUserManualConfiguration()
{
  //$ cat prova.txt
  //[atom]
  // symbol C count 100
  //[isotopes] 2
  // mass 12.000 prob 0.9899
  // mass 13.000 prob 0.010

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

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

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

  // At this point, make sure we reset the various member atomistic
  // data-storing.

  m_userManualAtomVector.clear();
  m_atomCountPairVector.clear();

  bool wasStartedOneAtom    = false;
  bool wasAtomLine          = false;
  bool wasSymbolCountLine   = false;
  bool wasIsotopesCountLine = false;
  bool wasMassProbLine      = false;

  QString symbol;
  int atomCount    = 0;
  int isotopeCount = 0;
  double mass      = 0;
  double prob      = 0;

  bool ok = false;

  QRegularExpression commentRegexp("^\\s+#");

  QRegularExpression symbolCountRegexp(
    "^\\s+symbol\\s+([A-Z][a-z]?)\\s+count\\s+(\\d+)");

  QRegularExpression isotopesRegexp("^\\s+\\[isotopes\\]\\s(\\d+)");

  // QRegularExpression massProbRegexp = QRegularExpression(
  //"^\\s+mass\\s+(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\s+prob\\s+([^\\d^\\.^-]+)(-?"
  //"\\d*\\.?\\d*[e]?[-]?[+]?\\d*)");

  QRegularExpression massProbRegexp = QRegularExpression(
    "^\\s+mass\\s(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\sprob\\s(\\d*\\.?\\d*[e]?[-]"
    "?["
    "+]?\\d*)");

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "The mass prob regexp is valid?" << massProbRegexp.isValid();

  //"\t\tmass 13.000000000000000000000000000000000000000000000000000000000000
  // prob 0.010000000000000000208166817117216851329430937767028808593750"

  // Instantiate an atom that we'll clear each time we push_back it to the
  // vector.
  msXpSlibmass::Atom atom;

  QTextStream in(&file);
  while(!in.atEnd())
    {
      QString line = in.readLine();

      // Ignore empty or comment lines
      if(line.length() < 1)
        continue;

      QRegularExpressionMatch match = commentRegexp.match(line);
      if(match.hasMatch())
        continue;

      if(line == "[atom]")
        {
          if(wasStartedOneAtom && !wasMassProbLine)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          if(wasStartedOneAtom)
            {
              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()";
              // We are starting a new element configuration stanza, but in
              // fact
              // aother was already going on. We need to terminate it.

              // Sanity check

              if(isotopeCount != atom.isotopeList().size())
                {
                  qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__
                           << "()"
                           << "Error";
                  return false;
                }

              // Now we can be confident.

              m_userManualAtomVector.push_back(atom);

              // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ <<
              // "()"
              //<< "The completed atom:" << atom.asText();

              // Clear the atom for next element configuration iteration.
              atom.clear();
            }

          wasStartedOneAtom = true;

          wasAtomLine          = true;
          wasSymbolCountLine   = false;
          wasIsotopesCountLine = false;
          wasMassProbLine      = false;
          continue;
        }

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

      match = symbolCountRegexp.match(line);
      if(match.hasMatch())
        {
          if(!wasAtomLine || wasSymbolCountLine)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          symbol = match.captured(1);
          atom.setName(symbol);
          atom.setSymbol(symbol);

          atomCount = match.captured(2).toInt(&ok);
          if(!ok || !atomCount)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          // Now that we have the symbol and the count, set the pair.

          std::pair<QString, int> atomCountPair(symbol, atomCount);
          m_atomCountPairVector.push_back(atomCountPair);

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "Appended new pair:" << symbol << " " << atomCount;

          wasSymbolCountLine   = true;
          wasAtomLine          = false;
          wasIsotopesCountLine = false;
          wasMassProbLine      = false;

          continue;
        }

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";
      match = isotopesRegexp.match(line);
      if(match.hasMatch())
        {
          if(!wasSymbolCountLine)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          isotopeCount = match.captured(1).toInt(&ok);
          if(!ok || !isotopeCount)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "The isotope count is:" << isotopeCount;

          wasIsotopesCountLine = true;
          wasSymbolCountLine   = false;
          wasAtomLine          = false;
          wasMassProbLine      = false;

          continue;
        }

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()";

      match = massProbRegexp.match(line);
      if(match.hasMatch())
        {
          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "The mass prob line match:" << match.capturedTexts();

          if(!wasIsotopesCountLine && !wasMassProbLine)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          mass = match.captured(1).toDouble(&ok);
          if(!ok)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          prob = match.captured(2).toDouble(&ok);
          if(!ok)
            {
              qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
                       << "Error";
              return false;
            }

          // At this point, we can allocate an isotope and append it to the
          // atom.

          atom.appendIsotope(new msXpSlibmass::Isotope(mass, prob));

          // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
          //<< "The atom now is:" << atom.asText();

          wasMassProbLine      = true;
          wasIsotopesCountLine = false;
          wasSymbolCountLine   = false;
          wasAtomLine          = false;

          continue;
        }
    }

  // We have fnished iterating in the file's lines but we were parsing an
  // atom,
  // append it.

  // Sanity check
  if(!wasStartedOneAtom)
    {
      qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
               << "Error: not a single atom could be parsed.";
      return false;
    }

  if(isotopeCount != atom.isotopeList().size())
    {
      qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
               << "Error";
      return false;
    }

  // Now we can be confident.

  m_userManualAtomVector.push_back(atom);

  // Clear the atom for next element configuration iteration.
  atom.clear();

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

  initializeIsoSpecManualConfigurationWidgets();

  return true;
}


bool
IsoSpecDlg::saveUserManualConfiguration()
{
  // We need to iterate into the various widgets, exactly as done for the run
  // of
  // the manual configuration.

  // First run the validation which has also the side effect to set aside in
  // member variables the needed atomistic data to later run IsoSpec or create
  // the config string.

  std::size_t elementNumber = validateManualConfig();

  if(!elementNumber)
    {
      // The validation function creates all the necessary feedback.
      return false;
    }

  QString configuration;

  for(std::vector<std::pair<QString, int>>::iterator it =
        m_atomCountPairVector.begin();
      it != m_atomCountPairVector.end();
      ++it)
    {
      std::pair<QString, int> atomSymbolCount = *it;

      QString symbol = it->first;
      int atomCount  = it->second;

      configuration += QString(
                         "[atom]\n"
                         "\tsymbol %1 count %2\n")
                         .arg(symbol)
                         .arg(atomCount);

      // Get the matching Atom from the user-configured atom list.

      bool atomFound = false;

      for(std::vector<msXpSlibmass::Atom>::iterator jt =
            m_userManualAtomVector.begin();
          jt != m_userManualAtomVector.end();
          ++jt)
        {
          msXpSlibmass::Atom atom = *jt;

          if(atom.symbol() == symbol)
            {
              int isotopeCount = atom.isotopeList().size();

              configuration += QString("\t[isotopes] %1\n").arg(isotopeCount);

              for(int jter = 0; jter < isotopeCount; ++jter)
                {
                  msXpSlibmass::Isotope *isotope = atom.isotopeList().at(jter);

                  configuration += QString("\t\tmass %1 prob %2\n")
                                     .arg(isotope->mass(), 0, 'f', 60)
                                     .arg(isotope->abundance(), 0, 'f', 60);
                }

              atomFound = true;
            }
          else
            continue;
        }
      // Finished searching for atom symbol in the list of user manual
      // configuration atom vector.

      if(!atomFound)
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "Programming error."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);

      configuration += "\n";
    }
  // Finished iterating in the vector of all the symbols found in the formula
  // submitted by the user.

  // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
  //<< "Configuration:" << configuration;

  // At this point let the user choose a file for that config.

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

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

  if(!fileName.isEmpty())
    {

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

      QTextStream out(&file);

      out << configuration;

      out.flush();

      file.close();
    }

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

  return true;
}


bool
IsoSpecDlg::initializeIsoSpecManualConfigurationWidgets()
{

  // This function must be called right after having loaded a manual
  // configuration file because during that file reading, all the atomistic
  // info
  // have been set in member data that we'll now parse.


  for(std::vector<std::pair<QString, int>>::iterator it =
        m_atomCountPairVector.begin();
      it != m_atomCountPairVector.end();
      ++it)
    {

      std::pair<QString, int> atomSymbolCount = *it;

      QString symbol = it->first;
      int atomCount  = it->second;

      // This is where we contruct a new elementGroupBox

      QGroupBox *elementGroupBox = addElementSkeletonGroupBox();

      // Now get the widgets to set their values.

      QLineEdit *symbolLineEdit =
        elementGroupBox->findChild<QLineEdit *>("symbolLineEdit");
      symbolLineEdit->setText(symbol);

      QSpinBox *atomCountSpinBox =
        elementGroupBox->findChild<QSpinBox *>("atomCountSpinBox");
      atomCountSpinBox->setValue(atomCount);

      // Get the matching Atom from the user-configured atom list.

      bool atomFound = false;

      for(std::vector<msXpSlibmass::Atom>::iterator jt =
            m_userManualAtomVector.begin();
          jt != m_userManualAtomVector.end();
          ++jt)
        {
          msXpSlibmass::Atom atom = *jt;

          if(atom.symbol() == symbol)
            {
              int isotopeCount = atom.isotopeList().size();

              // This is were we construct the various isotopeFrame widgets.

              for(int jter = 0; jter < isotopeCount; ++jter)
                {
                  msXpSlibmass::Isotope *isotope = atom.isotopeList().at(jter);

                  double mass = isotope->mass();
                  double prob = isotope->abundance();

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

                  QFrame *isotopeFrame = createIsotopeFrame(elementGroupBox);

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

                  massSpinBox->setValue(mass);

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

                  probSpinBox->setValue(prob);
                }

              atomFound = true;
            }
          else
            continue;
        }
      // Finished searching for atom symbol in the list of user manual
      // configuration atom vector.

      if(!atomFound)
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "Programming error."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);
    }
  // Finished iterating in the vector of all the symbols found in the formula
  // submitted by the user.

  return true;
}


} // namespace msXpSmineXpert


#if 0

Example from IsoSpec.

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

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


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

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

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

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

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

#endif
