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


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

/////////////////////// Qt includes
#include <QDebug>


/////////////////////// 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 "MsXpS/libXpertMassCore/Utils.hpp"
#include "MsXpS/libXpertMassCore/Isotope.hpp"
#include "MsXpS/libXpertMassCore/IsotopicData.hpp"

namespace MsXpS
{

namespace libXpertMassCore
{

/*!
\class MsXpS::libXpertMassCore::IsotopicData
\inmodule libXpertMassCore
\ingroup PolChemDefBuildingdBlocks
\inheaderfile IsotopicData.hpp

\brief The IsotopicData class provides a collection of \l{Isotope}s and
associated methods to access them in various ways.

The IsotopicData class provides a collection of \l{Isotope}s and
provides methods to access them in various ways. Methods are available to
return the monoisotopic mass of an isotope or the average mass calculated from
the data of all the isotopes listed for a given chemical element.
*/

/*!
\variable MsXpS::libXpertMassCore::IsotopicData::m_isotopes

\brief The vector of \l{MsXpS::libXpertMassCore::IsotopeQSPtr}.

The vector should never be sorted as we want to keep the order of the
isotopes in the way the vector has been populated, either by looking into
the IsoSpec library tables or by reading data from a user-configured file.
*/

/*!
\variable MsXpS::libXpertMassCore::IsotopicData::m_symbolMonoMassMap

\brief The map relating the Isotope::m_symbol to the monoisotopic mass.
*/

/*!
\variable MsXpS::libXpertMassCore::IsotopicData::m_symbolAvgMassMap

\brief The map relating the Isotope::m_symbol to the average mass.
*/

/*!
\variable MsXpS::libXpertMassCore::IsotopicData::m_isValid

\brief The validity status of the IsotopicData instance.

\sa isValid(), validate()
*/


/*!
\typedef MsXpS::libXpertMassCore::IsotopicDataSPtr
\relates IsotopicData

Synonym for std::shared_ptr<IsotopicData>.
*/

/*!
\typedef MsXpS::libXpertMassCore::IsotopicDataCstSPtr
\relates IsotopicData

Synonym for std::shared_ptr<const IsotopicData>.
*/

using IsotopeListCstIterator = QList<IsotopeQSPtr>::const_iterator;
using IsotopeListIterator    = QList<IsotopeQSPtr>::iterator;
using IsotopeListCstIteratorPair =
  std::pair<IsotopeListCstIterator, IsotopeListCstIterator>;

/*!
\typealias MsXpS::libXpertMassCore::IsotopeListCstIterator

Alias for QList<IsotopeQSPtr>::const_iterator.
*/

/*!
\typealias MsXpS::libXpertMassCore::IsotopeListIterator

Alias for QList<IsotopeQSPtr>::iterator.
*/

/*!
\typealias MsXpS::libXpertMassCore::IsotopeListCstIteratorPair

Alias for std::pair<IsotopeListCstIterator, IsotopeListCstIterator>.
*/

/*!
\brief Constructs the \l{IsotopicData}.

The instance will have empty member data.
*/
IsotopicData::IsotopicData(QObject *parent): QObject(parent)
{
}

/*!
\brief Constructs the \l{IsotopicData} as a copy of \a other.

This is a deep copy with all the data in the containers copied from \a other
to this IsotopicData.
*/
IsotopicData::IsotopicData(const IsotopicData &other, QObject *parent)
  : QObject(parent),
    m_filePath(other.m_filePath),
    m_isotopes(other.m_isotopes),
    m_symbolMonoMassMap(other.m_symbolMonoMassMap),
    m_symbolAvgMassMap(other.m_symbolAvgMassMap)
{
}

/*!
\brief Destructs the \l{IsotopicData}.

Nothing is explicitely deleted in the destructor.
*/
IsotopicData::~IsotopicData()
{
  // qDebug();
}

/*!
\brief Sets the path of the file from which data were loaded to \a file_path.
*/
void
IsotopicData::setFilePath(const QString &file_path)
{
  m_filePath = file_path;
}

/*!
\brief Gets the path of the file from which data were loaded.
*/
QString
IsotopicData::getFilePath() const
{
  return m_filePath;
}

/*!
\brief Appends a new \l{IsotopeQSPtr} to this \l{IsotopicData}.

\a isotope_qsp The new isotope to be added to this collection. The isotope is
added to the end of the collection using

\code
m_isotopes.push_back(isotope_qsp);
\endcode

Each time a new isotope is added to this collection, the chemical
signification of the corresponding chemical element changes at heart. It
might thus be required that the data in the two m_symbolMonoMassMap and
m_symbolAvgMassMap maps be recalculated.

\a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
to be updated with the new collection of isotopes.

\sa updateMassMaps(), appendNewIsotopes()
*/
void
IsotopicData::appendNewIsotope(IsotopeQSPtr isotope_qsp, bool update_maps)
{
  // We may append an unvalid isotope, but then that changes the status
  // of these isotopic data.

  QVector<QString> error_list;
  m_isValid = isotope_qsp->validate(&error_list);

  m_isotopes.push_back(isotope_qsp);

  // We have modified the fundamental data, we may need to recompute some data.
  // update_maps might be false when loading data from a file, in which case it
  // is the responsibility of the user to call updateMassMaps() at the end of
  // the file loading.

  if(update_maps)
    updateMassMaps();
}

/*!
\brief Appends a collection of new \l{IsotopeQSPtr} to this \l{IsotopicData}.

\a isotopes The collection (<vector>) of new isotopes to be added to this
collection. The isotope is added to the end of the collection using

\code
m_isotopes.insert(m_isotopes.end(), isotopes.begin(), isotopes.end());
\endcode

Each time new isotopes are added to this collection, the chemical
signification of all the corresponding chemical elements changes at heart. It
might thus be required that the data in the two m_symbolMonoMassMap and
m_symbolAvgMassMap maps be recalculated.

Internally, this function calls <vector>.insert() to append all the isotopes in
\a isotopes to the end of m_isotopes.

\a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
to be updated with the new collection of isotopes.

\sa updateMassMaps(), appendNewIsotope()
*/
void
IsotopicData::appendNewIsotopes(const QList<IsotopeQSPtr> &isotopes,
                                bool update_maps)
{
  qsizetype count_before = m_isotopes.size();

  m_isotopes.append(isotopes.begin(), isotopes.end());

  qsizetype count_after = m_isotopes.size();

  if(count_after - count_before != isotopes.size())
    qFatal("Programming error.");

  if(update_maps)
    updateMassMaps();
}

/*!
\brief Inserts a new \l{IsotopeQSPtr} to this \l{IsotopicData} at index
\a index.

\a isotope_qsp The new isotope to be inserted in this collection.

If \a index is out of bounds or this collection is empty, the isotope is
appended to this collection. Otherwise, the isotope is inserted at the
requested index, which means that the new isotope displaces to the bottom
(aka back) the isotope currently at \a index.

Each time a new isotope is added to this collection, the chemical
signification of the corresponding chemical element changes at heart. It
might thus be required that the data in the two m_symbolMonoMassMap and
m_symbolAvgMassMap maps be recalculated.

\a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
to be updated with the new collection of isotopes.

Returns true if the iterator at the inserted position is not m_isotopes.end().

\sa updateMassMaps(), appendNewIsotope(), appendNewIsotopes()
*/
bool
IsotopicData::insertNewIsotope(IsotopeQSPtr isotope_qsp,
                               qsizetype index,
                               bool update_maps)
{
  // qDebug() << "the size of the data:" << size() << "and" << m_isotopes.size()
  //<< "requested index:" << index;

  // We define that we insert the new isotope before the one at index, as is the
  // convention in the STL and in Qt code.

  if(!m_isotopes.size() || index > m_isotopes.size() - 1)
    {
      appendNewIsotope(isotope_qsp, update_maps);
      return true;
    }

  // Convert the index to an iterator.

  QList<IsotopeQSPtr>::iterator iter = m_isotopes.begin() + index;

  // Finally, do the insertion.

  iter = m_isotopes.insert(iter, isotope_qsp);

  // qDebug() << "Inserted isotope:" << (*iter)->getSymbol();

  // If inserting an empty isotope in relation to a row insertion in the table
  // view, then update_maps needs to be false because update_maps needs valid
  // symbols for isotopes!

  if(update_maps)
    updateMassMaps();

  // iter points to the inserted isotope.
  return iter != m_isotopes.end();
}

/*!
\brief Removes the isotopes located between \a begin_index and \a end_index.

The removed isotopes are contained inclusively between the two indices passed
as parameters.

Each time isotopes are removed from this collection, the chemical
signification of the corresponding chemical elements changes at heart. It
might thus be required that the data in the two m_symbolMonoMassMap and
m_symbolAvgMassMap maps be recalculated.

\a update_maps Tells if the m_symbolMonoMassMap and m_symbolAvgMassMap need
to be updated with the new collection of isotopes.

Returns an iterator to the end of this collection if either \a begin_index is
out of bounds or this collection is empty. Otherwise, returns an iterator to
the collection at the position below the last removed item.
*/
QList<IsotopeQSPtr>::const_iterator
IsotopicData::eraseIsotopes(qsizetype begin_index,
                            qsizetype end_index,
                            bool update_maps)
{

  // qDebug() << "Erasing isotopes in inclusive index range: [" << begin_index
  //<< "-" << end_index << "] - range is fully inclusive.";

  if(!m_isotopes.size() || begin_index > m_isotopes.size() - 1)
    return m_isotopes.cend();

  QList<IsotopeQSPtr>::const_iterator iter_begin =
    m_isotopes.cbegin() + begin_index;

  // Let's say that by default, we remove until the last inclusively:
  QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();

  // But, if end_index is less than the last index, then end() has to be the
  // next item after the one at that index.
  if(end_index < m_isotopes.size() - 1)
    iter_end = std::next(m_isotopes.cbegin() + end_index);

  // At this point we are confident we can assign the proper end() iterator
  // value for the erase function below.

  QList<IsotopeQSPtr>::iterator iter = m_isotopes.erase(iter_begin, iter_end);

  if(update_maps)
    {
      // qDebug() << "Now updating masses";
      updateMassMaps();
    }
    // else
    // qDebug() << "Not updating masses";

#if 0
if(m_isotopes.size())
{
qDebug() << "The avg mass of the first isotope symbol in the vector:"
<< computeAvgMass(
getIsotopesBySymbol(m_isotopes.front()->getSymbol()));
}
#endif

  return iter;
}

/*!
\brief Redefines the monoisotopic mass of the chemical element specified by \a
symbol.

For the set of isotopes corresponding to \a symbol, set the most
abundant isotope's mass as the value for key \a symbol in m_symbolMonoMassMap.

Returns true if the map pair was actually inserted in m_symbolMonoMassMap or
false if the monoisotopic mass value was set to an existing key.
*/
bool
IsotopicData::updateMonoMassMap(const QString &symbol)
{
  // We do only work with a single symbol here.

  // GCOVR_EXCL_START
  if(symbol.isEmpty())
    qFatal() << "Programming error. The element symbol cannot be empty.";
  // GCOVR_EXCL_STOP

  double highest_probability = std::numeric_limits<double>::min();
  double mass               = 0.0;

  IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);

