/* 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
 */


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/FragmentationPathway.hpp"
#include "MsXpS/libXpertMassCore/PolChemDef.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The FragmentationPathway class provides a model for specifying gas phase
fragmentation pathways of \l{Oligomer} \l{Sequence}s.

The FragmentationPathway class provides the description of a fragmentation
pathway. Fragmentation pathways determine the chemical reaction that governs the
fragmentation of the polymer in the gas-phase. The chemical reaction is embodied
by a formula. The side of the polymer (left or right) that makes the fragment
after the fragmentation has occurred is described by a fragmentation-end
enumeration.

A fragmentation specification might not be enough information to determine the
manner in which a polymer fragments in the gas-phase. Fragmentation rules
(\l{FragmentationRule}s) might be required to refine the specification. A
fragmentation specification might hold as many \l{FragmentationRule}s as
required.
*/

/*!
\variable MsXpS::libXpertMassCore::FragmentationPathway::mcsp_polChemDef

\brief The PolChemDef (polymer chemistry definition) that is the context in
which the Oligomer being fragmented exists.
*/

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

\brief The name of the FragmentationPathway.
*/

/*!
\variable MsXpS::libXpertMassCore::FragmentationPathway::m_formula

\brief A \l{Formula} instance describing the fragmentation reaction occurring on
the Monomer at which the decomposition occurs.
*/

/*!
\variable MsXpS::libXpertMassCore::FragmentationPathway::m_fragEnd

\brief The end of the Oligomer precursor ion that is found in the product ion.

This member datum defines the end of the Oligomer sequence being fragmented that
is kept in the product ions (a, b, c ions keep the left end of the Oligomer,
while x, y, z ions keep the right end of the Oligomer; instead in protein
ammonium ions, no end is found in the product ions).
*/

/*!
\variable MsXpS::libXpertMassCore::FragmentationPathway::m_monomerContribution

\brief Contribution of the Monomer skeleton when decomposition occurs.

In some situations (nucleic acids, for example), upon fragmentation,
the Oligomer looses the nucleic base at the location of the backbone
decomposition (which yields an abasic ion product). This member allows to
indicate that the monomer (that is, the residue) is to be accounted for (by a
-1 value, the program accounts that the monomer is lost during decomposition).
But removing the whole monomer is not correct because that removes too much
material, so the member formula should account for the compensation of the
removed backbone.
*/

/*!
\variable MsXpS::libXpertMassCore::FragmentationPathway::m_comment

\brief A comment associated to the FragmentationPathway.
*/

/*!
\variable MsXpS::libXpertMassCore::FragmentationPathway::m_rules

\brief The container of FragmentationRuleSPtr that allow refining how the
fragmentation occurs at site depending on the identity of the Monomer occurring
either at previous position or next position with respect to the position in the
Oligomer undergoing fragmentation. The FragmentationRule instances are required
in the gas phase chemistry of sugars where the way decomposition occurs at a
given site depends on the identity of the immediate proximity of the decomposing
backbone region.
*/


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

\brief The validity status of this FragmentationPathway instance.
*/


/*!
\brief Constructs a FragmentationPathway instance starting from an XML <fgp> \a
element according to \a version and using a reference \a pol_chem_def_csp
polymer chemistry definition.

This is the current format (FAKE fgr):
\code
<fgp>
<name>c</name>
<end>LE</end>
<formula>+N1H2</formula>
<sidechaincontrib>0</sidechaincontrib>
<comment>thefragmentationpathwaycomment</comment>
<fgr>
<name>a-fgr-2</name>
<formula>+H100</formula>
<prev-mnm-code>F</prev-mnm-code>
<curr-mnm-code>D</curr-mnm-code>
<next-mnm-code>E</next-mnm-code>
<comment>therulecomment</comment>
</fgr>
</fgp>

\endcode

The rendering of the FragmentationPathway instances requires that the PolChemDef
be available.

Depending on \a version, two different functions are used to actually render the
XML element. Before version 2, the XML element was named <fgs> (FragSpec class)
and starting with version 2, the XML is named <fgp> (FragmentationPathway
class).

\sa renderXmlFgpElement(), renderXmlFgsElement()
*/
FragmentationPathway::FragmentationPathway(PolChemDefCstSPtr pol_chem_def_csp,
                                           const QDomElement &element,
                                           int version)
  : mcsp_polChemDef(pol_chem_def_csp)
{
  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    qDebug() << "Constructing FragmentationPathway with no PolChemDef.";

  if(version == 1)
    {
      if(!renderXmlFgsElement(element, version))
        qDebug()
          << "Failed to fully render or validate the FragSpec XML element "
             "for construction of FragSpec instance.";
    }
  else if(version == 2)
    {
      if(!renderXmlFgpElement(element, version))
        qDebug() << "Failed to fully render or validate the "
                    "FragmentationPathway XML element "
                    "for construction of FragmentationPathway instance.";
    }
  else
    qFatal()
      << "Programming error. The polymer chemistry definition version "
         "is not correct.";
}

