/* 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/CrossLinker.hpp"
#include "MsXpS/libXpertMassCore/PolChemDef.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The CrossLinker class provides abstractions to define the chemical basis
of a cross-linking reaction.

The notion of a cross-linker is that it is the description of the chemical
reaction that is carried out by \l{Monomer}s in a \l Polymer or in an \l
Oligomer sequence to form a \l CrossLink.

There are two different kinds of chemical entities potentially involved in the
description of a CrossLinker:

\list
\a Modif instances that are applied to \l Monomer instances.
\a the formula that completes the chemical reactions described by
the \l Modif instances.
\endlist
*/


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

\brief The reference polymer chemistry definition.
*/


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

\brief The name of the CrossLinker, \"DisulphideBond\", for example.
*/


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

\brief The formula that must be applied when the cross-link occurs.

\note This formula can be empty because the CrossLinker can be fully
described, from a chemical standpoint, by listing Modif instances in the
member m_modifs container of Modif instances.
*/

/*!
\variable MsXpS::libXpertMassCore::CrossLinker::m_mono

\brief The monoisotopic mass of the CrossLinker.
*/

/*!
\variable MsXpS::libXpertMassCore::CrossLinker::m_avg

\brief The average mass of the CrossLinker.
*/

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

\brief Tells if the CrossLinker is valid.
*/

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

\brief The container of \l Modif instances that describe the reaction (or the
reactions) involved in the formation of a CrossLink between \l{Monomer}s of a
\l Polymer (or of an \l Oligomer) sequence.
*/

/*!
\brief Constructs a CrossLinker instance with the \a pol_chem_def_csp
PolChemDef.
*/
CrossLinker::CrossLinker(PolChemDefCstSPtr pol_chem_def_csp)
  : mcsp_polChemDef(pol_chem_def_csp)
{
}

/*!
\brief Constructs a CrossLinker instance starting from an XML <clk> \a element
and a \a pol_chem_def_csp PolChemDef. The \a version indicates what version of
the XML element is to be used.

This is the current format:
\code
<clk>
  <name>CFP-chromophore</name>
  <formula></formula>
  <modifname>Chromo-O</modifname>
  <modifname>Chromo-H3</modifname>
  <modifname>Chromo-H</modifname>
</clk>
\endcode

The rendering of the Modif instances requires that the PolChemDef be available.
If this is not the case, the rendering of the CrossLinker is stopped at the
first <modifname> element and false is returned.

\sa renderXmlClkElement()
*/
CrossLinker::CrossLinker(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 CrossLinker with no PolChemDef.";

  if(!renderXmlClkElement(element, version))
    qDebug()
      << "Failed to fully render or validate the CrossLinker XML element "
         "for construction of CrossLinker instance.";
}

/*!
\brief Constructs a CrossLinker instance.

\list
\li \a pol_chem_def_csp: the polymer chemistry definition (\l PolChemDef).
\li \a name: the name of this CrossLinker.
\li \a formula: the \l Formula that potentially complements the description of
the reaction that is the basis of this CrossLinker (used to initialize the
Formula base class).
\endlist
*/
CrossLinker::CrossLinker(PolChemDefCstSPtr pol_chem_def_csp,
                         const QString &name,
                         const QString &formula,
                         double mono,
                         double avg)
  : mcsp_polChemDef(pol_chem_def_csp),
    m_name(name),
    m_formula(formula),
    m_mono(mono),
    m_avg(avg)
{
}

/*!
\brief Constructs a CrossLinker instance as a copy of \a other.
*/
CrossLinker::CrossLinker(const CrossLinker &other)
  : mcsp_polChemDef(other.mcsp_polChemDef),
    m_name(other.m_name),
    m_formula(other.m_formula),
    m_mono(other.m_mono),
    m_avg(other.m_avg)
{
  m_modifs.clear();

  std::vector<ModifCstSPtr>::const_iterator the_iterator_cst =
    other.m_modifs.cbegin();
  std::vector<ModifCstSPtr>::const_iterator the_end_iterator_cst =
    other.m_modifs.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      m_modifs.push_back((*the_iterator_cst));

      ++the_iterator_cst;
    }

  ErrorList error_list;

  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "The copy constructor instantiated a CrossLink that is not "
                "valid,  with errors:"
             << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Destructs this CrossLinker instance
*/
CrossLinker::~CrossLinker()
{
  // We do not own the modifications in m_modifList!
}

