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


////////////////////////////// Stdlib includes
#include <map>
#include <vector>

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


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/PolChemDef.hpp"
#include "MsXpS/libXpertMassCore/Monomer.hpp"
#include "MsXpS/libXpertMassCore/CrossLinker.hpp"
#include "MsXpS/libXpertMassCore/CleavageAgent.hpp"
#include "MsXpS/libXpertMassCore/FragmentationPathway.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataLibraryHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp"


int polChemDefSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMassCore::PolChemDefSPtr>("PolChemDefSPtr");

int polChemDefCstSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMassCore::PolChemDefCstSPtr>(
    "PolChemDefCstSPtr");

namespace MsXpS
{
namespace libXpertMassCore
{

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

\brief The PolChemDef class provides a complete set of chemical entities
fully qualifying a polymer chemistry definition, like Proteins, Saccharides or
Nucleic acids.

The PolChemDef class provides a full set of chemical entity definitions
(\l{Isotope}s, \l{Monomer}s, chemical \l{Modif}ications, \l{CrossLink}s),
chemical reaction models (in the liquid or gas phase, like \l Polymer cleavage:
\l CleavageAgent or \l Oligomer fragmentation: \l FragmentationPathway)\dots
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_name

\brief The name of the polymer chemistry definition, like \e{protein-1-letter}
or \e{nucac}, for example.

This name is typically identical to both the name of the directory where all
the data defining this \c PolChemDef is stored and the name of the XML file
that contains the definition itself.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_xmlDataFilePath

\brief The path to the XML data file that contains the description of this
polymer chemistry definition.
*/


/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_isotopicDataFilePath

\brief The path to the file that contains this polymer chemistry definition's
isotopic data.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_leftCap

\brief The \l Formula that defines how of the left end of a polymer
sequence of this \l PolChemDef needs to be capped in order to finalize the
polymerization state of the \l Polymer sequence.

\sa PolChemDef::m_rightCap
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_rightCap

\brief The \l Formula that defines how of the right end of a polymer
sequence of this \l PolChemDef needs to be capped in order to finalize the
polymerization state of the \l Polymer sequence.

\sa PolChemDef::m_leftCap
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_codeLength

\brief The maximum length of a \l Monomer code in this defintion.

The valid syntax of a Monomer code is that the first character of the code is
uppercase and all the remaining ones are lowercase. The total number of
characters cannot exceed m_codeLength.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_delimitedCodes

\brief The set of \l Monomer codes separated by '@' characters, like
"@Ala@Tyr@Phe@".
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_ionizer

\brief The \l{Ionizer}{ionization agent} that governs the manner the \l
Polymer sequences of this polymer chemistry definition are ionized by default.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::msp_isotopicData

\brief The isotopic data defining the fundamentals of this \l PolChemDef
instance.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_monomers

\brief The container of \l{Monomer}s defined to be part of this \l PolChemDef
instance.
*/


/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_modifs

\brief The container of \l{Modif}s defined to be part of this \l PolChemDef
instance.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_crossLinkers

The container of \l{CrossLinker}s defined to be part of this \l PolChemDef
instance.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_cleavageAgents

\brief The container of \l{CleavageAgent}s defining the various ways to cleave
\l Polymer sequences of this \l PolChemDef instance.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_fragmentationPathways

\brief The container of \l{FragmentationPathway}s defining the various ways to
fragment \l Oligomer sequences of this \l PolChemDef instance.
*/

/*!
\variable MsXpS::libXpertMassCore::PolChemDef::m_polChemDefs

\brief The container of \l PolChemDef instances available in the repositories.
*/


/*!
\variable MsXpS::libXpertMassCore::POL_CHEM_DEF_FILE_FORMAT_VERSION

\brief The latest version of the format of the file containing the
polymer chemistry definition.

Brought to 1 30 january 2023 for massXpert2
Brought to 2 10 november 2024 for massXpert2
*/

const int POL_CHEM_DEF_FILE_FORMAT_VERSION = 2;

/*!
\brief Constructs a polymer chemistry definition.
*/
PolChemDef::PolChemDef()
{
}

/*!
\brief Constructs a polymer chemistry definition on the basis of \a
pol_chem_def_spec.

The  \a pol_chem_def_spec polymer chemistry definition specification provides
the name of the polymer chemistry definition and of the file that contains
it.
*/
PolChemDef::PolChemDef(const PolChemDefSpec &pol_chem_def_spec)
{
  m_name            = pol_chem_def_spec.getName();
  m_xmlDataFilePath = pol_chem_def_spec.getFilePath();
}

/*!
\brief Destroys the polymer chemistry definition
*/
PolChemDef::~PolChemDef()
{
}

/*!
\brief Sets the \a name of this PolChemDef instance.
*/
void
PolChemDef::setName(const QString &name)
{
  m_name = name;
}

/*!
\brief Returns the name of this PolChemDef instance.
*/
QString
PolChemDef::getName() const
{
  return m_name;
}

/*!
\brief Sets the \a file_path of this PolChemDef instance.
*/
void
PolChemDef::setXmlDataFilePath(const QString &file_path)
{
  m_xmlDataFilePath = file_path;
}

/*!
\brief Returns the file path of this PolChemDef instance.
*/
QString
PolChemDef::getXmlDataFilePath() const
{
  return m_xmlDataFilePath;
}

/*!
\brief Returns the absolute directory path of this PolChemDef instance.
*/
QString
PolChemDef::getXmlDataDirPath() const
{
  // qDebug() << "The xml data file path:" << m_xmlDataFilePath;

  QFileInfo fileInfo(m_xmlDataFilePath);
  QDir dir(fileInfo.dir());

  // qDebug() << "Returning the pol chem def data dir path:" <<
  dir.absolutePath();

  return dir.absolutePath();
}

/*!
\brief Sets the path of the isotopic data file to \a file_path.
*/
void
PolChemDef::setIsotopicDataFilePath(const QString &file_path)
{
  m_isotopicDataFilePath = file_path;
}

/*!
\brief Returns the path of the isotopic data file.
*/
QString
PolChemDef::getIsotopicDataFilePath() const
{
  return m_isotopicDataFilePath;
}

/*!
\brief Returns the path of the isotopic data file.

The deduction is based on the fact that the file should have
"isotopic-data.dat" as its name.
*/
QString
PolChemDef::deduceIsotopicDataFilePath() const
{
  // From the xml data file path, deduce the file name of the isotopic data.
  QFileInfo file_info(m_xmlDataFilePath);

  if(!file_info.exists())
    qFatal(
      "Programming error. The polymer chemistry definition file could not be "
      "found.");

  QDir dir(file_info.dir());

  QString isotopic_data_file_path =
    dir.absolutePath() + QDir::separator() + "isotopic-data.dat";

  return isotopic_data_file_path;
}

/*!
\brief Sets the left cap \a formula.
*/
void
PolChemDef::setLeftCap(const Formula &formula)
{
  m_leftCap = formula;
}

/*!
\brief Returns the left cap formula.
*/
const Formula &
PolChemDef::getLeftCap() const
{
  return m_leftCap;
}

/*!
\brief Sets the right cap \a formula.
*/
void
PolChemDef::setRightCap(const Formula &formula)
{
  m_rightCap = formula;
}

/*!
\brief Returns the right cap formula.
*/
const Formula &
PolChemDef::getRightCap() const
{
  return m_rightCap;
}

/*!
\brief Sets the \a code_length.
*/
void
PolChemDef::setCodeLength(int code_length)
{
  m_codeLength = code_length;
}

/*!
\brief Returns the code length.
*/
int
PolChemDef::getCodeLength() const
{
  return m_codeLength;
}

/*!
\brief Sets the ionizer to \a ionizer.
*/
void
PolChemDef::setIonizer(const Ionizer &ionizer)
{
  m_ionizer = ionizer;
}

/*!
\brief Returns a const reference to the ionizer.
*/
const Ionizer &
PolChemDef::getIonizerCstRef() const
{
  return m_ionizer;
}

/*!
\brief Returns a pointer to the ionizer.
*/
Ionizer &
PolChemDef::getIonizerRef()
{
  return m_ionizer;
}

/*!
\brief Sets the isotopic data to \a isotopic_data_sp.
*/
void
PolChemDef::setIsotopicDataSPtr(IsotopicDataSPtr isotopic_data_sp)
{
  msp_isotopicData = isotopic_data_sp;
}

/*!
\brief Returns the isotopic data as const shared pointer..
*/
IsotopicDataCstSPtr
PolChemDef::getIsotopicDataCstSPtr() const
{
  return msp_isotopicData;
}

/*!
\brief Returns the isotopic data as non-const shared pointer..
*/
IsotopicDataSPtr
PolChemDef::getIsotopicDataSPtr()
{
  return msp_isotopicData;
}

//////////////////// MODIFS //////////////////////
//////////////////// MODIFS //////////////////////

/*!
\brief Returns a const reference to the list of modifications.
*/
const std::vector<ModifSPtr> &
PolChemDef::getModifsCstRef() const
{
  return m_modifs;
}

/*!
\brief Returns a pointer to the list of modifications.
*/
std::vector<ModifSPtr> &
PolChemDef::getModifsRef()
{
  return m_modifs;
}

/*!
\brief Returns a pointer to the Modif instance known under \a name.

If no Modif by that \a name is known,  nullptr is returned.
*/
ModifCstSPtr
PolChemDef::getModifCstSPtrByName(const QString &name) const
{
  ModifsCstIterator the_iterator_cst = std::find_if(
    m_modifs.cbegin(), m_modifs.cend(), [name](const auto &modif_csp) {
      return modif_csp->getName() == name;
    });

  if(the_iterator_cst == m_modifs.cend())
    return nullptr;

  return (*the_iterator_cst);
}

/*!
\brief Returns the index of the \l Modif in the member container of
these instances or -1 if that was not found.

The search is performed using the \a name of the Modif.
*/
int
PolChemDef::getModifIndexByName(const QString &name) const
{
  ModifsCstIterator the_iterator_cst = std::find_if(
    m_modifs.cbegin(), m_modifs.cend(), [name](const auto &modif_csp) {
      bool same_name = (modif_csp->getName() == name);
      return same_name;
    });

  if(the_iterator_cst == m_modifs.cend())
    return -1;

  return std::distance(m_modifs.cbegin(), the_iterator_cst);
}

//////////////////// MONOMERS //////////////////////
//////////////////// MONOMERS //////////////////////

/*!
\brief Returns a const reference to the list of modifications.
*/
const std::vector<MonomerSPtr> &
PolChemDef::getMonomersCstRef() const
{
  return m_monomers;
}

/*!
\brief Returns a pointer to the list of Monomer instances.
*/
std::vector<MonomerSPtr> &
PolChemDef::getMonomersRef()
{
  return m_monomers;
}

/*!
\brief Returns a pointer to the Monomer instance known under \a name.

If no Monomer by that \a name is known,  nullptr is returned.
*/
MonomerSPtr
PolChemDef::getMonomerCstSPtrByName(const QString &name) const
{
  MonomersCstIterator the_iterator_cst = std::find_if(
    m_monomers.cbegin(), m_monomers.cend(), [name](const auto &monomer_csp) {
      return monomer_csp->getName() == name;
    });

  if(the_iterator_cst == m_monomers.cend())
    return nullptr;

  return (*the_iterator_cst);
}

/*!
\brief Returns a pointer to the Monomer instance known under \a code.

If no Monomer by that \a code is known,  nullptr is returned.
*/
MonomerSPtr
PolChemDef::getMonomerCstSPtrByCode(const QString &code) const
{
  MonomersCstIterator the_iterator_cst = std::find_if(
    m_monomers.cbegin(), m_monomers.cend(), [code](const auto &monomer_csp) {
      return monomer_csp->getCode() == code;
    });

  if(the_iterator_cst == m_monomers.cend())
    return nullptr;

  return (*the_iterator_cst);
}

/*!
\brief Returns the index of the \l Monomer in the member container of
these instances or -1 if that was not found.

The search is performed using the \a name of the Monomer.
*/
int
PolChemDef::getMonomerIndexByName(const QString &name) const
{
  MonomersCstIterator the_iterator_cst = std::find_if(
    m_monomers.cbegin(), m_monomers.cend(), [name](const auto &monomer_csp) {
      bool same_name = (monomer_csp->getName() == name);
      return same_name;
    });

  if(the_iterator_cst == m_monomers.cend())
    return -1;

  return std::distance(m_monomers.cbegin(), the_iterator_cst);
}

/*!
\brief Returns the index of the \l Monomer in the member container of
these instances or -1 if that was not found.

The search is performed using the \a code of the Monomer.
*/
int
PolChemDef::getMonomerIndexByCode(const QString &code) const
{
  MonomersCstIterator the_iterator_cst = std::find_if(
    m_monomers.cbegin(), m_monomers.cend(), [code](const auto &monomer_csp) {
      bool same_code = (monomer_csp->getCode() == code);
      return same_code;
    });

  if(the_iterator_cst == m_monomers.cend())
    return -1;

  return std::distance(m_monomers.cbegin(), the_iterator_cst);
}

//////////////////// CROSSLINKERS //////////////////////
//////////////////// CROSSLINKERS //////////////////////

/*!
\brief Returns a const reference to the list of modifications.
*/
const std::vector<CrossLinkerSPtr> &
PolChemDef::getCrossLinkersCstRef() const
{
  return m_crossLinkers;
}

/*!
\brief Returns a pointer to the list of CrossLinker instances.
*/
std::vector<CrossLinkerSPtr> &
PolChemDef::getCrossLinkersRef()
{
  return m_crossLinkers;
}

/*!
\brief Returns a pointer to the CrossLinker instance known under \a name.

If no CrossLinker by that \a name is known,  nullptr is returned.
*/
CrossLinkerCstSPtr
PolChemDef::getCrossLinkerCstSPtrByName(const QString &name) const
{
  CrossLinkersCstIterator the_iterator_cst =
    std::find_if(m_crossLinkers.cbegin(),
                 m_crossLinkers.cend(),
                 [name](const auto &crossLinker_csp) {
                   bool same_name = (crossLinker_csp->getName() == name);
                   return same_name;
                 });

  if(the_iterator_cst == m_crossLinkers.cend())
    return nullptr;

  return (*the_iterator_cst);
}

//////////////////// CLEAVAGE AGENTS //////////////////////
//////////////////// CLEAVAGE AGENTS //////////////////////

/*!
\brief Returns a const reference to the list of modifications.
*/
const std::vector<CleavageAgentSPtr> &
PolChemDef::getCleavageAgentsCstRef() const
{
  return m_cleavageAgents;
}

/*!
\brief Returns a pointer to the list of CleavageAgent instances.
*/
std::vector<CleavageAgentSPtr> &
PolChemDef::getCleavageAgentsRef()
{
  return m_cleavageAgents;
}

/*!
\brief Returns a pointer to the CleavageAgent instance known under \a name.

If no CleavageAgent by that \a name is known,  nullptr is returned.
*/
CleavageAgentCstSPtr
PolChemDef::getCleavageAgentCstSPtrByName(const QString &name) const
{
  CleavageAgentsCstIterator the_iterator_cst =
    std::find_if(m_cleavageAgents.cbegin(),
                 m_cleavageAgents.cend(),
                 [name](const auto &cleavage_agent_csp) {
                   bool same_name = (cleavage_agent_csp->getName() == name);
                   return same_name;
                 });

  if(the_iterator_cst == m_cleavageAgents.cend())
    return nullptr;

  return (*the_iterator_cst);
}

/*!
\brief Returns the index of the \l CleavageAgent in the member container of
these instances or -1 if that was not found.

The search is performed using the \a name of the CleavageAgent.
*/
int
PolChemDef::getCleavageAgentIndexByName(const QString &name) const
{
  CleavageAgentsCstIterator the_iterator_cst =
    std::find_if(m_cleavageAgents.cbegin(),
                 m_cleavageAgents.cend(),
                 [name](const auto &cleavage_agent_csp) {
                   bool same_name = (cleavage_agent_csp->getName() == name);
                   return same_name;
                 });

  if(the_iterator_cst == m_cleavageAgents.cend())
    return -1;

  return std::distance(m_cleavageAgents.cbegin(), the_iterator_cst);
}

//////////////////// FRAGMENTATION PATHWAYS //////////////////////
//////////////////// FRAGMENTATION PATHWAYS //////////////////////

/*!
\brief Returns a const reference to the list of modifications.
*/
const std::vector<FragmentationPathwaySPtr> &
PolChemDef::getFragmentationPathwaysCstRef() const
{
  return m_fragmentationPathways;
}

/*!
\brief Returns a pointer to the list of FragmentationPathway instances.
*/
std::vector<FragmentationPathwaySPtr> &
PolChemDef::getFragmentationPathwaysRef()
{
  return m_fragmentationPathways;
}

/*!
\brief Returns a pointer to the FragmentationPathway instance known under \a
name.

If no FragmentationPathway by that \a name is known,  nullptr is returned.
*/
FragmentationPathwayCstSPtr
PolChemDef::getFragmentationPathwayCstSPtrByName(const QString &name) const
{
  FragmentationPathwaysCstIterator the_iterator_cst =
    std::find_if(m_fragmentationPathways.cbegin(),
                 m_fragmentationPathways.cend(),
                 [name](const auto &fragmentation_pathway_csp) {
                   bool same_name =
                     (fragmentation_pathway_csp->getName() == name);
                   return same_name;
                 });

  if(the_iterator_cst == m_fragmentationPathways.cend())
    return nullptr;

  return (*the_iterator_cst);
}

/*!
\brief Returns the index of the \l FragmentationPathway in the member container
of these instances or -1 if that was not found.

The search is performed using the \a name of the FragmentationPathway.
*/
int
PolChemDef::getFragmentationPathwayIndexByName(const QString &name) const
{
  FragmentationPathwaysCstIterator the_iterator_cst =
    std::find_if(m_fragmentationPathways.cbegin(),
                 m_fragmentationPathways.cend(),
                 [name](const auto &cleavage_agent_csp) {
                   bool same_name = (cleavage_agent_csp->getName() == name);
                   return same_name;
                 });

  if(the_iterator_cst == m_fragmentationPathways.cend())
    return -1;

  return std::distance(m_fragmentationPathways.cbegin(), the_iterator_cst);
}

/*!
\brief Constructs a string with the codes of all the \l{Monomer}s in this
PolChemDef instance.

The codes are delimited by the '@' character.

Returns true.
*/
bool
PolChemDef::calculateDelimitedCodes()
{
  // We have to produce a QString containing all the codes from the
  // monomers known to this polymer chemistry definition.

  m_delimitedCodes.clear();
  m_delimitedCodes.append('@');

  for(auto monomer_csp : m_monomers)
    {

      m_delimitedCodes.append(monomer_csp->getCode());
      m_delimitedCodes.append('@');
    }

  //  Close the string with a delim char:
  m_delimitedCodes.append('@');

  return true;
}

/*!
\brief Returns a string with the codes of all the \l{Monomer}s in this
PolChemDef instance.

The codes are delimited by the '@' character.
*/
const QString &
PolChemDef::getDelimitedCodes()
{
  if(m_delimitedCodes.isEmpty())
    calculateDelimitedCodes();

  return m_delimitedCodes;
}

/*!
\brief Returns a string list with the mass difference between all the
\l{Monomer}s in this PolChemDef instace.

If the difference is below the \a threshold, the difference is added to
the string list, otherwise it is skipped. The masses that are compared
between every two monomers is of \a mass_type.
*/
QStringList
PolChemDef::differenceBetweenMonomers(double threshold,
                                      Enums::MassType mass_type)
{
  //     qDebug()
  // 	     << "threshold" << threshold;

  QStringList difference_list;

  if(mass_type != Enums::MassType::MONO && mass_type != Enums::MassType::AVG)
    {
      qDebug() << "Please set the mass type "
                  "to either Enums::MassType::MONO or "
                  "Enums::MassType::AVG";
      return difference_list;
    }

  // We iterate in the list of monomers and compute a difference of
  // mass for each monomer with respect to all the other ones. If
  // the mass difference is less or equal to the threshold, then the
  // pair is returned.

  if(m_monomers.size() < 2)
    return difference_list;

  MonomersCstIterator iterator_outer_loop_cst = m_monomers.cbegin();

  while(iterator_outer_loop_cst != m_monomers.cend())
    {
      Monomer outer_monomer = *(*iterator_outer_loop_cst);

      outer_monomer.calculateMasses(/*IsotopicDataCstSPtr*/ nullptr);

      MonomersCstIterator iterator_inner_loop_cst = m_monomers.cbegin();

      while(iterator_inner_loop_cst != m_monomers.cend())
        {
          if(*iterator_outer_loop_cst == *iterator_inner_loop_cst)
            {
              ++iterator_inner_loop_cst;
              continue;
            }

          Monomer inner_monomer = *(*iterator_inner_loop_cst);

          inner_monomer.calculateMasses(/*IsotopicDataCstSPtr*/ nullptr);

          // At this point we have the masses for both monomers.

          double mass_diff = 0;

          if(mass_type == Enums::MassType::MONO)
            {
              mass_diff = outer_monomer.getMass(Enums::MassType::MONO) -
                          inner_monomer.getMass(Enums::MassType::MONO);
            }
          else
            //(monoOrAvg == Enums::MassType::AVG)
            {
              mass_diff = outer_monomer.getMass(Enums::MassType::AVG) -
                          inner_monomer.getMass(Enums::MassType::AVG);
            }

          // At this point, make sure that diff is within the
          // threshold. Note that to avoid duplicates, we remove all
          // values that are negative.

          if(mass_diff >= 0 && mass_diff <= threshold)
            {
              QString line = QString("%1 - %2 = %3")
                               .arg(outer_monomer.getName())
                               .arg(inner_monomer.getName())
                               .arg(mass_diff);

              difference_list.append(line);
            }
          ++iterator_inner_loop_cst;
        }
      ++iterator_outer_loop_cst;
    }

  return difference_list;
}

//////////////// OPERATORS /////////////////////


/*!
\brief Returns true if this PolChemDef is equal to \a other, false otherwise.

The comparison is deep,  with all the various chemical entities in the
various containers being compared (not their pointers,  but the objects
themselves).

\note The names and the data file paths are not compared,  because this
comparison operator is aimed at comparing the chemistries of the PolChemDef.
*/
bool
PolChemDef::isChemicallyEquivalent(const PolChemDef &other) const
{
  if(&other == this)
    return true;

  if(m_name != other.m_name)
    {
      qDebug() << "The names are not identical.";
    }

  if(m_leftCap != other.m_leftCap)
    {
      qDebug() << "The left caps are not identical.";
      return false;
    }

  if(m_rightCap != other.m_rightCap)
    {
      qDebug() << "The right caps are not identical.";
      return false;
    }

  if(m_codeLength != other.m_codeLength)
    {
      qDebug() << "The code lengths are not identical.";
      return false;
    }

  if(m_delimitedCodes != other.m_delimitedCodes)
    {
      qDebug() << "The delimited codes are not identical.";
      return false;
    }

  if(m_ionizer != other.m_ionizer)
    {
      qDebug() << "The ionizers are not identical.";
      return false;
    }

  if(msp_isotopicData != nullptr && other.msp_isotopicData != nullptr)
    {
      qDebug() << "Now checking the isotopic data";

      if(*msp_isotopicData.get() != *other.msp_isotopicData.get())
        {
          qDebug() << "The isotopic data are not identical.";
          return false;
        }
    }

  if(m_modifs.size() != other.m_modifs.size())
    {
      qDebug() << "The modifs containers have not the same sizes.";
      return false;
    }

  ModifsCstIterator this_modif_iterator_cst  = m_modifs.cbegin();
  ModifsCstIterator other_modif_iterator_cst = other.m_modifs.cbegin();

  while(this_modif_iterator_cst != m_modifs.cend() &&
        other_modif_iterator_cst != other.m_modifs.cend())
    {
      const Modif this_modif  = *(*this_modif_iterator_cst);
      const Modif other_modif = *(*other_modif_iterator_cst);

      if(this_modif != other_modif)
        {
          qDebug() << "At least one modification is not identical in both "
                      "PolChemDef entities.";
          return false;
        }

      ++this_modif_iterator_cst;
      ++other_modif_iterator_cst;
    }

  // qDebug() << "The Modif instances are identical.";

  if(m_monomers.size() != other.m_monomers.size())
    {
      qDebug() << "The monomers containers have not the same sizes.";
      return false;
    }

  MonomersCstIterator this_monomer_iterator_cst  = m_monomers.cbegin();
  MonomersCstIterator other_monomer_iterator_cst = other.m_monomers.cbegin();

  while(this_monomer_iterator_cst != m_monomers.cend() &&
        other_monomer_iterator_cst != other.m_monomers.cend())
    {
      const Monomer this_monomer  = *(*this_monomer_iterator_cst);
      const Monomer other_monomer = *(*other_monomer_iterator_cst);

      if(this_monomer != other_monomer)
        {
          qDebug()
            << "At least one monomer is not identical in both PolChemDef "
               "entities.";
          return false;
        }

      ++this_monomer_iterator_cst;
      ++other_monomer_iterator_cst;
    }

  // qDebug() << "The Monomer instances are identical.";

  if(m_crossLinkers.size() != other.m_crossLinkers.size())
    {
      qDebug() << "The cross-linkers containers have not the same sizes.";
      return false;
    }

  CrossLinkersCstIterator this_cross_linker_iterator_cst =
    m_crossLinkers.cbegin();
  CrossLinkersCstIterator other_cross_linker_iterator_cst =
    other.m_crossLinkers.cbegin();

  while(this_cross_linker_iterator_cst != m_crossLinkers.cend() &&
        other_cross_linker_iterator_cst != other.m_crossLinkers.cend())
    {
      const CrossLinker this_cross_linker = *(*this_cross_linker_iterator_cst);
      const CrossLinker other_cross_linker =
        *(*other_cross_linker_iterator_cst);

      if(this_cross_linker != other_cross_linker)
        {
          qDebug().noquote()
            << "At least one cross-linker is not identical in both "
               "PolChemDef entities: \n"
            << this_cross_linker.toString() << "\nvs\n"
            << other_cross_linker.toString();
          return false;
        }

      ++this_cross_linker_iterator_cst;
      ++other_cross_linker_iterator_cst;
    }

  // qDebug() << "The CrossLinker instances are identical.";

  if(m_cleavageAgents.size() != other.m_cleavageAgents.size())
    {
      qDebug()
        << "The cleavage specifications containers have not the same sizes.";
      return false;
    }

  CleavageAgentsCstIterator this_cleavage_agent_iterator_cst =
    m_cleavageAgents.cbegin();
  CleavageAgentsCstIterator other_cleavage_agent_iterator_cst =
    other.m_cleavageAgents.cbegin();

  while(this_cleavage_agent_iterator_cst != m_cleavageAgents.cend() &&
        other_cleavage_agent_iterator_cst != other.m_cleavageAgents.cend())
    {
      CleavageAgent this_cleavage_agent;
      this_cleavage_agent.initialize(*(*this_cleavage_agent_iterator_cst));

      CleavageAgent other_cleavage_agent;
      other_cleavage_agent.initialize(*(*other_cleavage_agent_iterator_cst));

      if(this_cleavage_agent != other_cleavage_agent)
        {
          qDebug().noquote()
            << "At least one cleavage specification is not identical in "
               "both PolChemDef entities:\n"
            << this_cleavage_agent.toString() << "\nvs\n"
            << other_cleavage_agent.toString();
          return false;
        }

      ++this_cleavage_agent_iterator_cst;
      ++other_cleavage_agent_iterator_cst;
    }

  // qDebug() << "The CleavageAgent instances are identical.";

  if(m_fragmentationPathways.size() != other.m_fragmentationPathways.size())
    {
      qDebug() << "The fragmentation specifications containers have not the "
                  "same sizes.";
      return false;
    }

  FragmentationPathwaysCstIterator this_fragmentation_pathway_iterator_cst =
    m_fragmentationPathways.cbegin();
  FragmentationPathwaysCstIterator other_fragmentation_pathway_iterator_cst =
    other.m_fragmentationPathways.cbegin();

  while(this_fragmentation_pathway_iterator_cst !=
          m_fragmentationPathways.cend() &&
        other_fragmentation_pathway_iterator_cst !=
          other.m_fragmentationPathways.cend())
    {
      const FragmentationPathway this_fragmentation_pathway =
        *(*this_fragmentation_pathway_iterator_cst);
      const FragmentationPathway other_fragmentation_pathway =
        *(*other_fragmentation_pathway_iterator_cst);

      if(this_fragmentation_pathway != other_fragmentation_pathway)
        {
          qDebug() << "At least one fragmentation specification is not "
                      "identical in both PolChemDef entities.";
          return false;
        }

      ++this_fragmentation_pathway_iterator_cst;
      ++other_fragmentation_pathway_iterator_cst;
    }

  // qDebug() << "The FragmentationPathway instances are identical.";

  return true;
}

/*!
\brief Returns true if this PolChemDef is equal to \a other, false otherwise.

The comparison is deep,  with all the various chemical entities in the
various containers being compared (not their pointers,  but the objects
themselves).

\note The names and the data file paths are not compared,  because this
comparison operator is aimed at comparing the chemistries of the PolChemDef.
*/
bool
PolChemDef::operator==(const PolChemDef &other) const
{
  if(&other == this)
    return true;

  if(!isChemicallyEquivalent(other))
    {
      qDebug() << "The PolChemDef are not chemically identical.";
      return false;
    }

  if(m_name != other.m_name)
    {
      qDebug() << "The names are not identical.";
    }

  if(m_xmlDataFilePath != other.m_xmlDataFilePath)
    {
      qDebug() << "The definition file paths are not identical.";
    }

  if(m_isotopicDataFilePath != other.m_isotopicDataFilePath)
    {
      qDebug() << "The isotopic data file paths are not identical.";
    }

  return true;
}

/*!
\brief Returns true if \c this and \a other differ.

Returns the negated result of operator==().
*/
bool
PolChemDef::operator!=(const PolChemDef &other) const
{
  if(&other == this)
    return false;

  return !operator==(other);
}

/*!
\brief Loads the isotopic data stored in the file \a file_path.

The data loaded from file are parsed and validated. Upon parsing of the
file, the data are stored in the \a pol_chem_def_sp instance's isotopic
data member.

If \a file_path is empty, it is reconstructed using the directory of the
polymer chemistry definition and the fact that the isotopic data file name
is "isotopic-data.dat".

Returns the count of parsed isotopes.
*/
std::size_t
PolChemDef::loadIsotopicData(PolChemDefSPtr pol_chem_def_sp,
                             const QString &file_path)
{
  // There are three situations:
  //
  // 1. The file_path exists and the data are loaded from there.
  //
  // 2. A file named isotopic-data.dat is found in the polymer chemistry
  // definition directory and it is loaded.
  //
  // 3. No file named isotopic-data.dat is found in the polymer chemistry
  // defintion directory and then data are loaded from the IsoSpec tables.

  // If the provided argument is not empty and actually describes an
  // existing file, then save the data there.
  QString local_file_path(file_path);
  QFileInfo local_file_info(local_file_path);

  if(local_file_path.isEmpty() || !local_file_info.exists())
    {
      // qDebug() << "The provided file name" << file_path << "does not exist.";

      // Then try the member datum that provides the name of the
      // file from which to load the isotopic data.
      local_file_path = pol_chem_def_sp->getIsotopicDataFilePath();
      local_file_info.setFile(local_file_path);

      if(local_file_path.isEmpty() || !local_file_info.exists())
        {
          // qDebug() << "The member datum file name " << local_file_path
          //          << "does not exist.";

          // Last resort: deduce the isotopic data file name from the
          // directory of the polymer chemistry definition.

          local_file_path = pol_chem_def_sp->deduceIsotopicDataFilePath();

          // qDebug() << "Crafted the isotopic-data.dat file path:"
          //          << local_file_path;

          // Finally for a later last check:
          local_file_info.setFile(local_file_path);
        }
    }

  // qDebug() << "Allocating brand new isotopic data instance.";

  // At this point we can delete the pre-exsting isotopic data.
  pol_chem_def_sp->msp_isotopicData = std::make_shared<IsotopicData>();

  // Allocate a specific handler to the kind of isotopic data (library
  // tables or user file).

  std::size_t count = 0;

  if(local_file_path.isEmpty() || !local_file_info.exists())
    {
      // qDebug() << "Loading the isotopic data from the library.";

      IsotopicDataLibraryHandler isotopic_data_handler(
        pol_chem_def_sp->msp_isotopicData);

      qsizetype non_isotope_skipped_items = 0;
      count = isotopic_data_handler.loadData(non_isotope_skipped_items);
    }
  else
    {
      // qDebug() << "Loading the isotopic data from file:" << local_file_path;

      IsotopicDataUserConfigHandler isotopic_data_handler(
        pol_chem_def_sp->msp_isotopicData);

      count = isotopic_data_handler.loadData(local_file_path);

      if(!count)
        {
          qDebug() << "Failed to load any isotopic data.";
          return false;
        }

      // Now set the file path to the pol chem def.

      pol_chem_def_sp->setIsotopicDataFilePath(local_file_path);
    }

  if(!count)
    {
      qDebug() << "Failed to load any isotopic data.";
      return false;
    }

  return count;
}

/*!
\brief Writes the isotopic data in the \a pol_chem_def_sp PolChemDef
instance to file \a file_path.

Returns the count of isotopes written to file.
*/
std::size_t
PolChemDef::writeIsotopicData(PolChemDefSPtr pol_chem_def_sp,
                              const QString &file_path)
{
  // There are three situations:
  //
  // 1. The file_path is not empty and the data are saved there.
  //
  // 2. The file_path is empty and the data are stored in the member file
  // path name (if not empty).
  //
  // 3. The file path is crafted from the directory of the polymer
  // chemistry definition.


  // Whatever the situation, the isotopic data handler we need here is
  // this one:
  IsotopicDataUserConfigHandler isotopic_data_handler(
    pol_chem_def_sp->msp_isotopicData);

  // We'll instantiate the proper isotopic data handler depending on the
  // situation.

  if(!file_path.isEmpty())
    {
      // We have a file name, store in there.
      pol_chem_def_sp->setIsotopicDataFilePath(file_path);
      return isotopic_data_handler.writeData(file_path);
    }

  // Check the member datum.

  if(!pol_chem_def_sp->m_isotopicDataFilePath.isEmpty())
    {
      // We have a file name, store in there.
      return isotopic_data_handler.writeData(
        pol_chem_def_sp->m_isotopicDataFilePath);
    }

  // Last resort: deduce the isotopic data file name from the directory
  // of the polymer chemistry definition.

  QString local_file_path = pol_chem_def_sp->getXmlDataDirPath();
  QFileInfo local_file_info(local_file_path);

  if(!local_file_info.exists())
    qFatal(
      "Programming error. At this stage the name of the polymer "
      "chemistry definition file should be known.");

  QDir dir(local_file_info.dir());

  local_file_path =
    dir.absolutePath() + QDir::separator() + "isotopic-data.dat";

  pol_chem_def_sp->setIsotopicDataFilePath(local_file_path);

  return isotopic_data_handler.writeData(local_file_path);
}

/*!
\brief Parses the polymer chemistry definition file and updates \a
pol_chem_def_sp accordingly.

Upon parsing of the file and validation of the data, the \a
pol_chem_def_sp is updated, essentially initializing it with the data from
the file.

Note that the \a pol_chem_def_sp should have a working set of isotopic
data.

Returns true if the parsing was successful and false otherwise.
*/
bool
PolChemDef::renderXmlPolChemDefFileVersion1(PolChemDefSPtr pol_chem_def_sp)
{
  qDebug() << "Rendering PolChemDef file VERSION 1.";

  if(pol_chem_def_sp == nullptr && pol_chem_def_sp.get() == nullptr)
    qFatal()
      << "Programming error. The PolChemDef pointer cannot be nullptr.";

  // qDebug() << "The PolChemDef *:" << pol_chem_def_sp.get()
  //<< "and usage:" << pol_chem_def_sp.use_count();

  ///////////////////// ATTENTION ///////////////////
  // Before reading the polymer chemistry data file, we need to make sure
  // we actually have read the isotopic data!

  // qDebug() << "First check if we have isotopic data ready.";

  if(pol_chem_def_sp->getIsotopicDataSPtr() == nullptr)
    {
      // qDebug() << "No isotopic data found in the polymer chemistry "
      //             "definition, need to load the data.";

      // First read the data!
      std::size_t count = pol_chem_def_sp->loadIsotopicData(pol_chem_def_sp);

      if(!count)
        qFatal("Programming error. The isotopic data could not be loaded.");

      // qDebug() << "At this point the isotopic data were loaded fine
      // with"
      //<< count << "isotopes loaded.";
    }

  QDomDocument doc("polChemDefData");
  QDomElement element;
  QDomElement child;
  QDomElement indentedChild;

  QFile file(pol_chem_def_sp->m_xmlDataFilePath);

  // qDebug() << "The polymer chemistry definition file:"
  //          << pol_chem_def_sp->m_xmlDataFilePath;

  // The general structure of the file we are reading is this:
  //
  // <polchemdefinition version="1">
  // <polchemdefdata">
  //   <name>protein</name>
  //   <leftcap>+H</leftcap>
  //   <rightcap>+OH</rightcap>
  //   <codelen>1</codelen>
  //   <ionizerule>
  //     <formula>+H</formula>
  //     <charge>1</charge>
  //     <level>1</level>
  //   </ionizerule>
  //   <monomers>
  //     <mnm>
  //       <name>Glycine</name>
  //       <code>G</code>
  //       <formula>C2H3NO</formula>
  //     </mnm>
  //   </monomers>
  //   <modifs>
  //     <mdf>
  //       <name>Phosphorylation</name>
  //       <formula>-H+H2PO3</formula>
  //     </mdf>
  //   </modifs>
  //   <cleavespecs>
  //     <cls>
  //       <name>CyanogenBromide</name>
  //       <pattern>M/</pattern>
  //       <clr>
  //         <re-mnm-code>M</re-mnm-code>
  //         <re-formula>-CH2S+O</re-formula>
  //       </clr>
  //     </cls>
  //  </cleavespecs>
  //   <fragspecs>
  //     <fgs>
  //       <name>a</name>
  //       <end>LE</end>
  //       <formula>-C1O1</formula>
  //       <fgr>
  //         <name>a-fgr-1</name>
  //         <formula>+H200</formula>
  //         <prev-mnm-code>E</prev-mnm-code>
  //         <this-mnm-code>D</this-mnm-code>
  //         <next-mnm-code>F</next-mnm-code>
  //         <comment>comment here!</comment>
  //       </fgr>
  //     </fgs>
  //  </fragspecs>
  // </polchemdefdata>
  // </polchemdefinition>


  if(!file.open(QIODevice::ReadOnly))
    return false;

  if(!doc.setContent(&file))
    {
      qDebug() << "Failed to set doc content.";

      file.close();
      return false;
    }

  file.close();

  // qDebug() << "Closed the polymer chemistry definition file.";

  element = doc.documentElement();

  if(element.tagName() != "polchemdefinition")
    {
      qDebug() << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  ///////////////////////////////////////////////
  // Check the version of the document.

  QString text;

  if(!element.hasAttribute("version"))
    text = "1";
  else
    text = element.attribute("version");

  qDebug() << "The format of the definition:" << text;

  bool ok = false;

  int version = text.toInt(&ok, 10);

  if(version != 1 || !ok)
    {
      qDebug() << "Polymer chemistry definition file has bad "
                  "version number:"
               << version;

      return false;
    }

  //////////////////////////////////////////////
  // <polymer chemistry data>

  child = element.firstChildElement();
  if(child.tagName() != "polchemdefdata")
    {
      qDebug() << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  // <name>
  child = child.firstChildElement();
  if(child.tagName() != "name")
    return false;
  pol_chem_def_sp->m_name = child.text();

  // <leftcap>
  child = child.nextSiblingElement();
  if(child.tagName() != "leftcap")
    return false;
  pol_chem_def_sp->m_leftCap.setActionFormula(child.text());

  // <rightcap>
  child = child.nextSiblingElement();
  if(child.tagName() != "rightcap")
    return false;
  pol_chem_def_sp->m_rightCap.setActionFormula(child.text());

  // <codelen>
  child = child.nextSiblingElement();
  if(child.tagName() != "codelen")
    return false;
  ok                            = false;
  pol_chem_def_sp->m_codeLength = child.text().toInt(&ok);
  if(pol_chem_def_sp->m_codeLength == 0 && !ok)
    return false;
  qDebug() << "Now set the code length to" << pol_chem_def_sp->m_codeLength;

  // <ionizerule>
  child = child.nextSiblingElement();
  if(child.tagName() != "ionizerule")
    return false;

  //  It is essential that we endow the Ionizer with the IsotopicData !
  pol_chem_def_sp->m_ionizer.setIsotopicDataCstSPtr(
    pol_chem_def_sp->getIsotopicDataCstSPtr());

  //  Now we can render the Ionizer by looking into the <ionizerule> XML
  //  element.
  if(!pol_chem_def_sp->m_ionizer.renderXmlIonizeRuleElement(child))
    return false;

  // Finally, we have to ascertain that the Ionizer is valid.
  ErrorList error_list;
  if(!pol_chem_def_sp->m_ionizer.validate(&error_list))
    {
      qDebug() << "The Ionizer is invalid,  with errors:"
               << Utils::joinErrorList(error_list, ", ");
      return false;
    }

  qDebug() << "Now starting the rendering of the <monomers> element.";

  // <monomers>
  child = child.nextSiblingElement();
  if(child.tagName() != "monomers")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      qDebug() << "Now rendering one of all the mnm elements.";

      if(indentedChild.tagName() != "mnm")
        {
          qDebug() << "The expected <mnm> element was not found.";
          return false;
        }

      //  Constructing a Monomer using the XML element ensures
      //  that the masses are properly calculated for it,  so
      //  that MonomerCstSPtr point to fully qualified Monomer
      //  instances that properly play the role of reference
      //  throughout the chemical entities that involve Monomer
      //  instances in the context of this PolChemDef.
      MonomerSPtr monomer_sp =
        std::make_shared<Monomer>(pol_chem_def_sp, indentedChild, version);

      if(!monomer_sp->isValid())
        {
          qDebug() << "Failed to render mnm element.";

          monomer_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_monomers.push_back(monomer_sp);

      indentedChild = indentedChild.nextSiblingElement();
    }

  qDebug() << "Size of MonomerList:"
           << pol_chem_def_sp->getMonomersCstRef().size()
           << "pol chem def usage:" << pol_chem_def_sp.use_count();

  qDebug() << "Now starting the rendering of the <modifs> element.";

  // <modifs>
  child = child.nextSiblingElement();
  if(child.tagName() != "modifs")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "mdf")
        return false;

      ModifSPtr modif_sp =
        std::make_shared<Modif>(pol_chem_def_sp, indentedChild, version);

      if(!modif_sp->isValid())
        {
          qDebug()
            << "Failed to render the Modif instance from an XML <mdf> element.";
          modif_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_modifs.push_back(modif_sp);

      indentedChild = indentedChild.nextSiblingElement();
    }

  qDebug() << "Size of ModifList:" << pol_chem_def_sp->getModifsCstRef().size()
           << "pol chem def usage:" << pol_chem_def_sp.use_count();

  qDebug() << "Now starting the rendering of the <crosslinkers> element.";

  // <crosslinkers>
  // Note that crosslinkers have appeared since version 3.

  child = child.nextSiblingElement();
  if(child.tagName() != "crosslinkers")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "clk")
        return false;

      CrossLinkerSPtr crossLinker_sp =
        std::make_shared<CrossLinker>(pol_chem_def_sp, indentedChild, version);

      if(!crossLinker_sp->isValid())
        {
          qDebug() << "Failed to render the CrossLinker instance from an "
                      "XML <clk> element.";
          crossLinker_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_crossLinkers.push_back(crossLinker_sp);

      qDebug() << "Rendered CrossLinker: " << crossLinker_sp->getName()
               << "with formula:" << crossLinker_sp->getFormula()
               << "with masses:"
               << crossLinker_sp->getMass(Enums::MassType::MONO) << "/"
               << crossLinker_sp->getMass(Enums::MassType::AVG);

      indentedChild = indentedChild.nextSiblingElement();
    }

  qDebug() << "Size of CrossLinker container:"
           << pol_chem_def_sp->getCrossLinkersCstRef().size()
           << "pol chem def usage:" << pol_chem_def_sp.use_count();

  qDebug() << "Now starting the rendering of the <cleavespecs> element.";

  // <cleavespecs>
  child = child.nextSiblingElement();
  if(child.tagName() != "cleavespecs")
    {
      qDebug() << "Failed to load PolChemDef file: <cleavespecs> element "
                  "not found.";
      return false;
    }

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "cls")
        {
          qDebug()
            << "Failed to load PolChemDef file: <cls> element not found.";
          return false;
        }