  while(iter_pair.first != iter_pair.second)
    {
      if((*iter_pair.first)->getProbability() > highest_probability)
        {
          highest_probability = (*iter_pair.first)->getProbability();
          mass               = (*iter_pair.first)->getMass();
        }

      ++iter_pair.first;
    }

  // At this point we have the mono mass of the currently iterated symbol.
  bool key_found = m_symbolMonoMassMap.contains(symbol);
  m_symbolMonoMassMap.insert(symbol, mass);

  return key_found;
}

/*!
\brief Redefines the monoisotopic mass of all the chemical elements in this
collection of isotopes.

This function is generally called by default by all the functions that add
new isotopes to this collection [via updateMassMaps()].

First, a list of all the unique element symbols in this collection is
crafted. Then for each symbol in that list, updateMonoMassMap(symbol) is called.

Returns the number of updated symbols, that is, the unique symbol count in
this collection.

\sa updateMonoMassMap(const QString &symbol), updateMassMaps()
*/
std::size_t
IsotopicData::updateMonoMassMap()
{
  // For all the common chemical elements found in organic substances, the
  // monoisotopic mass is the mass of the most abundant isotope which
  // happens to be also the lightest isotope. However that is not true for
  // *all* the chemical elements. We thus need to iterate in the isotopes
  // of each symbol in the vector of isotopes and record the mass of the
  // isotope that is most abundant.

  m_symbolMonoMassMap.clear();

  // Get the list of all the isotope symbols.

  std::size_t count = 0;

  std::vector<QString> all_symbols = getUniqueSymbolsInOriginalOrder();

  for(const QString &symbol : all_symbols)
    {
      updateMonoMassMap(symbol);
      ++count;
    }

  return count;
}

/*!
\brief Recalculates the average mass of the chemical element specified by \a
symbol.

For the set of isotopes corresponding to \a symbol, compute the average mass
and set it in m_symbolAvgMassMap as the value for key \a symbol.

Returns true if the map pair was actually inserted in m_symbolAvgMassMap or
false if the average mass value was set to an already existing key.

\sa updateMonoMassMap(const QString &symbol), updateMonoMassMap(),
updateAvgMassMap(const QString &symbol), updateMassMaps()
*/
bool
IsotopicData::updateAvgMassMap(const QString &symbol)
{
  // For each chemical element (that is either name or symbol), we need to
  // compute the sum of the probabilities of all the corresponding
  // isotopes. Once that sum (which should be 1) is computed, it is
  // possible to compute the averag mass of "that symbol", so to say.

  // We do only work with a single symbol here.

  // GCOVR_EXCL_START
  if(symbol.isEmpty())
    qFatal("Programming error. The symbol cannot be empty.");
  // GCOVR_EXCL_STOP

#if 0

// Now this is in a function per se:
// computeAvgMass(IsotopeListCstIteratorPair iter_pair, bool *ok)

double cumulated_probabilities = 0.0;
double avg_mass        = 0.0;

IsotopeListCstIteratorPair pair = getIsotopesBySymbol(symbol);

// We need to use that iterator twice, so we do make a copy.

IsotopeCstIterator local_iter = pair.first;

while(local_iter != pair.second)
{
cumulated_probabilities += (*pair.first)->getProbability();

++local_iter;
}

// Sanity check
if(!cumulated_probabilities)
qFatal("Programming error. The cumulated probabilities cannot be naught.");

// And at this point we can compute the average mass.

local_iter = pair.first;

while(local_iter != pair.second)
{
avg_mass += (*local_iter)->getMass() *
((*local_iter)->getProbability() / cumulated_probabilities);

++local_iter;
}
#endif

  IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);

