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


/////////////////////// Qt includes
#include <QChar>
#include <QString>
#include <QUuid>


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/Monomer.hpp"
#include "MsXpS/libXpertMassCore/Formula.hpp"
#include "MsXpS/libXpertMassCore/Utils.hpp"


int monomerMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::Monomer>(
  "MsXpS::libXpertMassCore::Monomer");

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The Monomer class provides abstractions to work with monomers.

In libmass, a momomer is an entity that is part of a polymer chemistry
definition. A monomer models a chemical entity that is part of a polymer.

In protein chemistry, that would be a \e{residue}, that is, an amino-acid that
has been polymerized into a residue chain (that is, a protein, or a peptide).
The chemical reaction that polymerizez an amino acid into an elongating protein
structure is a condensation, with loss of H2O from the amino acid to actually
lead to a what is called a \e{residue} of a monomer, or for short a \e{residue}.

\note The monomer, that is partly defined by its formula, has the formula of the
\e{residue}, not of the amino acid. This is always true, whatever the polymer
chemistry definition at hand: protein, saccharide, nucleic acid.
*/


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

\brief The polymer chemistry definition that is the context in which the Monomer
instance exists.
*/

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

\brief The name of the monomer, like Lysine, Adenine.
*/

/*!
\variable MsXpS::libXpertMassCore::Monomer::m_code

\brief The code of the monomer, like K for lysine, A for adenine.
*/

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

\brief The formula of the monomer.
*/

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

\brief The container of \l Modif instances that are involved in the modification
of this Monomer.

\note

The Modif pointers stored in the m_modifs member are ModifSPtr pointers.
*/

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

\brief The validity status of the Monomer.
*/

/*!
    \typedef MsXpS::libXpertMassCore::MonomerRPtr
    \relates MsXpS::libXpertMassCore::Monomer

    Synonym for Monomer *.
*/

/*!
    \typedef MsXpS::libXpertMassCore::MonomerCstRPtr
    \relates MsXpS::libXpertMassCore::Monomer

    Synonym for const Monomer *.
*/

/*!
    \typedef MsXpS::libXpertMassCore::MonomerUPtr
    \relates MsXpS::libXpertMassCore::Monomer

    Synonym for std::unique_ptr<Monomer>.
*/

/*!
    \typedef MsXpS::libXpertMassCore::MonomerCstUPtr
    \relates MsXpS::libXpertMassCore::Monomer

    Synonym for std::unique_ptr<const Monomer>.
*/

/*!
    \typedef MsXpS::libXpertMassCore::MonomerSPtr
    \relates MsXpS::libXpertMassCore::Monomer

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

/*!
    \typedef MsXpS::libXpertMassCore::MonomerCstSPtr
    \relates MsXpS::libXpertMassCore::Monomer

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

/*!
    \typedef MsXpS::libXpertMassCore::MonomerWPtr
    \relates MsXpS::libXpertMassCore::Monomer

    Synonym for std::weak_ptr<Monomer>.
*/

/*!
    \typedef MsXpS::libXpertMassCore::MonomerCstWPtr
    \relates MsXpS::libXpertMassCore::Monomer

    Synonym for std::weak_ptr<const Monomer>.
*/

/*!
    \typealias MsXpS::libXpertMassCore::UuidMonomerCstWPtrPair
    \relates MsXpS::libXpertMassCore::Monomer

    Synonym for std::pair<QString, MonomerCstWPtr> items.

    These pairs are used to store a unique identifier (Uuid) string related to
    a std::shared_ptr<const Monomer> type. This kind of pair is used in a
    container in the \l CrossLink class. The fact that the std::shared_ptr is
   converted to a std::weak_ptr is interesting because the item in the pair will
   not increase the reference count.
*/


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

This is the current format:
\code
<mnm>
<name>Glycine</name>
<code>G</code>
<formula>C2H3N1O1</formula>
</mnm>
\endcode
*/
Monomer::Monomer(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 Monomer with no PolChemDef.";

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

  // We cannot ask for validation because the polymer chemistry definition
  // might be unavailable when using the XML element rendering.

  // qDebug() << "Constructed Monomer" << m_name << ":" << toString();
}

/*!
\brief Constructs a monomer with its member data set to \a name, \a code, \a
formula_string,
The \a pol_chem_def_csp and both masses \a mono and \a avg.

The member m_isValid validity status is set to the result of this Monomer
validation.
*/
Monomer::Monomer(PolChemDefCstSPtr pol_chem_def_csp,
                 const QString &name,
                 const QString &code,
                 const QString &formula_string,
                 double mono,
                 double avg)
  : mcsp_polChemDef(pol_chem_def_csp),
    m_name(name),
    m_code(code),
    m_formula(formula_string),
    m_mono(mono),
    m_avg(avg)
{
  if(mcsp_polChemDef != nullptr && mcsp_polChemDef.get() != nullptr)
    {
      ErrorList error_list;
      m_isValid = validate(&error_list);

      if(!m_isValid)
        {
          qDebug() << "Construction of Monomer with validation errors:\n"
                   << Utils::joinErrorList(error_list, ", ");
        }
    }
  else
    m_isValid = false;
}