      CleavageAgentSPtr cleavage_agent_sp = std::make_shared<CleavageAgent>(
        pol_chem_def_sp, indentedChild, version);

      if(!cleavage_agent_sp->isValid())
        {
          qDebug() << "Failed to render the CleaveSpec instance from an "
                      "XML <cls> element.";
          cleavage_agent_sp.reset();
          return false;
        }

      // qDebug() << "Rendered CleavageAgent: " << cleavage_agent_sp->getName()
      //          << "with pattern:" << cleavage_agent_sp->getPattern();

      pol_chem_def_sp->m_cleavageAgents.push_back(cleavage_agent_sp);


      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of CleavageAgent container:"
  //          << pol_chem_def_sp->getCleavageAgentsCstRef().size()
  //          << "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug() << "Now starting the rendering of the <fragspecs> element.";

  // <fragspecs>
  child = child.nextSiblingElement();
  if(child.tagName() != "fragspecs")
    {
      qDebug()
        << "Failed to load PolChemDef file: <fragspecs> element not found.";
      return false;
    }

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "fgs")
        {
          qDebug()
            << "Failed to load PolChemDef file: <fgs> element not found.";
          return false;
        }

      FragmentationPathwaySPtr fragmentation_pathway_sp =
        std::make_shared<FragmentationPathway>(
          pol_chem_def_sp, indentedChild, version);