  // qDebug() << "For symbol" << symbol << "the iter range was found to
  // be of distance:"
  //<< std::distance(iter_pair.first, iter_pair.second)
  //<< "with symbol: " << (*iter_pair.first)->getSymbol();

  ErrorList error_list;

  double avg_mass = computeAvgMass(iter_pair, &error_list);
  // GCOVR_EXCL_START
  if(error_list.size())
    {
      qFatal(
        "The calculation of the average mass for a given "
        "symbol failed.");
    }
  // GCOVR_EXCL_STOP

  bool key_found = m_symbolAvgMassMap.contains(symbol);
  m_symbolAvgMassMap.insert(symbol, avg_mass);

  return key_found;
}

/*!
\brief Recalculates the average mass of all the chemical elements in this
collection of isotopes.

This function is generally called by default by all the functions that add
new isotopes to this collection [via updateMassMaps()].

First, a list of all the unique element symbols in this collection is
crafted. Then for each symbol in that list, updateAvgMassMap(symbol) is called.

Returns the number of updated symbols, that is, the unique symbol count in
this collection.

\sa updateMonoMassMap(const QString &symbol), updateMassMaps()
*/
std::size_t
IsotopicData::updateAvgMassMap()
{
  // For each chemical element (that is either name or symbol), we need to
  // compute the sum of the probabilities of all the corresponding
  // isotopes. Once that sum (which should be 1) is computed, it is
  // possible to compute the averag mass of "that symbol", so to say.

  m_symbolAvgMassMap.clear();

  // Get the list of all the isotope symbols.

  std::size_t count = 0;

  std::vector<QString> all_symbols = getUniqueSymbolsInOriginalOrder();

  for(const QString &symbol : all_symbols)
    {
      updateAvgMassMap(symbol);
      ++count;
    }

  return count;
}

/*!
\brief Compute the average mass for isotopes contained in the \a
iter_pair iterator range.

\a iter_pair pair of [begin -- end[ iterators to the isotopes in this
collection

\a error_list_p vector of strings in which to store error messages

There are no sanity checks performed. The iterator pair should hold two
iterator values that frame isotopes of the same chemical element.

The average mass is computed on the basis of the isotopes contained in the
[\a iter_pair .first -- \a iter_pair .second[ range.

Returns 0 if the first member of \a iter_pair is the collection's end
iterator, the average mass otherwise.

*/
double
IsotopicData::computeAvgMass(IsotopeListCstIteratorPair iter_pair,
                             ErrorList *error_list_p) const
{
  // We get an iterator range for which we need to compute the average
  // mass. No check whatsoever, we do what we are asked to do. This
  // function is used to check or document user's actions in some places.

  double avg_mass = 0.0;

  // qDebug() << "Computing avg mass for iter range of distance:"
  //<< std::distance(iter_pair.first, iter_pair.second)
  //<< "with symbol: " << (*iter_pair.first)->getSymbol();

  if(iter_pair.first == m_isotopes.cend())
    {
      qDebug() << "First iterator is actually end of m_isotopes.";
      error_list_p->push_back(
        QString("First iterator is actually end of m_isotopes."));

      return avg_mass;
    }

  qsizetype previous_error_count = error_list_p->size();

  double cumulated_probabilities =
    getCumulatedProbabilities(iter_pair, error_list_p);

  if(error_list_p->size() > previous_error_count || !cumulated_probabilities)
    {
      // There was an error. We want to report it.

      error_list_p->push_back(
        QString("Failed to compute the cumulated probabilities needed to "
                "compute the average mass."));

      return avg_mass;
    }

  // At this point, compute the average mass.

  while(iter_pair.first != iter_pair.second)
    {
      avg_mass +=
        (*iter_pair.first)->getMass() *
        ((*iter_pair.first)->getProbability() / cumulated_probabilities);

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

      ++iter_pair.first;
    }

  return avg_mass;
}