/*!
\brief Initializes this CrossLinker instance using \a element, according to  \a
version.

Returns true if the rendering of \a element was successful, false otherwise.

\sa renderXmlClkElement()
*/
bool
CrossLinker::initialize(const QDomElement &element, int version)
{
  if(!renderXmlClkElement(element, version))
    {
      qDebug()
        << "Failed to fully render or validate the CrossLinker XML element "
           "for construction of CrossLinker instance.";

      return false;
    }

  return true;
}

//////////////// THE POLCHEMDEF /////////////////////

/*!
\brief Sets the PolChemDef to \a pol_chem_def_csp.
*/
void
CrossLinker::setPolChemDefCstSPtr(PolChemDefCstSPtr pol_chem_def_csp)
{
  mcsp_polChemDef = pol_chem_def_csp;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate the CrossLinker with errors:\n"
             << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Returns the PolChemDef.
*/
const PolChemDefCstSPtr &
CrossLinker::getPolChemDefCstSPtr() const
{
  return mcsp_polChemDef;
}

//////////////// THE NAME /////////////////////

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

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate the CrossLinker with errors:\n"
             << Utils::joinErrorList(error_list, ", ");
}

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

//////////////// THE FORMULA /////////////////////
/*!
\brief Sets the formula to \a formula_string.
*/
void
CrossLinker::setFormula(const QString &formula_string)
{
  m_formula = formula_string;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug() << "Failed to validate the CrossLinker with errors:\n"
             << Utils::joinErrorList(error_list, ", ");
}

/*
\brief Returns the formula of this CrossLinker.
 */
const QString &
CrossLinker::getFormula() const
{
  return m_formula;
}

//////////////// THE MODIFICATIONS /////////////////////

/*!
\brief Returns a constant reference to the container of Modif instances.
*/
const std::vector<ModifCstSPtr> &
CrossLinker::getModifsCstRef() const
{
  return m_modifs;
}

/*!
\brief Returns a reference to the container of Modif instances.
*/
std::vector<ModifCstSPtr> &
CrossLinker::getModifsRef()
{
  return m_modifs;
}

/*!
\brief Sets the Modif at index \a index to \a modif_csp.

\a index cannot be out-of-bounds (fatal error).

Returns true.
*/
bool
CrossLinker::insertModifAt(ModifCstSPtr modif_csp, std::size_t index)
{
  if(index >= m_modifs.size())
    qFatal() << "The index is out of bounds.";
  if(modif_csp == nullptr || modif_csp.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr";

  m_modifs.insert(m_modifs.cbegin() + index, modif_csp);

  return true;
}

/*!
\brief Adds \a modif_csp to this CrossLinker instance.

Returns true.
*/
bool
CrossLinker::appendModif(ModifCstSPtr modif_csp)
{
  if(modif_csp == nullptr || modif_csp.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr";

  m_modifs.push_back(modif_csp);

  return true;
}

/*!
\brief Returns the \l Modif instance at \a index.

\a index cannot be out-of-bounds.
*/
ModifCstSPtr
CrossLinker::getModifAt(std::size_t index) const
{
  if(index >= m_modifs.size())
    qFatal() << "Programming error. Index is out of bounds.";

  return m_modifs.at(index);
}

/*!
\brief Removes the \l Modif instance at \a index.

\a index cannot be out-of-bounds.

Returns true.
*/
bool
CrossLinker::removeModifAt(std::size_t index)
{
  if(index >= m_modifs.size())
    qFatal() << "Programming error. Index is out of bounds.";

  m_modifs.erase(m_modifs.cbegin() + index);

  return true;
}

/*!
\brief Returns an iterator to the found \l Modif instance by \a modif_name in
the container.
*/
bool
CrossLinker::hasModif(const QString &modif_name)
{
  std::vector<ModifCstSPtr>::const_iterator the_iterator_cst = std::find_if(
    m_modifs.cbegin(), m_modifs.cend(), [modif_name](const auto &modif_csp) {
      return modif_csp->getName() == modif_name;
    });

  return the_iterator_cst != m_modifs.cend();
}

/*!
\brief Returns the index of the found \l Modif instance by \a modif_name in
the container, -1 otherwise.
*/
int
CrossLinker::modifIndex(const QString &modif_name)
{
  std::vector<ModifCstSPtr>::const_iterator the_iterator_cst = std::find_if(
    m_modifs.cbegin(), m_modifs.cend(), [modif_name](const auto &modif_csp) {
      return modif_csp->getName() == modif_name;
    });

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

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

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

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

  mcsp_polChemDef = other.mcsp_polChemDef;
  m_name          = other.m_name;
  m_formula       = other.m_formula;
  m_mono          = other.m_mono;
  m_avg           = other.m_avg;

  m_modifs.clear();

  std::vector<ModifCstSPtr>::const_iterator the_iterator_cst =
    other.m_modifs.cbegin();
  std::vector<ModifCstSPtr>::const_iterator the_end_iterator_cst =
    other.m_modifs.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      m_modifs.push_back((*the_iterator_cst));

      ++the_iterator_cst;
    }

  ErrorList error_list;

  m_isValid = validate(&error_list);

  if(!m_isValid)
    qDebug()
      << "The assignment operator copying produced a CrossLink that is not "
         "valid,  with errors:"
      << Utils::joinErrorList(error_list, ", ");

  return *this;
}

/*!
\brief Returns true if this CrossLinker and \a other are identical.
*/
bool
CrossLinker::operator==(const CrossLinker &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_formula != other.m_formula ||
     m_mono != other.m_mono || m_avg != other.m_avg ||
     m_modifs.size() != other.m_modifs.size())
    return false;

  for(std::size_t iter = 0; iter < m_modifs.size(); ++iter)
    {
      if(*m_modifs.at(iter).get() != *other.m_modifs.at(iter).get())
        {
          qDebug() << "At least one Modif instance differs in both CrossLinker "
                      "instances.";
          return false;
        }
    }

  return true;
}

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

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

  return !operator==(other);
}

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

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