/*!
\brief Constructs a monomer as a copy of \a other.
*/
Monomer::Monomer(const Monomer &other)
  : PropListHolder(other),
    mcsp_polChemDef(other.mcsp_polChemDef),
    m_name(other.m_name),
    m_code(other.m_code),
    m_formula(other.m_formula),
    m_mono(other.m_mono),
    m_avg(other.m_avg)
{
  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    qDebug() << "Copy-constructing Monomer with no PolChemDef.";

  // We do a real duplication of the Modif instances.
  for(ModifSPtr modif_sp : other.m_modifs)
    storeModif(std::make_shared<Modif>(*modif_sp));

  ErrorList error_list;
  m_isValid = validate(&error_list);

  if(!m_isValid)
    {
      qDebug() << "Copy-construction of Monomer with validation errors:\n"
               << Utils::joinErrorList(error_list, ", ");
    }
}

/*!
\brief Destroys the monomer.
*/
Monomer::~Monomer()
{
  m_modifs.clear();
}

//////////////// THE POLCHEMDEF /////////////////////
/*!
\brief Sets the polymer chemistry definition to \a pol_chem_def_csp.
*/
void
Monomer::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 Monomer with errors:\n"
             << Utils::joinErrorList(error_list, ", ");
}

/*!
\brief Returns the polymer chemistry definition.
*/
const PolChemDefCstSPtr &
Monomer::getPolChemDefCstSPtr() const
{
  return mcsp_polChemDef;
}

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

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

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

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

//////////////// THE CODE /////////////////////
/*!
\brief Sets the code to \a code

*/
void
Monomer::setCode(const QString &code)
{
  m_code = code;

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Returns the code
*/
QString
Monomer::getCode() const
{
  return m_code;
}

/*!
\brief Checks the code's syntactic validity.

If \a code_length is not -1, then that length is used for the
check. Otherwise, the length from the polymer chemistry definition (if
available) is used.

The monomer code is verified and has to verify these criteria:

\list
\li It must be non-empty
\li Its character length has to be less or equal to the code length parameter
in the polymer chemistry definition (see PolChemDef::m_codeLength)
\li The first character is uppercase
\li All the remaining characters are lowercase
\endlist

Returns true if the code syntax checked successfully, false otherwise.

\sa validate()
*/
bool
Monomer::checkCodeSyntax(int code_length) const
{
  int local_code_length = code_length;

  if(local_code_length == -1)
    {
      if(mcsp_polChemDef != nullptr || mcsp_polChemDef.get() != nullptr)
        {
          local_code_length = mcsp_polChemDef->getCodeLength();
        }
      else
        {
          qDebug() << "Checking code syntax with unlimited length because "
                      "Monomer has no PolChemDef.";
          local_code_length = 1000;
        }
    }

  // qDebug() << "Checking code syntax:" << m_code
  //          << "with code length:" << local_code_length;

  // The code has to be at least one character long.

  // The first letter in the code has to be uppercase.
  // All the remaining authorized characters have to be
  // lowercase.

  if(m_code.length() < 1 || m_code.length() > local_code_length)
    {
      qDebug() << "The code has a length:" << m_code.length()
               << "that does not match the expected code length:"
               << local_code_length;
      m_isValid = false;
      return false;
    }

  // Note that the actual monomer code length might be less than the
  // codeLength member datum in the polymer chemistry
  // definition.

  QString regexp_string =
    QString("([A-Z])([a-z]{0,%1})").arg(local_code_length - 1);
  QRegularExpression regexp(regexp_string);
  QRegularExpressionMatch match = regexp.match(m_code);

  if(match.hasMatch())
    {
      // qDebug() << "First uppercase char:" << match.captured(1);
      // qDebug() << "Remaining lowercase chars:" << match.captured(2);

      return true;
    }
  else
    {
      qDebug() << "Failed to RegExp match the monomer code:" << m_code
               << "against an expected code length of:" << local_code_length;
    }

  return false;

#if 0
// Old version
for(int iter = 0; iter < m_code.size(); ++iter)
{
// Test that the m_code length is not greater than codeLength.
if(iter + 1 > codeLength)
{
PolChemDefEntity::m_isValid = false;
return false;
}

// And now check the character syntax.
QChar curChar = m_code.at(iter);

if(iter == 0)
{
if(curChar.category() != QChar::Letter_Uppercase)
{
PolChemDefEntity::m_isValid = false;
return false;
}
}
else if(curChar.category() == QChar::Letter_Uppercase)
{
PolChemDefEntity::m_isValid = false;
return false;
}
}

return true;
#endif
}

//////////////// THE FORMULA /////////////////////

/*!
\brief Sets the formula to \a formula_string.
*/
void
Monomer::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 Monomer with errors:\n"
             << Utils::joinErrorList(error_list, ", ");
}

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

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

/*!
\brief Returns the container of Modif instances that modify this Monomer.
*/
const std::vector<ModifSPtr> &
Monomer::getModifsCstRef() const
{
  return m_modifs;
}

/*!
\brief Returns the list of Modif names in the same order as the Modif instances
are stored in the member container.
*/
std::vector<QString>
Monomer::modifNamesInOrder() const
{
  std::vector<QString> names;

  for(const ModifSPtr &modif_sp : m_modifs)
    names.push_back(modif_sp->getName());

  return names;
}

/*
\brief Returns true if this monomer is a target of Modif \a modif, false
otherwise.
*/
bool
Monomer::isModifTarget(const Modif &modif) const
{
  // Pure convenience function.
  // qDebug() << "The modif:" << modif.toString();

  return modif.doesTargetMonomer(m_code);
}

/*!
\brief Modifies this monomer using \a modif.

These two verifications that are done:

\list

\li This monomer must be a target of \a modif;

\li The count of \a modif modifications in this monomer must be at most the
authorized count - 1, to accomodate this new modification [see
Modif::maxCount()].

\endlist

The two restrictions above can be overridden by setting \a override to true.

If errors are encountered, these are reported as strings in \a error_list_p.

Returns a string with the Uuid of the allocated Modif instance.
 */
