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


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/CleavageAgent.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The CleavageAgent class provides a model for specifying aqueous cleavage
specifications (patterns) of \l{Polymer} \l{Sequence}s.

Cleavage specifications determine the specificity of cleavage in a
polymer sequence using a simple syntax. For example, Trypsin is able
to cleave after lysyl and arginyl residues. Its cleavage pattern is
thus "Lys/;Arg/". However, it is known that Trypsin fails to cleave
after Lys if that monomer is followed by a Prolyl residue, thus the
complete cleavage agent specification for Trypsin comprises three cleavage
patterns: "Lys/;Arg/;-Lys/Pro".

A cleavage agent specification might not be enough information to
determine the manner in which a polymer is cleaved. Cleavage rules
might be required to refine the specification. A cleavage
agent specification might hold as many cleavage rules as required.

\sa CleavageMotif, CleavageRule
*/


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

\brief The \l PolChemDef polymer chemistry definition.
*/

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

\brief The name of the CleavageAgent.
*/

/*!
\variable MsXpS::libXpertMassCore::CleavageAgent::m_pattern

\brief The cleavage pattern, that might comprise more than one \l CleavageMotif.

\sa CleavageMotif, CleavageRule
*/

/*!
\variable MsXpS::libXpertMassCore::CleavageAgent::m_motifs

\brief The container of \l{CleavageMotif}s that together make the CleavageAgent.

\sa CleavageMotif
*/

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

\brief The container of \l{CleavageRule}s that might be requied to refine the
CleavageAgent.

\sa CleavageRule
*/


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

\brief The validity status of the instance.
*/


/*!
\typedef MsXpS::libXpertMassCore::CleavageAgentSPtr
\relates MsXpS::libXpertMassCore::CleavageAgent.

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

/*!
\typedef MsXpS::libXpertMassCore::CleavageAgentCstSPtr
\relates MsXpS::libXpertMassCore::CleavageAgent.

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


/*!
\brief Constructs a totally empty CleavageAgent instance
*/
CleavageAgent::CleavageAgent(QObject *parent): QObject(parent)
{
}