If there is no PolChemDef available, nullptr is returned.

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

  if(m_name.isEmpty())
    return nullptr;

  std::vector<CrossLinkerSPtr>::const_iterator the_iterator_cst =
    std::find_if(mcsp_polChemDef->getCrossLinkersCstRef().cbegin(),
                 mcsp_polChemDef->getCrossLinkersCstRef().cend(),
                 [&](const auto &cross_linker_csp) {
                   return cross_linker_csp->getName() == m_name;
                 });

  if(the_iterator_cst == mcsp_polChemDef->getCrossLinkersCstRef().cend())
    return nullptr;

  return (*the_iterator_cst);
}

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

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

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

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

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

  return Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;
};

/*!
\brief Returns true if this CrossLinker instance validates successfully,
false otherwise.

The validation involves the following:

\list
\li The member polymer chemistry definition must exist.
\li The name cannot be empty.
\li The formula (if not empty) must validate successfully against the member
polymer chemistry definition.
\li The list of \l Modif instances must contain either no or more than two
Modif instances. In the latter case, the Modif instances must be found in the
member polymer chemistry definition and must validate successfully.
\endlist

When an error occurs,  the message is stored in \a error_list_p.
*/
bool
CrossLinker::validate(ErrorList *error_list_p) const
{
  Q_ASSERT(error_list_p != nullptr);

  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr ||
     mcsp_polChemDef->getIsotopicDataCstSPtr() == nullptr ||
     mcsp_polChemDef->getIsotopicDataCstSPtr().get() == nullptr ||
     !mcsp_polChemDef->getIsotopicDataCstSPtr()->size())
    {
      error_list_p->push_back(
        "The PolChemDef or IsotopicData are not available");
      qDebug()
        << "The polymer chemistry definition member datum is nullptr or "
           "its isotopic data are be either nullptr or empty. CrossLinker "
           "validation "
           "failed.";

      m_isValid = false;
      return m_isValid;
    }

  if(m_name.isEmpty())
    {
      qDebug() << "The CrossLinker name is empty.";
      error_list_p->push_back("The CrossLinker name is empty");
    }

  // Remember that the formula of the crosslinker might be empty because the
  // cross-linker might be fully accounted for by the modifications (below).
  if(!m_formula.isEmpty())
    {
      Formula temp_formula(m_formula);
      if(!temp_formula.validate(mcsp_polChemDef->getIsotopicDataCstSPtr(),
                                error_list_p))
        {
          qDebug() << "The CrossLinker formula failed to validate.";
          error_list_p->push_back("The CrossLinker formula failed to validate");
        }
    }

  double mono = 0.0;
  double avg  = 0.0;

  if(!calculateMasses(mcsp_polChemDef->getIsotopicDataCstSPtr(), mono, avg))
    {
      qDebug() << "Failed to calculate the CrossLinker masses.";
      error_list_p->push_back("Failed to calculate the CrossLinker masses");
    }

  // This is mainly a sanity check, as the pointers to Modif in the
  // list all point to modification objects in the polymer chemistry
  // definition, which have been validated already...

  // The validation actually is simple, it might be that there are NO
  // modifs, or if this is not the case there must be at least
  // 2. Indeed, either none of the monomers in the crosslink get
  // modified, or each one has to be(otherwise we cannot know which
  // modif goes to which monomer).

  int size = m_modifs.size();

  // qDebug() << "There are " << size
  //          << "Modif instances in this CrossLinker instance.";

  if(size > 0 && size < 2)
    {
      qDebug() << "The CrossLinker may have either zero or more at least "
                  "two Modif instances.";
      error_list_p->push_back(
        "The CrossLinker may have either zero or at least two Modif instances");

      m_isValid = false;
      return m_isValid;
    }

  for(const ModifCstSPtr &modif_csp : m_modifs)
    {
      // qDebug() << "Iterating in CrossLinker Modif instance:"
      //          << modif_csp->getName()
      //          << "with formula:" << modif_csp->getFormula();

      //  The modif must be in the context of our PolChemDef.
      if(modif_csp->getPolChemDefCstSPtr() != mcsp_polChemDef)
        {
          qDebug()
            << "One Modif:" << modif_csp->getName()
            << "has not the same PolChemDef as that of this CrossLinker.";
          error_list_p->push_back(
            QString("One Modif: %1 has not the same "
                    "PolChemDef as that of this CrossLinker")
              .arg(modif_csp->getName()));
          m_isValid = false;
          return m_isValid;
        }
      if(!modif_csp->validate(error_list_p))
        {
          qDebug() << "One Modif:" << modif_csp->getName()
                   << "failed to validate successfully.";
          error_list_p->push_back(
            QString("Modif: %1 failed to validate successfully")
              .arg(modif_csp->getName()));
          m_isValid = false;
          return m_isValid;
        }
    }

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

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