/*!
\brief Update the monoisotopic and average symbol-mass maps only for \a
symbol.

\sa updateMonoMassMap(const QString &symbol), updateAvgMassMap(const QString
&symbol)
*/
void
IsotopicData::updateMassMaps(const QString &symbol)
{
  updateMonoMassMap(symbol);
  updateAvgMassMap(symbol);
}

/*!
\brief Update the monoisotopic and average symbol-mass maps for all the
symbols in the collection.

This function is typically called each time new isotopes are added to this
collection.

Returns the count of updated symbols, that is, the unique symbol count in this
collection.

\sa updateMonoMassMap(), updateAvgMassMap()
*/
std::size_t
IsotopicData::updateMassMaps()
{
  std::size_t count_mono = updateMonoMassMap();

  if(!count_mono)
    qDebug("There are no isotopes. Cleared the mono mass map.");

  std::size_t count_avg = updateAvgMassMap();

  if(!count_avg)
    qDebug("There are no isotopes. Cleared the avg mass map.");

  if(count_mono != count_avg)
    qFatal("Programming error.");

  // Number of symbols for which the mass was updated.
  return count_mono;
}

/*!
\brief Returns the monoisotopic mass for element of \a symbol.

Returns the monoisotopic mass for element \a symbol if it is found in this
Isotope collection and sets \a ok to true. If the symbol is not found returns
0.0 and set \a ok to false.
*/
double
IsotopicData::getMonoMassBySymbol(const QString &symbol, bool &ok) const
{
  if(symbol.isEmpty())
    qFatal("Programming error. The symbol cannot be empty.");

  // qDebug() << "The symbol/mono mass map has size:"
  //<< m_symbolMonoMassMap.size();

  QMap<QString, double>::const_iterator found_iter =
    m_symbolMonoMassMap.find(symbol);

  if(found_iter == m_symbolMonoMassMap.cend())
    {
      qDebug() << "Failed to find the symbol in the map.";

      ok = false;
      return 0.0;
    }

  ok = true;

  // qDebug() << "The mono mass is found to be" << found_iter->second;

  return found_iter.value();
}

/*!
\brief Returns the mass of the most abundant isotope in a range of isotopes.

The range of isotopes is defined by \a iter_pair, that is [ \a iter_pair
.first -- \a iter_pair .second [.

If errors are encountered, these are appended to \a error_list_p.

For all the common chemical elements found in organic substances, the
monoisotopic mass is the mass of the most abundant isotope which
happens to be also the lightest isotope. However that is not true for
*all* the chemical elements. We thus need to iterate in the isotopes
of each symbol in the vector of isotopes and record the mass of the
isotope that is most abundant.
*/
double
IsotopicData::getMonoMass(IsotopeListCstIteratorPair iter_pair,
                          ErrorList *error_list_p) const
{
  // The mono mass of a set of isotopes is the mass of the most abundant
  // isotope (not the lightest!).

  double mass               = 0.0;
  double greatest_abundance = 0.0;

  if(iter_pair.first == m_isotopes.cend())
    {
      qDebug() << "First iterator is actually end of m_isotopes.";

      error_list_p->push_back(
        QString("First iterator is actually end of m_isotopes."));

      return mass;
    }

  while(iter_pair.first != iter_pair.second)
    {
      if((*iter_pair.first)->getProbability() > greatest_abundance)
        {
          greatest_abundance = (*iter_pair.first)->getProbability();
          mass               = (*iter_pair.first)->getMass();
        }

      ++iter_pair.first;
    }

  // qDebug() << "The mono mass is found to be" << mass;

  return mass;
}

/*!
\brief Returns the average mass of \a symbol.

The returned mass is found as the value for key \a symbol in
m_symbolAvgMassMap. If \a ok is not nullptr, it is set to true.

If the symbol is not found, 0 is returned and \a ok is set to false if \a ok
is not nullptr.
*/
double
IsotopicData::getAvgMassBySymbol(const QString &symbol, bool &ok) const
{
  if(symbol.isEmpty())
    qFatal("Programming error. The symbol cannot be empty.");

  // qDebug() << "The symbol/avg mass map has size:" <<
  // m_symbolAvgMassMap.size();

  QMap<QString, double>::const_iterator found_iter =
    m_symbolAvgMassMap.find(symbol);

  if(found_iter == m_symbolAvgMassMap.cend())
    {
      qDebug() << "Failed to find the symbol in the map.";

      ok = false;
      return 0.0;
    }

  ok = true;

  // qDebug() << "The avg mass is found to be" << found_iter->second;

  return found_iter.value();
}

/*!
\brief Returns the sum of the probabilities of all the isotopes of \a
symbol.

If errors occur, they will be described as strings appended in \a error_list_p.
*/
double
IsotopicData::getCumulatedProbabilitiesBySymbol(const QString &symbol,
                                                ErrorList *error_list_p) const
{
  // qDebug() << "symbol: " << symbol;

  double cumulated_probabilities = 0.0;

  // We'll need this to calculate the indices of the isotopes in the
  // m_isotopes vector.
  IsotopeListCstIterator iter_begin = m_isotopes.cbegin();

  // Get the isotopes iter range for symbol.
  IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);

  while(iter_pair.first != iter_pair.second)
    {
      QString error_text;
      ErrorList iter_error_list;

      bool result = (*iter_pair.first)->validate(&iter_error_list);

      if(!result)
        {
          error_text = QString("Isotope symbol %1 at index %2:\n%3")
                         .arg(symbol)
                         .arg(std::distance(iter_begin, iter_pair.first))
                         .arg(Utils::joinErrorList(iter_error_list, "\n"));
          error_list_p->push_back(error_text);
          cumulated_probabilities = 0.0;
        }
      else
        cumulated_probabilities += (*iter_pair.first)->getProbability();

      ++iter_pair.first;
    }

  return cumulated_probabilities;
}