/*!
\brief Constructs a CleavageAgent instance starting from an XML \a element
 according to \a version and a \a pol_chem_def_csp PolChemDef.

The \a version indicates what version of the XML element is to be used.

Before version 2, the CleavageAgent class was named CleaveSpec and thus the
XML element was called <cls>:

This is the current format (FAKE cls):
\code
<cls>
<name>CyanogenBromide</name>
<pattern>M/A</pattern>
<clr>
<le-mnm-code>M</le-mnm-code>
<le-formula>-C1H2S1+O1</le-formula>
<re-mnm-code>A</re-mnm-code>
<re-formula>-CH3COOH</re-formula>
</clr>
</cls>

\endcode


After version 2, the CleaveSpec class has been named CleavageAgent and thus the
XML element is now called <cla>.The inner structure of the element has not
changed.

Depending on the \a version argument,  one function (older) or the other
(current) is used to parse the XML element.

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

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

  if(version == 1)
    {
      qDebug() << "Rendering XmlClsElement version" << version;

      if(!renderXmlClsElement(element, version))
        qDebug()
          << "Failed to fully render or validate the CleavageAgent XML element "
             "for construction of CleavageAgent instance.";
    }
  else if(version == 2)
    {
      qDebug() << "Rendering XmlClaElement version" << version;

      if(!renderXmlClaElement(element, version))
        qDebug()
          << "Failed to fully render or validate the CleavageAgent XML element "
             "for construction of CleavageAgent instance.";
    }
  else
    qFatal()
      << "Programming error. The polymer chemistry definition version "
         "is not correct.";
}

/*!
\brief Constructs a CleavageAgent instance.

\list
\li \a pol_chem_def_csp: The polymer chemistry definition.
\li \a name: The name of the CleavageAgent.
\li \a pattern: The pattern of the CleavageAgent, like ("Lys/;Arg/;-Lys/Pro").
\endlist

Upon setting all the member data,  the \a pattern is parsed. After this,  this
instance is validated and the member m_isValid is set to the result.
*/
CleavageAgent::CleavageAgent(PolChemDefCstSPtr pol_chem_def_csp,
                             const QString &name,
                             const QString &pattern,
                             QObject *parent)
  : QObject(parent),
    mcsp_polChemDef(pol_chem_def_csp),
    m_name(name),
    m_pattern(pattern)
{
  if(!parse())
    qDebug() << "Upon construction of the CleavageAgent, the pattern could not "
                "be parsed.";

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

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

The copying of the member containers of CleavageMotif and CleavageRule instances
is shallow (only the shared pointers are copied).

Upon setting all the member data, this instance is validated and the member
m_isValid is set to the result.
*/
CleavageAgent::CleavageAgent(const CleavageAgent &other, QObject *parent)
  : QObject(parent),
    mcsp_polChemDef(other.mcsp_polChemDef),
    m_name(other.m_name),
    m_pattern(other.m_pattern),
    m_motifs(other.m_motifs),
    m_rules(other.m_rules)
{
  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Destructs this CleavageAgent instance.
*/
CleavageAgent::~CleavageAgent()
{
}

/*!
\brief Initialized this CleavageAgent using \a other.

After initialization, validity is checked, the result is set to m_isValid and is
returned.
*/
bool
CleavageAgent::initialize(const CleavageAgent &other)
{
  mcsp_polChemDef = other.mcsp_polChemDef;
  m_name          = other.m_name;
  m_pattern       = other.m_pattern;
  m_motifs.assign(other.m_motifs.begin(), other.m_motifs.end());
  m_rules.assign(other.m_rules.begin(), other.m_rules.end());

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to initalize the CleavageAgent instance.";

  return m_isValid;
}

//////////////// THE POLCHEMDEF /////////////////////
/*!
\brief Sets the PolChemDef to \a pol_chem_def_csp.

Upon setting the member data, this instance is validated and the member
m_isValid is set to the result.
*/
void
CleavageAgent::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 CleavageAgent, the instance failed to "
         "validate with errors:"
      << Utils::joinErrorList(error_list, ", ");
}

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

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

Upon setting the member data, this instance is validated and the member
m_isValid is set to the result.
*/
void
CleavageAgent::setName(const QString &name)
{
  m_name = name;

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

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

//////////////// THE PATTERN /////////////////////
/*!
\brief Sets the \a pattern.

Upon setting the member data, this instance is validated and the member
m_isValid is set to the result.
*/
void
CleavageAgent::setPattern(const QString &pattern)
{
  m_pattern = pattern;

  if(!parse())
    qDebug() << "The pattern that was set could not be parsed.";

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Returns the pattern.
*/
const QString &
CleavageAgent::getPattern() const
{
  return m_pattern;
}

/*!
\brief Returns a const reference to the container of CleavageMotif instances.
*/
const std::vector<CleavageMotifSPtr> &
CleavageAgent::getMotifsCstRef() const
{
  return m_motifs;
}

/*!
\brief Returns a reference to the container of CleavageMotif instances.
*/
std::vector<CleavageMotifSPtr> &
CleavageAgent::getMotifsRef()
{
  return m_motifs;
}

/*!
\brief Returns a const reference to the container of CleavageRule instances.
*/
const std::vector<CleavageRuleSPtr> &
CleavageAgent::getRulesCstRef() const
{
  return m_rules;
}

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

/*!
\brief Returns the CleavageRule that has name \a name, or nullptr if not found.
*/
CleavageRuleSPtr
CleavageAgent::getCleavageRuleSPtrByName(const QString &name) const
{
  std::vector<CleavageRuleSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_rules.cbegin(),
                 m_rules.cend(),
                 [name](const CleavageRuleSPtr &cleavage_rule_sp) {
                   return cleavage_rule_sp->getName() == name;
                 });

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

  return *the_iterator_cst;
}

//////////////// PARSING /////////////////////
/*!
\brief Parses this CleavageAgent instance.

The parsing involves separating the components found in the m_pattern string
and making CleavageMotif instances out of them.

Starting from a pattern "Lys/;Arg/;-Lys/Pro", the parsing would
first split it into three cleavage site strings:

\list
\li "Lys/";
\li "Arg/";
\li "-Lys/Pro"
\endlist

Each of these site strings will be deconstructed into motifs, stored
in CleavageMotif objects:

\list
\li First motif has a code container "[0] Lys", an offset of 1 and is for
cleavage;
\li Second motif has a code container "[0] Arg", an offset of 1 and is for
cleavage;
\li Third motif has a code container "[0] Lys, [1] Pro", an offset of 1
and is not for cleavage (mind the '-')
\endlist

If the pattern string is empty,  m_isValid is set to false and false is
returned.

Returns true if parsing is successful, false otherwise.
*/
bool
CleavageAgent::parse()
{
  if(m_pattern.isEmpty())
    {
      m_isValid = false;
      return false;
    }

  // The 'm_pattern' is a ';'-delimited string, in which each
  // sub-string is a 'site'. Each site is in turn constituted by a
  // motif and a '/' that indicates where the motif is actually
  // cleaved.
  //
  // For example the "-Lys/Pro" site is actually a motif of sequence
  // "LysPro" and the site holds two more informations with respect to
  // the mere motif: it says that the motif should not be cleaved
  //('-') and that if the '-' were not there, the cleavage would
  // occur between the Lys and the Pro('/' symbolizes the cleavage).

  // For example, if the cleavageagent had a "Lys/;Arg/;-Lys/Pro" string,
  // it would be split into 3 strings: "Lys/" and "Arg/" and
  // "-Lys/Pro"(these are 'site' strings). These three site string
  // would further be deconstructed into motif string(removal of '-'
  // and '/' characters). Where would these 3 motif strings be stored?
  // They would be set into one cleavagemotif instance for each
  // motif. Thus, for example, "-Lys/Pro" would yield a cleavagemotif of
  // 'motif' LysPro, with a FALSE cleave member and a 1 offset member.

  // Will return the number of cleavagemotif instances that were created.
  // Upon error -1 is returned.


  // "Lys/;Arg/;-Lys/Pro" --> [0] Lys/, [1] Arg/, [2] -Lys/Pro
  QStringList sites = m_pattern.split(";", Qt::SkipEmptyParts);

  //  for (int iter = 0; iter < sites.size() ; ++iter)
  //   qDebug() << __FILE__ << __LINE__
  //    << sites.at(iter);

  Enums::CleavageAction cleavage_action;

  for(int iter = 0; iter < sites.size(); ++iter)
    {
      cleavage_action = Enums::CleavageAction::CLEAVE;

      QString iter_cleavage_site = sites.at(iter);

      if(iter_cleavage_site.length() < 2)
        {
          qDebug() << iter_cleavage_site << "is an invalid cleavage site.";

          m_isValid = false;
          return false;
        }

      int count = iter_cleavage_site.count(QChar('/'));
      if(count != 1)
        {
          qDebug() << "The must be one and only one '/' in the cleavage site.";

          m_isValid = false;
          return false;
        }

      // Remove spaces.
      iter_cleavage_site.remove(QRegularExpression("\\s+"));

      // Is there a '-' in the site string indicating that this site
      // is NOT for cleavage? If so, there should be only one such
      // sign and in position 0.

      count = iter_cleavage_site.count(QChar('-'));
      if(count > 1)
        {
          qDebug() << "There must be at most one '-' in the cleavage site.";

          m_isValid = false;
          return false;
        }
      else if(count == 1)
        {
          if(iter_cleavage_site.indexOf(QChar('-'), 0, Qt::CaseInsensitive) !=
             0)
            {
              qDebug() << "The '-' in the cleavage site must be at the "
                          "beginning of the cleavage site.";

              m_isValid = false;
              return false;
            }

          cleavage_action = Enums::CleavageAction::NO_CLEAVE;

          // We can remove the '-' character, as we now know
          // that the site is NOT for cleavage.
          iter_cleavage_site.remove(0, 1);
        }
      // else: site is for cleavage: no '-' found.

      // We can create a new cleavage motif.
      CleavageMotifSPtr cleavage_motif_sp =
        std::make_shared<CleavageMotif>(mcsp_polChemDef);
      cleavage_motif_sp->setCleavageAction(cleavage_action);
      cleavage_motif_sp->parseSite(iter_cleavage_site);

      if(!cleavage_motif_sp->isValid())
        {
          qDebug() << "Failed to create a valid CleavageMotif by parsing "
                      "cleavage site:"
                   << iter_cleavage_site;
          cleavage_motif_sp.reset();
          return false;
        }

      m_motifs.push_back(cleavage_motif_sp);
    }

  return true;
}

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

Because the CleavageMotifSPtr in m_motifs and the CleavageRuleSPtr in m_rules do
not necessarily reference data from the PolChemDef,  the comparison of the two
instances involves a deep comparison (not a comparison of the pointers
themselves).

*/
bool
CleavageAgent::operator==(const CleavageAgent &other) const
{
  if(&other == this)
    return true;

  // We cannot compare the PolChemDef, because that would cause
  // an infinite loop: (each instance of this class in the PolChemDef would
  // try to compare the PolChemDef...).

  if(m_name != other.m_name || m_pattern != other.m_pattern)
    {
      qDebug() << "Either the name or the pattern or both is/are different.";
      return false;
    }

  if(m_motifs.size() != other.m_motifs.size())
    {
      qDebug() << "The CleavageMotif containers have differing sizes.";
      return false;
    }

  for(std::size_t iter = 0; iter < m_motifs.size(); ++iter)
    {
      // qDebug() << "Will compare two motifs:" << m_motifs.at(iter)->getMotif()
      //          << "versus" << other.m_motifs.at(iter)->getMotif();

      if(*m_motifs.at(iter).get() != *other.m_motifs.at(iter).get())
        {
          qDebug()
            << "At least one CleavageMotif is different in both CleavageAgent "
               "instances.";
          return false;
        }
    }

  if(m_rules.size() != other.m_rules.size())
    {
      qDebug() << "The CleavageRule containers have differing sizes.";
      return false;
    }

  for(std::size_t iter = 0; iter < m_rules.size(); ++iter)
    {
      if(*m_rules.at(iter).get() != *other.m_rules.at(iter).get())
        {
          qDebug()
            << "At least one CleavageMotif is different in both CleavageAgent "
               "instances.";
          return false;
        }
    }

  return true;
}

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

  return !operator==(other);
}

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

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

If there is no PolChemDef available, nullptr is returned.

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

  if(m_name.isEmpty())
    return nullptr;

  return mcsp_polChemDef->getCleavageAgentCstSPtrByName(m_name);
}

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

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

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