QString
Monomer::modify(const Modif &modif, bool override, ErrorList *error_list_p)
{
  if(!isModifTarget(modif) && !override)
    {
      // This monomer is not a target for the modif, and no override
      // is allowed.

      error_list_p->push_back(
        QString("%1 not target of Modif %2 (no overriding allowed)")
          .arg(m_name)
          .arg(modif.getName()));

      return QString();
    }

  qDebug() << "Good, the monomer is target of this modif.";

  int count = countModifsByName(modif.getName());

  qDebug() << "There are" << count << "modifs by that name.";

  if(count >= modif.getMaxCount() && !override)
    {
      // This monomer has already the maximum count of 'modif' objects.

      error_list_p->push_back(
        QString("%1 already modified %2 times (no overriding allowed)")
          .arg(m_name)
          .arg(count));

      return QString();
    }

  // We do a real allocation of a new Modif instance.
  qDebug() << "Now allocating ModifSPtr:" << modif.getName()
           << "and calling storeModifSPtrAsUuid with it.";

  ModifSPtr modif_sp = std::make_shared<Modif>(modif);

  if(modif_sp == nullptr || modif_sp.get() == nullptr)
    qFatal() << "Failed to allocated new Modif object.";

  QString uuid = storeModif(modif_sp);

  qDebug() << "The stored Modif:" << uuid << getModifForUuid(uuid)->getName();

  return uuid;
}

/*!
\brief Modifies this monomer using \a modif_name.

\a modif_name is used to find a Modif instance in the polymer chemistry
definition.

If such a Modif is found, it is used to modify this Monomer.

Returns a string with the Uuid of the allocated Modif instance.
 */
QString
Monomer::modify(const QString &modif_name,
                bool override,
                ErrorList *error_list_p)
{
  ModifCstSPtr modif_csp = mcsp_polChemDef->getModifCstSPtrByName(modif_name);

  if(modif_csp == nullptr)
    {
      qDebug() << "Modif by name" << modif_name << "is not known.";
      return QString();
    }

  return modify(*modif_csp, override, error_list_p);
}

/*!
\brief Removes from this monomer the Modif instance tagged using the Uuid \a
uuid string.

Returns true if the unmodification was actually performed, false otherwise.
*/
bool
Monomer::unmodify(const QString &uuid)
{
  std::vector<UuidModifWPtrPair>::iterator the_iterator =
    std::find_if(m_uuidModifPairs.begin(),
                 m_uuidModifPairs.end(),
                 [uuid](UuidModifWPtrPair &pair) {
                   return pair.first == uuid;
                 });

  if(the_iterator == m_uuidModifPairs.end())
    {
      qDebug() << "The modification was not found.";
      return false;
    }

  // Sanity check
  if((*the_iterator).second.expired() ||
     (*the_iterator).second.lock() == nullptr ||
     !hasModif((*the_iterator).second.lock()))
    qFatal() << "Inconsistency between m_modifs and m_uuidModifPairs.";

  if(!removeModif((*the_iterator).second.lock()))
    qFatal() << "Inconsistency between m_modifs and m_uuidModifPairs.";

  return true;
}

/*!
\brief Removes \e{all} the modification from this monomer.

Returns true if at least one Modif instance was removed, false if this Monomer
instance was not modified.
*/
bool
Monomer::unmodify()
{
  std::size_t modif_count = m_modifs.size();

  // Sanity check
  if(m_modifs.size() != m_uuidModifPairs.size())
    qFatal() << "Inconsistency between m_modifs and m_uuidModifPairs.";

  m_modifs.clear();
  m_uuidModifPairs.clear();

  return modif_count != 0;
}

/*!
\brief Returns true if this monomer has at least one modification, false
otherwise.
*/
bool
Monomer::isModified() const
{
  return m_modifs.size();
}

/*!
\brief Returns the count of modifications by name \a modif_name in this
monomer.
*/
int
Monomer::countModifsByName(const QString &modif_name)
{
  int count = std::count_if(
    m_modifs.begin(), m_modifs.end(), [modif_name](const ModifSPtr &modif_sp) {
      return modif_sp->getName() == modif_name;
    });

  return count;
}

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

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

The copy is deep, in particular with the mpa_modifList being copied.

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

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

  // We want the modifs to be stored anew. So first clear.
  m_modifs.clear();

  // We do a real duplication of the Modif instances.
  for(const ModifSPtr &modif_sp : other.m_modifs)
    storeModif(std::make_shared<Modif>(*modif_sp));

  PropListHolder::operator=(other);

  ErrorList error_list;

  m_isValid = validate(&error_list);

  if(!m_isValid)
    {
      qDebug() << "Assignment of Monomer with validation errors:\n"
               << Utils::joinErrorList(error_list, ", ");
    }

  // qDebug() << "Assignment of Monomer produced *this Monomer:" << toString();

  return *this;
}

/*!
\brief Returns true if this monomer and \a other are identical, false
otherwise.

The comparison involves also the comparison of the Modif objects in
mpa_modifList.
*/
bool
Monomer::operator==(const Monomer &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_code != other.m_code ||
     m_formula != other.m_formula || m_mono != other.m_mono ||
     m_avg != other.m_avg)
    return false;

  if(m_modifs.size() != other.m_modifs.size())
    {
      // qDebug() << "The Modif containers have different sizes.";
      return false;
    }

  // We do a deep Modif instance comparison.
  for(std::size_t iter = 0; iter < m_modifs.size(); ++iter)
    {
      if(*m_modifs.at(iter) != *other.m_modifs.at(iter))
        {
          // qDebug() << "At least one Modif instance differs in both Monomer
          // instances.";
          return false;
        }
    }

  return true;
}