//////////////// MASS OPERATIONS /////////////////////

/*!
\brief Returns true if the mass calculations for this CrossLinker instance are
performed without error, false otherwise.

The calculation involved accounting masses for the Modifs (if any) and for the
Formula (if non-empty). The masses calculated are set to \a mono and \a avg.

The calculations are performed using reference data in \a isotopic_data_csp.
*/
bool
CrossLinker::calculateMasses(const IsotopicDataCstSPtr &isotopic_data_csp,
                             double &mono,
                             double &avg) const
{
  IsotopicDataCstSPtr local_isotopic_data_csp = isotopic_data_csp;

  if(local_isotopic_data_csp == nullptr ||
     local_isotopic_data_csp.get() == nullptr)
    {
      if(mcsp_polChemDef != nullptr)
        local_isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr();

      if(local_isotopic_data_csp == nullptr ||
         local_isotopic_data_csp.get() == nullptr)
        {
          qDebug() << "Failed to find usable isotopic data.";
          m_isValid = false;
          return m_isValid;
        }
    }

  // qDebug() << "Calculating masses for the cross-link";

  mono = 0;
  avg  = 0;

  bool ok;

  if(!m_formula.isEmpty())
    {
      // Formula temp_formula(m_formula);
      Formula(m_formula).accountMasses(ok, local_isotopic_data_csp, mono, avg);

      if(!ok)
        {
          qDebug() << "Failed accounting masses for CrossLinker:" << m_name
                   << "and formula:" << m_formula;
          m_isValid = false;
          return m_isValid;
        }
    }

  // Now, for each modif in the crossLinker, have to account their
  // mass.

  std::vector<ModifCstSPtr>::const_iterator the_iterator_cst =
    m_modifs.cbegin();
  std::vector<ModifCstSPtr>::const_iterator the_end_iterator_cst =
    m_modifs.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      (*the_iterator_cst)->accountMasses(mono, avg);
      ++the_iterator_cst;
    }

  // qDebug() << "At this point, the masses of the CrossLinker are:" << m_mono
  //<< "/" << m_avg;

  return true;
}

