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


/////////////////////// Qt includes
#include <QDateTime>
#include <QFile>
#include <QIODevice>

/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/Polymer.hpp"
#include "MsXpS/libXpertMassCore/Modif.hpp"
#include "MsXpS/libXpertMassCore/PolChemDef.hpp"
#include "MsXpS/libXpertMassCore/CrossLink.hpp"


int polymerMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::Polymer>(
  "MsXpS::libXpertMassCore::Polymer");

int polymerSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMassCore::PolymerSPtr>(
    "MsXpS::libXpertMassCore::PolymerSPtr");

int PolymerCstQSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMassCore::PolymerCstQSPtr>(
    "MsXpS::libXpertMassCore::PolymerCstQSPtr");

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The Polymer class provides abstractions to work with
a polymer molecule (protein or saccharide , for example).

The Polymer class provides a polymer sequence. In the
protein world, a polymer sequence is a protein. From a computing standpoint,
that sequence is first created by chaining amino acid residues (the residue
chain). In a second step, the entity is set to a finished polymer state by
adding the N-terminal proton and the C-terminal hydroxyl residue. It might
happen also that the ends of a polymer be chemically modified (acetylation of
the N-terminal residue, for example).

The Polymer class allows modelling a polymer sequence in its finest details.

\note As a design decision, Polymer instances can only be created as shared
pointer instances. Hence, the constructor is private and is called by
\l{createSPtr()}.

\note The Polymer class derives from std::enable_shared_from_this<Polymer>
because in determinate situations it is necessary to access the shared pointer
from the raw pointer (in the particular case of CrossLink instances that need to
reference the Polymer in which they occur).
*/

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

\brief Version number of the polymer sequence file format.
*/

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

\brief The \l PolChemDef polymer chemistry definition that is the context in
which the Polymer exists.
*/

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

\brief The name of the polymer, for example, "Apomyoglobin".
*/

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

\brief The code of the polymer, for example, the accession number in
SwissProt.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::m_author

\brief The author or creator of the file.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::m_filePath

\brief The file path to the Polymer sequence file.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::m_dateTime

\brief The date and time of last modification.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::m_sequence

\brief The Sequence instance that enshrines the actual Polymer sequence of
Monomer instances.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::m_leftEndModif

\brief The left end modification.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::m_rightEndModif

\brief The right end modification.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::mp_calcOptions

\brief The CalcOptions instance that configure the way masses and formulas are
computed.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::mp_ionizer

\brief The Ionizer instance that configure the ionization of this Polymer
instance.
*/

/*!
\variable MsXpS::libXpertMassCore::Polymer::m_crossLinks

\brief The container of \l UuidCrossLinkWPtrPair instances occurring in this
Polymer sequence.
*/

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

\brief The monoisotopic mass computed for this Polymer instance.
*/

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

\brief The average mass computed for this Polymer instance.
*/

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

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

/*!
\typedef MsXpS::libXpertMassCore::PolymerSPtr
\relates Polymer

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

/*!
\typedef MsXpS::libXpertMassCore::PolymerCstQSPtr
\relates Polymer

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


/*!
\brief Constructs a polymer with a number of arguments.

\list
\li \a pol_chem_def_csp The polymer chemistry definition context in which this
Polymer instance exists

\li \a name The name of the Polymer instance (could be the name of the protein
from the SwissProt or the GenBank database, for example)

\li \a code The code of Polymer sequence (could be an accession number from the
SwissProt or the GenBank database, for example)

\li \a author The author creating the Polymer sequence

\li \a sequence_string The monomer codes that make the Polymer sequence.

\endlist

This function call the actual private constructor.
*/
PolymerQSPtr
Polymer::createSPtr(PolChemDefCstSPtr pol_chem_def_csp,
                    const QString &name,
                    const QString &code,
                    const QString &author,
                    const QString &sequence_string)
{
  Polymer *polymer_p =
    new Polymer(pol_chem_def_csp, name, code, author, sequence_string);
  return QSharedPointer<Polymer>(polymer_p);
}

// This constructor is private on purpose, so that a Polymer can only be
// constructed as a QSharedPointer using the public createSPtr() above.
// See how mp_calcOptions and mp_ionizer can never be nullptr at construction
// time.
Polymer::Polymer(PolChemDefCstSPtr pol_chem_def_csp,
                 const QString &name,
                 const QString &code,
                 const QString &author,
                 const QString &sequence_string)
  : mcsp_polChemDef(pol_chem_def_csp),
    m_name(name),
    m_code(code),
    m_author(author),
    m_sequence(Sequence(pol_chem_def_csp, sequence_string)),
    mp_calcOptions(new CalcOptions(this)),
    mp_ionizer(new Ionizer(this))
{
  m_leftEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);
  m_rightEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);
}

/*!
\brief Destructs the polymer.
*/
Polymer::~Polymer()
{
  m_crossLinks.clear();
}

//////////////// THE SHARED POINTER /////////////////////
/*!
\brief Returns a const-cast of the PolymerSPtr (std::shared_ptr<Polymer>) that
manages this Polymer *.

This function should never fail because a Polymer can
only be allocated as a PolymerSPtr (private Polymer::Polymer() constructor
called by a public createSPtr() function).

\sa CrossLink::CrossLink(), renderXmlCrossLinksElement()
*/
PolymerCstQSPtr
Polymer::getCstSharedPtr()
{
  PolymerCstQSPtr polymer_cqsp = nullptr;
  try
    {
      polymer_cqsp = sharedFromThis();
    }
  catch(const std::exception &e)
    {
      QString message =
        QString("Exception caught: %1\n%2.")
          .arg(e.what())
          .arg(
            "Programming error. The pointer cannot be nullptr. If that "
            "pointer was gotten from Polymer::getCstSharedPtr(), ensure that "
            "the used raw pointer is indeed managed by a "
            "QSharedPointer<Polymer>.");
      qFatal().noquote() << message;
    }

  return polymer_cqsp;
}

/*!
\brief Returns the PolymerSPtr (std::shared_ptr<Polymer>) that
manages this Polymer *.

This function should never fail because a Polymer can
only be allocated as a PolymerSPtr (private Polymer::Polymer() constructor
called by a public createSPtr() function).

\sa CrossLink::CrossLink(), renderXmlCrossLinksElement()
*/
PolymerQSPtr
Polymer::getSharedPtr()
{
  PolymerQSPtr polymer_qsp = nullptr;
  try
    {
      polymer_qsp = sharedFromThis();
    }
  catch(const std::exception &e)
    {
      QString message =
        QString("Exception caught: %1\n%2.")
          .arg(e.what())
          .arg(
            "Programming error. The pointer cannot be nullptr. If that "
            "pointer was gotten from Polymer::getCstSharedPtr(), ensure that "
            "the used raw pointer is indeed managed by a "
            "QSharedPointer<Polymer>.");
      qFatal().noquote() << message;
    }

  return polymer_qsp;
}

//////////////// THE POLCHEMDEF /////////////////////
/*!
\brief Sets the polymer chemistry definition to \a pol_chem_def_csp.
*/
void
Polymer::setPolChemDefCstSPtr(PolChemDefCstSPtr pol_chem_def_csp)
{
  mcsp_polChemDef = pol_chem_def_csp;

  ErrorList error_list;
  m_isValid = validate(&error_list);

  m_leftEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);
  m_rightEndModif.setPolChemDefCstSPtr(pol_chem_def_csp);

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

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

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

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

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

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