/*!
\brief Returns true if \c this monomer and \a other differ, false
otherwise.

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

  return !operator==(other);
}

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

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

If there is no PolChemDef available, nullptr is returned.

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

  if(m_name.isEmpty())
    return nullptr;

  return mcsp_polChemDef->getMonomerCstSPtrByName(m_name);
}

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

The key to search the Monomer is this instance's member code.

If there is no PolChemDef available, nullptr is returned.

If no Monomer instance is found by this instance's code, nullptr is returned.
*/
MonomerSPtr
Monomer::getFromPolChemDefByCode() const
{
  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    return nullptr;

  if(m_code.isEmpty())
    return nullptr;

  return mcsp_polChemDef->getMonomerCstSPtrByCode(m_code);
}

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

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

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

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

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

  return Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;
}

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

The key to search the Monomer is this instance's member code.

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

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

  if(mcsp_polChemDef->getMonomerCstSPtrByCode(m_code) != nullptr)
    return Enums::PolChemDefEntityStatus::ENTITY_KNOWN;

  return Enums::PolChemDefEntityStatus::ENTITY_NOT_KNOWN;
}

/*!
\brief Returns true if this monomer is valid, false otherwise.

Validation of the monomer occurs if:

\list
\li Its name is not empty
\li Its code is not empty and its syntax is correct
\li Its formula validates
\li Its modifications (if any) validate
\endlist

If errors are encountered, describing message are stored in \a error_list_p.

\sa checkCodeSyntax()
*/
bool
Monomer::validate(ErrorList *error_list_p) const
{
  qsizetype error_count = error_list_p->size();

  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. Monomer "
                  "validation "
                  "failed.";

      m_isValid = false;
      return false;
    }

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

  // qDebug() << "Now checking the code syntax.";
  if(!checkCodeSyntax())
    {
      qDebug() << "The Monomer code does not pass the syntax test.";
      error_list_p->push_back("The Monomer code does not pass the syntax test");
    }

  Formula temp_formula(m_formula);
  if(!temp_formula.validate(mcsp_polChemDef->getIsotopicDataCstSPtr(),
                            error_list_p))
    {
      qDebug() << "The Monomer formula failed to validate.";
      error_list_p->push_back("The Monomer formula failed to validate");
    }

  double mono = 0.0;
  double avg  = 0.0;

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

  // qDebug() << qSetRealNumberPrecision(6)
  //          << "Validation calculated masses (not member data):" << mono <<
  //          "-"
  //          << avg;

  for(const ModifSPtr &modif_sp : m_modifs)
    {
      ErrorList local_error_list;
      if(!modif_sp->validate(&local_error_list))
        {
          qDebug() << "Monomer's Modif" << modif_sp->getName()
                   << "failed to validate successfully.";

          error_list_p->push_back(
            QString("Failed to validate Monomer's Modif %1")
              .arg(modif_sp->getName()));
        }
    }

  // If we added errors, then that means that the Monomer was not valid.
  m_isValid = (error_list_p->size() > error_count ? false : true);

  return m_isValid;
}

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

\sa validate()
*/
bool
Monomer::isValid() const
{
  return m_isValid;
}

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

/*!
\brief Calculates this monomer's monoisotopic and avg masses and sets the
results to \a mono and \a avg.

The calculation is performed by computing the masses of this monomer's formula,
accounting the chemical entities defined by \a monomer_chemical_entities.

The calculations are performed using reference data in \a isotopic_data_csp.

Returns true if the calculations were successful, false otherwise.

\sa Formula::accountMasses()
*/
bool
Monomer::calculateMasses(const IsotopicDataCstSPtr &isotopic_data_csp,
                         double &mono,
                         double &avg,
                         Enums::ChemicalEntity monomer_chemical_entities) 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;
        }
    }

  mono = 0;
  avg  = 0;

  bool ok;

  // Formula temp_formula(m_formula);
  Formula(m_formula).accountMasses(ok, local_isotopic_data_csp, mono, avg);

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

  if(static_cast<int>(monomer_chemical_entities) &
     static_cast<int>(Enums::ChemicalEntity::MODIF))
    {
      for(const ModifSPtr &modif_sp : m_modifs)
        modif_sp->accountMasses(mono, avg, /*times*/ 1);
    }

  return true;
}

/*!
\brief Calculates this monomer's monoisotopic and avg masses.

The calculation is performed by computing the masses
of this monomer's formula, accounting or not the entities described by \a
monomer_chemical_entities.

The reference data for the computations are accessed at \a isotopic_data_csp.

Returns true if the calculations were successful, false otherwise.

\sa Formula::accountMasses()
*/
bool
Monomer::calculateMasses(const IsotopicDataCstSPtr &isotopic_data_csp,
                         Enums::ChemicalEntity monomer_chemical_entities)
{
  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;
        }
    }

  m_mono = 0;
  m_avg  = 0;

  bool ok;

  // 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 Monomer:" << m_name
               << "and formula:" << m_formula;
      m_isValid = false;
      return m_isValid;
    }

  // qDebug() << "After calculating the masses: the Monomer:" << toString();
  // qDebug() << "flags:" << static_cast<int>(monomer_chemical_entities)
  //  << chemicalEntityMap[monomer_chemical_entities];

  if(static_cast<int>(monomer_chemical_entities) &
     static_cast<int>(Enums::ChemicalEntity::MODIF))
    {
      for(const ModifSPtr &modif_sp : m_modifs)
        modif_sp->accountMasses(&m_mono, &m_avg, /*times*/ 1);
    }

  return true;
}