/*!
\brief Returns true if the mass calculations for this CrossLinker instance are
performed without error, false otherwise.

The calculation involved accounting masses for the Modifs (if any) and for the
Formula (if non-empty). The member masses (m_mono and m_avg) are
updated.

The reference data for the computations are accessed at \a isotopic_data_csp.
*/
bool
CrossLinker::calculateMasses(const IsotopicDataCstSPtr &isotopic_data_csp)
{
  IsotopicDataCstSPtr local_isotopic_data_csp = isotopic_data_csp;

  if(local_isotopic_data_csp == nullptr ||
     local_isotopic_data_csp.get() == nullptr)
    {
      if(mcsp_polChemDef != nullptr)
        local_isotopic_data_csp = mcsp_polChemDef->getIsotopicDataCstSPtr();

      if(local_isotopic_data_csp == nullptr ||
         local_isotopic_data_csp.get() == nullptr)
        {
          qDebug() << "Failed to find usable isotopic data.";
          m_isValid = false;
          return m_isValid;
        }
    }

  // qDebug() << "Calculating masses for the cross-link";

  m_mono = 0;
  m_avg  = 0;

  bool ok;

  if(!m_formula.isEmpty())
    {
      // Formula temp_formula(m_formula);
      Formula(m_formula).accountMasses(
        ok, local_isotopic_data_csp, m_mono, m_avg);

      if(!ok)
        {
          qDebug() << "Failed accounting masses for CrossLinker:" << m_name
                   << "and formula:" << m_formula;
          m_isValid = false;
          return m_isValid;
        }
    }

  // Now, for each modif in the crossLinker, have to account their
  // mass.

  std::vector<ModifCstSPtr>::const_iterator the_iterator_cst =
    m_modifs.cbegin();
  std::vector<ModifCstSPtr>::const_iterator the_end_iterator_cst =
    m_modifs.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      (*the_iterator_cst)->accountMasses(m_mono, m_avg);
      ++the_iterator_cst;
    }

  // qDebug() << "At this point, the masses of the CrossLinker are:" << m_mono
  //<< "/" << m_avg;

  return true;
}

/*!
\brief Increases \a mono_p and \a avg_p by the corresponding member masses
first compounded by \a times.

Returns this CrossLinker.
*/
const CrossLinker &
CrossLinker::accountMasses(double *mono_p, double *avg_p, int times) const
{
  // qDebug() << "Accounting masses for cross-linker -- mono:" << m_mono
  //<< "avg:" << m_avg << "by" << times << "times.";

  if(mono_p)
    *mono_p += m_mono * times;

  if(avg_p)
    *avg_p += m_avg * times;

  return *this;
}

/*!
\brief Increases \a mono and \a avg by the corresponding member masses
first compounded by \a times.

Returns this CrossLinker.
*/
const CrossLinker &
CrossLinker::accountMasses(double &mono, double &avg, int times) const
{
  // qDebug() << "Accounting masses for cross-linker -- mono:" << m_mono
  //<< "avg:" << m_avg << "by" << times << "times.";

  mono += m_mono * times;
  avg  += m_avg * times;

  return *this;
}

/*!
\brief Returns the mass of the type defined by \a mass_type.
*/
double
CrossLinker::getMass(Enums::MassType mass_type) const
{
  if(mass_type == Enums::MassType::MONO)
    return m_mono;
  else if(mass_type == Enums::MassType::AVG)
    return m_avg;

  qFatal() << "Mass cannot be anything else than Enums::MassType::MONO "
                    "or Enums::MassType::AVG.";

  return -1;
}