double
IsotopicData::getHighestProbability(const QString &symbol,
                                    ErrorList *error_list_p) const
{
  if(symbol.isEmpty())
    {
      error_list_p->append("Error: symbol is empty.");

      return 0;
    }

  IsotopeListCstIterator highest_probability_const_iterator =
    getHighestProbabilityIterator(symbol, error_list_p);

  if(highest_probability_const_iterator == m_isotopes.cend())
    {
      qDebug() << "Failed to find a single isotope by symbol" << symbol;

      error_list_p->push_back(
        QString("Error: failed to find a single isotope by symbol %1.")
          .arg(symbol));

      return 0;
    }

  return (*highest_probability_const_iterator)->getProbability();
}

IsotopeListCstIterator
IsotopicData::getHighestProbabilityIterator(const QString &symbol,
                                            ErrorList *error_list_p) const
{
  IsotopeListCstIterator highest_probability_const_iterator = m_isotopes.cend();

  if(symbol.isEmpty())
    {
      error_list_p->append("Error: symbol is empty.");

      return highest_probability_const_iterator;
    }


  IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);

  if(iter_pair.first == m_isotopes.cend())
    {
      qDebug() << "First iterator is actually end of m_isotopes.";

      error_list_p->push_back(
        QString("Error: first iterator is actually end of m_isotopes."));

      return highest_probability_const_iterator;
    }

  double highest_probability = std::numeric_limits<double>::min();

  while(iter_pair.first != iter_pair.second)
    {
      if((*iter_pair.first)->getProbability() > highest_probability)
        {
          highest_probability = (*iter_pair.first)->getProbability();
          highest_probability_const_iterator = iter_pair.first;
        }

      ++iter_pair.first;
    }

  return highest_probability_const_iterator;
}

/*!
\brief Returns the sum of the probabilities of all the isotopes in the \a
iter_pair range of iterators.

The range of isotopes is defined by \a iter_pair, that is [ \a iter_pair
.first -- \a iter_pair .second [.

If errors are encountered, these are appended to \a error_list_p.
*/
double
IsotopicData::getCumulatedProbabilities(IsotopeListCstIteratorPair iter_pair,
                                        ErrorList *error_list_p) const
{
  double cumulated_probabilities = 0.0;

  if(iter_pair.first == m_isotopes.cend())
    {
      qDebug() << "First iterator is actually end of m_isotopes.";

      error_list_p->push_back(
        QString("First iterator is actually end of m_isotopes."));

      return cumulated_probabilities;
    }

  while(iter_pair.first != iter_pair.second)
    {
      cumulated_probabilities += (*iter_pair.first)->getProbability();

      ++iter_pair.first;
    }

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

  return cumulated_probabilities;
}

/*!
\brief Returns a range of iterators framing the isotopes of \a symbol.

\note The order of the isotopes in the collection is not alphabetical (it
is the order of the atomic number. This function works on the assumption that
all the isotopes of a given symbol are clustered together in the isotopes
vector with *no* gap in between.

If \a symbol is empty, the iterators are set to be end() of the
Isotopes collection. The returned pair of iterators frame the isotopes of
\a symbol as a [begin,end[ pair of iterators.
*/
IsotopeListCstIteratorPair
IsotopicData::getIsotopesBySymbol(const QString &symbol) const
{

  if(symbol.isEmpty())
    return IsotopeListCstIteratorPair(m_isotopes.cend(), m_isotopes.cend());

  // qDebug() << "The symbol by which isotopes are being searched for: " <<
  // symbol;

  // We want to "extract" from the vector of isotopes, the ones that share
  // the same symbol under the form of a [begin,end[ pair of iterators.

  //////////////////////////// ASSUMPTION /////////////////////////
  //////////////////////////// ASSUMPTION /////////////////////////
  // The order of the isotopes is not alphabetical (it is the order of the
  // atomic number. This function works on the assumption that all the
  // isotopes of a given symbol are clustered together in the isotopes
  // vector with *no* gap in between.
  //////////////////////////// ASSUMPTION /////////////////////////
  //////////////////////////// ASSUMPTION /////////////////////////

  // We will start iterating in the vector of isotopes at the very
  // beginning.
  QList<IsotopeQSPtr>::const_iterator iter = m_isotopes.cbegin();

  // Never reach the end of the isotopes vector.
  QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();

  // This iterator will be the end iterator of the range that comprises
  // the isotopes all sharing the same symbol. We initialize it to
  // iter_end in case we do not find the symbol at all. Otherwise it will
  // be set to the right value.
  QList<IsotopeQSPtr>::const_iterator symbol_iter_end = iter_end;

  while(iter != iter_end)
    {
      QString current_symbol = (*iter)->getSymbol();

      // qDebug() << "First loop iteration in isotope with symbol:"
      //     << current_symbol;

      if(current_symbol != symbol)
        {
          // qDebug() << "Current isotope has symbol" << current_symbol
          //<< "and we are looking for" << symbol
          //<< "incrementing to next position.";
          ++iter;
        }
      else
        {
          // qDebug() << "Current isotope has symbol" << current_symbol
          //<< "and we are looking for" << symbol
          //<< "with mass:" << (*iter)->getMass()
          //<< "Now starting inner iteration loop.";

          // At this point we encountered one isotope that has the right
          // symbol. The iterator "iter" will not change anymore because
          // of the inner loop below that will go on iterating in vector
          // using another set of iterators. "iter" will thus point
          // correctly to the first isotope in the vector having the right
          // symbol.

          // Now in this inner loop, continue iterating in the vector,
          // starting at the present position and go on as long as the
          // encountered isotopes have the same symbol.

          // Set then end iterator to the current position and increment
          // to the next one, since current position has been iterated
          // into already (position is stored in "iter") and go on. This
          // way, if there was a single isotope by given symbol,
          // "symbol_iter_end" rightly positions at the next isotope. If
          // that is not the case, its value updates and is automatically
          // set to the first isotope that has not the right symbol (or
          // will be set to iter_end if that was the last set of isotopes
          // in the vector).

          symbol_iter_end = iter;
          ++symbol_iter_end;

          while(symbol_iter_end != iter_end)
            {
              // qDebug() << "Second loop iteration in: "
              //<< (*symbol_iter_end)->getSymbol()
              //<< "while we search for" << symbol
              //<< "Iterated isotope has mass:"
              //<< (*symbol_iter_end)->getMass();

              if((*symbol_iter_end)->getSymbol() == symbol)
                {
                  // We can iterate further in the isotopes vector because
                  // the current iterator pointed to an isotope that still
                  // had the right symbol. qDebug()
                  //<< "Good symbol, going to next inner iteration
                  // position.";
                  ++symbol_iter_end;
                }
              else
                {
                  // We currently iterate in an isotope that has a symbol
                  // different from the searched one: the symbol_iter_end
                  // thus effectively plays the role of the
                  // iterator::end() of the isotopes range having the
                  // proper symbol.

                  // qDebug() << "The symbols do not match, breaking the inner
                  // loop.";
                  break;
                }
            }

          // We can break the outer loop because we have necessarily gone
          // through the isotopes of the requested symbol at this point.
          // See at the top of this outer loop that when an isotope has
          // not the right symbol, the iter is incremented.
          break;
        }
      // End of block
      // else of if(current_symbol != symbol)
    }
  // End of outer
  // while(iter != iter_end)

  // qDebug() << "For symbol" << symbol << "the iter range was found to be:"
  //<< std::distance(iter, symbol_iter_end);

  return IsotopeListCstIteratorPair(iter, symbol_iter_end);
}