/*!
\brief Calculates this monomer's monoisotopic and avg masses.

The calculation is performed by computing the masses
of this monomer's formula.

If \a monomer_chemical_entities & MONOMER_CHEMENT_MODIF is true, then the
masses are updated to account for the mass of modifications.

Set \a ok to true if the calculations were successful, false otherwise.

Returns this object.

\sa Formula::accountMasses()
*/
Monomer &
Monomer::calculateMasses(bool &ok,
                         const IsotopicDataCstSPtr &isotopic_data_csp,
                         Enums::ChemicalEntity monomer_chemical_entities)
{
  ok = calculateMasses(isotopic_data_csp, monomer_chemical_entities);
  return *this;
}

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

Returns true.
*/
const Monomer &
Monomer::accountMasses(double *mono_p, double *avg_p, int times) const
{
  if(mono_p != nullptr)
    *mono_p += m_mono * times;

  if(avg_p != nullptr)
    *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 true.
*/
const Monomer &
Monomer::accountMasses(double &mono, double &avg, int times) const
{
  mono += m_mono * times;
  avg  += m_avg * times;

  return *this;
}

/*!
\brief Returns the mass of the type defined by \a mass_type.
*/
double
Monomer::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;
}

//////////////// FORMULA OPERATIONS /////////////////////

/*
\brief Calculates a Formula representing this monomer .

The calculated formula accounts for this monomer's formula and for its
modifications formulas if any and if \a (monomer_chemical_entities &
MONOMER_CHEMENT_MODIF) is true.

This monomer's formula must validate using Modif::validate.

Returns the Formula.

\sa Modif::accountFormula()
*/
QString
Monomer::calculateFormula(Enums::ChemicalEntity monomer_chemical_entities) const
{
  // We want to return the calculated formula of this monomer that accounts
  // for its modifications if so is requested.

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // qDebug() << "Calculating formula for monomer: " << m_name
  //<< "with chemical entities:" << monomer_chemical_entities;

  Formula formula(m_formula);

  // The m_formula above is only a text string. We need to convert that
  // into the symbol/count pair by validating it with true true params.
  // The formula is asked to validate with storage of the found symbol/count
  // pairs and with resetting of the previous contents of the symbol/count
  // map.

  // We need to seed the symbol/count pairs because the following call
  // (accountFormula()) will update the pairs' values.
  ErrorList error_list;
  if(!formula.validate(
       isotopic_data_csp, /*store*/ true, /*reset*/ true, &error_list))
    {
      qDebug() << "Formula:" << formula.getActionFormula()
               << "failed to validate with errors:"
               << Utils::joinErrorList(error_list, ", ");

      return QString();
    }

  bool ok = false;

  if(static_cast<int>(monomer_chemical_entities) &
     static_cast<int>(Enums::ChemicalEntity::MODIF))
    {
      for(const ModifSPtr &modif_sp : m_modifs)
        {
          formula.accountFormula(modif_sp->formula(/*with_title*/ false),
                                 mcsp_polChemDef->getIsotopicDataCstSPtr(),
                                 1,
                                 ok);

          if(!ok)
            return QString();
        }
    }

  return formula.getActionFormula();
}

/*!
\brief Parses the monomer XML \a element specifically for \a version.

Parses the monomer XML element passed as argument and for each
encountered data will set the data to this monomer (this is
called XML rendering).The parsing is delegated to a function that is specific
for for \a version of the polymer chemistry definition.

The XML element is found in the polymer chemistry definition and has the
following form:

\code
<monomers>
<mnm>
<name>Glycine</name>
<code>G</code>
<formula>C2H3N1O1</formula>
</mnm>
<mnm>
<name>Alanine</name>
<code>A</code>
<formula>C3H5N1O1</formula>
</mnm>
\endcode

After setting all the data, this monomer calculates it masses and
validates itself. If any of these steps fails, the error is reported
by returning false.

\sa validate()
*/
bool
Monomer::renderXmlMnmElement(const QDomElement &element,
                             [[maybe_unused]] int version)
{

  /* In a polymer chemistry definition, the xml node we are in is
   * structured this way:
   *
   * <mnm>
   * <name>Glycine</name>
   * <code>G</code>
   * <formula>C2H3N1O1</formula>
   * </mnm>
   *
   * And the element parameter points to the
   *
   * <mnm> element tag:
   * ^
   * |
   * +----- here we are right now.
   *
   * Which means that element.tagName() == "mnm" 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.
   *
   */

  m_isValid = true;

  // QString str;
  // QTextStream stream(&str);
  // QDomNode node = element;
  // node.save(stream, 2 /*indent*/);
  // qDebug().noquote() << "The element:\n" << str;

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

  QDomElement child;

  child = element.firstChildElement("name");

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

  if(child.isNull())
    {
      qDebug() << "The Monomer did not render correctly: problem with the "
                  "<code> element.";
      m_isValid = false;
      return m_isValid;
    }
  m_code = child.text();
  // qDebug() << "The code:" << m_code;

  if(!checkCodeSyntax())
    {
      qDebug() << "The Monomer did not render correctly: problem with the "
                  "code text.";
      m_isValid = false;
      return m_isValid;
    }

  child = child.nextSiblingElement("formula");

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

  Formula temp_formula;

  if(!temp_formula.renderXmlFormulaElement(child))
    {
      qDebug() << "The Monomer did not render correctly: the formula did not "
                  "render correctly.";
      m_isValid = false;
      return m_isValid;
    }
  m_formula = temp_formula.getActionFormula(/*with_title*/ true);
  // qDebug() << "The formula:" << m_formula;

  ErrorList error_list;
  m_isValid = validate(&error_list);
  if(!m_isValid)
    {
      qDebug() << "The Monomer did not validate successfully after "
                  "rendering, with errors:";
      Utils::joinErrorList(error_list, ", ");
    }
  else
    {
      // At this point, because we are creating a Monomer from scratch,
      // and not by copying or by assignment, we calculate the masses
      // explicitely (validate() does that but not on member m_mono/m_avg
      // because the method is const.).

      if(!calculateMasses(nullptr))
        {
          qDebug() << "The Monomer's masses could not be calculated.";
        }
    }

  // qDebug() << "Correctly rendered Monomer" << m_name << ":" << toString();

  return m_isValid;
}