If no CleavageAgent instance is found by this instance's name,
Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN is returned, otherwise
Enums::PolChemDefEntityStatus::ENTITY_KNOWN is returned.
*/
Enums::PolChemDefEntityStatus
CleavageAgent::isKnownByNameInPolChemDef() const
{

  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    return Enums::PolChemDefEntityStatus::POL_CHEM_DEF_NOT_AVAILABLE;

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

  return Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;
}

/*!
\brief Returns the CleavageRule from the container that has \a name.

If no \l CleavageRule is found, nullptr is returned.
*/
CleavageRuleCstSPtr
CleavageAgent::getCleavageRuleCstSPtrByName(const QString &name) const
{
  std::vector<CleavageRuleSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_rules.cbegin(),
                 m_rules.cend(),
                 [name](const CleavageRuleSPtr &cleave_rule_sp) {
                   return cleave_rule_sp->getName() == name;
                 });

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

  return *(the_iterator_cst);
}

/*!
\brief Returns the index of the CleavageRule instance named \a name in the
member container of CleavageRule intances. If no CleavageRule by name \a name is
found, -1 is returned.
*/
int
CleavageAgent::getCleavageRuleIndexByName(const QString &name) const
{
  std::vector<CleavageRuleSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_rules.cbegin(),
                 m_rules.cend(),
                 [name](const CleavageRuleSPtr &cleave_rule_sp) {
                   return cleave_rule_sp->getName() == name;
                 });

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

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