/*!
\brief Returns the count of isotopes of \a symbol.
*/
qsizetype
IsotopicData::getIsotopeCountBySymbol(const QString &symbol) const
{
  IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);

  return std::distance(iter_pair.first, iter_pair.second);
}

/*!
\brief Returns a range of iterators framing the isotopes of element \a name.

\note The order of the isotopes in the collection is not alphabetical (it
is the order of the atomic number. This function works on the assumption that
all the isotopes of a given symbol are clustered together in the isotopes
vector with *no* gap in between.

If \a name is empty, the iterators are set to be end() of the
Isotopes collection. The returned pair of iterators frame the isotopes of
\a name as a [begin,end[ pair of iterators.
*/
IsotopeListCstIteratorPair
IsotopicData::getIsotopesByName(const QString &name) const
{
  if(name.isEmpty())
    return IsotopeListCstIteratorPair(m_isotopes.cend(), m_isotopes.cend());

  // qDebug() << "The name by which isotopes are being searched for: " << name;

  // We want to "extract" from the vector of isotopes, the ones that share
  // the same name under the form of a [begin,end[ pair of iterators.

  //////////////////////////// ASSUMPTION /////////////////////////
  //////////////////////////// ASSUMPTION /////////////////////////
  // The order of the isotopes is not alphabetical (it is the order of the
  // atomic number. This function works on the assumption that all the
  // isotopes of a given symbol are clustered together in the isotopes
  // vector with *no* gap in between.
  //////////////////////////// ASSUMPTION /////////////////////////
  //////////////////////////// ASSUMPTION /////////////////////////

  // We will start iterating in the vector of isotopes at the very
  // beginning.
  QList<IsotopeQSPtr>::const_iterator iter = m_isotopes.cbegin();

  // Never reach the end of the isotopes vector.
  QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();

  // This iterator will be the end iterator of the range that comprises
  // the isotopes all sharing the same name. We initialize it to iter_end
  // in case we do not find the name at all. Otherwise it will be set to
  // the right value.
  QList<IsotopeQSPtr>::const_iterator name_iter_end = iter_end;

  while(iter != iter_end)
    {
      QString current_name = (*iter)->getName();

      // qDebug() << "First loop iteration in isotope with name:" <<
      // current_name;

      if(current_name != name)
        {
          // qDebug() << "Current isotope has name" << current_name
          //<< "and we are looking for" << name
          //<< "incrementing to next position.";
          ++iter;
        }
      else
        {
          // qDebug() << "Current isotope has name" << current_name
          //<< "and we are looking for" << name
          //<< "with mass:" << (*iter)->getMass()
          //<< "Now starting inner iteration loop.";

          // At this point we encountered one isotope that has the right name.
          // The iterator "iter" will not change anymore because of the inner
          // loop below that will go on iterating in vector using another set
          // of iterators. "iter" will thus point correctly to the first
          // isotope in the vector having the right name.

          // Now in this inner loop, continue iterating in the vector,
          // starting at the present position and go on as long as the
          // encountered isotopes have the same name.

          // Set then end iterator to the current position and increment to
          // the next one, since current position has been iterated into
          // already (position is stored in "iter") and go on. This way, if
          // there was a single isotope by given name, "name_iter_end" rightly
          // positions at the next isotope. If that is not the case, its value
          // updates and is automatically set to the first isotope that has
          // not the right name (or will be set to iter_end if that was the
          // last set of isotopes in the vector).

          name_iter_end = iter;
          ++name_iter_end;

          while(name_iter_end != iter_end)
            {
              // qDebug() << "Second loop iteration in: "
              //<< (*name_iter_end)->getName()
              //<< "while we search for" << name
              //<< "Iterated isotope has mass:"
              //<< (*name_iter_end)->getMass();

              if((*name_iter_end)->getName() == name)
                {
                  // We can iterate further in the isotopes vector because the
                  // current iterator pointed to an isotope that still had the
                  // right name.
                  // qDebug() << "Going to next iterator position.";
                  ++name_iter_end;
                }
              else
                {
                  // We currently iterate in an isotope that has a name
                  // different from the searched one: the name_iter_end thus
                  // effectively plays the role of the iterator::end() of the
                  // isotopes range having the proper name.

                  // qDebug() << "The names do not match, breaking the inner
                  // loop.";
                  break;
                }
            }

          // We can break the outer loop because we have necessarily gone
          // through the isotopes of the requested name at this point.
          // See at the top of this outer loop that when an isotope has
          // not the right name, the iter is incremented.
          break;
        }
      // End of block
      // else of if(current_name != name)
    }
  // End of outer
  // while(iter != iter_end)

  // qDebug() << "For name" << name << "the iter range was found to be:"
  //<< std::distance(iter, name_iter_end);

  return IsotopeListCstIteratorPair(iter, name_iter_end);
}