/*!
\brief Formats this monomer's data as a string suitable to be used as an XML
element in the polymer chemistry definition.

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

\code
<monomers>
<mnm>
<name>Glycine</name>
<code>G</code>
<formula>C2H3N1O1</formula>
</mnm>
\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
Monomer::formatXmlMnmElement(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<mnm>\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<code>%2</code>\n").arg(lead).arg(m_code);

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

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

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

  return text;
}

/*!
\brief Parses into this monomer the XML monomer \a element passed as argument.

The XML element comes from a polymer sequence file, where the monomer is
singled out (not in a sequence string) because it might be modified, like
this:

\code
<monomer>
<code>S</code>
<mdf>
<name>Phosphorylation</name>
<formula>H1O3P1</formula>
<targets>*</targets>
<maxcount>1</maxcount>
</mdf>
</monomer>
<codes>GRKASGSSPTSPINADKVENEDAFLEEVAEEKPHVKPYFTKTILDMEVVEGSAARFDCKIEGYPDPEVM</codes>
<monomer>
<code>W</code>
<mdf>
<name>Oxidation</name>
<formula>O1</formula>
<targets>*</targets>
<maxcount>1</maxcount>
</mdf>
</monomer>
<codes>YKDDQPVKESRHFQIDYDEEGNCSLTISEVCGDDDAKYTCKAVNSLGEATCTAELLVETMGKEGEGEGEGEEDEEEEEE</codes>
\endcode

\a version indicates the format version of this XML \a element.

As soon as the monomer code is known, while parsing the \a element, the
corresponding monomer is searched in the list of monomers in the member
polymer chemistry definition (\c mcsp_polChemDef). Then, the found monomer is
copied into \c this monomer so that both monomers are identical, effectively
initializing this monomer to the monomer described by the \a element.

If the \a element contains one or more \c mdf modifications, these
modifications are allocated as \l{Modif}'s and validated. If these
modifications validate successfully, they are appended to this monomer's list
of modifications.

Returns true if initialization of his monomer with the contents of \a
element succeeded, false otherwise.

\sa formatXmlMonomerElement(int offset, const QString &indent)
*/
bool
Monomer::renderXmlMonomerElement(const QDomElement &element,
                                 [[maybe_unused]] int version)
{
  qDebug();

  m_isValid = true;

  // QString str;
  // QTextStream stream(&str);
  // QDomNode node = element;
  // node.save(stream, 2 /*indent*/);
  // qDebug().noquote() << "The element:\n" << str;

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

  qDebug() << "Indeed a monomer element.";

  QDomElement child;

  child = element.firstChildElement("code");

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

  qDebug() << "The code:" << code;

  // Use the code to access the corresponding Monomer in the list of Monomers
  // in the polymer chemistry definition.
  MonomerSPtr monomer_csp = mcsp_polChemDef->getMonomerCstSPtrByCode(code);

  qDebug() << "monomer pointer:" << monomer_csp.get();

  if(monomer_csp == nullptr)
    {
      qDebug() << "The monomer's code is not found in the polymer chemistry "
                  "definition.";

      m_isValid = false;
      return m_isValid;
    }

  qDebug() << "monomer name:" << monomer_csp->getName();

  *this = *monomer_csp;

  // Sanity check
  if(m_code != code)
    qFatal("Programming error. Both codes should be identical.");

  // And now we have to manage the mdf objects.
  child = child.nextSiblingElement();

  while(!child.isNull())
    {
      if(child.tagName() != "mdf")
        {
          qDebug() << "The Monomer did not render correctly: problem with the "
                      "<mdf> element.";

          m_isValid = false;
          return m_isValid;
        }

      Modif modif(mcsp_polChemDef, child, version);

      qDebug() << "20250428-Created Modif:" << modif.getName()
               << "with Formula:" << modif.getFormula();

      if(!modif.calculateMasses(mcsp_polChemDef->getIsotopicDataCstSPtr()))
        {
          qDebug() << "Failed to calculate masses for Monomer's Modif"
                   << modif.getName();

          m_isValid = false;
          return m_isValid;
        }

      // The validation will take care of checking that the <targets>
      // element did have correct text inside.

      ErrorList error_list;
      if(!modif.validate(&error_list))
        {
          qDebug() << "Failed to validate modification" << modif.getName()
                   << "with errors:" << Utils::joinErrorList(error_list, ", ");

          m_isValid = false;
          return m_isValid;
        }

      error_list.clear();

      // qDebug() << "At this point, going to modify the monomer.";
      QString uuid = modify(modif, /*override*/ false, &error_list);

      // qDebug() << "The returned uuid:" << uuid;

      if(uuid.isEmpty())
        {
          qDebug() << "The monomer could not be modified, with errors:"
                   << Utils::joinErrorList(error_list, ", ");

          m_isValid = false;
          return m_isValid;
        }

      child = child.nextSiblingElement();
    }

  m_isValid = true;
  return m_isValid;
}