/*!
\brief Sets the \a code.

The code might identify the polymer in a database, like the Swissprot code, for
example.
*/
void
Polymer::setCode(const QString &code)
{
  m_code = code;

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

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

/*!
\brief Sets the \a author.
*/
void
Polymer::setAuthor(const QString &author)
{
  m_author = author;

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Returns the author.
*/
QString
Polymer::getAuthor() const
{
  return m_author;
}

/*!
\brief Sets the polymer's \l{Sequence} file path to \a file_path.
*/
void
Polymer::setFilePath(const QString &file_path)
{
  m_filePath = file_path;

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Returns the polymer sequence file path.
*/
QString
Polymer::getFilePath() const
{
  return m_filePath;
}

/*!
\brief Sets the date and time to \a date_time.
*/
void
Polymer::setDateTime(const QString &date_time)
{
  m_dateTime = QDateTime::fromString(date_time, "yyyy-MM-dd:mm:ss");

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Sets the date and time to now.
*/
void
Polymer::setDateTime()
{
  m_dateTime = QDateTime::currentDateTime();

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Returns the date and time.
*/
QString
Polymer::getDateTime() const
{
  return m_dateTime.toString("yyyy-MM-dd:mm:ss");
}

//////////////// THE SEQUENCE /////////////////////

/*!
\brief Sets the \l{Sequence} as created using text \a sequence_string.
*/
void
Polymer::setSequence(const QString &sequence_string)
{
  m_sequence = Sequence(mcsp_polChemDef, sequence_string);

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Sets the \l{Sequence} as \a sequence.
*/
void
Polymer::setSequence(const Sequence &sequence)
{
  m_sequence = sequence;

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

/*!
\brief Returns a const reference to the member Sequence instance.
*/
const Sequence &
Polymer::getSequenceCstRef() const
{
  return m_sequence;
}

/*!
\brief Returns a reference to the member Sequence instance.
*/
Sequence &
Polymer::getSequenceRef()
{
  return m_sequence;
}

/*!
\brief Returns this Polymer instance's \l{Sequence}'s sequence as a text string.
*/
QString
Polymer::getSequenceText() const
{
  return m_sequence.getSequence();
}

/*!
\brief Returns the size of this Polymer instance as the count of Monomer
instances contained in the Monomer container belonging to the member
Sequence instance.
*/
std::size_t
Polymer::size() const
{
  return m_sequence.size();
}

// MONOMER MODIFICATIONS
/////////////////////////////
/*!
\brief Returns the count of modified Monomers occurring in the IndexRange
instances contained in \a index_range_collection.
*/
std::size_t
Polymer::modifiedMonomerCount(
  const IndexRangeCollection &index_range_collection) const
{
  qsizetype count = 0;

  foreach(const IndexRange *item, index_range_collection.getRangesCstRef())
    {
      for(qsizetype iter = item->m_start; iter < item->m_stop + 1; ++iter)
        {
          if(m_sequence.getMonomerCstSPtrAt(iter)->isModified())
            ++count;
        }
    }

  return count;
}

/*!
\brief Returns true if the modification of Monomer instance at \a index using \a
modif_name was succesful, false otherwise.

If \a override is true, the Monomer at \a index is modified even if it is
current modified the maximum count for \a modif_name.

\sa Modif::m_maxCount
*/
bool
Polymer::modifyMonomer(std::size_t index,
                       const QString modif_name,
                       bool override)
{
  // qDebug() << "The modification name:" << modif_name;

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

  return m_sequence.modifyMonomer(index, modif_name, override);
}

/*!
\brief Return true if this Polymer's \l{Sequence} contains at least one modified
\l{Monomer} in the sequence range delimited by \a left_index and \a right_index,
false otherwise.
*/
bool
Polymer::hasModifiedMonomer(std::size_t left_index,
                            std::size_t right_index) const
{
  return m_sequence.hasModifiedMonomer(left_index, right_index);
}

// POLYMER MODIFICATIONS
/////////////////////////////

// ANY END

/*!
\brief Removes this Polymer instance's modification on the end specified in \a
polymer_end.
*/
void
Polymer::unmodify(Enums::PolymerEnd polymer_end)
{
  if(static_cast<int>(polymer_end) & static_cast<int>(Enums::PolymerEnd::LEFT))
    setLeftEndModifByName("");

  if(static_cast<int>(polymer_end) & static_cast<int>(Enums::PolymerEnd::RIGHT))
    setRightEndModifByName("");
}

// LEFT END

/*!
\brief Sets this polymer's left end modification to \a name.

If \a name is empty, the corresponding Modif instance of this Polymer object is
removed (ie, the polymer's end is unmodifed.)

Returns true if a modification by \a name was found in the member polymer
chemistry definition's list of modifications and if that modification could be
set and its masses could be calculated successfully, otherwise returns false.

After setting the member data, the instance is validated and the result is set
to m_isValid.
*/
bool
Polymer::setLeftEndModifByName(const QString &name)
{
  if(name.isNull() || name.isEmpty())
    {
      // Reset the modif to nothing.
      m_leftEndModif.clear();
    }

  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    {
      qDebug() << "Cannot search for Modif name if no PolChemDef is available.";
      return false;
    }

  ModifCstSPtr modif_csp = mcsp_polChemDef->getModifCstSPtrByName(name);

  if(modif_csp == nullptr)
    {
      qDebug() << "Failed to find a Modif by name " << name
               << "in the polChemDef.";
      return false;
    }

  m_leftEndModif = *modif_csp.get();

  if(m_leftEndModif.getPolChemDefCstSPtr() != mcsp_polChemDef)
    qFatal()
      << "Programming error. The pointers to PolChemDef cannot differ.";

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

  return true;
}

/*!
\brief Sets this polymer's left end modification to \a modif.

Returns true if \a modif could validate successfully and if that modification
could be set and its masses could be calculated successfully, otherwise returns
false.

After setting the member data, the instance is validated and the result is set
to m_isValid.
*/
bool
Polymer::setLeftEndModif(const Modif &modif)
{
  ErrorList error_list;

  m_leftEndModif = modif;
  m_leftEndModif.setPolChemDefCstSPtr(mcsp_polChemDef);

  if(!m_leftEndModif.validate(&error_list))
    {
      qDebug() << "The Modif is not valid, with errors:"
               << Utils::joinErrorList(error_list, ", ");
      return false;
    }

  m_isValid = validate(&error_list);

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

  return true;
}

/*!
\brief Returns a const reference to this Polymer's left end modif.
*/
const Modif &
Polymer::getLeftEndModifCstRef() const
{
  return m_leftEndModif;
}

/*!
\brief Returns a reference to this Polymer's left end modif.
*/
Modif &
Polymer::getLeftEndModifRef()
{
  return m_leftEndModif;
}

/*!
\brief Returns true if this Polymer is modified at its left end.
*/
bool
Polymer::isLeftEndModified() const
{
  return m_leftEndModif.isValid();
}

// RIGHT END
/*!
\brief Sets this polymer's right end modification to \a name.

Returns true if a modification by \a name was found in the member polymer
chemistry definition's list of modifications and if that modification could be
set and its masses could be calculated successfully, otherwise returns false.

After setting the member data, the instance is validated and the result is set
to m_isValid.
*/
bool
Polymer::setRightEndModifByName(const QString &name)
{
  if(name.isNull() || name.isEmpty())
    {
      // Reset the modif to nothing.
      m_rightEndModif.clear();
    }

  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    {
      qDebug() << "Cannot search for Modif name if no PolChemDef is available.";
      return false;
    }

  ModifCstSPtr modif_csp = mcsp_polChemDef->getModifCstSPtrByName(name);

  if(modif_csp == nullptr)
    {
      qDebug() << "Failed to find a Modif by name " << name
               << "in the polChemDef.";
      return false;
    }

  m_rightEndModif = *modif_csp.get();

  if(m_rightEndModif.getPolChemDefCstSPtr() != mcsp_polChemDef)
    qFatal()
      << "Programming error. The pointers to PolChemDef cannot differ.";

  ErrorList error_list;
  m_isValid = validate(&error_list);

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

  return true;
}

/*!
\brief Sets this polymer's right end modification to \a modif.

Returns true if \a modif could validate successfully and if that modification
could be set and its masses could be calculated successfully, otherwise returns
false.

After setting the member data, the instance is validated and the result is set
to m_isValid.
*/
bool
Polymer::setRightEndModif(const Modif &modif)
{
  ErrorList error_list;

  m_rightEndModif = modif;
  m_rightEndModif.setPolChemDefCstSPtr(mcsp_polChemDef);

  if(!m_rightEndModif.validate(&error_list))
    {
      qDebug() << "The Modif is not valid, with errors:"
               << Utils::joinErrorList(error_list, ", ");
      return false;
    }

  m_isValid = validate(&error_list);

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

  return true;
}

/*!
\brief Returns a const reference to this Polymer's right end modif.
*/
const Modif &
Polymer::getRightEndModifCstRef() const
{
  return m_rightEndModif;
}

/*!
\brief Returns a reference to this Polymer's right end modif.
*/
Modif &
Polymer::getRightEndModifRef()
{
  return m_rightEndModif;
}

/*!
\brief Returns true if this Polymer is modified at its right end.
*/
bool
Polymer::isRightEndModified() const
{
  return m_rightEndModif.isValid();
}

//////////////// THE CALCULATION OPTIONS /////////////////////

/*!
\brief Sets \a calc_options to the member datum.
*/
void
Polymer::setCalcOptions(const CalcOptions &calc_options)
{
  mp_calcOptions->initialize(calc_options);
}

/*!
\brief Sets \a calc_options to the member datum.
*/
void
Polymer::setCalcOptions(const CalcOptions *calc_options)
{
  mp_calcOptions->initialize(*calc_options);
}

/*!
\brief Returns a const reference to the member CalcOptions.
*/
const CalcOptions &
Polymer::getCalcOptionsCstRef() const
{
  return *mp_calcOptions;
}

/*!
\brief Returns a reference to the member CalcOptions.
*/
const CalcOptions &
Polymer::getCalcOptionsRef()
{
  return *mp_calcOptions;
}

/*!
\brief Returns a reference to the member CalcOptions.
*/
CalcOptions *
Polymer::getCalcOptions()
{
  // qDebug() << "Returning calculation options:" << mp_calcOptions->toString();
  return mp_calcOptions;
}

// IONIZATION
/////////////////////////////

/*!
\brief Sets the member Ionizer instance to \a ionizer_p.

Calls the Polymer::setIonizer(const Ionizer &ionizer) by dereferencing
the pointer.
*/
void
Polymer::setIonizer(const Ionizer *ionizer_p)
{
  // qDebug() << "Setting ionizer:" << ionizer_p->toString();
  setIonizer(*ionizer_p);
}

/*!
\brief Sets the member Ionizer instance to \a ionizer.

If presently ionized, this Polymer is first deionized. Then this Polymer
undergoes ionization according to the current ionization status of \a ionizer.
This is required so that we put ourselves in line with the contents of \a
ionizer.
*/
void
Polymer::setIonizer(const Ionizer &ionizer)
{
  // qDebug() << "Setting ionizer:" << ionizer.toString() << "The current masses
  // are:"
  // << m_mono << "-" << m_avg;

  // Check if ionizer is valid.

  ErrorList error_list;

  if(!ionizer.validate(&error_list))
    qFatal()
      << "Programming error. Trying to set a new Ionizer that does not "
         "validate successfully, with errors:"
      << Utils::joinErrorList(error_list);

  if(mp_ionizer->isIonized())
    {
      // qDebug() << "Before setting a new ionizer, undergo deionization.";

      if(deionize() == Enums::IonizationOutcome::FAILED)
        qFatal()
          << "Programming error. Failed to deionize an ionized analyte.";
    }

  // qDebug() << "Now setting the ionizer to the member mp_ionizer.";

  mp_ionizer->initialize(ionizer);

  // We now have to put this Polymer in conformity with the contents of the
  // new Ionizer. That is, we should ionize this Polymer according to the
  // current state of mp_ionizer. Only do that if the current state is ionized,
  // otherwise the ionizer validation below will fail miserably.

  if(mp_ionizer->currentStateCharge())
    {
      // qDebug()
      //   << "The newly set mp_ionizer has a charge, which means it is
      //   ionized."
      //      "put this Polymer in the right ionization state, matching the "
      //      "current state of the mp_ionizer.";

      Ionizer current_state_ionizer =
        mp_ionizer->makeIonizerWithCurrentStateData();

      // Make sure the created ionizer is valid
      if(!current_state_ionizer.isValid())
        qFatal() << "The created ionizer is invalid.";

      // qDebug() << "The created ionizer:" << current_state_ionizer.toString();

      // Now use that current_state_ionizer to ionize this Polymer without
      // changing its internal Ionizer instance.

      if(ionize(current_state_ionizer) == Enums::IonizationOutcome::FAILED)
        qFatal() << "Programming error. Failed to ionize the Polymer.";
    }

  // qDebug() << "At this point the member masses:" << m_mono << "-" << m_avg;

  // qDebug() << "Now returning.";
  // No use to validate because validation does not care of
  // optional Ionizer.
}

/*!
\brief Returns a const reference to the member Ionizer instance.
*/
const Ionizer &
Polymer::getIonizerCstRef() const
{
  return *mp_ionizer;
}

/*!
\brief Returns a reference to the member Ionizer instance.
*/
Ionizer &
Polymer::getIonizerRef()
{
  return *mp_ionizer;
}

/*!
\brief Returns a reference to the member Ionizer instance.
*/
Ionizer *
Polymer::getIonizer()
{
  return mp_ionizer;
}

/*!
\brief Returns the result of the ionization process.

The ionization is performed by the member mp_ionizer and the masses
that are updated are the member masses of this Polymer instance.
*/
Enums::IonizationOutcome
Polymer::ionize()
{
  // qDebug() << "The masses before ionization:" << m_mono << "-" << m_avg;
  Enums::IonizationOutcome ionization_outcome =
    mp_ionizer->ionize(m_mono, m_avg);
  // qDebug() << "The masses after ionization:" << m_mono << "-" << m_avg;
  return ionization_outcome;
}

/*!
\brief Returns the result of the ionization process using \a ionizer.

The \a ionizer instance is used to perform the ionization and the masses
that are updated are the member masses of this Polymer instance.

\sa Ionizer::ionize()
*/
Enums::IonizationOutcome
Polymer::ionize(const Ionizer &ionizer)
{
  // qDebug() << "The ionizer:" << ionizer.toString();

  return ionizer.ionize(m_mono, m_avg);
}

/*!
\brief Returns the result of the deionization process.

The new masses, if a change occurred, are updated in the member monoisotopic and
average masses.
*/
Enums::IonizationOutcome
Polymer::deionize()
{
  return mp_ionizer->deionize(m_mono, m_avg);
}

/*!
\brief Returns the result of the deionization process using \a ionizer.

The new masses, if a change occurred, are updated in the member monoisotopic and
average masses.
*/
Enums::IonizationOutcome
Polymer::deionize(const Ionizer &ionizer)
{
  return ionizer.deionize(m_mono, m_avg);
}

/*!
\brief Sets \a mono and \a avg to the masses of an unionized Polymer analyte.

If the member Ionizer instance reports that this Polymer analyte is ionized,
that Ionizer instance is first used to deionize this Polymer instance into
copied monoisotopic and average mass values. Then these values are returned as
the molecular masses of this Polymer instance.

\note Since no member data were touched, that this function is marked const.

Returns the process outcome.
*/
Enums::IonizationOutcome
Polymer::molecularMasses(double &mono, double &avg) const
{
  double temp_mono = m_mono;
  double temp_avg  = m_avg;

  // If not ionized, returns Enums::IonizationOutcome::UNCHANGED which is not an
  // error.
  Enums::IonizationOutcome ionization_outcome =
    mp_ionizer->deionize(temp_mono, temp_avg);

  if(ionization_outcome == Enums::IonizationOutcome::FAILED)
    {
      qDebug() << "Failed to deionize the analyte.";
      return ionization_outcome;
    }

  // The masses of the deionized analyte are its Mr molecular mass.
  mono = temp_mono;
  avg  = temp_avg;

  return ionization_outcome;
}

// CROSS-LINKS
/////////////////////////////


/*!
\brief Returns the number of CrossLink instances in the member container.
*/
std::size_t
Polymer::crossLinkCount() const
{
  return m_crossLinks.size();
}

/*!
\brief Returns a const reference to the container of CrossLink instances.
*/
const std::vector<CrossLinkSPtr> &
Polymer::getCrossLinksCstRef() const
{
  return m_crossLinks;
}

/*!
\brief Returns a reference to the container of CrossLink instances.
*/
std::vector<CrossLinkSPtr> &
Polymer::getCrossLinksRef()
{
  return m_crossLinks;
}

/*!
\brief Fills-in the \a indices container with indices of Monomer instances
involved in cross-links found to be entirely contained in the \a start_index --
\a stop_index sequence range.
f
This function iterates in all of this polymer's cross-links and checks, for
each cross-link if:

\list

\li It is fully contained in the indices range [\a start_index, \a
stop_index] (the indices of the monomer in between the two cross-linked monomers
are added to \a indices)

\li It is only partially contained in the range (the \a partials counter is then
incremented)

\li Not contained at all in the range (nothing is done).

\endlist

Returns true if at least one cross-link was found to be fully encompassed by
the range, false otherwise.

\sa crossLinksInRange()
*/
bool
Polymer::crossLinkedMonomersIndicesInRange(std::size_t start_index,
                                           std::size_t stop_index,
                                           std::vector<std::size_t> &indices,
                                           std::size_t &partials) const
{
  // qDebug() << "Now determining all the Monomer indices of Monomers involved
  // in "
  //             "cross-links in the Sequence index range"
  //          << start_index << "-" << stop_index;

  // We are asked to fill-in a list of the indices of all the
  // monomers involved in cross-links that link any monomer in the
  // region delimited by start_index and stop_index.

  bool one_fully_encompassed_was_found = false;

  // Iterate in the list of cross-links set to *this polymer. For
  // each iterated cross-link, check if it is fully encompassed by
  // the region delimited by [startIndex--endIndex]. If so, get the
  // indices of the monomers involved in that cross-link and append
  // these (no duplicates) to the indexList passed as param to the
  // function.

  // qDebug() << "Iterating in all the CrossLink instances of the Polymer.";

  for(const CrossLinkCstSPtr cross_link_csp : m_crossLinks)
    {
      if(cross_link_csp == nullptr)
        qFatal() << "Programming error. The CrossLink is not more alive.";

      // qDebug() << "Iterating in cross-link:" << cross_link_csp->toString();

      std::size_t in_cross_link;
      std::size_t out_cross_link;

      Enums::CrossLinkEncompassed result =
        cross_link_csp->isEncompassedByIndexRange(
          start_index, stop_index, in_cross_link, out_cross_link);

      if(result == Enums::CrossLinkEncompassed::FULLY)
        {
          // qDebug() << "Cross-link:" << cross_link_csp->getCrossLinkerName()
          //          << "is fully encompassed by range:" << start_index << "-"
          //          << stop_index;

          std::vector<std::size_t> temp_indices =
            cross_link_csp->continuumOfLocationsOfInclusiveSequenceMonomers(
              Enums::LocationType::INDEX);

          QString indices_as_text;
          for(std::size_t index : temp_indices)
            indices_as_text += QString("%1;").arg(index);

          // qDebug() << "Found" << temp_indices.size()
          //          << "indices of all the Monomers inclusively contained in "
          //             "the current CrossLink:"
          //          << indices_as_text;

          // Avoid duplicating indices in the target index
          // list. This will have the excellent side effect of
          // condensating into one single region a number of
          // contained cross-linked regions. For example, from the
          // Kunitz inhibitor, there are the following cross-links:

          // 90 -- 187
          // 230 -- 280
          // 239 -- 263
          // 255 -- 276
          // 286 -- 336
          // 295 -- 319
          // 311 -- 332

          // Thanks to our strategy below, the cross-links
          // 230 -- 280
          // 239 -- 263
          // 255 -- 276
          // become contained in one single region:
          // 230--276.

          // Same for the cross-links
          // 286 -- 336
          // 295 -- 319
          // 311 -- 332
          // Which become contained in 286--336

          for(const int index : temp_indices)
            {
              if(std::find(indices.begin(), indices.end(), index) ==
                 indices.end())
                indices.push_back(index);
            }

          one_fully_encompassed_was_found = true;
        }
      else if(result == Enums::CrossLinkEncompassed::PARTIALLY)
        {
          ++partials;
        }
    }
  // End of
  // for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)

  return one_fully_encompassed_was_found;
}

/*!
\brief Fills-in the \a cross_links CrossLink container with cross-links found
to be entirely contained in the \a start_index -- \a stop_index sequence range.

This function iterates in all of this polymer's cross-links and checks, for
each cross-link if:
\list

\li It is fully contained in the indices range [\a start_index, \a
stop_index] (it is then added to \a cross_links)

\li It is only partially
contained in the range (the \a partials counter is then incremented)

\li Not
contained at all in the range (nothing is done).

\endlist

Returns true if at least one cross-link was found to be fully encompassed by
the range, false otherwise.

\sa crossLinkedMonomersIndicesInRange()
*/
bool
Polymer::crossLinksInRange(std::size_t start_index,
                           std::size_t stop_index,
                           std::vector<CrossLinkSPtr> &cross_links,
                           std::size_t &partials) const
{
  // We are asked to return a list of all the cross-links that are
  // fully encompassed by the [start_index--stop_index] region.

  bool one_fully_encompassed_was_found = false;

  // Iterate in the list of cross-links set to *this polymer. For
  // each iterated cross-link, check if it is fully encompassed by
  // the [start_index--stop_index] range. If so simply add it to the
  // list of cross-links.

  for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
    {
      if(cross_link_sp == nullptr)
        qFatal() << "Programming error. The CrossLink is not more alive.";

      // qDebug() << "Cross-link:" << cross_link_csp;
      std::size_t in_cross_link;
      std::size_t out_cross_link;

      Enums::CrossLinkEncompassed result =
        cross_link_sp->isEncompassedByIndexRange(
          start_index, stop_index, in_cross_link, out_cross_link);

      if(result == Enums::CrossLinkEncompassed::FULLY)
        {
          // qDebug() << __FILE__ << __LINE__
          //  << "Cross-link:" << cross_link_csp
          //  << "is encompassed by region:"
          //  << startIndex << "-" << endIndex;

          cross_links.push_back(cross_link_sp);

          one_fully_encompassed_was_found = true;
        }
      else if(result == Enums::CrossLinkEncompassed::PARTIALLY)
        {
          ++partials;
        }
    }

  return one_fully_encompassed_was_found;
}

/*!
\brief Searches for all the CrossLink instances that involve \a monomer_crp and
returns a container with the index of each one of them in the member container
of CrossLink instances.
*/
std::vector<std::size_t>
Polymer::crossLinkIndicesInvolvingMonomer(MonomerCstRPtr monomer_crp) const
{
  std::vector<std::size_t> cross_link_indices;

  // We will need iter, so use this loop.
  for(std::size_t iter = 0; iter < m_crossLinks.size(); ++iter)
    {
      CrossLinkCstSPtr cross_link_csp = m_crossLinks.at(iter);

      if(cross_link_csp == nullptr)
        qFatal() << "Programming error. The CrossLink is not more alive.";

      for(const MonomerCstSPtr &monomer_csp :
          cross_link_csp->getMonomersCstRef())
        {
          if(monomer_csp.get() == monomer_crp)
            {
              cross_link_indices.push_back(iter);
            }
        }
    }

  return cross_link_indices;
}

/*!
\brief Searches for all the CrossLink instances that involve \a monomer_sp and
returns a container with all of them.
*/
std::vector<std::size_t>
Polymer::crossLinkIndicesInvolvingMonomer(MonomerSPtr monomer_sp) const
{
  return crossLinkIndicesInvolvingMonomer(monomer_sp.get());
}

/*!
\brief Performs the actual cross-linking as described in \a cross_link_sp.

The chemical representation of the cross-link must have been previously defined
in \a cross_link_sp.

Upon performing the actual cross-link, a Uuid string is generated to identify
that cross-link unambiguously. That string is returned. If the cross-link fails,
the empty string is returned.

Returns true upon success or false if the CrossLink did not not validate
successfully.

\note Emits the \c{crossLinkChangedSignal(this)} signal to let interested
parties know that the Polymer has seen its cross-linking state changed.
*/
QString
Polymer::crossLink(CrossLinkSPtr cross_link_sp)
{
  if(cross_link_sp == nullptr || cross_link_sp.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  // This function must be called once all the members taking part
  // into the crossLink have been set.

  ErrorList error_list;

  if(!cross_link_sp->validate(&error_list))
    {
      qDebug() << "Requesting uncross-linking with invalid cross-link.";
      return QString();
    }

  // OK, from the perspective of the chemical modification of the
  // monomers involved in the crosslink, everything is fine.

  // Now is the moment that we actually perform the crossLink : this
  // is done simply by adding *this crossLink to the list of
  // crossLinks that belongs to the polymer.
  QString uuid = storeCrossLink(cross_link_sp);

  if(uuid.isEmpty())
    qFatal() << "Programming error. Failed to cross-link monomers.";

  // If the crossLink dialog is open, inform it that it can refresh
  // the data.
  emit(crossLinkChangedSignal(this));

  return uuid;
}

/*!
\brief Undoes the cross-link \a cross_link_sp.

Returns true upon success or false if the CrossLink does not validate
successfully.

\note Emits the \c{crossLinkChangedSignal(this)} signal to let interested
parties know that the Polymer has seen its cross-linking state changed.
*/
bool
Polymer::uncrossLink(CrossLinkSPtr cross_link_sp)
{
  if(cross_link_sp == nullptr || cross_link_sp.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  ErrorList error_list;

  if(!cross_link_sp->validate(&error_list))
    qFatal() << "Requesting uncross-linking with invalid cross-link.";

  // qDebug() << "The CrossLink validated succesfully. Now removing it.";

  if(!removeCrossLink(cross_link_sp))
    qFatal() << "Programming error. Failed to remove CrossLink.";

  // If the crossLink dialog is open, inform it that it can refresh
  // the data.
  emit(crossLinkChangedSignal(this));

  return true;
}

// MONOMER REMOVAL
/////////////////////////////
/*!
\brief Prepares for the removal of \a monomer_csp from this polymer's sequence.

The challenge is that monomers might be involved in cross-links. In that
case, removing a Monomer instance that was involved in a cross-link needs
preparation: it needs first to be uncross-linked.

Returns the count of uncross-links that were performed.

\sa uncrossLink()
*/
std::size_t
Polymer::prepareMonomerRemoval(MonomerSPtr monomer_csp)
{
  if(monomer_csp == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  if(!m_crossLinks.size())
    {
      // qDebug() << "There are no cross-links, just return 0.";
      return 0;
    }

  // We are asked to destroy **all** the crossLinks that involve the
  // 'monomer'.
  bool ok = false;

  // std::size_t monomer_index = m_sequence.monomerIndex(monomer_csp, ok);
  m_sequence.monomerIndex(monomer_csp, ok);
  if(!ok)
    qFatal() << "Programming error.";

  // qDebug() << "Preparing Monomer removal of"
  //          << monomer_csp->getCode()
  //          << "at polymer Sequence index:" << monomer_index;

  // Now process all the CrossLinks in the member container and for each one
  // that involves the Monomer being removed, just uncrosslink.

  // We cannot use the simple for(CrossLinkSPtr cross_link_sp : m_crossLinks)
  // range-based iteration because when we remove one CrossLink, we
  // invalidate all the iterators used internally to make the loop work.

  // One strategy is to first store in a vector all the CrossLinks that
  // are found to involve the Monomer we are removing.

  std::vector<CrossLinkSPtr> cross_links_to_un_cross_link;

  // Iterate in the list of crossLinks, and for each cross-link check
  // if it involves 'monomer_csp'. If so, remove the cross-link from
  // the container.

  for(CrossLinkSPtr cross_link_sp : m_crossLinks)
    {
      // qDebug()
      //   << "Iterating in the cross-link container in Polymer:"
      //   << cross_link_sp->getCrossLinkerName()
      //   << ". Now check if that "
      //      "cross-link contains the Monomer we are preparing the removal
      //      of.";

      bool ok = false;
      cross_link_sp->monomerIndex(monomer_csp, ok);

      if(ok)
        {
          // qDebug() << "Yes, the iterated cross-link does contain the Monomer
          // "
          //             "we are handling. Adding it to the list of cross-links
          //             " "to un-cross-link.";

          cross_links_to_un_cross_link.push_back(cross_link_sp);
        }
    }

  std::size_t count_of_cross_links_to_remove =
    cross_links_to_un_cross_link.size();

  // qDebug() << "There are" << count_of_cross_links_to_remove
  //          << "cross-links to uncrosslink because they involved the Monomer
  //          we "
  //             "are preparing the removal of.";

  // We can now safely iterate in the new container of cross-links to
  // be un-cross-linked.

  std::size_t count_of_actually_un_crossed_links = 0;

  for(CrossLinkSPtr cross_link_sp : cross_links_to_un_cross_link)
    {
      // Now un-cross-linking that cross-link
      uncrossLink(cross_link_sp);

      // qDebug() << "Done un-cross-linking cross-link.";

      ++count_of_actually_un_crossed_links;
    }

  // qDebug() << "Finished iterating the the vector of cross-links and"
  //          << count_of_actually_un_crossed_links
  //          << "cross-links were un-cross-linked.";

  // Sanity check
  if(count_of_cross_links_to_remove != count_of_actually_un_crossed_links)
    qFatal() << "Programming error.";

  return count_of_actually_un_crossed_links;
}

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

An index that is out of bounds is fatal.

The monomer is first uncross-linked (if it was).

Returns true if the removal was succesful, false otherwise.
*/
bool
Polymer::removeMonomerAt(std::size_t index)
{
  // qDebug() << "Asking to remove Monomer at index" << index;

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

  MonomerSPtr monomer_csp = m_sequence.getMonomerCstSPtrAt(index);

  // qDebug() << "The Monomer being removed at index:" << index
  //          << "is:" << monomer_csp->getName()
  //          << "with modification status:" << monomer_csp->isModified();

  prepareMonomerRemoval(monomer_csp);

  // qDebug() << "Done removing the Monomer.";

  // qDebug() << "Now asking the Sequence to remove the Monomer.";

  return m_sequence.removeMonomerAt(index);
}

// MASS CALCULATION FUNCTIONS

/*!
\brief Calculates the masses of \a polymer_p.

The calculated masses are set to \a mono and \a avg.

If \a reset is true, \a mono and \a avg are reset to 0.0, otherwise they are
left unchanged and incremented with the masses obtained from the calculation.

The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

The ionization of the polymer is not accounted for in this function.

Returns true if all the calculation steps were performed succesfully, false
otherwise.

\sa calculateMasses()
*/
bool
Polymer::calculateMasses(const Polymer *polymer_p,
                         const CalcOptions &calc_options,
                         double &mono,
                         double &avg,
                         bool reset)
{
  if(polymer_p == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  if(reset)
    {
      // Reset the masses to 0.
      mono = 0;
      avg  = 0;
    }

  // The calc_options parameter holds a SequenceRanges instance
  // listing all the sequence_range of the different(if any) region
  // selections of the polymer sequence. This SequenceRanges is
  // never empty, as it should at least contain the pseudo-selection
  // of the sequence, that is [start of sequence, cursor index] or
  // the [-1, -1] values for whole sequence mass
  // calculation. Iterate in this SequenceRanges and for each item
  // call this function.

  const IndexRangeCollection &index_range_collection =
    calc_options.getIndexRangeCollectionCstRef();

  if(!index_range_collection.size())
    {
      qDebug() << "Calculating masses for a Polymer with no SequenceRange.";
      return false;
    }

  // For each SequenceRange item in the
  // calc_options.getIndexRangeCollectionCstRef() list of such items, perform
  // the mass calculation.

  foreach(const IndexRange *item, index_range_collection.getRangesCstRef())
    {
      IndexRange local_index_range;
      local_index_range.initialize(*item);

      // If the end value is greater than the polymer size, set it
      // to the polymer size.
      if(local_index_range.m_stop >= (qsizetype)polymer_p->size())
        local_index_range.m_stop = polymer_p->size() - 1;

      // qDebug() << "Iterating in SequenceRange:"
      //  << QString("[%1-%2]")
      //  .arg(local_index_range.start)
      //  .arg(local_index_range.stop);

      // First account for the residual chain masses.

      // qDebug() << "calculateMasses: accounting for residual chain indices "
      //          << "[" << item->m_start << "--" << item->m_stop << "]";

      for(qsizetype iter = local_index_range.m_start;
          iter <= local_index_range.m_stop;
          ++iter)
        {
          // qDebug() << "Iterating in range index:" << iter;

          MonomerSPtr monomer_sp = std::const_pointer_cast<Monomer>(
            polymer_p->getSequenceCstRef().getMonomerCstSPtrAt(iter));

          if(calc_options.isDeepCalculation())
            {
              // qDebug() << "Deep calculation or Enums::ChemicalEntity::MODIF
              // requested.";

              monomer_sp->calculateMasses(nullptr,
                                          calc_options.getMonomerEntities());
            }

          // qDebug() << "Iterating in monomer:" << monomer_sp->getCode()
          //  << "with masses:" << monomer_sp->getMass(Enums::MassType::MONO)
          //  << "-" << monomer_sp->getMass(Enums::MassType::MONO);

          monomer_sp->accountMasses(mono, avg);
        }
    }

  // qDebug() << "After having accounted the masses of all the monomers:" <<
  // mono
  //  << "-" << avg;

  // Even if we are not in the residual chain loop, we have to account
  // for the crossLinks, if so requires it. The crossLinks are a
  // monomer chemical entity, but because it is unpractical to
  // calculate their ponderable contribution in the loop above, we
  // deal with them here. This is difficult stuff. In fact, the
  // crossLinks, which in reality belong to at least two monomers
  //(monomers can be engaged in more than a crossLink), are not
  // stored as properties in the monomers(contrary to monomer
  // modifications, for example). The crossLinks are stored in a list
  // of such instances in the polymer(m_crossLinkList of CrossLink
  // pointers). Now, the point is: if one of the monomers of a
  // crossLink is selected but not the other partners, then what
  // should be do about that crossLink accounting ?

  if(static_cast<int>(calc_options.getMonomerEntities()) &
     static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
    {
      // qDebug() << "Cross-links are to be accounted for.";

      // We have to take into account the crossLinks. Hmmm... hard
      // task. The calculation is to be performed for the sequence
      // stretch from localStart to localEnd. We can iterate in the
      // crossLink list and for each crossLink check if it involves
      // monomers that *all* are contained in the sequence stretch
      //(or sequence stretches, that is a number of SequenceRange
      // items in the calc_options.coordinateList()) we're
      // calculating the mass of. If at least one monomer of any
      // crossLink is not contained in the [localStart--localEnd]
      // sequence stretch, than increment a count variable and do
      // not account the mass.

      int partial_cross_links_count = 0;

      for(CrossLinkSPtr cross_link_sp : polymer_p->m_crossLinks)
        {

          std::size_t in_count;
          std::size_t out_count;

          Enums::CrossLinkEncompassed result =
            cross_link_sp->isEncompassedByIndexRangeCollection(
              calc_options.getIndexRangeCollectionCstRef(),
              in_count,
              out_count);

          if(result == Enums::CrossLinkEncompassed::FULLY)
            {
              // qDebug() << "CrossLink:"
              //  << cross_link_sp->getCrossLinkerCstSPtr()->getName()
              //  << "is fully encompassed: accounting its masses.";

              // The cross_link_sp is fully encompassed by our monomer
              // stretch, so we should take it into account.

              cross_link_sp->accountMasses(mono, avg);
            }
          else if(result == Enums::CrossLinkEncompassed::PARTIALLY)
            {
              // qDebug() << "CrossLink:"
              //  << cross_link_sp->getCrossLinkerCstSPtr()->getName()
              //  << "is only partially encompassed: not accounting its"
              //  "masses.";

              ++partial_cross_links_count;
            }
          else
            {
              // qDebug()
              // << "CrossLink:"
              // << cross_link_sp->getCrossLinkerCstSPtr()->getName()
              // << "is not encompassed at all: not accounting its masses.";
            }
        }

      emit(polymer_p->crossLinksPartiallyEncompassedSignal(
        partial_cross_links_count));
    }

  // qDebug() << "After having accounted the masses of all the cross-links:"
  //  << mono << "-" << avg;

  // We now have to account for the left/right cappings. However,
  // when there are multiple region selections(that is multiple
  // Coordinate elements in the calc_options.coordinateList()) it is
  // necessary to know if the user wants each of these SequenceRange
  // to be considered real oligomers(each one with its left/right
  // caps) or as residual chains. Thus there are two cases:

  // 1. Each SequenceRange item should be considered an oligomer
  //(Enums::SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
  // the left and right caps should be accounted for. This is
  // typically the case when the user selects multiple regions to
  // compute the mass of cross-linked oligomers.

  // 2. Each SequenceRange item should be considered a residual chain
  //(Enums::SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
  // one item should see its left and right caps accounted for. This
  // is typically the case when the user selects multiple regions
  // like it would select repeated sequence elements in a polymer
  // sequence: all the regions selected are treated as a single
  // oligomer.

  // Holds the number of times the chemical entities are to be
  // accounted for.
  int times = 0;

  if(calc_options.getSelectionType() == Enums::SelectionType::RESIDUAL_CHAINS)
    {
      // qDebug() << __FILE__ << __LINE__
      // << "SELECTION_TYPE_RESIDUAL_CHAINS";

      times = 1;
    }
  else
    {
      // qDebug() << __FILE__ << __LINE__
      // << "SELECTION_TYPE_OLIGOMERS";

      times = calc_options.getIndexRangeCollectionCstRef().size();
    }

  // Account for the left and right cap masses, if so required.
  if(static_cast<int>(calc_options.getCapType()) &
     static_cast<int>(Enums::CapType::LEFT))
    {
      bool result = Polymer::accountCappingMasses(
        polymer_p, Enums::CapType::LEFT, mono, avg, times);
      Q_ASSERT(result);
    }

  if(static_cast<int>(calc_options.getCapType()) &
     static_cast<int>(Enums::CapType::RIGHT))
    {
      bool result = Polymer::accountCappingMasses(
        polymer_p, Enums::CapType::RIGHT, mono, avg, times);

      Q_ASSERT(result);
    }

  // Account for the left and right modification masses, if so
  // required and the region(s) require(s) it: we have to make it
  // clear if the selection encompasses indices 0(left end) and/or
  // polymerSize-1(right end).

  // Note that if we are forced to take into account either or both
  // the left/right end modif, then even if the selected region does
  // not encompass the end(s), their modif(s) must be taken into
  // account.

  if(static_cast<int>(calc_options.getPolymerEntities()) &
     static_cast<int>(Enums::ChemicalEntity::LEFT_END_MODIF))
    {
      if(calc_options.getIndexRangeCollectionCstRef().encompassIndex(0) ||
         static_cast<int>(calc_options.getPolymerEntities()) &
           static_cast<int>(Enums::ChemicalEntity::FORCE_LEFT_END_MODIF))
        {
          // qDebug() << "Before accounting for LEFT_END_MODIF, masses:" << mono
          //          << "-" << avg;
          Polymer::accountEndModifMasses(
            polymer_p, Enums::ChemicalEntity::LEFT_END_MODIF, mono, avg);
          // qDebug() << "After accounting for LEFT_END_MODIF, masses:" << mono
          //          << "-" << avg;
        }
    }

  if(static_cast<int>(calc_options.getPolymerEntities()) &
     static_cast<int>(Enums::ChemicalEntity::RIGHT_END_MODIF))
    {
      if(calc_options.getIndexRangeCollectionCstRef().encompassIndex(
           polymer_p->size() - 1) ||
         static_cast<int>(calc_options.getPolymerEntities()) &
           static_cast<int>(Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF))
        {
          // qDebug() << "Before accounting for RIGHT_END_MODIF, masses:" <<
          // mono
          //          << "-" << avg;
          Polymer::accountEndModifMasses(
            polymer_p, Enums::ChemicalEntity::RIGHT_END_MODIF, mono, avg);
          // qDebug() << "After accounting for RIGHT_END_MODIF, masses:" << mono
          //          << "-" << avg;
        }
    }

  // qDebug() << "CalculateMasses Mono:" << mono << "Avg:" << avg;

  return true;
}

/*!
\overload calculateMasses()

\brief Calculates the masses of \a polymer_p.

The calculated masses are set to \a mono and \a avg. Prior to setting the
masses to \a mono and \a avg, these masses are reset to 0.0.

The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

After the masses are computed, the ionization described in \a ionizer is
accounted for.

Returns true if all steps could be performed successfully, false otherwise.

\sa calculateMasses(), CalcOptions, Ionizer
*/
bool
Polymer::calculateMasses(const Polymer *polymer_p,
                         const CalcOptions &calc_options,
                         const Ionizer &ionizer,
                         double &mono,
                         double &avg)
{
  if(!calculateMasses(polymer_p, calc_options, mono, avg, true))
    return false;

  Ionizer local_ionizer_copy(ionizer);

  // If the ionizer is valid use it.
  if(local_ionizer_copy.isValid())
    {
      if(local_ionizer_copy.ionize(mono, avg) ==
         Enums::IonizationOutcome::FAILED)
        {
          qDebug() << "Failed to ionize the Oligomer.";
          return false;
        }
    }

  return true;
}

/*!
\overload calculateMasses()

\brief Calculates the masses of this polymer.

The calculated masses are set to this polymer's m_mono and m_avg members. If
\a reset is true, these masses are first reset to 0.0, otherwise this
polymer's masses are left unchanged and incremented with those obtained from
the calculation.

The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

No ionization is accounted for in this function.

Returns true.

\sa calculateMasses(), CalcOptions
*/
bool
Polymer::calculateMasses(const CalcOptions &calc_options, bool reset)
{
  return calculateMasses(this, calc_options, m_mono, m_avg, reset);
}

/*!
\overload calculateMasses()

\brief Calculates the masses of this polymer.

The calculated masses are set to this polymer's m_mono and m_avg members.
These masses are first reset to 0.0.

The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

After the masses are computed, the ionization described in \a ionizer is
accounted for.

Returns true if all steps could be performed successfully, false otherwise.

\sa calculateMasses(), CalcOptions, Ionizer
*/
bool
Polymer::calculateMasses(const CalcOptions &calc_options,
                         const Ionizer &ionizer)
{
  return calculateMasses(this, calc_options, ionizer, m_mono, m_avg);
}

/*!
\overload calculateMasses()

\brief Calculates the masses of this polymer.

The calculated masses are set to this polymer's m_mono and m_avg members.
These masses are first reset to 0.0.

The calculation of the masses (monoisotopic and average) is configured in \a
calc_options_p.

After the masses are computed, the ionization described in \a ionizer_p is
accounted for.

Returns true if all steps could be performed successfully, false otherwise.

\sa calculateMasses(), CalcOptions, Ionizer
 */
bool
Polymer::calculateMasses(const CalcOptions *calc_options_p,
                         const Ionizer *ionizer_p)
{
  bool result =
    calculateMasses(this, *calc_options_p, *ionizer_p, m_mono, m_avg);
  // qDebug() << "After computation of the masses:" << m_mono << "-"
  //          << m_avg;
  return result;
}

/*!
\overload calculateMasses()

\brief Calculates the masses of this polymer.

The calculated masses are set to this polymer's m_mono and m_avg members.
These masses are first reset to 0.0.

The calculation of the masses (monoisotopic and average) is configured in the
member CalcOptions object.

After the masses are computed, the ionization described in the member Ionizer
object is accounted for.

Returns true if all steps could be performed successfully, false otherwise.

\sa calculateMasses(), CalcOptions, Ionizer
*/
bool
Polymer::calculateMasses()
{
  return calculateMasses(*mp_calcOptions, *mp_ionizer);
}

/*!
\brief Accounts the masses of this polymer.

Accounting masses means calculating masses and adding the results to
objects. Here the masses of this polymer are calculated and added to those of
this polymer.

The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

Returns true upon success, false otherwise.

\sa CalcOptions
*/
bool
Polymer::accountMasses(const CalcOptions &calc_options)
{
  // We do not want to reset masses prior to calculating the masses
  // because we are accounting them in the polymer itself.
  return calculateMasses(calc_options, false);
}

/*!
\overload accountMasses()

\brief Accounts the masses of \a polymer_p.

\a polymer_p cannot be nullptr.

Accounting masses means calculating masses and adding the results to
objects. Here the masses of this polymer are calculated and set to \a mono and
\a avg.

The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

\sa CalcOptions
*/
bool
Polymer::accountMasses(const Polymer *polymer_p,
                       const CalcOptions &calc_options,
                       double &mono,
                       double &avg)
{
  // qDebug().noquote() << "Going to account masses for calculation options:"
  //                    << calc_options.toString();

  // We do not want to reset masses prior to calculating the masses
  // because we are not accounting them in the polymer itself.
  return calculateMasses(polymer_p, calc_options, mono, avg, /*reset*/ false);
}

/*!
\brief Accounts for the mass of the end caps.

The polymer sequence is actually a chain of monomers (that is, residues). In
order to compute the mass of the polymer in its finished state, it is
necessary to add the masses of its end caps (typically, a proton and a
hydroxyl group in protein chemistry, respectively capping the N-terminus and
the C-terminus).

The mass of the the left end cap is added to the monoisotopic and average
masses of this polymer if (\a cap_type & Enums::CapType::LEFT). The mass of the
the right end cap is added to the monoisotopic and average masses of this
polymer if (\a cap_type & Enums::CapType::RIGHT).

The masses of the caps are multiplied by \a times before accounting them to
this polymer's masses.

Returns true.
*/
bool
Polymer::accountCappingMasses(Enums::CapType cap_type, int times)
{
  return accountCappingMasses(this, cap_type, m_mono, m_avg, times);
}

/*!
\brief Accounts for the \a{polymer_p}'s end caps masses to \a mono and
\a avg.

\a polymer_p cannot be nullptr.

The polymer sequence is actually a chain of monomers (that is, residues). In
order to compute the mass of the polymer in its finished state, it is
necessary to add the masses of its end caps (typically, a proton and a
hydroxyl group in protein chemistry, respectively capping the N-terminus and
the C-terminus).

The mass of the the left end cap is added to the \a mono and \a avg masses if
(\a cap_type & Enums::CapType::LEFT). The mass of the the right end cap is added
to the \a mono and \a avg masses if (\a cap_type & Enums::CapType::RIGHT).

The masses of the caps are multiplied by \a times before accounting them to
\a mono and \a avg.
Returns true.
*/
bool
Polymer::accountCappingMasses(const Polymer *polymer_p,
                              Enums::CapType cap_type,
                              double &mono,
                              double &avg,
                              int times)
{
  if(polymer_p == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  IsotopicDataCstSPtr isotopic_data_csp =
    polymer_p->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr();

  Formula formula;

  if(static_cast<int>(cap_type) & static_cast<int>(Enums::CapType::LEFT))
    {
      formula = polymer_p->getPolChemDefCstSPtr()->getLeftCap();
    }
  else if(static_cast<int>(cap_type) & static_cast<int>(Enums::CapType::RIGHT))
    {
      formula = polymer_p->getPolChemDefCstSPtr()->getRightCap();
    }
  else if(static_cast<int>(cap_type) & static_cast<int>(Enums::CapType::NONE))
    return true;
  else
    qFatal() << "Programming error";

  bool ok = false;
  formula.accountMasses(ok, isotopic_data_csp, mono, avg, times);
  if(!ok)
    return false;

  return true;
}

/*!
\brief Accounts for this Polymer instance's end modifications masses as
defined by \a polymer_chem_entities.

\sa accountEndModifMasses()
*/
void
Polymer::accountEndModifMasses(Enums::ChemicalEntity polymer_chem_entities)
{
  return accountEndModifMasses(this, polymer_chem_entities, m_mono, m_avg);
}

/*!
\brief Accounts for the \a{polymer_p}'s masses as defined by
\a polymer_chem_entities.

The end modifications masses are accounted for into the \a mono and \a avg
masses without prior resetting of them.

If \c{(polymer_chem_entities & Enums::ChemicalEntity::LEFT_END_MODIF)}, the left
end modification masses are accounted for; if \c{(polymer_chem_entities &
Enums::ChemicalEntity::RIGHT_END_MODIF)} the right end modification masses are
accounted for.
*/
void
Polymer::accountEndModifMasses(const Polymer *polymer_p,
                               Enums::ChemicalEntity polymer_chem_entities,
                               double &mono,
                               double &avg)
{
  if(polymer_p == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  // Make a safe copy of the polymer's left/right end modif and use it
  // for doing the calculation INTO the 'mono' and 'avg' variables.

  if(static_cast<int>(polymer_chem_entities) &
     static_cast<int>(Enums::ChemicalEntity::LEFT_END_MODIF))
    {
      Modif modif(polymer_p->getLeftEndModifCstRef());
      modif.accountMasses(mono, avg);
    }

  if(static_cast<int>(polymer_chem_entities) &
     static_cast<int>(Enums::ChemicalEntity::RIGHT_END_MODIF))
    {
      Modif modif(polymer_p->getRightEndModifCstRef());
      modif.accountMasses(mono, avg);
    }
}

/*!
\brief Returns the Polymer's mass of the type defined by \a mass_type.
*/
double
Polymer::getMass(Enums::MassType mass_type) const
{
  if(mass_type != Enums::MassType::MONO && mass_type != Enums::MassType::AVG)
    qFatal() << "The mass_type needs to be either MONO or AVG.";

  if(mass_type == Enums::MassType::MONO)
    return m_mono;

  return m_avg;
}

/*!
\brief Returns a reference to the Polymer's mass of the type defined by \a
mass_type.
*/
double &
Polymer::getMassRef(Enums::MassType mass_type)
{
  if(mass_type != Enums::MassType::MONO && mass_type != Enums::MassType::AVG)
    qFatal() << "The mass_type needs to be either MONO or AVG.";

  if(mass_type == Enums::MassType::MONO)
    return m_mono;

  return m_avg;
}

// ELEMENTAL COMPOSITION
////////////////////////////


/*!
\brief Determines the elemental composition of this polymer.

The elemental composition is computed by looking into the core chemical
entities of the polymer, like the monomers, the modifications, but also by
accounting for the Ionizer \a ionizer, and the CalcOptions \a
calc_options. The sequence elements that are accounted for are described in \l
IndexRange instances stored in \a index_range_collection.

\a ionizer is checked for validity and if valid, it is accounted for.

Returns the elemental composition.

\sa elementalComposition(), IndexRange, Ionizer, CalcOptions
*/
QString
Polymer::elementalComposition(
  const IndexRangeCollection &index_range_collection,
  const CalcOptions &calc_options,
  const Ionizer &ionizer) const
{
  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // Iterate in all the oligomers that are encompassed in the
  // IndexRangeCollection.

  // qDebug() << "The index range collection has" <<
  // index_range_collection.size()
  //          << "IndexRange objects";

  Formula formula;

  foreach(const IndexRange *item, index_range_collection.getRangesCstRef())
    {
      // qDebug() << "Iterating into IndexRange (indices):"
      //          << item->indicesAsText();

      for(qsizetype iter = item->m_start; iter <= item->m_stop; ++iter)
        {
          const MonomerSPtr monomer_csp = m_sequence.getMonomerCstSPtrAt(iter);

          formula.setActionFormula(monomer_csp->getFormula());

          if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
            qFatal()
              << "Programming error. Accounting symbols should not fail "
                 "at this point.";

          if(static_cast<int>(calc_options.getMonomerEntities()) &
             static_cast<int>(Enums::ChemicalEntity::MODIF))
            {
              for(const ModifSPtr &modif_sp : monomer_csp->getModifsCstRef())
                {
                  // qDebug() << "Before accounting for Monomer Modif"
                  //          << modif_sp->getName() << "elemental composition:"
                  //          << formula.elementalComposition();

                  formula.setActionFormula(
                    modif_sp->formula(/*with_title*/ false));

                  // Incrementally account for the new formula in the same
                  // atomcount list in the formula.
                  if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
                    qFatal()
                      << "Programming error. Accounting symbols should "
                         "not fail at this point.";

                  // qDebug() << "After accounting for Monomer Modif"
                  //          << modif_sp->getName() << "elemental composition:"
                  //          << formula.elementalComposition();
                }

              // qDebug() << "After having accounted for monomer:"
              //          << monomer_csp->getCode() << "the formula has become:"
              //          << formula.elementalComposition();
            }
        }
    }

  // qDebug() << "Formula after accounting for all the residual chains:"
  //          << formula.elementalComposition();

  // We now have to account for the left/right cappings. However,
  // when there are multiple region selections(that is multiple
  // Coordinate elements in the calc_options.coordinateList()) it is
  // necessary to know if the user wants each of these SequenceRange
  // to be considered real oligomers(each one with its left/right
  // caps) or as residual chains. Thus there are two cases:

  // 1. Each SequenceRange item should be considered an oligomer
  //(Enums::SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
  // the left and right caps should be accounted for. This is
  // typically the case when the user selects multiple regions to
  // compute the mass of cross-linked oligomers.

  // 2. Each SequenceRange item should be considered a residual chain
  //(Enums::SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
  // one item should see its left and right caps accounted for. This
  // is typically the case when the user selects multiple regions
  // like it would select repeated sequence elements in a polymer
  // sequence: all the regions selected are treated as a single
  // oligomer.

  // Holds the number of times the chemical entities are to be
  // accounted for.
  int times = 0;

  if(calc_options.getSelectionType() == Enums::SelectionType::RESIDUAL_CHAINS)
    {
      times = 1;
      // qDebug() << "Enums::SelectionType::RESIDUAL_CHAINS ; times:" << times;
    }
  else if(calc_options.getSelectionType() == Enums::SelectionType::OLIGOMERS)
    {
      times = index_range_collection.size();

      // qDebug() << "Enums::SelectionType::OLIGOMERS ; times:" << times;
    }
  else
    qFatal()
      << "Programming error. Cannot be that this point is reached.";

  // Account for the left and right cap masses, if so required.

  if(static_cast<int>(calc_options.getCapType()) &
     static_cast<int>(Enums::CapType::LEFT))
    {
      formula.setActionFormula(mcsp_polChemDef->getLeftCap());

      // Incrementally account for the new formula in the same
      // atomcount list in the formula.
      if(!formula.accountSymbolCounts(isotopic_data_csp, times))
        qFatal() << "Programming error.";

      // qDebug() << "Formula after accounting left cap:"
      //          << formula.elementalComposition();
    }

  if(static_cast<int>(calc_options.getCapType()) &
     static_cast<int>(Enums::CapType::RIGHT))
    {
      formula.setActionFormula(mcsp_polChemDef->getRightCap());

      // Incrementally account for the new formula in the same
      // atomcount list in the formula.
      if(!formula.accountSymbolCounts(isotopic_data_csp, times))
        qFatal() << "Programming error.";

      // qDebug() << "Formula after accounting right cap:"
      //          << formula.elementalComposition();
    }

  // Account for the left and right modification masses, if so
  // required and the region(s) require(s) it: we have to make it
  // clear if the selection encompasses indices 0(left end) and/or
  // polymerSize-1(right end).

  if(static_cast<int>(calc_options.getPolymerEntities()) &
     static_cast<int>(Enums::ChemicalEntity::LEFT_END_MODIF))
    {
      if(index_range_collection.encompassIndex(0) ||
         static_cast<int>(calc_options.getPolymerEntities()) &
           static_cast<int>(Enums::ChemicalEntity::FORCE_LEFT_END_MODIF))
        {
          formula.setActionFormula(
            m_leftEndModif.formula(/*with_title*/ false));

          // qDebug() << "Accounting for left end modif:"
          //          << m_leftEndModif.getName()
          //          << "Formula before accounting it:"
          //          << formula.elementalComposition();

          // Incrementally account for the new formula in the same
          // atomcount list in the formula.
          if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
            qFatal() << "Programming error.";

          // qDebug() << "Formula after accounting left end modif:"
          //          << formula.elementalComposition();
        }
    }

  if(static_cast<int>(calc_options.getPolymerEntities()) &
     static_cast<int>(Enums::ChemicalEntity::RIGHT_END_MODIF))
    {
      if(index_range_collection.encompassIndex(size() - 1) ||
         static_cast<int>(calc_options.getPolymerEntities()) &
           static_cast<int>(Enums::ChemicalEntity::FORCE_RIGHT_END_MODIF))
        {
          formula.setActionFormula(
            m_rightEndModif.formula(/*with_title*/ false));

          // qDebug() << "Accounting for right end modif:"
          //          << m_rightEndModif.getName();

          // Incrementally account for the new formula in the same
          // atomcount list in the formula.
          if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
            qFatal() << "Programming error.";

          // qDebug() << "Formula after accounting left end modif:"
          //          << formula.elementalComposition();
        }
    }

  // At this point we should not forget if the user asks to take into
  // account the cross-links... However, BE CAREFUL that cross-links
  // can only be taken into account if all the partners of a given
  // cross-link are actually encompassed into the selection.

  if(static_cast<int>(calc_options.getMonomerEntities()) &
     static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
    {
      for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
        {
          std::size_t in_count;
          std::size_t out_count;

          if(cross_link_sp->isEncompassedByIndexRangeCollection(
               index_range_collection, in_count, out_count) ==
             Enums::CrossLinkEncompassed::FULLY)
            {
              // The crossLink is fully encompassed by our monomer
              // stretch, so we should take it into account.

              qDebug() << "Accounting for fully encompassed cross-link:"
                       << cross_link_sp->getCrossLinkerCstSPtr()->getName();

              if(!cross_link_sp->getCrossLinkerCstSPtr()
                    ->getFormula()
                    .isEmpty())
                {
                  formula.setActionFormula(
                    cross_link_sp->getCrossLinkerCstSPtr()->getFormula());

                  qDebug()
                    << "Cross-linker formula:"
                    << cross_link_sp->getCrossLinkerCstSPtr()->getFormula();

                  // Incrementally account for the new formula in the same
                  // atomcount list in the formula.
                  if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
                    qFatal() << "Programming error.";
                }

              // And now each modification that belongs to the
              // crosslinker.

              for(const ModifCstSPtr &modif_csp :
                  cross_link_sp->getCrossLinkerCstSPtr()->getModifsCstRef())
                {
                  qDebug() << "Cross-linker's modif formula:"
                           << modif_csp->formula(/*with_title*/ false);

                  formula.setActionFormula(
                    modif_csp->formula(/*with_title*/ false));

                  // Incrementally account for the new formula in the same
                  // atomcount list in the formula.
                  if(!formula.accountSymbolCounts(isotopic_data_csp, 1))
                    qFatal() << "Programming error.";
                }
            }
          // End of
          // if(cross_link_sp->encompassedBy(sequence_ranges) ==
          // Enums::CrossLinkEncompassed::FULLY)
        }
      // End of
      // for (const CrossLinkSPtr &cross_link_sp : m_crossLinks)
    }
  // End of
  // if(static_cast<int>(calc_options.getMonomerEntities()) &
  // static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))

  // Attention, only account for the Ionizer if it is valid !!!
  if(ionizer.isValid())
    {
      // The ionizer passed as param. Do not forget to take into account the
      // level and not the compound (nominalCharge * level)!
      formula.setActionFormula(ionizer.getFormulaCstRef().getActionFormula());

      // Incrementally account for the new formula in the same
      // atomcount list in the formula.
      if(!formula.accountSymbolCounts(isotopic_data_csp, ionizer.getLevel()))
        qFatal() << "Programming error.";

      // qDebug() << "Formula after accounting ionization: "
      //          << formula.elementalComposition();
    }

#if 0

  // As of 20250925, we remove this block because this introduced unexpected ionization
  // if the user of this function has called it on purpose with an invalid ionizer.
  else if(mp_ionizer->isValid())
    {
      // The member ionizer. Do not forget to take into account the
      // level and not the compound (nominalCharge * level)!
      formula.setActionFormula(
        mp_ionizer->getFormulaCstRef().getActionFormula());

      // Incrementally account for the new formula in the same
      // atomcount list in the formula.
      if(!formula.accountSymbolCounts(isotopic_data_csp,
                                      mp_ionizer->getLevel()))
        qFatal() << "Programming error.";

      qDebug() << "Formula after accounting ionization: "
               << formula.elementalComposition();
    }
#endif

  // qDebug() << "Returning Formula: " << formula.elementalComposition();

  return formula.elementalComposition();
}

/*!
 \ *brief Determines the elemental composition of this polymer.

 The elemental composition is computed by looking into the core chemical
 entities of the polymer, like the monomers, the modifications, and the
 CalcOptions \a calc_options. The sequence elements that are accounted for are
 described in \l IndexRange instances stored in \a index_range_collection.

 The member ionizer is checked for validity and if valid is taken into account.

 Returns the elemental composition.

 \sa elementalComposition(), IndexRange, Ionizer, CalcOptions
 */
QString
Polymer::elementalComposition(
  const IndexRangeCollection &index_range_collection,
  const CalcOptions &calc_options) const
{
  return elementalComposition(
    index_range_collection, calc_options, *mp_ionizer);
}

/*!
\overload
\brief Determines the elemental composition of this polymer.

The elemental composition is computed by looking into the core chemical
entities of the polymer, like the monomers, the modifications, but also by
accounting for the \l Ionizer \a ionizer, and the \l CalcOptions \a
calc_options.

The IndexRange instances of the Polymer for which the elemental composition is
to be computed are found in the IndexRangeCollection instance of \a
calc_options.

Returns the elemental composition.

\sa elementalComposition()
*/
QString
Polymer::elementalComposition(const CalcOptions &calc_options,
                              const Ionizer &ionizer) const
{
  return elementalComposition(
    calc_options.getIndexRangeCollectionCstRef(), calc_options, ionizer);
}

/*!
\overload
\brief Determines the elemental composition of this polymer.

The elemental composition is computed by looking into the core chemical
entities of the polymer, like the monomers, the modifications, but also by
accounting for the \l CalcOptions \a calc_options.

The IndexRange instances of the Polymer for which the elemental composition
is to be computed are found in the IndexRangeCollection instance of \a
calc_options.

If the member ionizer is valid, it is accounted for.

Returns the elemental composition.

\sa elementalComposition()
 */
QString
Polymer::elementalComposition(const CalcOptions &calc_options) const
{
  return elementalComposition(
    calc_options.getIndexRangeCollectionCstRef(), calc_options, *mp_ionizer);
}

/*!
\brief Parses the XML \a element representing a sequence of monomer codes.

We are getting this: \c{<codes>MEFEEGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>}
about which we need to create \l{Monomer}s and add them to this polymer's
\l{Sequence}.

Returns true if parsing and conversion of the text to a monomer list
were successful, false otherwise.

\sa
\sa elementalComposition(), Sequence::makeMonomers()
*/
bool
Polymer::renderXmlCodesElement(const QDomElement &element)
{
  QString sequence;

  // We are getting this:
  // <codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
  // We have to make monomers and add them to the list of monomers.

  if(element.tagName() != "codes")
    {
      qDebug() << "Expected <codes> element not found.";
      return false;
    }

  std::vector<std::size_t> failing_indices;

  // qDebug() << "Now appending the <codes> sequence:" << element.text();

  if(m_sequence.appendSequence(element.text(), failing_indices) == -1)
    {
      qDebug() << "The obtained sequence could not translate into full "
                  "Monomer instances:"
               << element.text();
      return false;
    }

  return true;
}

/*!
\brief Extracts the name of the polymer chemistry definition from the \a
file_path polymer sequence file.

Returns the polymer chemistry definition name.
*/
QString
Polymer::xmlPolymerFileGetPolChemDefName(const QString &file_path)
{
  QDomDocument doc("polSeqData");
  QDomElement element;
  QDomElement child;
  QDomElement indentedChild;

  QFile file(file_path);

  /*
  <polseqdata>
  <polchemdef_name>protein</polchemdef_name>
  ...
  */

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

  if(!doc.setContent(&file))
    {
      file.close();
      return QString("");
    }

  file.close();

  element = doc.documentElement();

  if(element.tagName() != "polseqdata")
    {
      qDebug() << "Polymer sequence file is erroneous\n";
      return QString("");
    }

  // <polchemdef_name>
  child = element.firstChildElement();
  if(child.tagName() != "polchemdef_name")
    return QString("");

  return child.text();
}

/*!
\brief Extracts from \a element, using the proper function (\a version), the
polymer end modification.

The \a element tag is found in the polymer sequence XML file.

If the \a element tag name is \c{le_modif}, the modification name is set to
the left end modification of this polymer sequence; if the tag name is
\c{re_modif}, the right end of this polymer is modifified. The modifications
are then rendered in place.

Returns true if no error was encountered, false otherwise.

\sa Modif::renderXmlMdfElement()
*/
bool
Polymer::renderXmlPolymerModifElement(const QDomElement &element,
                                      [[maybe_unused]] int version)
{
  if(element.tagName() != "le_modif" && element.tagName() != "re_modif")
    {
      qDebug()
        << "The element has not the expected 'le_modif' or 're_modif' tag.";
      return false;
    }

  // Go down to the <mdf> element.
  QDomElement child = element.firstChildElement();

  if(child.isNull())
    return true;

  bool result = false;
  if(element.tagName() == "le_modif")
    result = m_leftEndModif.renderXmlMdfElement(child, version);
  else if(element.tagName() == "re_modif")
    result = m_rightEndModif.renderXmlMdfElement(child, version);

  if(!result)
    qDebug() << "Failed to render one end modifications.";

  return result;
}

/*!
\brief Extracts from \a element, using the proper function (\a version),
all the \l{CrossLink}s contained in it.

Each cross-link is rendered apart and applied to this polymer.

Returns true if no error was encountered, false otherwise.

\sa crossLink()
*/
bool
Polymer::renderXmlCrossLinksElement(const QDomElement &element,
                                    [[maybe_unused]] int version)
{
  QDomElement child;
  QDomElement indentedChild;

  // element is <crosslinks>

  // <crosslinks>
  // <crosslink>
  // <name>DisulfideBond</name>
  // <targets>;2;6;</targets>
  // </crosslink>
  // </crosslinks>

  if(element.tagName() != "crosslinks")
    return false;

  child = element.firstChildElement();

  // There can be any number of <crosslink> elements.
  while(!child.isNull())
    {
      if(child.tagName() != "crosslink")
        return false;

      indentedChild = child.firstChildElement();

      if(indentedChild.tagName() != "name")
        return false;

      // We actually do have a <crosslink> element, so we can allocate
      // one now.

      // qDebug() << "Rendering a polymer sequence CrossLink by name:"
      //<< indentedChild.text();

      CrossLinkerCstSPtr cross_linker_csp =
        mcsp_polChemDef->getCrossLinkerCstSPtrByName(indentedChild.text());

      if(cross_linker_csp == nullptr)
        return false;

      CrossLinkSPtr cross_link_sp =
        std::make_shared<CrossLink>(cross_linker_csp, getCstSharedPtr());

      indentedChild = indentedChild.nextSiblingElement();

      if(indentedChild.tagName() != "targets")
        {
          cross_link_sp.reset();
          return false;
        }

      bool ok = false;
      cross_link_sp->fillInMonomers(indentedChild.text(), ok);
      if(!ok)
        {
          cross_link_sp.reset();
          return false;
        }

      indentedChild = indentedChild.nextSiblingElement();

      if(!indentedChild.isNull())
        {
          if(indentedChild.tagName() != "comment")
            {
              cross_link_sp.reset();
              return false;
            }
          if(!indentedChild.text().isEmpty())
            cross_link_sp->setComment(indentedChild.text());
        }

      // At this point the crossLink element is finished rendering,
      // all we have to do is perform the crossLink proper.

      if(crossLink(cross_link_sp).isEmpty())
        {
          cross_link_sp.reset();
          return false;
        }

      child = child.nextSiblingElement();
    }

  return true;
}

/*!
\brief Parses the \a file_path polymer sequence file.

During parsing, the encountered data are set to this polymer. This parsing
is called "rendering".

Returns true if parsing succeeded, false otherwise.
*/
bool
Polymer::renderXmlPolymerFile(const QString &file_path)
{
  // qDebug() << "Starting the rendering of the XmlPolymerfile:" << file_path;

  QString localFilePath;

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

  /*
  <polseqdata>
  <polchemdef_name>protein</polchemdef_name>
  <name>Sample</name>
  <code>SP2003</code>
  <author>rusconi</author>
  <datetime>1967-09-224:09:23</datetime>
  */

  if(file_path.isEmpty())
    localFilePath = m_filePath;
  else
    localFilePath = file_path;

  QFile file(localFilePath);

  if(!file.open(QIODevice::ReadOnly))
    {
      qDebug() << "Could not open file.";
      return false;
    }

  if(!doc.setContent(&file))
    {
      qDebug() << "Failed to set file contents to doc object.";
      file.close();
      return false;
    }

  file.close();

  element = doc.documentElement();

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

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

  QString text;

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

  bool ok = false;

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

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

      return false;
    }

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

  // <polchemdef_name>
  child = element.firstChildElement();
  if(child.tagName() != "polchemdef_name")
    return false;
  // mcsp_polChemDef->setName(child.text());

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

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

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

  // <datetime>
  child = child.nextSiblingElement();
  if(child.tagName() != "datetime")
    return false;
  m_dateTime = QDateTime::fromString(child.text(), "yyyy-MM-dd:mm:ss");

  // <polseq>
  child = child.nextSiblingElement();

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

  /*
  <polseq>
  <codes>MEFEEDF</codes>
  <monomer>
  <code>S</code>
  <prop>
  <name>MODIF</name>
  <data>Phosphorylation</data>
  </prop>
  </monomer>
  <codes>GRKDKNFLKMGRK</codes>
  </polseq>
  <le_modif>
  <mdf>
  <name>Acetylation</name>
  <formula>-H+C2H3O</formula>
  <targets>*</targets>
  <maxcount>1</maxcount>
  </mdf>
  </le_modif>
  <re_modif>
  <mdf>
  <name>Phosphorylation</name>
  <formula>-H+H2PO3</formula>
  <targets>*</targets>
  <maxcount>1</maxcount>
  </mdf>
  </re_modif>
  */

  // qDebug() << "Now starting to render the <polseq> element.";

  // There can be any number of <codes> and <monomer> elements, in
  // whatever order.

  indentedChild = child.firstChildElement();

  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() == "codes")
        {
          // qDebug() << "Iterating in <codes> element.";

          if(!renderXmlCodesElement(indentedChild))
            {
              qDebug() << "Failed to render the XML codes element.";
              return false;
            }
        }
      else if(indentedChild.tagName() == "monomer")
        {
          // qDebug() << "Iterating in <monomer> element.";

          MonomerSPtr monomer_sp = std::make_shared<Monomer>(mcsp_polChemDef);

          if(!monomer_sp->renderXmlMonomerElement(indentedChild, version))
            {
              qDebug() << "Failed to render the XML monomer element.";
              monomer_sp.reset();

              return false;
            }
          m_sequence.storeMonomer(monomer_sp);
        }
      else
        return false;

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "The sequence turns out to be:"
  //  << m_sequence.getSequence(0, size() - 1, /*with_modifs*/ true);

  // Go on to the next element(has to be <le_modif>.

  QString errors;

  child = child.nextSiblingElement();

  if(child.tagName() != "le_modif")
    errors += "Expected le_modif element not found.\n";

  if(!renderXmlPolymerModifElement(child, version))
    errors += "Failed to render the left end modif element.\n";

  // Go on to the next element(has to be <re_modif>.

  child = child.nextSiblingElement();

  if(child.tagName() != "re_modif")
    errors += "Expected re_modif element not found.\n";

  if(!renderXmlPolymerModifElement(child, version))
    errors += "Failed to render the right end modif element.\n";

  // Go on to the next element(has to be <crosslinks>.

  child = child.nextSiblingElement();

  if(child.tagName() != "crosslinks")
    errors += "Expected crosslinks element not found.\n";

  if(!renderXmlCrossLinksElement(child, version))
    errors += "Failed to render the crosslinks element.";

  if(!errors.isEmpty())
    {
      qDebug().noquote() << "Rendering of XML file failed with error:"
                         << errors;
      return false;
    }

  setFilePath(localFilePath);

  // qDebug() << "Finished rendering the XML file, returning true.";

  return true;
}

/*!
\brief Creates the XML DTD for a polymer sequence file.

Returns the DTD in string.
*/
QString
Polymer::formatXmlDtd()
{
  return QString(
    "<?xml version=\"1.0\"?>\n"
    "<!-- DTD for polymer sequences, used by the\n"
    "'massXpert' mass spectrometry application.\n"
    "Copyright 2006, 2007, 2008 Filippo Rusconi - Licensed under "
    "the GNU GPL -->\n"
    "<!DOCTYPE polseqdata [\n"
    "<!ELEMENT polseqdata "
    "(polchemdef_name, name, code, author, datetime, polseq, le_modif, "
    "re_modif, crosslinks, prop*)>\n"
    "<!ATTLIST polseqdata version NMTOKEN #REQUIRED>\n"
    "<!ELEMENT polchemdef_name (#PCDATA)>\n"
    "<!ELEMENT mdf (name, formula, targets)>\n"
    "<!ELEMENT name (#PCDATA)>\n"
    "<!ELEMENT formula (#PCDATA)>\n"
    "<!ELEMENT targets (#PCDATA)>\n"
    "<!ELEMENT code (#PCDATA)>\n"
    "<!ELEMENT author (#PCDATA)>\n"
    "<!ELEMENT datetime (#PCDATA)>\n"
    "<!ELEMENT polseq (codes|monomer)*>\n"
    "<!ELEMENT le_modif (mdf?)>\n"
    "<!ELEMENT re_modif (mdf?)>\n"
    "<!ELEMENT codes (#PCDATA)>\n"
    "<!ELEMENT crosslink (name, targets)>\n"
    "<!ELEMENT crosslinks (crosslink*)>\n"
    "<!ELEMENT monomer (code, mdf*)>\n"
    "<!ELEMENT prop (name, data+)>\n"
    "<!ATTLIST data type (str | int | dbl) \"str\">\n"
    "<!ELEMENT data (#PCDATA)>\n"
    "]>\n");
}

/*!
\brief Writes this polymer to file.

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

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

  QFile file(m_filePath);

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

      return false;
    }

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

  // The DTD
  stream << formatXmlDtd();

  // Open the <polseqdata> element.
  //"<!ELEMENT polseqdata(polchemdef_name, name, code,
  // author, datetime, polseq, prop*)>\n"

  stream << QString("<polseqdata version=\"%1\">\n")
              .arg(POL_SEQ_FILE_FORMAT_VERSION);

  if(mcsp_polChemDef->getName().isEmpty())
    qFatal()
      << "Programming error. The PolChemDef's name cannot be empty.";

  stream << QString("%1<polchemdef_name>%2</polchemdef_name>\n")
              .arg(indent)
              .arg(mcsp_polChemDef->getName());

  stream << QString("%1<name>%2</name>\n")
              .arg(indent)
              .arg(m_name.isEmpty() ? "Not Set" : m_name);

  stream << QString("%1<code>%2</code>\n")
              .arg(indent)
              .arg(m_code.isEmpty() ? "Not Set" : m_code);

  if(m_author.isEmpty())
    qFatal()
      << "Programming error. The Polymer's author cannot be empty.";

  stream << QString("%1<author>%2</author>\n").arg(indent).arg(m_author);

  m_dateTime = QDateTime::currentDateTime();
  stream
    << QString("%1<datetime>%2</datetime>\n").arg(indent).arg(getDateTime());

  stream << formatXmlPolSeqElement(POL_SEQ_FILE_FORMAT_VERSION);

  // Now deal with the polymer modifications. These are represented as
  // <mdf> elements.

  // Left end modif
  stream << QString("%1<le_modif>\n").arg(indent);
  if(m_leftEndModif.isValid())
    stream << m_leftEndModif.formatXmlMdfElement(/*offset*/ 1);
  stream << QString("%1</le_modif>\n").arg(indent);


  stream << QString("%1<re_modif>\n").arg(indent);
  if(m_rightEndModif.isValid())
    stream << m_rightEndModif.formatXmlMdfElement(/*offset*/ 1);
  stream << QString("%1</re_modif>\n").arg(indent);

  stream << formatXmlCrossLinksElement(/*offset*/ 1);

  // Note that at some point, there might be any number of polymer
  // <prop> elements at this place...

  // Finally close the polseqdata.

  stream << QString("</polseqdata>\n");

  return true;
}

/*!
\brief Formats this polymer's sequence as a string suitable to use as an XML
element.

This function generates a string holding all the elements pertaining to this
polymer' \e sequence (the list of
monomers, potentially modified, \e not all the other data). The typical
element that is generated in this function looks like this:

\code
<polseq>
<codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
<monomer>
<code>S</code>
<mdf>
<name>Phosphorylation</name>
<formula></formula>
<targets>*</targets>
</mdf>
</monomer>
</polseq>
\endcode

\a offset times the \a indent string must be used as a lead in the
formatting of elements.

Returns a dynamically allocated string that needs to be freed after
use.

\sa writeXmlFile()
*/
QString
Polymer::formatXmlPolSeqElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString codesString("");
  QString text;

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

  // At this point, we have to iterate in the sequence. If the
  // monomers are not modified, then put their codes in a raw, like
  // "ETGSH", in a <codes> element. As soon as a monomer is modified,
  // whatever the modification --that is, it has a prop object in its
  // --m_propList, it and its contents should be listed in a detailed
  // <monomer> element.

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

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

  // Iterate in the polymer sequence.

  for(const MonomerSPtr &monomer_sp : m_sequence.getMonomersCstRef())
    {
      // Check if the monomer_csp is modified. If not, we just append its
      // code to the elongating codesString, else we use a more
      // thorough monomer_csp element-parsing function.

      if(!monomer_sp->isModified())
        {
          codesString += monomer_sp->getCode();
          continue;
        }
      else
        {
          // If something was baking in codesString, then we have to
          // create the element right now, fill the data in it and
          // close it before opening one <monomer> element below.

          if(!codesString.isEmpty())
            {
              text += QString("%1<codes>%2%3")
                        .arg(lead)
                        .arg(codesString)
                        .arg("</codes>\n");

              codesString.clear();
            }

          text += monomer_sp->formatXmlMonomerElement(newOffset);
        }
    }

  // If something was baking in codesString, then we have to
  // create the element right now, fill the data in it and
  // close it before opening one <monomer> element below.

  if(!codesString.isEmpty())
    {
      text +=
        QString("%1<codes>%2%3").arg(lead).arg(codesString).arg("</codes>\n");

      codesString.clear();
    }


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

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

  return text;
}

/*!
\brief Formats an XML element suitable to describe the \c <crosslinks>
element.

Iterates in the cross-link list of this polymer and crafts XML elements
describing them.

The XML element looks like this:

\code
<crosslinks>
<crosslink>
<name>DisulfideBond</name>
<targets>;2;6;</targets>
</crosslink>
</crosslinks>
\endcode

\a offset times the \a indent string must be used as a lead in the
formatting of elements.

Returns the XML element as a dynamically allocated string.
*/
QString
Polymer::formatXmlCrossLinksElement(int offset, const QString &indent)
{

  int newOffset;
  int iter = 0;

  QString lead("");
  QString text;

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

  // This is the kind of string we have to generate.

  // <crosslinks>
  // <crosslink>
  // <name>DisulfideBond</name>
  // <targets>;2;6;</targets>
  // </crosslink>
  // </crosslinks>


  // At this point, we have to iterate in the list of crosslinks and
  // for each crosslink determine what's the crosslinker and which
  // monomer are actually crosslinked together.

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

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

  for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
    {
      if(cross_link_sp == nullptr)
        continue;

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

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

      text += QString("%1<name>%2</name>\n")
                .arg(lead)
                .arg(cross_link_sp->getCrossLinkerCstSPtr()->getName());

      // Create the string with all the monomer indices(which are the
      // targets of the crossLink).

      text += QString("%1<targets>%2</targets>\n")
                .arg(lead)
                .arg(cross_link_sp->locationsOfInvolvedSequenceMonomersAsText(
                  Enums::LocationType::INDEX));

      text += QString("%1<comment>%2</comment>\n")
                .arg(lead)
                .arg(cross_link_sp->getComment());

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

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

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

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

  return text;
}

// VALIDATIONS
////////////////////////////
/*!
\brief Validates the Sequence of this polymer, setting errors as messages in
\a error_list_p.

Returns true if validation was successful, false, otherwise.

\sa Sequence::validate()
*/
bool
Polymer::validate(ErrorList *error_list_p) const
{
  m_isValid = true;

  qsizetype error_count = error_list_p->size();

  if(mcsp_polChemDef == nullptr || mcsp_polChemDef.get() == nullptr)
    {
      qDebug() << "The polymer has no available PolChemDef.";
      error_list_p->push_back("The polymer has no available PolChemDef");
    }

  // Name, Code, Author, FilePath all are not compulsory.
  // DateTime, LE and RE modifs, Ionizer all are not compulsory.

  // Sequence has to be non-empty and must be valid.

  if(!m_sequence.validate(error_list_p))
    {
      qDebug() << "The Sequence of the polymer does not validate.";
      error_list_p->push_back("The Sequence of the polymer does not validate");
      m_isValid = false;
    }

  // CrossLinks are not compulsory, but if there, they need to validate
  // successfully.


  for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
    {
      if(!cross_link_sp->validate(error_list_p))
        {
          qDebug() << "One cross-link of the polymer does not validate.";
          error_list_p->push_back(
            "One cross-link of the polymer does not validate");
          m_isValid = false;
        }
    }

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

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

// UTILITIES
////////////////////////////
/*!
\brief Computes a MD5SUM has with the data described in \a
hash_data_specifs.

If hash_data_specifs & HASH_ACCOUNT_SEQUENCE, the sequence is included in
the hash computation. If hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF, the
monomer modifications are included in the hash computation. If
hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF, the polymer modifications are
include in the hash computation.

Returns the hash.
*/
QByteArray
Polymer::md5Sum(int hash_data_specifs) const
{
  // We first need to craft a complete string that encapsulates the
  // maximum number of information from the polymer sequence (sequence,
  // modifications in the monomers...) depending on the parameter passed
  // to the function.

  QString text;

  if(static_cast<int>(hash_data_specifs) &
     static_cast<int>(Enums::HashAccountData::SEQUENCE))
    {
      bool with_mnm_modifs =
        static_cast<int>(hash_data_specifs) &
        static_cast<int>(Enums::HashAccountData::MONOMER_MODIF);
      text += m_sequence.getSequence(0, m_sequence.size(), with_mnm_modifs);
    }

  bool with_polymer_modifs =
    static_cast<int>(hash_data_specifs) &
    static_cast<int>(Enums::HashAccountData::POLYMER_MODIF);

  if(with_polymer_modifs)
    {
      if(isLeftEndModified())
        {
          text += m_leftEndModif.getFormula();
        }
      if(isRightEndModified())
        {
          text += m_rightEndModif.getFormula();
        }
    }

  // Now that we have the data string, we can craft the hash itself:

  QByteArray hash =
    QCryptographicHash::hash(text.toUtf8(), QCryptographicHash::Md5);

  return hash;
}

/*!
\brief Stores \a cross_link_sp as a \l UuidCrossLinkWPtrPair in the member
container.

A Uuid string is computed and associated unambiguously to \a cross_link_sp via a
\l UuidCrossLinkWPtrPair object.

Returns the Uuid string (a \l QUuid).
*/
QString
Polymer::storeCrossLink(CrossLinkSPtr cross_link_sp)
{
  if(cross_link_sp == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  if(hasCrossLink(cross_link_sp) ||
     !getUuidForCrossLink(cross_link_sp).isEmpty())
    qFatal()
      << "It is prohibited to store the same CrossLinkSPtr more than once.";

  // Even if we get a ref to shared_ptr, the reference count increment will
  // occur.
  m_crossLinks.push_back(cross_link_sp);

  QString uuid = QUuid::createUuid().toString();
  m_uuidCrossLinkPairs.push_back(UuidCrossLinkWPtrPair(uuid, cross_link_sp));

  return uuid;
}

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

  std::vector<CrossLinkSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_crossLinks.cbegin(),
                 m_crossLinks.cend(),
                 [cross_link_sp](const CrossLinkSPtr &the_cross_link_sp) {
                   return the_cross_link_sp == cross_link_sp;
                 });

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

  // No sanity checks with getCrossLinkFromUuid() or getUuidForCrossLink()
  // because that makes circular calls (these functions make sanity
  // checks by calling this hasMonomer().)

  return true;
}

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

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

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

  // Sanity check

  if(!hasCrossLink(cross_link_sp))
    qFatal()
      << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";

  return true;
}

/*!
\brief Returns the CrossLinkSPtr that is associated with \a uuid,  if it is
found in the member container of  \l UuidCrossLinkWPtrPair objects.

If no such pair is found to hold \a uuid as its first member,  then nullptr is
returned.

\sa getCrossLinkFromUuid()
*/
CrossLinkSPtr
Polymer::getCrossLinkFromUuid(const QString &uuid) const
{
  std::vector<UuidCrossLinkWPtrPair>::const_iterator the_iterator_cst =
    std::find_if(m_uuidCrossLinkPairs.cbegin(),
                 m_uuidCrossLinkPairs.cend(),
                 [uuid](const UuidCrossLinkCstWPtrPair &the_pair) {
                   return the_pair.first == uuid;
                 });

  if(the_iterator_cst == m_uuidCrossLinkPairs.end())
    return nullptr;

  CrossLinkSPtr cross_link_sp = (*the_iterator_cst).second.lock();

  // Sanity check

  if(!hasCrossLink(cross_link_sp))
    qFatal()
      << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";

  return cross_link_sp;
}

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

If no such CrossLink is found, an empty string is returned.
*/
QString
Polymer::getUuidForCrossLink(const CrossLinkSPtr &cross_link_sp) const
{
  if(cross_link_sp == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

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

  if(the_iterator_cst == m_uuidCrossLinkPairs.cend())
    {
      // Sanity check
      if(hasCrossLink(cross_link_sp))
        qFatal() << "Inconsistency between the m_crossLinks and the "
                          "m_uuidCrossLinkPairs vectors.";

      return QString();
    }

  // Sanity check
  if(!hasCrossLink(cross_link_sp))
    qFatal() << "Inconsistency between the m_crossLinks and the "
                      "m_uuidCrossLinkPairs vectors.";

  return (*the_iterator_cst).first;
}

/*!
\brief Returns the MonomerCstSPtr that is associated with \a uuid,  if it is
found in any CrossLink belonging to this Polymer.

If no such Monomer is found,  nullptr is returned.

\sa getCrossLinkFromUuid()
*/
MonomerCstSPtr
Polymer::getCrossLinkedMonomerCstSPtrFromUuid(const QString &uuid) const
{
  for(const CrossLinkSPtr &cross_link_csp : m_crossLinks)
    {
      MonomerCstSPtr monomer_csp = cross_link_csp->getMonomerForUuid(uuid);

      if(monomer_csp != nullptr)
        return monomer_csp;
    }

  return nullptr;
}

std::vector<QString>
Polymer::getAllCrossLinkUuids() const
{
  std::vector<QString> the_uuid_strings;

  for(const UuidCrossLinkWPtrPair &pair : m_uuidCrossLinkPairs)
    the_uuid_strings.push_back(pair.first);

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

  return the_uuid_strings;
}

/*!
\brief Removes the \l UuidCrossLinkWPtrPair object from the member container if
the second member of that  pair references the same CrossLinkSPtr as that
referenced by \a cross_link_wp.

If no UuidCrossLinkWPtrPair object matches that criterion, nothing happens.

\sa cleanupCrossLinks()
*/
bool
Polymer::removeCrossLink(CrossLinkSPtr cross_link_sp)
{
  if(cross_link_sp == nullptr || cross_link_sp.get() == nullptr)
    qFatal() << "Cannot be that pointer is nullptr.";

  // qDebug() << "Going to remove cross-link:"
  //          << cross_link_sp->getCrossLinkerName();

  // We will need this anyway.
  QString uuid = getUuidForCrossLink(cross_link_sp);

  // qDebug() << "The Uuid of the cross-link is:" << uuid;

  std::vector<CrossLinkSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_crossLinks.begin(),
                 m_crossLinks.end(),
                 [cross_link_sp](CrossLinkSPtr iter_monomer_sp) {
                   return iter_monomer_sp == cross_link_sp;
                 });

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

      if(!uuid.isEmpty())
        qFatal()
          << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";

      return false;
    }

  // At this point, both containers contain cross_link_sp.

  // qDebug() << "Now effectively erasing the CrossLink from the Polymer "
  //             "container of CrossLink instances.";

  m_crossLinks.erase(the_iterator_cst);

  qDebug() << "Done.";

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

  if(the_pair_iterator_cst == m_uuidCrossLinkPairs.cend())
    qFatal()
      << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";

  // qDebug()
  //   << "Now effectively erasing the Uuid/CrossLink pair from the Polymer "
  //      "container of Uuid/CrossLink pairs.";

  m_uuidCrossLinkPairs.erase(the_pair_iterator_cst);

  // qDebug() << "Done.";

  return true;
}

/*!
\brief Removes the \l UuidCrossLinkWPtrPair object from the member container if
the first member of that pair matches \a uuid.

If no such pair is found to hold \a uuid as its first member, then nothing
happens.

\sa cleanupCrossLinks()
*/
void
Polymer::removeCrossLink(const QString &uuid)
{
  std::vector<UuidCrossLinkWPtrPair>::const_iterator the_pair_iterator_cst =
    std::find_if(m_uuidCrossLinkPairs.cbegin(),
                 m_uuidCrossLinkPairs.cend(),
                 [uuid](const UuidCrossLinkWPtrPair &the_pair) {
                   // Do not query the cross_link_sp managed object because it
                   // can be nullptr!
                   return the_pair.first == uuid;
                 });

  if(the_pair_iterator_cst == m_uuidCrossLinkPairs.cend())
    return;

  CrossLinkSPtr cross_link_sp = (*the_pair_iterator_cst).second.lock();

  std::vector<CrossLinkSPtr>::const_iterator the_iterator_cst =
    std::find_if(m_crossLinks.begin(),
                 m_crossLinks.end(),
                 [cross_link_sp](CrossLinkSPtr iter_monomer_sp) {
                   return iter_monomer_sp == cross_link_sp;
                 });

  if(the_iterator_cst == m_crossLinks.end())
    qFatal()
      << "Inconsistency between m_crossLinks and m_uuidCrossLinkPairs.";

  m_crossLinks.erase(the_iterator_cst);
}

/*!
\brief Removes from the member container of \l UuidCrossLinkWPtrPair objects all
the items having a CrossLinkWPtr referencing a dead CrossLinkSPtr.
*/
void
Polymer::cleanupCrossLinks()
{
  // qDebug() << "At beginning, count of UUID-CrossLink pairs:"
  //          << m_uuidCrossLinkPairs.size();

  std::vector<UuidCrossLinkWPtrPair>::iterator the_iterator =
    m_uuidCrossLinkPairs.begin();
  std::vector<UuidCrossLinkWPtrPair>::iterator the_end_iterator =
    m_uuidCrossLinkPairs.end();

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

  // qDebug() << "At end, count of UUID-CrossLink pairs:"
  //          << m_uuidCrossLinkPairs.size();
}

/*!
\brief Returns a string describing the polymer.
*/
QString
Polymer::toString() const
{
  QString text = QString("%1 - %2 - %3 - %4 - %5 - %6 - %7\n")
                   .arg(m_name)
                   .arg(m_code)
                   .arg(m_author)
                   .arg(mcsp_polChemDef->getName())
                   .arg(m_filePath)
                   .arg(m_leftEndModif.getName())
                   .arg(m_rightEndModif.getName());

  QString codes;
  for(const MonomerSPtr &monomer_sp : m_sequence.getMonomersCstRef())
    codes += monomer_sp->getCode();

  text += codes;

  return text;
}

/*!
\brief Clears all the member data.
*/
void
Polymer::clear()
{
  m_name.clear();
  m_code.clear();
  m_author.clear();
  m_filePath.clear();
  m_sequence.clear();
  m_leftEndModif.clear();
  m_rightEndModif.clear();
  m_crossLinks.clear();
  m_uuidCrossLinkPairs.clear();
}

void
Polymer::registerJsConstructor(QJSEngine *engine)
{
  if(!engine)
    {
      qDebug() << "Cannot register Polymer class: engine is null";
      return;
    }

  // Register the meta object as a constructor

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


} // namespace libXpertMassCore
} // namespace MsXpS