/*!
\brief Returns all the unique symbols from the collection as they are stored.
*/
std::vector<QString>
IsotopicData::getUniqueSymbolsInOriginalOrder() const
{
  // The way IsoSpec works and the way we configure the
  // symbol/count/isotopes data depend in some situations on the order in
  // which the Isotopes were read either from the library tables or from
  // user-created disk files.
  //
  // This function wants to craft a list of unique isotope symbols exactly
  // as they are found in the member vector.

  std::set<QString> symbol_set;

  std::vector<QString> symbols;

  for(const IsotopeQSPtr &isotope_qsp : m_isotopes)
    {
      QString symbol = isotope_qsp->getSymbol();

      auto res = symbol_set.insert(symbol);
      // res.second is true if the symbol was inserted, which mean it did
      // not exist in the set.
      if(res.second)
        symbols.push_back(symbol);
    }

  return symbols;
}

/*!
\brief Returns true if the collection contains at least one isotope of \a
symbol, false otherwise.

If \a count is not nullptr, its value is set to the count of isotopes of \a
symbol or unchanged if no isotopes by \a symbol were found.
*/
bool
IsotopicData::containsSymbol(const QString &symbol, int &count) const
{
  IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);

  if(iter_pair.first == m_isotopes.cend())
    return false;

  // Now compute the distance between the two iterators to know how many
  // isotopes share the same chemical element symbol.

  count = std::distance(iter_pair.first, iter_pair.second);

  return true;
}

/*!
\brief Returns true if the collection contains at least one isotope of
element \a name, false otherwise.

If \a count is not nullptr, its value is set to the count of isotopes of \a
name element.
*/
bool
IsotopicData::containsName(const QString &name, int &count) const
{
  IsotopeListCstIteratorPair iter_pair = getIsotopesByName(name);

  if(iter_pair.first == m_isotopes.cend())
    return false;

  // Now compute the distance between the two iterators to know how many
  // isotopes share the same chemical element symbol.

  count = std::distance(iter_pair.first, iter_pair.second);

  return true;
}

/*!
\brief Returns a string containing a stanza describing each isotope of \a
symbol.

\sa Isotope::toString, Isotope::getMass
*/
QString
IsotopicData::isotopesAsStringBySymbol(const QString &symbol) const
{
  if(symbol.isEmpty())
    qFatal("Programming error. The symbol cannot be empty.");

  QString text;

  IsotopeListCstIteratorPair iter_pair = getIsotopesBySymbol(symbol);

  if(iter_pair.first == m_isotopes.cend())
    {
      qDebug() << "The symbol was not found in the vector of isotopes.";
      return text;
    }

  while(iter_pair.first != iter_pair.second)
    {
      text += (*iter_pair.first)->toString();
      text += "\n";
    }

  return text;
}

/*!
\brief Returns true if the \a isotope_cqsp is isotope of its
symbol having the greatest probability, false otherwise.
*/
bool
IsotopicData::isMonoMassIsotope(IsotopeCstQSPtr isotope_cqsp)
{
  // Is the passed isotope the one that has the greatest abundance among
  // all the isotopes having the same symbol.

  if(isotope_cqsp == nullptr)
    qFatal()
      << "Programming error. The isotope pointer cannot be nullptr.";

  QString symbol(isotope_cqsp->getSymbol());

  if(!m_symbolMonoMassMap.contains(symbol))
    qFatal() << "Programming error. The symbol was not found in the map.";

  double mass = m_symbolMonoMassMap.value(symbol);

  if(mass == isotope_cqsp->getMass())
    return true;

  return false;
}

/*!
\brief Returns a constant reference to the container of
\l{MsXpS::libXpertMassCore::Isotope}s.
*/
const QList<IsotopeQSPtr> &
IsotopicData::getIsotopes() const
{
  return m_isotopes;
}

/*!
\brief Assigns member data from \a other to this instance's member data.

The copying of \a other into this collection is deep, making *this
collection essentially identical to \a other.

Returns a reference to this collection.
*/
IsotopicData &
IsotopicData::operator=(const IsotopicData &other)
{
  if(&other == this)
    return *this;

  m_filePath          = other.m_filePath;
  m_isotopes          = other.m_isotopes;
  m_symbolMonoMassMap = other.m_symbolMonoMassMap;
  m_symbolAvgMassMap  = other.m_symbolAvgMassMap;

  return *this;
}

/*!
\brief Returns true if \a other and \c this are identical.
*/
bool
IsotopicData::operator==(const IsotopicData &other) const
{
  if(&other == this)
    return true;

  if(m_filePath != other.m_filePath)
    return false;

  if(m_isotopes.size() != other.m_isotopes.size())
    return false;

  for(qsizetype iter = 0; iter < m_isotopes.size(); ++iter)
    {
      // qDebug() << "Now checking Isotope" << m_isotopes.at(iter)->getName();

      if(*m_isotopes.at(iter) != *other.m_isotopes.at(iter))
        {
          qDebug() << "At least one isotope differs in each data set.";
          return false;
        }
    }

  // qDebug() << "Done checking the isotopes.";

  if(m_symbolMonoMassMap != other.m_symbolMonoMassMap)
    return false;

  if(m_symbolAvgMassMap != other.m_symbolAvgMassMap)
    return false;

  // qDebug() << "The isotopic data are identical.";

  return true;
}

/*!
\brief Returns true if \a other and \c this are different.
*/
bool
IsotopicData::operator!=(const IsotopicData &other) const
{
  if(&other == this)
    return false;

  return !operator==(other);
}

/*!
\brief Returns the count of isotopes in the collection.

\note The count that is returned is for \e all isotopes, that is, the count
if items in the collection.
*/
qsizetype
IsotopicData::size() const
{
  return m_isotopes.size();
}

/*!
\brief Returns the validity status of this IsotopicData instance.

\sa validate(), validateBySymbol(), validateAllBySymbol()
*/
bool
IsotopicData::isValid()
{
  return m_isValid;
}