/*!
\brief Formats a string suitable to be used as an XML element in a
polymer sequence file.

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

\code
<monomer>
<code>S</code>
<prop>
<name>MODIF</name>
<data>Phosphorylation</data>
</prop>
<prop>
<name>COMMENT</name>
<data>Phosphorylation is only partial</data>
</prop>
</monomer>
\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
Monomer::formatXmlMonomerElement(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.append(QString("%1<monomer>\n").arg(lead));

  // Prepare the lead for the <code> child that is indented.
  ++newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  QString code_element_string =
    QString("%1<code>%2</code>\n").arg(lead).arg(m_code);
  qDebug() << "code element:" << code_element_string;

  text.append(code_element_string);

  // The monomer may have any number of modif objects, which we have
  // to document here.

  // Continue with indented <mdf> element(s) (same indent as for <code>).

  for(const ModifSPtr &modif_sp : m_modifs)
    text.append(modif_sp->formatXmlMdfElement(newOffset, indent));

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

  text.append(QString("%1</monomer>\n").arg(lead));

  // QString debug_message =
  // QString("%1\n%2\n").arg("Returning string:").arg(text);
  // qDebug().noquote() << debug_message;

  return text;
}

//////////////// UTILS /////////////////////

/*!
\brief Stores the Modif instance \a modif_sp pointer in the member container.

The \a modif_sp is stored as is, without duplication.

Returns the Uuid string associated to the stored Modif.
*/
QString
Monomer::storeModif(const ModifSPtr &modif_sp)
{
  if(modif_sp == nullptr)
    qFatal() << "The provided ModifSPtr is nullptr.";

  qDebug() << "Right before storage, there are currently" << m_modifs.size()
           << "modifications.";

  // Do not store an item twice.
  if(hasModif(modif_sp) || !getUuidForModif(modif_sp).isEmpty())
    qFatal()
      << "It is prohibited to store the same ModifSPtr more than once.";

  qDebug() << "Ok, we can go on.";

  // Even if we get a ref to shared_ptr, the reference count increment will
  // occur.
  m_modifs.push_back(modif_sp);
  QString uuid = QUuid::createUuid().toString();
  m_uuidModifPairs.push_back(UuidModifWPtrPair(uuid, modif_sp));

  qDebug() << "Right after storage, there are currently" << m_modifs.size()
           << "modifications.";

  return uuid;
}

/*!
\brief Stores the Modif instance \a modif in the member container.

The \a modif is used to craft a ModifSPtr that is stored.

Returns the Uuid string associated to the stored Modif.
*/
QString
Monomer::storeModif(const Modif &modif)
{
  ModifSPtr modif_sp = std::make_shared<Modif>(modif);

  if(modif_sp == nullptr)
    {
      qFatal() << "Failed to allocate ModifSPtr.";
      return "";
    }

  return storeModif(modif_sp);
}

/*!
\brief Returns true if \a modif_sp was found in the member container of Modif
instances, false otherwise.
*/
bool
Monomer::hasModif(const ModifSPtr &modif_sp) const
{
  if(modif_sp == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  std::vector<ModifSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_modifs.cbegin(),
                 m_modifs.cend(),
                 [modif_sp](const ModifSPtr &the_modif_sp) {
                   return the_modif_sp == modif_sp;
                 });

  if(the_iterator_cst == m_modifs.cend())
    {
      std::vector<UuidModifWPtrPair>::const_iterator the_pair_iterator_cst =
        std::find_if(m_uuidModifPairs.cbegin(),
                     m_uuidModifPairs.cend(),
                     [modif_sp](const UuidModifWPtrPair &the_pair) {
                       return the_pair.second.lock() == modif_sp;
                     });

      if(the_pair_iterator_cst != m_uuidModifPairs.cend())
        qFatal()
          << "Inconsistency between m_modifs and m_uuidModifPairs.";

      return false;
    }

  return true;
}