/*!
\brief Constructs a FragmentationPathway instance with a number of parameters.

\list

\li \a pol_chem_def_csp The polymer chemistry definition (PolChemDef)

\li \a name The name of the fragmentation pathway (like 'y' for protein gas
phase chemistry)

\li \a formula_string The formula that describes the gas phase reaction

\li \a frag_end The end of the Oligomer sequence being fragmented that is kept
in the product ions (a, b, c ions keep the left end of the Oligomer, while x,
y, z ions keep the right end of the Oligomer)

\li \a comment A comment associated to the fragmentation pathway ("Observed with
high energy collisions", for example)

\endlist

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.

\sa validate()
*/
FragmentationPathway::FragmentationPathway(PolChemDefCstSPtr pol_chem_def_csp,
                                           const QString &name,
                                           const QString &formula_string,
                                           Enums::FragEnd frag_end,
                                           const QString &comment)
  : mcsp_polChemDef(pol_chem_def_csp),
    m_name(name),
    m_formula(Formula(formula_string)),
    m_fragEnd(frag_end),
    m_comment(comment)
{
  qDebug() << "Constructing FragmentationPathway with name:" << m_name;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Upon construction of the FragmentationPathway, the "
                "instance failed to "
                "validate with errors:"
             << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Constructs a FragmentationPathway instance as a copy of \a other.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.

\sa validate()
*/
FragmentationPathway::FragmentationPathway(const FragmentationPathway &other)
  : mcsp_polChemDef(other.mcsp_polChemDef),
    m_name(other.m_name),
    m_formula(other.m_formula),
    m_fragEnd(other.m_fragEnd),
    m_monomerContribution(other.m_monomerContribution),
    m_comment(other.m_comment)
{
  m_rules = other.m_rules;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Upon copy-construction of the FragmentationPathway, the "
                "instance failed to "
                "validate with errors:"
             << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Destroys this FragmentationPathway instance.
*/
FragmentationPathway::~FragmentationPathway()
{
  m_rules.clear();
}

//////////////// THE POLCHEMDEF /////////////////////
/*!
\brief Sets the PolChemDef (polymer chemistry definition) to \a
pol_chem_def_csp.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.

\sa validate()
*/
void
FragmentationPathway::setPolchemDefCstSPtr(PolChemDefCstSPtr pol_chem_def_csp)
{
  mcsp_polChemDef = pol_chem_def_csp;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Upon setting PolChemDef of FragmentationPathway, the "
                "instance failed to "
                "validate with errors:"
             << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Returns the PolChemDef (polymer chemistry definition).
*/
PolChemDefCstSPtr
FragmentationPathway::getPolchemDefCstSPtr() const
{
  return mcsp_polChemDef;
}

//////////////// THE NAME /////////////////////
/*!
\brief Sets the name to \a name.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.

\sa validate()
*/
void
FragmentationPathway::setName(const QString &name)
{
  m_name = name;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "Upon setting name of FragmentationPathway, the instance failed to "
         "validate with errors:"
      << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Returns the name.
*/
const QString &
FragmentationPathway::getName() const
{
  return m_name;
}

//////////////// THE FORMULA /////////////////////
/*!
\brief Sets the member Formula to \a formula.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.

\sa validate()
*/
void
FragmentationPathway::setFormula(const Formula &formula)
{
  m_formula = formula;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "Upon setting Formula of FragmentationPathway, the instance failed to "
         "validate with errors:"
      << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Sets the member Formula using \a formula_string.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.

\sa validate()
*/
void
FragmentationPathway::setFormula(const QString &formula_string)
{
  m_formula = Formula(formula_string);

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "Upon setting Formula of FragmentationPathway, the instance failed to "
         "validate with errors:"
      << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Returns a const reference to the member \l Formula.
*/
const Formula &
FragmentationPathway::getFormulaCstRef() const
{
  return m_formula;
}

/*!
\brief Returns a reference to the member \l Formula.
*/
Formula &
FragmentationPathway::getFormulaRef()
{
  return m_formula;
}

//////////////// THE FRAG END /////////////////////

/*!
\brief Sets the precursor end in the fragment to \a frag_end.

Upon fragmentation, two fragments are generated, one holding the left end of the
initial precursor ion and one holding the right end. \a frag_end indicates if
the fragment is a left end fragment or a right end fragment. For example, in
protein gas phase chemistry, the ions of the series a, b and c hold the left
end of the precursor ion, while the ions of the series x, y, and z hold the
right end of the precursor ion.
*/
void
FragmentationPathway::setFragEnd(Enums::FragEnd frag_end)
{
  m_fragEnd = frag_end;
}

/*!
\brief Returns the \l Enums::FragEnd.
*/
Enums::FragEnd
FragmentationPathway::getFragEnd() const
{
  return m_fragEnd;
}

//////////////// THE MONOMER CONTRIBUTION /////////////////////
/*!
\brief Sets the Monomer contribution to \a monomer_contribution.

In certain fragmentation pathways, the monomer undergoing decomposition might
loose a portion of its structure. This is the case in nucleic acids
fragmentation, when the nucleic base might be detached from the monomer
undergoing the fragmentation reaction.

This member allows to indicate if a part of the monomer is detaching upon
decomposition. In this case, this value is negative (-1) and indicates that the
mass of the monomer is to be removed from the mass of the fragment. However,
because it is not the full monomer that decomposes away, but only a part of it,
then it is the responsibility of the polymer chemistry definition designer to
add back the formula of the conserved monomer structure.

For example, in DNA fragmentation, the \c{abasic a} fragmentation pathway is
defined with a monomer contribution of -1, with the \c{-HOH+C5H8O5P} formula to
account for the remaining structure of the monomer (this combination has a net
loss of the base only and does not account for loss of the phospho moiety of the
monomer).
*/
void
FragmentationPathway::setMonomerContribution(int monomer_contribution)
{
  m_monomerContribution = monomer_contribution;
}

/*!
\brief Returns the Monomer contribution.
*/
int
FragmentationPathway::getMonomerContribution() const
{
  return m_monomerContribution;
}

//////////////// THE COMMENT /////////////////////
/*!
\brief Sets the comment to \a comment.
*/
void
FragmentationPathway::setComment(const QString &comment)
{
  m_comment = comment;
}

/*!
\brief Returns the comment.
*/
QString
FragmentationPathway::getComment() const
{
  return m_comment;
}

//////////////// THE RULES CONTAINER /////////////////////
/*!
\brief Returns a const reference to the container of \l FragmentationRule
instances.
*/
const std::vector<FragmentationRuleSPtr> &
FragmentationPathway::getRulesCstRef() const
{
  return m_rules;
}

/*!
\brief Returns a reference to the container of \l FragmentationRule instances.
*/
std::vector<FragmentationRuleSPtr> &
FragmentationPathway::getRulesRef()
{
  return m_rules;
}

//////////////// OPERATORS /////////////////////
/*!
\brief Assigns \a other to this FragmentationPathway instance.

Returns a reference to this FragmentationPathway.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.

\sa validate()
*/
FragmentationPathway &
FragmentationPathway::operator=(const FragmentationPathway &other)
{
  if(&other == this)
    return *this;

  mcsp_polChemDef = other.mcsp_polChemDef;

  m_name                = other.m_name;
  m_formula             = other.m_formula;
  m_fragEnd             = other.m_fragEnd;
  m_monomerContribution = other.m_monomerContribution;
  m_comment             = other.m_comment;
  m_rules               = other.m_rules;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "Upon assignment-configuration of the FragmentationPathway, the "
         "instance failed to "
         "validate with errors:"
      << Utils::joinErrorList(error_list, ", ");

  return *this;
}

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

The member PolChemDef is compared deeply and the member rules also.

Because the FragmentationRule instances are not a reference to the PolChemDef,
their comparison is deep.
*/
bool
FragmentationPathway::operator==(const FragmentationPathway &other) const
{
  if(&other == this)
    return true;

  // We cannot do this, because each chemical entity that references the
  // polymer chemistry definition will call this function and an
  // infinite loop ensues.
  // if(!mcsp_polChemDef->isChemicallyEquivalent(*other.mcsp_polChemDef))
  // return false;

  if(!(m_name == other.m_name && m_formula == other.m_formula &&
       m_fragEnd == other.m_fragEnd &&
       m_monomerContribution == other.m_monomerContribution &&
       m_comment == other.m_comment && m_rules.size() == other.m_rules.size()))
    return false;

  for(std::size_t iter = 0; iter < m_rules.size(); ++iter)
    if((*m_rules.at(iter)) != (*other.m_rules.at(iter)))
      return false;

  return true;
}

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

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

  return !operator==(other);
}

//////////////// RULES /////////////////////
/*!
\brief Adds the \a frag_rule_sp FragmentationRule instance to the member
container of FragmentationRule instances.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.
*/
void
FragmentationPathway::addRule(FragmentationRuleSPtr frag_rule_sp)
{
  if(frag_rule_sp == nullptr || frag_rule_sp.get() == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  m_rules.push_back(frag_rule_sp);

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "Upon assignment-configuration of the FragmentationPathway, the "
         "instance failed to "
         "validate with errors:"
      << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Inserts in the container of FragmentationRule instances at \a index the
\a frag_rule_sp FragmentationRule instance. If index is the container size, the
FragmentationRule is added at the end of the container.

Validation of this instance occurs after member data initialization and the
result of the validation process is set to member datum m_isValid.
*/
void
FragmentationPathway::insertRuleAt(FragmentationRuleSPtr frag_rule_sp,
                                   std::size_t index)
{
  if(frag_rule_sp == nullptr || frag_rule_sp.get() == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(index > m_rules.size())
    qFatal() << "Programming error. Index out of bounds.";

  if(index == m_rules.size())
    addRule(frag_rule_sp);

  m_rules.insert(m_rules.cbegin() + index, frag_rule_sp);

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "Upon assignment-configuration of the FragmentationPathway, the "
         "instance failed to "
         "validate with errors:"
      << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Removes from the container of FragmentationRule instances the item at
index \a index.
*/
void
FragmentationPathway::removeRuleAt(size_t index)
{
  if(index >= m_rules.size())
    qFatal() << "Programming error. Index is out of bounds.";

  m_rules.erase(m_rules.begin() + index);
}

//////////////// VALIDATIONS /////////////////////
/*!
\brief Returns the FragmentationPathway instance from the polymer chemistry
definition registered in this instance.

The key to search the FragmentationPathway is this instance's name member.

If there is no PolChemDef available, nullptr is returned.

If no FragmentationPathway instance is found by this instance's name, nullptr is
returned.
*/
FragmentationPathwayCstSPtr
FragmentationPathway::getFromPolChemDefByName() const
{
  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    return nullptr;

  if(m_name.isEmpty())
    return nullptr;

  return mcsp_polChemDef->getFragmentationPathwayCstSPtrByName(m_name);
}

/*!
\brief Returns the status of this FragmentationPathway instance the polymer
chemistry definition registered in this instance.

The key to search the FragmentationPathway is this instance's name member.

If there is no PolChemDef available,
Enums::PolChemDefEntityStatus::POL_CHEM_DEF_NOT_AVAILABLE is returned.

If no FragmentationPathway instance is found by this instance's name,
Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN is returned, otherwise
Enums::PolChemDefEntityStatus::ENTITY_KNOWN is returned.
*/
Enums::PolChemDefEntityStatus
FragmentationPathway::isKnownByNameInPolChemDef() const
{
  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    return Enums::PolChemDefEntityStatus::POL_CHEM_DEF_NOT_AVAILABLE;

  if(mcsp_polChemDef->getFragmentationPathwayCstSPtrByName(m_name) != nullptr)
    return Enums::PolChemDefEntityStatus::ENTITY_KNOWN;

  return Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;
}

/*!
\brief Validates the FragmentationPathway, recording any error with a message in
\a error_list_p.

The validation involves checking that:

\list

\li The polymer chemistry definition is available (non-nullptr)

\li The name is not empty

\li The formula is not empty and validates successfully

\li Any defined fragmentation rule validates successfully

\endlist

If the validation is successful, m_isValid is set to true, otherwise it is set
to false.

Returns the outcome of the validation.
*/
bool
FragmentationPathway::validate(ErrorList *error_list_p) const
{
  qsizetype error_count = error_list_p->size();

  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    {
      qDebug() << "A FragmentationPathway with no PolChemDef available cannot "
                  "validate successfully.";
      error_list_p->push_back(
        "A FragmentationPathway with no PolChemDef available cannot validate "
        "successfully");
    }

  if(m_name.isEmpty())
    {
      qDebug()
        << "A FragmentationPathway with no name cannot validate successfully.";
      error_list_p->push_back(
        "A FragmentationPathway with no name cannot validate successfully");
    }

  if(m_formula.getActionFormula().isEmpty())
    {
      qDebug() << "A FragmentationPathway with no formula cannot "
                  "validate successfully.";
      error_list_p->push_back(
        "A FragmentationPathway with no formula cannot validate "
        "successfully");
    }
  else if(mcsp_polChemDef != nullptr && mcsp_polChemDef.get() != nullptr &&
          !m_formula.validate(mcsp_polChemDef->getIsotopicDataCstSPtr(),
                              error_list_p))
    {
      qDebug() << "A FragmentationPathway with an invalid formula cannot "
                  "validate successfully.";
      error_list_p->push_back(
        "A FragmentationPathway with an invalid formula cannot validate "
        "successfully");
    }

  // Do not test contribution, that might be any value.
  // Comment is optional

  // FragmentationRule instances are optional, but if there, they should
  // validate successfully.
  for(const FragmentationRuleSPtr &frag_rule_sp : m_rules)
    {
      if(!frag_rule_sp->validate(error_list_p))
        {
          qDebug() << "A FragmentationPathway with an invalid fragmentation "
                      "rule cannot "
                      "validate successfully.";
          error_list_p->push_back(
            "A FragmentationPathway with an invalid fragmentation rule cannot "
            "validate "
            "successfully");
        }
    }

  m_isValid = error_list_p->size() > error_count ? false : true;

  return m_isValid;
}

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

//////////////// UTILITIES /////////////////////

/*!
\brief Returns a string with the textual representation of this
FragmentationPathway instance.
*/
QString
FragmentationPathway::toString() const
{
  QString text = "Fragmentation pathway:\n";

  QString frag_end_string = (*(fragEndMap.find(m_fragEnd))).second;

  text +=
    QString("Name: %1 - Formula: %2 - Frag end: %3 - Monomer contribution: %4")
      .arg(m_name)
      .arg(m_formula.getActionFormula(/*with_title*/ true))
      .arg(frag_end_string)
      .arg(m_monomerContribution);

  if(m_rules.size())
    {
      text += " - Rules:\n";
      for(const FragmentationRuleCstSPtr frag_rule_sp : m_rules)
        {
          text += frag_rule_sp->toString();
        }
      text += "\n";
    }
  else
    text += "\n";

  text += QString("isValid: %1\n").arg(m_isValid ? "true" : "false");

  return text;
}

//////////////// XML DATA LOADING WRITING /////////////////////
/*!
\brief Parses a fragmentation specification XML \a element using a
\a{version}ed function.

This function is used for polymer chemistry definition documents of version less
than 2 (the XML element tag is <fgs>, for FragSpec class).

Upon parsing and validation of the parsed data, the member data are updated,
thus essentially initializing this FragmentationPathway instance.

Validation of this instance occurs after member data initialization.

Returns true if parsing and formula validation were successful, false otherwise.

\sa renderXmlFgpElement(), validate()
*/
bool
FragmentationPathway::renderXmlFgsElement(const QDomElement &element,
                                          [[maybe_unused]] int version)
{
  QDomElement child;
  QDomElement rule_child_element;

  bool comment_was_parsed = false;

  /* The xml node we are in is structured this way:
  *
  <fgs>
  <name>a</name>
  <end>LE</end>
  <formula>-C1O1</formula>
  <sidechaincontrib>0</sidechaincontrib>
  <comment>opt_comment</comment>
  <fgr>
  <name>a-fgr-1</name>
  <formula>+H200</formula>
  <prev-mnm-code>E</prev-mnm-code>
  <curr-mnm-code>D</curr-mnm-code>
  <next-mnm-code>F</next-mnm-code>
  <comment>opt_comment</comment>
  </fgr>
  <fgr>
  <name>a-fgr-2</name>
  <formula>+H100</formula>
  <prev-mnm-code>F</prev-mnm-code>
  <curr-mnm-code>D</curr-mnm-code>
  <next-mnm-code>E</next-mnm-code>
  <comment>opt_comment</comment>
  </fgr>
  </fgs>
  *
  * And the element parameter points to the
  *
  * <fgs> element tag:
  * ^
  * |
  * +----- here we are right now.
  *
  * Which means that element.tagName() == "fgs" and that
  * we'll have to go one step down to the first child of the
  * current node in order to get to the <name> element.
  *
  * The DTD says this:
  * <!ELEMENT fgs(name, end, formula, comment?, fgr*)>
  */

  if(element.tagName() != "fgs")
    {
      qDebug() << "The expected <fgs> element is not found.";
      m_isValid = false;
      return m_isValid;
    }

  child = element.firstChildElement("name");

  if(child.isNull() || child.text().isEmpty())
    {
      qDebug() << "The FragSpec did not render correctly: problem with the "
                  "<name> element.";
      m_isValid = false;
      return m_isValid;
    }
  m_name = child.text();
  // qDebug() << "The name:" << m_name;

  child = child.nextSiblingElement();

  if(child.isNull() || child.text().isEmpty() || child.tagName() != "end")
    {
      qDebug() << "The FragSpec did not render correctly: problem with the "
                  "<end> element.";
      m_isValid = false;
      return m_isValid;
    }

  if(child.text() == "NE")
    m_fragEnd = Enums::FragEnd::NE;
  else if(child.text() == "LE")
    m_fragEnd = Enums::FragEnd::LE;
  else if(child.text() == "RE")
    m_fragEnd = Enums::FragEnd::RE;
  else if(child.text() == "BE")
    m_fragEnd = Enums::FragEnd::BE;
  else
    {
      qDebug() << "The FragSpec did not render correctly: problem with the "
                  "<end> element's text value.";
      m_isValid = false;
      return m_isValid;
    }

  child = child.nextSiblingElement();

  if(child.isNull() || child.text().isEmpty() || child.tagName() != "formula")
    {
      qDebug() << "The FragSpec did not render correctly: problem with the "
                  "<formula> element.";
      m_isValid = false;
      return m_isValid;
    }

  if(!m_formula.renderXmlFormulaElement(child))
    {
      qDebug() << "The FragSpec did not render correctly: the formula did not "
                  "render correctly.";
      m_isValid = false;
      return m_isValid;
    }

  // The next element must be <sidechaincontrib>
  child = child.nextSiblingElement();
  if(child.isNull() || child.text().isEmpty() ||
     child.tagName() != "sidechaincontrib")
    {
      qDebug() << "The FragSpec did not render correctly: problem with the "
                  "<sidechaincontrib> element.";
      m_isValid = false;
      return m_isValid;
    }
  QString text          = child.text();
  bool ok               = false;
  m_monomerContribution = text.toInt(&ok);

  if(!m_monomerContribution && !ok)
    {
      qDebug() << "The FragSpec did not render correctly: problem with the "
                  "<sidechaincontrib> element's value.";
      m_isValid = false;
      return m_isValid;
    }

  // The next element might be either comment or(none, one or more)
  // fgr.
  child = child.nextSiblingElement();

  while(!child.isNull())
    {
      // Is it a comment or the first of one|more <fgr> elements ?
      // Remember: <!ELEMENT fgs(name, end, formula, comment?, fgr*)>

      if(child.tagName() == "comment")
        {
          if(comment_was_parsed)
            {
              qDebug() << "The FragSpec did not render correctly: problem with "
                          "multiple <comment> elements.";
              m_isValid = false;
              return m_isValid;
            }

          m_comment          = child.text();
          comment_was_parsed = true;

          child = child.nextSiblingElement();
          continue;
        }

      // At this point, yes, if there is still a sibling, then it
      // has to be one <fgr>, either alone or the first of multiple.

      while(!child.isNull())
        {
          FragmentationRuleSPtr frag_rule_sp =
            std::make_shared<FragmentationRule>(mcsp_polChemDef, child, 1);
          if(!frag_rule_sp->isValid())
            {
              qDebug() << "The FragSpec did not render correctly: problem with "
                          "a <fgr> FragRule element.";
              frag_rule_sp.reset();
              m_isValid = false;
              return m_isValid;
            }

          m_rules.push_back(frag_rule_sp);

          child = child.nextSiblingElement();
        }
    }

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate FragSpec instance right after "
                "rendering it from <fgs> XML element, with errors:"
             << Utils::joinErrorList(error_list, ", ");

  return m_isValid;
}

/*!
\brief Parses a fragmentation pathway XML \a element using a
\a{version}ed function.

This function is used for polymer chemistry definition documents of version
greater or equal to 2 (the XML element tag is <fgp>, for FragmentationPathway
class).

Upon parsing and validation of the parsed data, the member data are updated,
thus essentially initializing this FragmentationPathway instance.

Validation of this instance occurs after member data initialization.

Returns true if parsing and formula validation were successful, false otherwise.

\sa renderXmlFgsElement(), validate()
*/
bool
FragmentationPathway::renderXmlFgpElement(const QDomElement &element,
                                          [[maybe_unused]] int version)
{
  QDomElement child;
  QDomElement rule_child_element;

  bool comment_was_parsed = false;

  /* The xml node we are in is structured this way:
  *
  <fgp>
  <name>a</name>
  <end>LE</end>
  <formula>-C1O1</formula>
  <sidechaincontrib>0</sidechaincontrib>
  <comment>opt_comment</comment>
  <fgr>
  <name>a-fgr-1</name>
  <formula>+H200</formula>
  <prev-mnm-code>E</prev-mnm-code>
  <curr-mnm-code>D</curr-mnm-code>
  <next-mnm-code>F</next-mnm-code>
  <comment>opt_comment</comment>
  </fgr>
  <fgr>
  <name>a-fgr-2</name>
  <formula>+H100</formula>
  <prev-mnm-code>F</prev-mnm-code>
  <curr-mnm-code>D</curr-mnm-code>
  <next-mnm-code>E</next-mnm-code>
  <comment>opt_comment</comment>
  </fgr>
  </fgs>
  *
  * And the element parameter points to the
  *
  * <fgp> element tag:
  * ^
  * |
  * +----- here we are right now.
  *
  * Which means that element.tagName() == "fgs" and that
  * we'll have to go one step down to the first child of the
  * current node in order to get to the <name> element.
  *
  * The DTD says this:
  * <!ELEMENT fgs(name, end, formula, comment?, fgr*)>
  */

  if(element.tagName() != "fgp")
    {
      qDebug() << "The expected <fgp> element is not found.";
      m_isValid = false;
      return m_isValid;
    }

  child = element.firstChildElement("name");

  if(child.isNull() || child.text().isEmpty())
    {
      qDebug() << "The FragmentationPathway did not render correctly: "
                  "problem with the "
                  "<name> element.";
      m_isValid = false;
      return m_isValid;
    }
  m_name = child.text();
  // qDebug() << "The name:" << m_name;

  child = child.nextSiblingElement();

  if(child.isNull() || child.text().isEmpty() || child.tagName() != "end")
    {
      qDebug() << "The FragmentationPathway did not render correctly: "
                  "problem with the "
                  "<end> element.";
      m_isValid = false;
      return m_isValid;
    }

  if(child.text() == "NE")
    m_fragEnd = Enums::FragEnd::NE;
  else if(child.text() == "LE")
    m_fragEnd = Enums::FragEnd::LE;
  else if(child.text() == "RE")
    m_fragEnd = Enums::FragEnd::RE;
  else if(child.text() == "BE")
    m_fragEnd = Enums::FragEnd::BE;
  else
    {
      qDebug() << "The FragmentationPathway did not render correctly: "
                  "problem with the "
                  "<end> element's text value.";
      m_isValid = false;
      return m_isValid;
    }

  child = child.nextSiblingElement();

  if(child.isNull() || child.text().isEmpty() || child.tagName() != "formula")
    {
      qDebug() << "The FragmentationPathway did not render correctly: "
                  "problem with the "
                  "<formula> element.";
      m_isValid = false;
      return m_isValid;
    }

  if(!m_formula.renderXmlFormulaElement(child))
    {
      qDebug() << "The FragmentationPathway did not render correctly: the "
                  "formula did not "
                  "render correctly.";
      m_isValid = false;
      return m_isValid;
    }

  // The next element must be <sidechaincontrib>
  child = child.nextSiblingElement();
  if(child.isNull() || child.text().isEmpty() ||
     child.tagName() != "sidechaincontrib")
    {
      qDebug() << "The FragmentationPathway did not render correctly: "
                  "problem with the "
                  "<sidechaincontrib> element.";
      m_isValid = false;
      return m_isValid;
    }
  QString text          = child.text();
  bool ok               = false;
  m_monomerContribution = text.toInt(&ok);

  if(!m_monomerContribution && !ok)
    {
      qDebug() << "The FragmentationPathway did not render correctly: "
                  "problem with the "
                  "<sidechaincontrib> element's value.";
      m_isValid = false;
      return m_isValid;
    }

  // The next element might be either comment or(none, one or more)
  // fgr.
  child = child.nextSiblingElement();

  while(!child.isNull())
    {
      // Is it a comment or the first of one|more <fgr> elements ?
      // Remember: <!ELEMENT fgs(name, end, formula, comment?, fgr*)>

      if(child.tagName() == "comment")
        {
          if(comment_was_parsed)
            {
              qDebug() << "The FragmentationPathway did not render "
                          "correctly: problem with "
                          "multiple <comment> elements.";
              m_isValid = false;
              return m_isValid;
            }

          m_comment          = child.text();
          comment_was_parsed = true;

          child = child.nextSiblingElement();
          continue;
        }

      // At this point, yes, if there is still a sibling, then it
      // has to be one <fgr>, either alone or the first of multiple.

      while(!child.isNull())
        {
          FragmentationRuleSPtr frag_rule_sp =
            std::make_shared<FragmentationRule>(mcsp_polChemDef, child, 1);
          if(!frag_rule_sp->isValid())
            {
              qDebug() << "The FragmentationPathway did not render "
                          "correctly: problem with "
                          "a <fgr> FragmentationRule element.";
              frag_rule_sp.reset();
              m_isValid = false;
              return m_isValid;
            }

          m_rules.push_back(frag_rule_sp);

          child = child.nextSiblingElement();
        }
    }

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate FragmentationPathway instance right after "
                "rendering it from <fgp> XML element, with errors:"
             << Utils::joinErrorList(error_list, ", ");

  return m_isValid;
}

/*!
\brief Formats a string representing this FragmentationPathway instance suitable
to use as an XML element.

The typical fragmentation pathway element that is generated in this function
looks like this:

\code
<fgp>
<name>a</name>
<end>LE</end>
<formula>-C1O1</formula>
<sidechaincontrib>0</sidechaincontrib>
<comment>opt_comment</comment>
<fgr>
<name>a-fgr-1</name>
<formula>+H200</formula>
<prev-mnm-code>E</prev-mnm-code>
<curr-mnm-code>D</curr-mnm-code>
<next-mnm-code>F</next-mnm-code>
<comment>opt_comment</comment>
</fgr>
<fgr>
<name>a-fgr-2</name>
<formula>+H100</formula>
<prev-mnm-code>F</prev-mnm-code>
<curr-mnm-code>D</curr-mnm-code>
<next-mnm-code>E</next-mnm-code>
<comment>opt_comment</comment>
</fgr>
</fgp>
\endcode

The formatting of the XML element takes into account \a offset and \a
indent by prepending the string with \a offset * \a indent character substring.

\a indent defaults to two spaces.

Returns a dynamically allocated string that needs to be freed after use.
*/
QString
FragmentationPathway::formatXmlFgpElement(int offset,
                                          const QString &indent) const
{

  int newOffset;
  int iter = 0;

  QString lead("");
  QString text;


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

  text += QString("%1<fgp>\n").arg(lead);

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

  // Continue, with indented elements.

  text += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);
  text += QString("%1<end>%2</end>\n").arg(lead).arg(fragEndMap[m_fragEnd]);
  text += QString("%1<formula>%2</formula>\n")
            .arg(lead)
            .arg(m_formula.getActionFormula());
  text += QString("%1<sidechaincontrib>%2</sidechaincontrib>\n")
            .arg(lead)
            .arg(m_monomerContribution);

  if(!m_comment.isEmpty())
    text += QString("%1<comment>%2</comment>\n").arg(lead).arg(m_comment);

  for(const FragmentationRuleSPtr &frag_rule_sp : m_rules)
    text += frag_rule_sp->formatXmlFgrElement(newOffset);

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

  text += QString("%1</fgp>\n").arg(lead);

  return text;
}


} // namespace libXpertMassCore
} // namespace MsXpS