/*!
\brief Validates this CleavageAgent instance setting error messages to \a
error_list_p.

The validation involves checking that:

\list
\li The PolChemDef must be available;
\li The name is not empty;
\li The pattern is nt empty;
\li The parsing of the pattern is successful.;
\li Each CleavageRule instance (if any) validates successfully.
\endlist

Returns true if the validation is successful, false otherwise.
*/
bool
CleavageAgent::validate(ErrorList *error_list_p) const
{
  qsizetype error_count = error_list_p->size();

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

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

  if(m_pattern.isEmpty())
    {
      qDebug()
        << "A CleavageAgent with no pattern cannot validate successfully.";
      error_list_p->push_back(
        "A CleavageAgent with no pattern cannot validate successfully");
    }

  // If there are motifs, we have to check them all.
  for(const CleavageMotifSPtr &cleavage_motif_sp : m_motifs)
    {
      if(!cleavage_motif_sp->validate(error_list_p))
        {
          qDebug() << "A CleavageAgent with invalid CleavageMotif instances "
                      "cannot validate successfully.";
          error_list_p->push_back(
            "A CleavageAgent with invalid CleavageMotif instances cannot "
            "validate "
            "successfully");
          break;
        }
    }

  // If there are rules, we have to check them all.
  for(const CleavageRuleSPtr &cleavage_rule_sp : m_rules)
    {
      if(!cleavage_rule_sp->validate(error_list_p))
        {
          qDebug() << "A CleavageAgent with invalid CleavageRule instances "
                      "cannot validate successfully.";
          error_list_p->push_back(
            "A CleavageAgent with invalid CleavageRule instances cannot "
            "validate "
            "successfully");
          break;
        }
    }

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

  return m_isValid;
}

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

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