/*!
\brief Returns true if \a modif_sp was found in the member container of
Uuid-Modif pairs, false otherwise.
*/
bool
Monomer::hasUuid(const ModifSPtr &modif_sp) const
{
  if(modif_sp == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  std::vector<UuidModifWPtrPair>::const_iterator the_iterator_cst =
    std::find_if(m_uuidModifPairs.cbegin(),
                 m_uuidModifPairs.cend(),
                 [modif_sp](const UuidModifWPtrPair &the_pair) {
                   return the_pair.second.lock() == modif_sp;
                 });

  if(the_iterator_cst == m_uuidModifPairs.cend())
    return false;

  // Sanity check

  if(!hasModif(modif_sp))
    qFatal() << "Inconsistency between m_modifs and m_uuidModifPairs.";

  return true;
}

/*!
\brief Returns the Monomer instance pointer in the member container that is
associated to the \a uuid Uuid string.

If no such Monomer instance pointer is found,  nullptr is returned.
*/
ModifSPtr
Monomer::getModifForUuid(const QString &uuid) const
{
  qDebug() << "There are currently" << m_modifs.size()
           << "modifications. The Modif uuid that is asked for:" << uuid;

  std::vector<std::pair<QString, ModifWPtr>>::const_iterator the_iterator_cst =
    std::find_if(m_uuidModifPairs.cbegin(),
                 m_uuidModifPairs.cend(),
                 [uuid](const UuidModifWPtrPair &the_pair) {
                   qDebug() << "Iterating into" << the_pair.first
                            << "while searching for" << uuid;
                   return the_pair.first == uuid;
                 });

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

  ModifSPtr modif_sp = (*the_iterator_cst).second.lock();

  // Sanity check

  if(!hasModif(modif_sp))
    qFatal() << "Inconsistency between m_modifs and m_uuidModifPairs.";

  return modif_sp;
}

/*!
\brief Returns the UUID string identifying \a modif_sp in the member container.

If no such Modif is found, an empty string is returned.
*/
QString
Monomer::getUuidForModif(const ModifSPtr &modif_sp) const
{
  if(modif_sp == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  std::vector<UuidModifWPtrPair>::const_iterator the_iterator_cst =
    std::find_if(m_uuidModifPairs.cbegin(),
                 m_uuidModifPairs.cend(),
                 [modif_sp](const UuidModifWPtrPair &the_pair) {
                   // Do not query the modif_sp managed object because it can
                   // be nullptr!
                   return the_pair.second.lock() == modif_sp;
                 });

  if(the_iterator_cst == m_uuidModifPairs.cend())
    {
      // Sanity check
      if(hasModif(modif_sp))
        qFatal() << "Inconsistency between the m_modifs and the "
                          "m_uuidModifPairs vectors.";

      return QString();
    }

  // Sanity check
  if(!hasModif(modif_sp))
    qFatal()
      << "Inconsistency between the m_modifs and the m_uuidModifPairs vectors.";

  return (*the_iterator_cst).first;
}

/*!
\brief Returns a container of QString instances that correspond to the UUID
strings that identify all the Modif instances involved in this Monomer.

If no Modif is found, an empty container is returned.
*/
std::vector<QString>
Monomer::getAllModifUuids() const
{
  std::vector<QString> the_uuid_strings;

  for(const UuidModifCstWPtrPair pair : m_uuidModifPairs)
    the_uuid_strings.push_back(pair.first);

  // Sanity check
  if(the_uuid_strings.size() != m_modifs.size())
    qFatal()
      << "Inconsistency between the <object>_s and <uuid-object> pairs.";

  return the_uuid_strings;
}

void
Monomer::cleanupModifs()
{
  qDebug() << "At beginning, count of UUID-Modif pairs:"
           << m_uuidModifPairs.size();

  std::vector<UuidModifWPtrPair>::iterator the_iterator =
    m_uuidModifPairs.begin();
  std::vector<UuidModifWPtrPair>::iterator the_end_iterator =
    m_uuidModifPairs.end();

  while(the_iterator != the_end_iterator)
    {
      if((*the_iterator).second.expired() ||
         (*the_iterator).second.lock() == nullptr ||
         !hasModif((*the_iterator).second.lock()))
        the_iterator = m_uuidModifPairs.erase(the_iterator);
      else
        ++the_iterator;
    }

  qDebug() << "At end, count of UUID-Modif pairs:" << m_uuidModifPairs.size();
}

bool
Monomer::removeModif(ModifSPtr modif_sp)
{
  if(modif_sp == nullptr || modif_sp.get() == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  // We will need this anyway.
  QString uuid = getUuidForModif(modif_sp);

  // Some controls are in order: we have to check that at index, there is
  // a ModifSPtr that is present both in m_modifs and in
  // m_uuidModifPairs.

  std::vector<ModifSPtr>::const_iterator the_iterator_cst = std::find_if(
    m_modifs.begin(), m_modifs.end(), [modif_sp](ModifSPtr iter_modif_sp) {
      return iter_modif_sp == modif_sp;
    });

  if(the_iterator_cst == m_modifs.end())
    {
      qDebug() << "The ModifSPtr was not found in the container.";

      if(!uuid.isEmpty())
        qFatal()
          << "Inconsistency between m_modifs and m_uuidModifPairs.";

      return false;
    }

  // At this point, both containers contain modif_sp.

  m_modifs.erase(the_iterator_cst);

  std::vector<UuidModifWPtrPair>::const_iterator the_pair_iterator_cst =
    std::find_if(m_uuidModifPairs.cbegin(),
                 m_uuidModifPairs.cend(),
                 [uuid](const UuidModifWPtrPair &the_pair) {
                   // Do not query the modif_sp managed object because it can
                   // be nullptr!
                   return the_pair.first == uuid;
                 });

  if(the_pair_iterator_cst == m_uuidModifPairs.cend())
    qFatal() << "Inconsistency between m_modifs and m_uuidModifPairs.";

  m_uuidModifPairs.erase(the_pair_iterator_cst);

  return true;
}

/*!
\brief Clears all the data of the Monomer instance.
*/
void
Monomer::clear()
{
  mcsp_polChemDef = nullptr;
  m_name          = "";
  m_code          = "";
  m_formula       = "";

  m_mono = 0.0;
  m_avg  = 0.0;

  m_modifs.clear();

  m_isValid = false;
}

/*!
\brief Returns a text string representing this Monomer instance.
*/
QString
Monomer::toString() const
{
  return QString(
           "Monomer: %1 - %2 - %3 - %4 - %5 - modifs: %6 - is valid: %7\n")
    .arg(m_name)
    .arg(m_code)
    .arg(m_formula)
    .arg(m_mono, 0, 'f', ATOM_DEC_PLACES)
    .arg(m_avg, 0, 'f', ATOM_DEC_PLACES)
    .arg(m_modifs.size())
    .arg(m_isValid);
}


} // namespace libXpertMassCore
} // namespace MsXpS