//////////////// XML /////////////////////
/*!
\brief Returns true if parsing of XML \a element, using a \a{version}ed
function was successful, false otherwise.

The data in \a element are set to this CrossLinker instance if they validated
successfully, thus essentially initializing this CrossLinker instance.

The format of the element is:

\code
<clk>
  <name>CFP-chromophore</name>
  <formula></formula>
  <modifname>Chromo-O</modifname>
  <modifname>Chromo-H3</modifname>
  <modifname>Chromo-H</modifname>
</clk>
\endcode

*/
bool
CrossLinker::renderXmlClkElement(const QDomElement &element,
                                 [[maybe_unused]] int version)
{
  if(element.tagName() != "clk")
    return false;

  QDomElement child;

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();
  if(m_name.isEmpty())
    return false;

  // qDebug() << "The crosslinker name:" << m_name;

  child = child.nextSiblingElement("formula");

  if(child.isNull())
    {
      qDebug() << "The CrossLinker did not render correctly: the <formula> "
                  "element tag was not found.";
      m_isValid = false;
      return m_isValid;
    }

  // Here, it is possible that the formula element be empty because the
  // crosslinker might be accounted for by using the modifications in it.
  if(!child.isNull() && !child.text().isEmpty())
    {
      Formula temp_formula;

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

      ErrorList error_list;

      if(!temp_formula.validate(mcsp_polChemDef->getIsotopicDataCstSPtr(),
                                &error_list))
        {
          qDebug()
            << "The CrossLinker did not render correctly: the formula did not "
               "render correctly with errors:"
            << Utils::joinErrorList(error_list);
          m_isValid = false;
          return m_isValid;
        }

      m_formula = temp_formula.getActionFormula(/*with_title*/ true);
      // qDebug() << "The formula:" << m_formula;
    }

  //  At this point we need the PolChemDef to render the modifications,
  //  so if we do not have that,  return false.

  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    {
      qDebug() << "The PolChemDef is unavailable,  cannot render any Modif.";
      return false;
    }

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

#if 0
//  Old version
  while(!child.isNull())
    {
      // qDebug() << "Now handling CrossLinker modif:" << child.text();

      int index = Modif::isNameInList(child.text(), refList);

      if(index == -1)
        {
          qDebug() << "Failed to parse one modification of the crosslink:"
                   << m_name;

          return false;
        }
      else
        {
          // qDebug()
          //<< "Found the CrossLinker modification in the reference list:"
          //<< m_name;
        }

      // Modif *modif = mcsp_polChemDef->modifList().at(index);
      // qDebug() << "The found modif has name:" << modif->name()
      //<< "and masses:" << modif->mono() << "/" << modif->avg();

      m_modifList.append(mcsp_polChemDef->modifList().at(index));

      child = child.nextSiblingElement("modifname");
    }
#endif

  while(!child.isNull())
    {
      if(child.tagName() != "modifname")
        {
          qDebug() << "The CrossLinker did not render correctly: the "
                      "<modifname> element tag was not found.";

          return false;
        }

      QString modif_name = child.text();
      // qDebug() << "Currently parsed Modif name:" << modif_name;

      ModifCstSPtr modif_csp =
        mcsp_polChemDef->getModifCstSPtrByName(modif_name);

      if(modif_csp != nullptr)
        m_modifs.push_back(modif_csp);
      else
        {
          qDebug() << "The CrossLinker references a Modif by name that "
                      "could not be found in the PolChemDef.";

          return false;
        }

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

  if(!calculateMasses(nullptr))
    {
      qDebug() << __FILE__ << __LINE__
               << "Failed to calculate masses for crossLinker" << m_name;

      return false;
    }

  ErrorList error_list;

  if(!validate(&error_list))
    {
      qDebug() << "The CrossLinker rendered as an XML <clk> element failed "
                  "to validate with errors:"
               << Utils::joinErrorList(error_list, ", ");

      m_isValid = false;
      return m_isValid;
    }

  return true;
}

/*!
\brief Formats this CrossLinker instance in a heap-allocated string to be used
as an XML element in the polymer chemistry definition.

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
CrossLinker::formatXmlClkElement(int offset, const QString &indent) const
{
  QString text;

  int newOffset;
  int iter = 0;

  QString lead("");

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

  /* We are willing to create an <modif> node that should look like this:
   *
   <clk>
   <name>Phosphorylation</name>
   <formula>-H+H2PO3</formula>
   <modifname>Phosphorylation</modifname>
   <modifname>Acetylation</modifname>
   </clk>
   *
   */

  text += QString("%1<clk>\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<formula>%2</formula>\n").arg(lead).arg(m_formula);

  std::vector<ModifCstSPtr>::const_iterator the_iterator_cst =
    m_modifs.cbegin();
  std::vector<ModifCstSPtr>::const_iterator the_end_iterator_cst =
    m_modifs.cend();

  while(the_iterator_cst != the_end_iterator_cst)
    {
      text += QString("%1<modifname>%2</modifname>\n")
                .arg(lead)
                .arg((*the_iterator_cst)->getName());

      ++the_iterator_cst;
    }

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

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

  return text;
}

//////////////// UTILS /////////////////////
/*!
\brief Returns a string representing this CrossLinker instance.
*/
QString
CrossLinker::toString() const
{
  QString text;

  text += QString("%1 [%2]\n").arg(m_name).arg(m_formula);

  if(m_modifs.size())
    {
      std::vector<ModifCstSPtr>::const_iterator the_iterator_cst =
        m_modifs.cbegin();
      std::vector<ModifCstSPtr>::const_iterator the_end_iterator_cst =
        m_modifs.cend();

      while(the_iterator_cst != the_end_iterator_cst)
        {
          text += QString("%1,").arg((*the_iterator_cst)->getName());
          ++the_iterator_cst;
        }

      text += "\n";
    }

  text += "masses: ";
  text += QString::number(m_mono);
  text += " - ";
  text += QString::number(m_avg);

  return text;
}


} // namespace libXpertMassCore
} // namespace MsXpS