      if(!fragmentation_pathway_sp->isValid())
        {
          qDebug() << "Failed to render the FragSpec instance from an "
                      "XML <fgs> element.";
          fragmentation_pathway_sp.reset();
          return false;
        }

      qDebug() << "Rendered FragmentationPathway: "
               << fragmentation_pathway_sp->getName();

      pol_chem_def_sp->m_fragmentationPathways.push_back(
        fragmentation_pathway_sp);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of FragmentationPathway container:"
  //          << pol_chem_def_sp->getFragmentationPathwaysCstRef().size()
  //          << "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug() << "Done rendering the PolChemDef file. Setting m_isValid to true
  // "
  //             "and returning it.";

  pol_chem_def_sp->m_isValid = true;

  return pol_chem_def_sp->m_isValid;
}

/*!
\brief Parses the polymer chemistry definition file and updates \a
pol_chem_def_sp accordingly.

Upon parsing of the file and validation of the data, the \a
pol_chem_def_sp is updated, essentially initializing it with the data from
the file.

Note that the \a pol_chem_def_sp should have a working set of isotopic
data.

Returns true if the parsing was successful and false otherwise.
*/
bool
PolChemDef::renderXmlPolChemDefFileVersion2(PolChemDefSPtr pol_chem_def_sp)
{
  // qDebug() << "Rendering PolChemDef file VERSION 2.";

  if(pol_chem_def_sp == nullptr && pol_chem_def_sp.get() == nullptr)
    qFatal()
      << "Programming error. The PolChemDef pointer cannot be nullptr.";

  // qDebug() << "The PolChemDef *:" << pol_chem_def_sp.get()
  //<< "and usage:" << pol_chem_def_sp.use_count();

  ///////////////////// ATTENTION ///////////////////
  // Before reading the polymer chemistry data file, we need to make sure
  // we actually have read the isotopic data!

  // qDebug() << "First check if we have isotopic data ready.";

  if(pol_chem_def_sp->getIsotopicDataSPtr() == nullptr)
    {
      // qDebug() << "No isotopic data found in the polymer chemistry "
      //             "definition, need to load the data.";

      // First read the data!
      std::size_t count = pol_chem_def_sp->loadIsotopicData(pol_chem_def_sp);

      if(!count)
        qFatal("Programming error. The isotopic data could not be loaded.");

      // qDebug() << "At this point the isotopic data were loaded fine
      // with"
      //<< count << "isotopes loaded.";
    }

  QDomDocument doc("polChemDefData");
  QDomElement element;
  QDomElement child;
  QDomElement indentedChild;

  QFile file(pol_chem_def_sp->m_xmlDataFilePath);

  // qDebug() << "The polymer chemistry definition file:"
  //          << pol_chem_def_sp->m_xmlDataFilePath;

  // The general structure of the file we are reading is this:
  //
  // <polchemdefinition version="1">
  // <polchemdefdata">
  //   <name>protein</name>
  //   <leftcap>+H</leftcap>
  //   <rightcap>+OH</rightcap>
  //   <codelen>1</codelen>
  //   <ionizerule>
  //     <formula>+H</formula>
  //     <charge>1</charge>
  //     <level>1</level>
  //   </ionizerule>
  //   <monomers>
  //     <mnm>
  //       <name>Glycine</name>
  //       <code>G</code>
  //       <formula>C2H3NO</formula>
  //     </mnm>
  //   </monomers>
  //   <modifs>
  //     <mdf>
  //       <name>Phosphorylation</name>
  //       <formula>-H+H2PO3</formula>
  //     </mdf>
  //   </modifs>
  //   <cleavageagents>
  //     <cla>
  //       <name>CyanogenBromide</name>
  //       <pattern>M/</pattern>
  //       <clr>
  //         <re-mnm-code>M</re-mnm-code>
  //         <re-formula>-CH2S+O</re-formula>
  //       </clr>
  //     </cla>
  //  </cleavageagents>
  //   <fragmentationpathways>
  //     <fgp>
  //       <name>a</name>
  //       <end>LE</end>
  //       <formula>-C1O1</formula>
  //       <fgr>
  //         <name>a-fgr-1</name>
  //         <formula>+H200</formula>
  //         <prev-mnm-code>E</prev-mnm-code>
  //         <this-mnm-code>D</this-mnm-code>
  //         <next-mnm-code>F</next-mnm-code>
  //         <comment>comment here!</comment>
  //       </fgr>
  //     </fgp>
  //  </fragmentationpathways>
  // </polchemdefdata>
  // </polchemdefinition>


  if(!file.open(QIODevice::ReadOnly))
    return false;

  if(!doc.setContent(&file))
    {
      qDebug() << "Failed to set doc content.";

      file.close();
      return false;
    }

  file.close();

  // qDebug() << "Closed the polymer chemistry definition file.";

  element = doc.documentElement();

  if(element.tagName() != "polchemdefinition")
    {
      qDebug() << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  ///////////////////////////////////////////////
  // Check the version of the document.

  QString text;

  if(!element.hasAttribute("version"))
    text = "1";
  else
    text = element.attribute("version");

  // qDebug() << "The format of the definition:" << text;

  bool ok = false;

  int version = text.toInt(&ok, 10);

  if(version != 2 || !ok)
    {
      // qDebug() << "Polymer chemistry definition file has bad "
      //             "version number:"
      //          << version;

      return false;
    }

  //////////////////////////////////////////////
  // <polymer chemistry data>

  child = element.firstChildElement();
  if(child.tagName() != "polchemdefdata")
    {
      qDebug() << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  // <name>
  child = child.firstChildElement();
  if(child.tagName() != "name")
    return false;
  pol_chem_def_sp->m_name = child.text();

  // <leftcap>
  child = child.nextSiblingElement();
  if(child.tagName() != "leftcap")
    return false;
  pol_chem_def_sp->m_leftCap.setActionFormula(child.text());

  // <rightcap>
  child = child.nextSiblingElement();
  if(child.tagName() != "rightcap")
    return false;
  pol_chem_def_sp->m_rightCap.setActionFormula(child.text());

  // <codelen>
  child = child.nextSiblingElement();
  if(child.tagName() != "codelen")
    return false;
  ok                            = false;
  pol_chem_def_sp->m_codeLength = child.text().toInt(&ok);
  if(pol_chem_def_sp->m_codeLength == 0 && !ok)
    return false;
  // qDebug() << "Set the code length to" << pol_chem_def_sp->m_codeLength;

  // <ionizerule>
  child = child.nextSiblingElement();
  if(child.tagName() != "ionizerule")
    return false;

  //  It is essential that we endow the Ionizer with the IsotopicData !
  pol_chem_def_sp->m_ionizer.setIsotopicDataCstSPtr(
    pol_chem_def_sp->getIsotopicDataCstSPtr());

  //  Now we can render the Ionizer by looking into the <ionizerule> XML
  //  element.
  if(!pol_chem_def_sp->m_ionizer.renderXmlIonizeRuleElement(child))
    return false;

  // Finally, we have to ascertain that the Ionizer is valid.
  ErrorList error_list;
  if(!pol_chem_def_sp->m_ionizer.validate(&error_list))
    {
      qDebug() << "The Ionizer is invalid,  with errors:"
               << Utils::joinErrorList(error_list, ", ");
      return false;
    }

  // qDebug() << "Now starting the rendering of the <monomers> element.";

  // <monomers>
  child = child.nextSiblingElement();
  if(child.tagName() != "monomers")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      // qDebug() << "Now rendering one of all the mnm elements.";

      if(indentedChild.tagName() != "mnm")
        {
          qDebug() << "The expected <mnm> element was not found.";
          return false;
        }

      //  Constructing a Monomer using the XML element ensures
      //  that the masses are properly calculated for it,  so
      //  that MonomerCstSPtr point to fully qualified Monomer
      //  instances that properly play the role of reference
      //  throughout the chemical entities that involve Monomer
      //  instances in the context of this PolChemDef.
      MonomerSPtr monomer_sp =
        std::make_shared<Monomer>(pol_chem_def_sp, indentedChild, version);

      if(!monomer_sp->isValid())
        {
          qDebug() << "Failed to render mnm element.";

          monomer_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_monomers.push_back(monomer_sp);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of MonomerList:"
  //          << pol_chem_def_sp->getMonomersCstRef().size()
  //          << "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug() << "Now starting the rendering of the <modifs> element.";

  // <modifs>
  child = child.nextSiblingElement();
  if(child.tagName() != "modifs")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "mdf")
        return false;

      ModifSPtr modif_sp =
        std::make_shared<Modif>(pol_chem_def_sp, indentedChild, version);

      if(!modif_sp->isValid())
        {
          qDebug()
            << "Failed to render the Modif instance from an XML <mdf> element.";
          modif_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_modifs.push_back(modif_sp);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of ModifList:" <<
  // pol_chem_def_sp->getModifsCstRef().size()
  //          << "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug() << "Now starting the rendering of the <crosslinkers> element.";

  // <crosslinkers>
  // Note that crosslinkers have appeared since version 3.

  child = child.nextSiblingElement();
  if(child.tagName() != "crosslinkers")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "clk")
        return false;

      CrossLinkerSPtr crossLinker_sp =
        std::make_shared<CrossLinker>(pol_chem_def_sp, indentedChild, version);

      if(!crossLinker_sp->isValid())
        {
          qDebug() << "Failed to render the CrossLinker instance from an "
                      "XML <clk> element.";
          crossLinker_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_crossLinkers.push_back(crossLinker_sp);

      // qDebug() << "Rendered CrossLinker: " << crossLinker_csp->getName()
      //          << "with formula:" << crossLinker_csp->getFormula()
      //          << "with masses:" <<
      //          crossLinker_csp->getMass(Enums::MassType::MONO)
      //          << "/" << crossLinker_csp->getMass(Enums::MassType::AVG);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of CrossLinker container:"
  //          << pol_chem_def_sp->getCrossLinkersCstRef().size()
  //          << "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug() << "Now starting the rendering of the <cleavageagents> element.";

  // <cleavageagents>
  child = child.nextSiblingElement();
  if(child.tagName() != "cleavageagents")
    {
      qDebug() << "Failed to load PolChemDef file: <cleavageagents> element "
                  "not found.";
      return false;
    }

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "cla")
        {
          qDebug()
            << "Failed to load PolChemDef file: <cla> element not found.";
          return false;
        }

      // qDebug() << "Will allocate a new CleavageAgent instance->shared
      // pointer.";

      CleavageAgentSPtr cleavage_agent_sp = std::make_shared<CleavageAgent>(
        pol_chem_def_sp, indentedChild, version);

      if(!cleavage_agent_sp->isValid())
        {
          qDebug() << "Failed to render the CleavageAgent instance from an "
                      "XML <cla> element.";
          cleavage_agent_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_cleavageAgents.push_back(cleavage_agent_sp);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of CleavageAgent container:"
  //          << pol_chem_def_sp->getCleavageAgentsCstRef().size()
  //          << "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug()
  //   << "Now starting the rendering of the <fragmentationpathways> element.";

  // <fragmentationpathways>
  child = child.nextSiblingElement();
  if(child.tagName() != "fragmentationpathways")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "fgp")
        {
          qDebug()
            << "Failed to load PolChemDef file: <fgp> element not found.";
          return false;
        }

      FragmentationPathwaySPtr fragmentation_pathway_sp =
        std::make_shared<FragmentationPathway>(
          pol_chem_def_sp, indentedChild, version);

      if(!fragmentation_pathway_sp->isValid())
        {
          qDebug()
            << "Failed to render the FragmentationPathway instance from an "
               "XML <fgs> element.";
          fragmentation_pathway_sp.reset();
          return false;
        }

      pol_chem_def_sp->m_fragmentationPathways.push_back(
        fragmentation_pathway_sp);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of FragmentationPathway container:"
  //          << pol_chem_def_sp->getFragmentationPathwaysCstRef().size()
  //          << "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug() << "Done rendering the PolChemDef file. Setting m_isValid to true
  // "
  //             "and returning it.";

  pol_chem_def_sp->m_isValid = true;

  return pol_chem_def_sp->m_isValid;
}

/*!
\brief Parses the polymer chemistry definition file and updates \a
pol_chem_def_sp accordingly.

Upon parsing of the file and validation of the data, the \a
pol_chem_def_sp is updated, essentially initializing it with the data from
the file.

Note that the \a pol_chem_def_sp should have a working set of isotopic
data.

Returns true if the parsing was successful and false otherwise.
*/
bool
PolChemDef::renderXmlPolChemDefFile(PolChemDefSPtr pol_chem_def_sp)
{
  if(pol_chem_def_sp == nullptr && pol_chem_def_sp.get() == nullptr)
    qFatal()
      << "Programming error. The PolChemDef pointer cannot be nullptr.";

  // qDebug() << "The PolChemDef *:" << pol_chem_def_sp.get()
  //<< "and usage:" << pol_chem_def_sp.use_count();

  ///////////////////// ATTENTION ///////////////////
  // Before reading the polymer chemistry data file, we need to make sure
  // we actually have read the isotopic data!

  // qDebug() << "First check if we have isotopic data ready.";

  if(pol_chem_def_sp->getIsotopicDataSPtr() == nullptr)
    {
      // qDebug() << "No isotopic data found in the polymer chemistry "
      //             "definition, need to load the data.";

      // First read the data!
      std::size_t count = pol_chem_def_sp->loadIsotopicData(pol_chem_def_sp);

      if(!count)
        qFatal("Programming error. The isotopic data could not be loaded.");

      // qDebug() << "At this point the isotopic data were loaded fine
      // with"
      //<< count << "isotopes loaded.";
    }

  QDomDocument doc("polChemDefData");
  QDomElement element;
  QDomElement child;
  QDomElement indentedChild;

  QFile file(pol_chem_def_sp->m_xmlDataFilePath);

  // qDebug() << "The polymer chemistry definition file:"
  //          << pol_chem_def_sp->m_xmlDataFilePath;

  // The general structure of the file we are reading is this:
  //
  // <polchemdefinition version="1">
  // <polchemdefdata">
  //   <name>protein</name>
  //   <leftcap>+H</leftcap>
  //   <rightcap>+OH</rightcap>
  //   <codelen>1</codelen>
  //   <ionizerule>
  //     <formula>+H</formula>
  //     <charge>1</charge>
  //     <level>1</level>
  //   </ionizerule>
  //   <monomers>
  //     <mnm>
  //       <name>Glycine</name>
  //       <code>G</code>
  //       <formula>C2H3NO</formula>
  //     </mnm>
  //   </monomers>
  //   <modifs>
  //     <mdf>
  //       <name>Phosphorylation</name>
  //       <formula>-H+H2PO3</formula>
  //     </mdf>
  //   </modifs>
  //   <cleavageagents>
  //     <cla>
  //       <name>CyanogenBromide</name>
  //       <pattern>M/</pattern>
  //       <clr>
  //         <re-mnm-code>M</re-mnm-code>
  //         <re-formula>-CH2S+O</re-formula>
  //       </clr>
  //     </cla>
  //  </cleavageagents>
  //   <fragmentationpathways>
  //     <fgp>
  //       <name>a</name>
  //       <end>LE</end>
  //       <formula>-C1O1</formula>
  //       <fgr>
  //         <name>a-fgr-1</name>
  //         <formula>+H200</formula>
  //         <prev-mnm-code>E</prev-mnm-code>
  //         <this-mnm-code>D</this-mnm-code>
  //         <next-mnm-code>F</next-mnm-code>
  //         <comment>comment here!</comment>
  //       </fgr>
  //      </fgp>
  //  </fragmentationpathways>
  // </polchemdefdata>
  // </polchemdefinition>


  if(!file.open(QIODevice::ReadOnly))
    return false;

  if(!doc.setContent(&file))
    {
      qDebug() << "Failed to set doc content.";

      file.close();
      return false;
    }

  file.close();

  // qDebug() << "Closed the polymer chemistry definition file.";

  element = doc.documentElement();

  if(element.tagName() != "polchemdefinition")
    {
      qDebug() << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  ///////////////////////////////////////////////
  // Check the version of the document.

  QString text;

  if(!element.hasAttribute("version"))
    text = "1";
  else
    text = element.attribute("version");

  // qDebug() << "The format of the definition:" << text;

  bool ok = false;

  int version = text.toInt(&ok, 10);

  if(version < 1 || !ok)
    {
      qDebug() << "Polymer chemistry definition file has bad "
                  "version number:"
               << version;

      return false;
    }

  // FIXME, done already  file.close();

  // qDebug() << "The version of the Polymer Chemistry Definition file is:"
  //          << version;

  if(version == 1)
    return PolChemDef::renderXmlPolChemDefFileVersion1(pol_chem_def_sp);
  else if(version == 2)
    return PolChemDef::renderXmlPolChemDefFileVersion2(pol_chem_def_sp);

  return false;
}

/*!
\brief Returns a string with the XML DTD for a polymer chemistry
definition file.
*/
QString
PolChemDef::formatXmlDtd()
{
  QString string = QString(
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
    "<!-- DTD for polymer definitions, used by the\n"
    "'massXpert' mass spectrometry application.\n"
    "Copyright 2006,2007,2008 Filippo Rusconi\n"
    "Licensed under the GNU GPL -->\n"
    "<!DOCTYPE polchemdefinition [\n"
    "<!ELEMENT polchemdefinition (polchemdefdata)>\n"
    "<!ATTLIST polchemdefinition version NMTOKEN #REQUIRED>\n"
    "<!ELEMENT polchemdefdata "
    "(name,leftcap,rightcap,codelen,ionizerule,monomers,modifs,"
    "crosslinkers,"
    "cleavageagents,fragmentationpathways)>\n"
    "<!ELEMENT ionizerule (formula,charge,level)>\n"
    "<!ELEMENT monomers (mnm*)>\n"
    "<!ELEMENT modifs (mdf*)>\n"
    "<!ELEMENT crosslinkers (clk*)>\n"
    "<!ELEMENT cleavageagents (cla*)>\n"
    "<!ELEMENT fragmentationpathways (fgp*)>\n"
    "<!ELEMENT mnm (name,code,formula)>\n"
    "<!ELEMENT mdf (name,formula,targets,maxcount)>\n"
    "<!ELEMENT clk (name,formula,modifname*)>\n"
    "<!ELEMENT cla (name,pattern,clr*)>\n"
    "<!ELEMENT fgp (name,end,formula,sidechaincontrib,comment?,fgr*)>\n"
    "<!ELEMENT clr "
    "(name,(le-mnm-code,le-formula)?,(re-mnm-code,re-formula)?)>\n"
    "<!ELEMENT fgr "
    "(name,formula,prev-mnm-code?,curr-mnm-code?,next-mnm-code?,comment?)"
    ">\n"
    "<!ELEMENT leftcap (#PCDATA)>\n"
    "<!ELEMENT rightcap (#PCDATA)>\n"
    "<!ELEMENT codelen (#PCDATA)>\n"
    "<!ELEMENT charge (#PCDATA)>\n"
    "<!ELEMENT maxcount (#PCDATA)>\n"
    "<!ELEMENT level (#PCDATA)>\n"
    "<!ELEMENT name (#PCDATA)>\n"
    "<!ELEMENT modifname (#PCDATA)>\n"
    "<!ELEMENT code (#PCDATA)>\n"
    "<!ELEMENT formula (#PCDATA)>\n"
    "<!ELEMENT sidechaincontrib (#PCDATA)>\n"
    "<!ELEMENT targets (#PCDATA)>\n"
    "<!ELEMENT pattern (#PCDATA)>\n"
    "<!ELEMENT end (#PCDATA)>\n"
    "<!ELEMENT le-mnm-code (#PCDATA)>\n"
    "<!ELEMENT re-mnm-code (#PCDATA)>\n"
    "<!ELEMENT le-formula (#PCDATA)>\n"
    "<!ELEMENT re-formula (#PCDATA)>\n"
    "<!ELEMENT comment (#PCDATA)>\n"
    "<!ELEMENT prev-mnm-code (#PCDATA)>\n"
    "<!ELEMENT curr-mnm-code (#PCDATA)>\n"
    "<!ELEMENT next-mnm-code (#PCDATA)>\n"
    "]>\n");

  return string;
}

/*!
\brief Writes the polymer chemistry definition to file.

The file's name is from m_xmlDataFilePath.

Returns true if successful, false otherwise.
*/
bool
PolChemDef::writeXmlFile()
{
  QString indent("  ");
  QString lead;

  int offset = 0;
  int iter   = 0;

  // We are asked to send an xml description of the polymer chemistry
  // definition.

  QFile file(m_xmlDataFilePath);

  if(!file.open(QIODevice::WriteOnly))
    {
      qDebug() << "Failed to open file" << m_xmlDataFilePath << "for writing.";

      return false;
    }

  QTextStream stream(&file);
  stream.setEncoding(QStringConverter::Utf8);

  // The DTD
  stream << formatXmlDtd();


  // Open the <polchemdefinition> element.
  stream << QString("<polchemdefinition version=\"%1\">\n")
              .arg(POL_CHEM_DEF_FILE_FORMAT_VERSION);

  // Prepare the lead.
  ++offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  // Open the <polchemdefdata> element.
  stream << QString("%1<polchemdefdata>\n").arg(lead);

  // Prepare the lead.
  ++offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  stream << QString("%1<leftcap>%2</leftcap>\n")
              .arg(lead)
              .arg(m_leftCap.getActionFormula(/*with_title*/ true));

  stream << QString("%1<rightcap>%2</rightcap>\n")
              .arg(lead)
              .arg(m_rightCap.getActionFormula(/*with_title*/ true));

  stream << QString("%1<codelen>%2</codelen>\n").arg(lead).arg(m_codeLength);

  // Before writing the ionization rule, set the level to 1. This
  // member datum is set to 0 in the constructor.
  m_ionizer.setLevel(1);
  stream << m_ionizer.formatXmlIonizeRuleElement(offset);

  stream << QString("%1<monomers>\n").arg(lead);

  // Prepare the lead.
  ++offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  for(const auto &monomer_csp : m_monomers)
    stream << monomer_csp->formatXmlMnmElement(offset);

  // Prepare the lead.
  --offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1</monomers>\n").arg(lead);


  stream << QString("%1<modifs>\n").arg(lead);

  // Prepare the lead.
  ++offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  for(const auto &modif_csp : m_modifs)
    stream << modif_csp->formatXmlMdfElement(offset);

  // Prepare the lead.
  --offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1</modifs>\n").arg(lead);

  stream << QString("%1<crosslinkers>\n").arg(lead);

  // Prepare the lead.
  ++offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  for(const auto &cross_linker_csp : m_crossLinkers)
    stream << cross_linker_csp->formatXmlClkElement(offset);

  // Prepare the lead.
  --offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1</crosslinkers>\n").arg(lead);


  stream << QString("%1<cleavageagents>\n").arg(lead);

  // Prepare the lead.
  ++offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  for(const auto &cleavage_agent_csp : m_cleavageAgents)
    stream << cleavage_agent_csp->formatXmlClaElement(offset);

  // Prepare the lead.
  --offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1</cleavageagents>\n").arg(lead);


  stream << QString("%1<fragmentationpathways>\n").arg(lead);


  // Prepare the lead.
  ++offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  for(const auto &fragmentation_pathway_csp : m_fragmentationPathways)
    stream << fragmentation_pathway_csp->formatXmlFgpElement(offset);

  // Prepare the lead.
  --offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1</fragmentationpathways>\n").arg(lead);

  // Prepare the lead.
  --offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1</polchemdefdata>\n").arg(lead);

  // Prepare the lead.
  --offset;
  lead.clear();
  iter = 0;
  while(iter < offset)
    {
      lead += indent;
      ++iter;
    }

  stream << QString("%1</polchemdefinition>\n").arg(lead);

  file.close();

  return true;
}

/*!
\brief Validates this PolChemDef instance by checking the validity of all its
members data. Returns true if validation was successful, false otherwise. Any
error is added to \a error_list_p if non-nullptr.
*/
bool
PolChemDef::validate(ErrorList *error_list_p) const
{
  // So we can return immediately upon error.
  m_isValid = false;

  if(m_isotopicDataFilePath.isEmpty())
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The IsotopicData file path is empty.");
      return false;
    }

  QFileInfo file_info(m_isotopicDataFilePath);
  if(!file_info.exists())
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The IsotopicData file cannot be found.");
      return false;
    }

  if(msp_isotopicData == nullptr || msp_isotopicData.get() == nullptr)
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The IsotopicData are not available.");
      return false;
    }

  if(m_name.isEmpty())
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The PolChemDef name is empty.");
      return false;
    }

  if(m_xmlDataFilePath.isEmpty())
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The PolChemDef file path is empty.");
      return false;
    }

  file_info.setFile(m_xmlDataFilePath);
  if(!file_info.exists())
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The PolChemDef file cannot be found.");
      return false;
    }

  if(!m_leftCap.validate(msp_isotopicData, error_list_p))
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The left cap formula failed to validate.");
      return false;
    }

  if(!m_rightCap.validate(msp_isotopicData, error_list_p))
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The right cap formula failed to validate.");
      return false;
    }

  if(m_codeLength < 1)
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The code length is less than 1.");
      return false;
    }

  if(!m_ionizer.validate(error_list_p))
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("The ionization rule failed to validate.");
      return false;
    }

  if(!m_modifs.size())
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("There is not a single Modif instance.");
      return false;
    }

  if(!m_monomers.size())
    {
      if(error_list_p != nullptr)
        error_list_p->push_back("There is not a single Monomer instance.");
      return false;
    }

  // Good!
  m_isValid = true;
  return m_isValid;
}

/*!
\brief Returns the validity status of thie PolChemDef instance.
*/
bool
PolChemDef::isValid() const
{
  return m_isValid;
}


} // namespace libXpertMassCore
} // namespace MsXpS