The data in the element are validated and if successful they are set to this
instance, thus initializing it.

After setting the member data, the instance is validated and the result is set
to m_isValid that is returned.
*/
bool
CleavageAgent::renderXmlClsElement(const QDomElement &element,
                                   [[maybe_unused]] int version)
{
  // For the time being the version is not necessary here. As of
  // version up to 2, the current function works ok.

  QDomElement child;
  QDomElement childRule;

  /* The xml node we are in is structured this way:
   *
   * <cls>
   *  <name>CyanogenBromide</name>
   *  <pattern>M/</pattern>
   *  <clr>
   *   <le-mnm-code>M</le-mnm-code>
   *   <le-formula>-C1H2S1+O1</le-formula>
   *   <re-mnm-code>M</re-mnm-code>
   *   <re-formula>-C1H2S1+O1</re-formula>
   *  </clr>
   * </cls>
   *
   * And the element parameter points to the
   *
   * <cls> element tag:
   * ^
   * |
   * +----- here we are right now.
   *
   * Which means that element.tagName() == "cls" 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.
   */

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

  child = element.firstChildElement("name");

  if(child.isNull() || child.text().isEmpty())
    {
      qDebug() << "The CleaveSpec 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("pattern");

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

  if(!parse())
    {
      qDebug() << "The CleaveSpec being initialized with a <cls> XML "
                  "element had an unparseable pattern.";
      m_isValid = false;
      return false;
    }

  // At this point there might be 0, 1 or more cleavage rules.
  child = child.nextSiblingElement("clr");

  while(!child.isNull())
    {
      CleavageRuleSPtr cleavage_rule_sp =
        std::make_shared<CleavageRule>(mcsp_polChemDef);

      if(!cleavage_rule_sp->renderXmlClrElement(child, version))
        {
          qDebug() << "Failed to render CleaveRule from <clr> XML element.";
          m_isValid = false;
          cleavage_rule_sp.reset();
          return false;
        }

      ErrorList error_list;
      if(!cleavage_rule_sp->validate(&error_list))
        {
          qDebug() << "Failed to validate CleaveRule rendered from <clr> "
                      "XML element, with errors:";
          Utils::joinErrorList(error_list, ", ");
          cleavage_rule_sp.reset();
          return false;
        }

      m_rules.push_back(cleavage_rule_sp);

      child = child.nextSiblingElement("clr");
    }

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

  return m_isValid;
}

/*!
\brief Parses a cleavage agent XML <cla> \a element using a \a{version}ed
function.

The data in the element are validated and if successful they are set to this
instance, thus initializing it.

After setting the member data, the instance is validated and the result is set
to m_isValid that is returned.
*/
bool
CleavageAgent::renderXmlClaElement(const QDomElement &element,
                                   [[maybe_unused]] int version)
{
  // For the time being the version is not necessary here. As of
  // version up to 2, the current function works ok.

  QDomElement child;
  QDomElement childRule;

  /* The xml node we are in is structured this way:
   *
   * <cla>
   *  <name>CyanogenBromide</name>
   *  <pattern>M/</pattern>
   *  <clr>
   *   <le-mnm-code>M</le-mnm-code>
   *   <le-formula>-C1H2S1+O1</le-formula>
   *   <re-mnm-code>M</re-mnm-code>
   *   <re-formula>-C1H2S1+O1</re-formula>
   *  </clr>
   * </cla>
   *
   * And the element parameter points to the
   *
   * <cla> element tag:
   * ^
   * |
   * +----- here we are right now.
   *
   * Which means that element.tagName() == "cla" 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.
   */

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

  child = element.firstChildElement("name");

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

  child = child.nextSiblingElement("pattern");

  if(child.isNull() || child.text().isEmpty())
    {
      qDebug()
        << "The CleavageAgent did not render correctly: problem with the "
           "<pattern> element.";
      m_isValid = false;
      return m_isValid;
    }
  m_pattern = child.text();
  qDebug() << "The CleavageAgent has pattern:" << m_pattern;

  if(!parse())
    {
      qDebug() << "The CleavageAgent being initialized with a <cls> XML "
                  "element had an unparseable pattern.";
      m_isValid = false;
      return false;
    }
  qDebug() << "The CleavageAgent pattern was parsed successfully.";

  // At this point there might be 0, 1 or more cleavage rules.
  child = child.nextSiblingElement("clr");

  while(!child.isNull())
    {
      qDebug() << "Iterating in <clr> child element.";

      CleavageRuleSPtr cleavage_rule_sp =
        std::make_shared<CleavageRule>(mcsp_polChemDef);

      qDebug() << "Will now render the <clr> element.";

      if(!cleavage_rule_sp->renderXmlClrElement(child, version))
        {
          qDebug() << "Failed to render CleavageRule from <clr> XML element.";
          m_isValid = false;
          cleavage_rule_sp.reset();
          return false;
        }

      qDebug() << "The <clr> element was parsed successfully.";

      ErrorList error_list;
      if(!cleavage_rule_sp->validate(&error_list))
        {
          qDebug() << "Failed to validate CleavageRule rendered from <clr> "
                      "XML element, with errors:";
          Utils::joinErrorList(error_list, ", ");
          cleavage_rule_sp.reset();
          return false;
        }

      qDebug() << "Now storing the CleavageRule in the container.";

      m_rules.push_back(cleavage_rule_sp);

      child = child.nextSiblingElement("clr");
    }

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate CleavageAgent instance right after "
                "rendering it from <cls> XML element, with errors:"
             << Utils::joinErrorList(error_list, ", ");
  else
    qDebug() << "Successfully parsed the <cla> element.";

  return m_isValid;
}

/*!
\brief Formats and returns a string describing this CleavageAgent instance in a
format suitable to be used as an XML element.

The XML element is typically used in a polymer chemistry defintion and looks
like this:

\code
<cla>
<name>CyanogenBromide</name>
<pattern>M/</pattern>
<clr>
<re-mnm-code>M</re-mnm-code>
<re-formula>-CH2S+O</re-formula>
</clr>
</cla>
\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 string.
*/
QString
CleavageAgent::formatXmlClaElement(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<cla>\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<pattern>%2</pattern>\n").arg(lead).arg(m_pattern);

  for(const CleavageRuleSPtr &cleavage_rule_sp : m_rules)
    text += cleavage_rule_sp->formatXmlClrElement(newOffset);

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

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

  return text;
}

//////////////// UTILS /////////////////////
/*!
\brief Returns a string describing this CleavageAgent instance.
 */
QString
CleavageAgent::toString() const
{
  return QString("%1 - %2 - %3 motifs - %4 rules")
    .arg(m_name)
    .arg(m_pattern)
    .arg(m_motifs.size())
    .arg(m_rules.size());
}

void

CleavageAgent::registerJsConstructor(QJSEngine *engine)

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

  // Register the meta object as a constructor

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


} // namespace libXpertMassCore
} // namespace MsXpS