/*!
\brief Returns the count of unique symbols.
*/
qsizetype
IsotopicData::getUniqueSymbolsCount() const
{
  // Go trough the vector of IsotopeQSPtr and check how many different
  // symbols it contains.

  std::set<QString> symbols_set;

  QList<IsotopeQSPtr>::const_iterator iter     = m_isotopes.cbegin();
  QList<IsotopeQSPtr>::const_iterator iter_end = m_isotopes.cend();

  while(iter != iter_end)
    {
      symbols_set.insert((*iter)->getSymbol());
      ++iter;
    }

  // Remove these checks because in fact it is possible that the values
  // be different: if appending new isotopes without forcing the udpate
  // of the maps.

  // Sanity checks:
  // if(symbols_set.size() != m_symbolAvgMassMap.size())
  //  qFatal("Not possible that the sizes (avg) do not match");
  //
  // if(symbols_set.size() != m_symbolMonoMassMap.size())
  //  qFatal("Not possible that the sizes (mono) do not match");

  return symbols_set.size();
}

/*!
\brief Validates this Isotope collection.

The validation involves iterating in the whole collection and for each item
in it invoke its Isotope::validate(). If errors occurred during these
validations, they are reported as strings in \a error_list_p.

Return true if no error was encountered, false otherwise.
*/
bool
IsotopicData::validate(ErrorList *error_list_p) const
{
  int error_count = 0;

  IsotopeListCstIterator iter_begin = m_isotopes.cbegin();
  IsotopeListCstIterator iter_end   = m_isotopes.cend();

  IsotopeListCstIterator iter = iter_begin;

  while(iter != iter_end)
    {
      ErrorList isotope_error_list;

      bool result = (*iter)->validate(&isotope_error_list);

      if(!result)
        {
          QString error_text = QString("Isotope at index %1:\n")
                                 .arg(std::distance(iter_begin, iter));

          error_text.append(Utils::joinErrorList(isotope_error_list, "\n"));

          error_list_p->push_back(error_text);

          ++error_count;
        }

      ++iter;
    }

  if(error_count)
    m_isValid = false;
  else
    m_isValid = true;

  return m_isValid;
}

/*!
\brief Validates the \l{Isotope}s of \a symbol in this collection.

This function is more powerful than IsotopicData::validate()
because it actually verifies the integrity of the data \e{symbol}-wise. For
example, a set of isotopes by the same symbol cannot have a cumulated
probability different than 1. If that were the case, the error would be
reported.

Encountered errors are stored in \a error_list_p.

Returns true if validation succeeded, false otherwise.

\sa validateAllBySymbol(), getCumulatedProbabilitiesBySymbol()
*/
bool
IsotopicData::validateBySymbol(const QString &symbol,
                               ErrorList *error_list_p) const
{
  // This function is more powerful than the other one because it actually
  // looks the integrity of the data symbol-wise. For example, a set of
  // isotopes by the same symbol cannot have a cumulated probability greater
  // than 1. If that were the case, that would be reported.

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

  // Validating by symbol means looking into each isotope that has the same
  // 'symbol' and validating each one separately. However, it also means looking
  // if all the cumulated isotope probabilities (abundances) for a given symbol
  // are different than 1.

  // Record the size of the error_list so that we can report if we added
  // errors in this block.
  qsizetype previous_error_count = error_list_p->size();

  // The function below performs validation of the isotopes.
  double cumulated_probabilities =
    getCumulatedProbabilitiesBySymbol(symbol, error_list_p);

  if(cumulated_probabilities != 1)
    {
      QString prob_error =
        QString(
          "Isotope symbol %1: has cumulated probabilities not equal to 1.\n")
          .arg(symbol);

      error_list_p->push_back(prob_error);
    }

  // If we added errors, then return false.
  return !(error_list_p->size() > previous_error_count);
}

/*!
\brief Validates all the isotopes of the collection.

The validation of the \l{Isotope}s is performed by grouping them
by symbol. See validateBySymbol().

Encountered errors are stored in \a error_list_p.

Returns true if all the Isotopes validated successfully, false otherwise.

\sa validateBySymbol(), validate(), getCumulatedProbabilitiesBySymbol()
*/
bool
IsotopicData::validateAllBySymbol(ErrorList *error_list_p) const
{
  // This validation of all the isotopes as grouped by symbol is more
  // informative than the validation isotope by isotope idependently
  // because it can spot cumulated probabilities problems.

  qsizetype previous_error_count = error_list_p->size();

  std::vector<QString> all_symbols = getUniqueSymbolsInOriginalOrder();

  for(const QString &symbol : all_symbols)
    {
      validateBySymbol(symbol, error_list_p);
    }

  // If we added errors, then return false.
  return !(error_list_p->size() > previous_error_count);
}

/*!
\brief Replaces \a old_isotope_sp by \a new_isotope_sp in this collection.
*/
void
IsotopicData::replace(IsotopeQSPtr old_isotope_sp, IsotopeQSPtr new_isotope_sp)
{
  std::replace(
    m_isotopes.begin(), m_isotopes.end(), old_isotope_sp, new_isotope_sp);
}

/*!
\brief Clears (empties) the symbol/mass maps only, not the vector if Isotope
instances.

These two containers are cleared:

\list
\li m_symbolMonoMassMap
\li m_symbolAvgMassMap
\endlist
*/
void
IsotopicData::clearSymbolMassMaps()
{
  m_symbolMonoMassMap.clear();
  m_symbolAvgMassMap.clear();
}

/*!
\brief Clears (empties) all the containers in this collection, essentially
resetting it completely.

These three containers are cleared:

\list
\li m_isotopes
\li m_symbolMonoMassMap
\li m_symbolAvgMassMap
\endlist

\sa clearSymbolMassMaps()
*/
void
IsotopicData::clear()
{
  m_isotopes.clear();
  clearSymbolMassMaps();
}

void
IsotopicData::registerJsConstructor(QJSEngine *engine)

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

  // Register the meta object as a constructor

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


} // namespace libXpertMassCore

} // namespace MsXpS


#if 0

Example from IsoSpec.

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

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


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

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

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

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

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

#endif
