/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright 2009--2026 by Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the MsXpertSuite project.
 *
 * The MsXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/Oligomer.hpp"
#include "MsXpS/libXpertMassCore/Polymer.hpp"
#include "MsXpS/libXpertMassCore/Ionizer.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The Oligomer class provides abstractions to work with
an oligomer molecule (peptide or oligonucleotide, for example).

The notion of an \l Oligomer is that it is part of a \l Polymer and is thus
defined by a range of \l Monomer indices in this \l Polymer (the \l
IndexRangeCollection).

The start index cannot be less than 0 nor greater than the size of the polymer
minus one, and the end index follows the same rule.

An \l Oligomer is also characterized by a monoisotopic
mass and an average mass.

The ionization of the Oligomer is handled by the member \l Ionizer instance.

All computations about an oligomer (fragmentation, composition, for
example, isoelectric point, ...) can only be performed by referring
to the sequence of its "enclosing" \l Polymer.
*/

/*!
\variable MsXpS::libXpertMassCore::Oligomer::mcsp_polymer

\brief The \l Polymer instance of which this Oligomer is part.
*/

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

\brief The name of the Oligomer.
*/

/*!
\variable MsXpS::libXpertMassCore::Oligomer::m_description

\brief The description of the Oligomer.
*/

/*!
\variable MsXpS::libXpertMassCore::Oligomer::m_isModified

\brief Tells if the Oligomer is modified.
*/

/*!
\variable MsXpS::libXpertMassCore::Oligomer::m_ionizer

\brief The Ionizer instance that drives the ionization of the Oligomer.
*/

/*!
\variable MsXpS::libXpertMassCore::Oligomer::m_calcOptions

\brief The CalcOptions instance that drives the mass and formula calculations.
*/

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

\brief The monoisotopic mass.
*/

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

\brief The average mass.
*/

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

\brief The container of CrossLink instances formed in the Oligomer sequence.
*/

/*!
\variable MsXpS::libXpertMassCore::Oligomer::m_partialCleavage

\brief Stores the partial cleavage out of which the Oligomer has been obtained
if that process was a (bio)-chemical cleavage.
*/

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

\brief Stores the formula of the Oligomer.
*/


/*!
\typedef MsXpS::libXpertMassCore::OligomerSPtr
\relates Oligomer

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

/*!
\typedef MsXpS::libXpertMassCore::OligomerCstSPtr
\relates Oligomer

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

/*!
\brief Constructs an Oligomer.

The Oligomer instance is constructed with these arguments:

\list

\li \a polymer_cqsp The \l Polymer instance that encloses this Oligomer.

\li \a name The name of this Oligomer, used to intialize the Ionizable base
class

\li \a description The description of this Oligomer (m_description)

\li \a is_modified Tells if the Oligomer is modified

\li \a ionizer The Ionizer used to ionize this Oligomer

\li \a calc_options Used to initialize the m_calcOptions member

\endlist
*/
Oligomer::Oligomer(PolymerCstQSPtr polymer_cqsp,
                   const QString &name,
                   const QString &description,
                   bool is_modified,
                   const Ionizer &ionizer,
                   const CalcOptions &calc_options)
  : mcsp_polymer(polymer_cqsp),
    m_name(name),
    m_description(description),
    m_isModified(is_modified),
    m_ionizer(ionizer),
    m_calcOptions(calc_options)
{
  qDebug() << "Allocating new Oligomer with other calc options:"
           << calc_options.toString()
           << "and this calc options:" << m_calcOptions.toString();
}

/*!
\brief Constructs the Oligomer as a copy of \a other.
*/
Oligomer::Oligomer(const Oligomer &other)
  : PropListHolder(other),
    mcsp_polymer(other.mcsp_polymer),
    m_name(other.m_name),
    m_description(other.m_description),
    m_isModified(other.m_isModified),
    m_ionizer(other.m_ionizer),
    m_calcOptions(other.m_calcOptions),
    m_mono(other.m_mono),
    m_avg(other.m_avg),
    m_crossLinks(other.m_crossLinks),
    m_partialCleavage(other.m_partialCleavage),
    m_formula(other.m_formula)
{
}

/*!
\brief Destructs this Oligomer.
*/
Oligomer::~Oligomer()
{
  //  qDebug() << "~Oligomer()";
}

//////////////// THE POLYMER /////////////////////
/*!
\brief Set the \l{PolChemDef} \l{mcsp_polymer} member to \a polymer_cqsp.

\sa getPolymerCstSPtr()
*/
void
Oligomer::setPolymerCstSPtr(PolymerCstQSPtr polymer_cqsp)
{
  mcsp_polymer = polymer_cqsp;
}

/*!
\brief Returns the polymer.

\sa getPolymerCstSPtr()
*/
const PolymerCstQSPtr &
Oligomer::getPolymerCstSPtr() const
{
  return mcsp_polymer;
}

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

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

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

//////////////// THE DESCRIPTION /////////////////////

/*!
\brief Sets the \a description.
*/
void
Oligomer::setDescription(const QString &description)
{
  m_description = description;
}

/*!
\brief Returns the description.
*/
QString
Oligomer::getDescription() const
{
  return m_description;
}

//////////////// MODIFICATION STATUS /////////////////////

/*!
\brief Sets the modification status to \a is_modified.
*/
void
Oligomer::setModified(bool is_modified)
{
  m_isModified = is_modified;
}

/*!
\brief Returns the chemical modification status of this Oligomer.

If \a deep is true, the member Polymer must exist (mcsp_polymer cannot be
nullptr) because the polymer itself is asked for the count of Monomer instances
being modified. The sequence ranges of the polymer that are surveyed are those
in this Oligomer's IndexRangeCollection. Upon checking the result, if the count
of modified Monomer instances is not 0, then m_isModified is set to true,
otherwise it is set to false. That m_isModified value is then returned.

If \a deep is false, the value of m_isModified is immediately returned.

\sa Polymer::hasModifiedMonomer, Monomer::isModified()
*/
bool
Oligomer::isModified(bool deep /*false*/) const
{
  // Either we truly go to the polymer instance and check if the oligomer is
  // modified or we just ask for the member datum, that might have been set,
  // for example, during creation of the oligomer in the Cleaver::cleave()
  // function. We need the possibility to ask for the member datum because
  // there are circumstances where the oligomer exists and not the original
  // polymer (for example if the polymer sequence is edited while a set of
  // cleavage oligomers is displayed in the cleavage dialog. When the
  // tableviewmodel needs to refresh the contents of the cells, it crashes
  // because the polymer has been edited and one monomer is missing from the
  // sequence of the oligomer as it had been configured in the first place.

  if(deep)
    {
      m_isModified = modifiedMonomerCount() != 0 ? true : false;
    }

  return m_isModified;
}

/*!
\brief Return the count of the \l{Polymer}'s \l{Sequence}'s \l{Monomer}
instances that are modified.
\sa Polymer::modifiedMonomerCount
*/
std::size_t
Oligomer::modifiedMonomerCount() const
{
  if(mcsp_polymer == nullptr)
    qFatal("Programming error. The pointer cannot be nullptr.");

  return mcsp_polymer->modifiedMonomerCount(
    m_calcOptions.getIndexRangeCollectionCstRef());
}

//////////////// THE IONIZER /////////////////////

/*!
\brief Sets \a ionizer to the member datum.
*/
void
Oligomer::setIonizer(const Ionizer &ionizer)
{
  m_ionizer = ionizer;
}

/*!
\brief Returns a const reference to the Ionizer.
*/
const Ionizer &
Oligomer::getIonizerCstRef() const
{
  return m_ionizer;
}

/*!
\brief Returns a reference to the Ionizer.
*/
Ionizer &
Oligomer::getIonizerRef()
{
  return m_ionizer;
}

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

The new masses, if a change occurred, are updated in the member m_mono and
m_avg variables.
*/
Enums::IonizationOutcome
Oligomer::ionize()
{
  return m_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 m_mono and
m_avg variables.
*/
Enums::IonizationOutcome
Oligomer::deionize()
{
  return m_ionizer.deionize(m_mono, m_avg);
}

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

The member Ionizer is used to first deionize the \a mono and \a avg masses (on
copy data, if the Ionizer reports that the analyte is ionized).

\a mono and \a avg are then set to the masses of this polymer instance in an
unionized status.

Nothing is modified in this Oligomer instance.

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

  Enums::IonizationOutcome ionization_outcome =
    m_ionizer.deionize(temp_mono, temp_avg);

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

  mono = temp_mono;
  avg  = temp_avg;

  return ionization_outcome;
}

//////////////// THE SEQUENCE RANGES /////////////////////

/*!
\brief Sets \a index_ranges to the member datum.
*/
void
Oligomer::setIndexRanges(const IndexRangeCollection &index_ranges)
{
  m_calcOptions.getIndexRangeCollectionRef().initialize(index_ranges);
}

/*!
\brief Sets \a index_range to the member datum as the sole range in the
member IndexRange container.
*/
void
Oligomer::setIndexRange(const IndexRange &index_range)
{
  m_calcOptions.getIndexRangeCollectionRef().clear();
  m_calcOptions.getIndexRangeCollectionRef().setIndexRange(index_range);
}

/*!
\brief Returns a const reference to the IndexRangeCollection.
*/
const IndexRangeCollection &
Oligomer::getIndexRangeCollectionCstRef() const
{
  return m_calcOptions.getIndexRangeCollectionCstRef();
}

/*!
\brief Returns a reference to the IndexRangeCollection.
*/
IndexRangeCollection &
Oligomer::getIndexRangeCollectionRef()
{
  return m_calcOptions.getIndexRangeCollectionRef();
}

/*!
\brief Sets the \a start_index and \a stop_index indices to the first
IndexRange instance of the member IndexRangeCollection container.
*/
void
Oligomer::setStartAndStopIndices(std::size_t start_index,
                                 std::size_t stop_index)
{
  if(!m_calcOptions.getIndexRangeCollectionRef().size())
    {
      m_calcOptions.getIndexRangeCollectionRef().setIndexRange(start_index,
                                                               stop_index);
    }
  else
    {
      m_calcOptions.getIndexRangeCollectionRef()
        .getRangesRef()
        .front()
        ->m_start = start_index;
      m_calcOptions.getIndexRangeCollectionRef()
        .getRangesRef()
        .front()
        ->m_stop = stop_index;
    }
}

/*!
\brief Sets \a start_index as the the first IndexRange instance'start value
of the member IndexRangeCollection container.
*/
void
Oligomer::setStartIndex(int start_index)
{
  if(!m_calcOptions.getIndexRangeCollectionRef().size())
    {
      m_calcOptions.getIndexRangeCollectionRef().setIndexRange(start_index, 0);
    }
  else
    {
      m_calcOptions.getIndexRangeCollectionRef()
        .getRangesRef()
        .front()
        ->m_start = start_index;
    }
}

/*!
\brief Returns the start index, or 0 (\a ok is set to false) if the
IndexRangeCollection member is empty.

The returned start index is that of the first IndexRange instance in the
IndexRangeCollection member (\a ok is set to true).
*/
qsizetype
Oligomer::startIndex(bool &ok) const
{
  if(!m_calcOptions.getIndexRangeCollectionCstRef().size())
    {
      ok = false;
      return 0;
    }

  ok = true;
  return m_calcOptions.getIndexRangeCollectionCstRef()
    .getRangesCstRef()
    .front()
    ->m_start;
}

/*!
\brief Sets \a stop_index as the the first IndexRange instance'stop value
of the member IndexRangeCollection container.
*/
void
Oligomer::setStopIndex(int stop_index)
{
  if(!m_calcOptions.getIndexRangeCollectionRef().size())
    {
      m_calcOptions.getIndexRangeCollectionRef().setIndexRange(0, stop_index);
    }
  else
    {
      m_calcOptions.getIndexRangeCollectionRef()
        .getRangesRef()
        .front()
        ->m_stop = stop_index;
    }
}

/*!
\brief Returns the stop index, or 0 (\a ok is set to false) if the
IndexRangeCollection member is empty.

The returned stop index is that of the first IndexRange instance in the
IndexRangeCollection member (\a ok is set to true).
*/
qsizetype
Oligomer::stopIndex(bool &ok) const
{
  if(!m_calcOptions.getIndexRangeCollectionCstRef().size())
    {
      ok = false;
      return 0;
    }

  ok = true;
  return m_calcOptions.getIndexRangeCollectionCstRef()
    .getRangesCstRef()
    .front()
    ->m_stop;
}

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

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

/*!
\brief Returns the CalcOptions member.
*/
const CalcOptions &
Oligomer::getCalcOptionsCstRef() const
{
  return m_calcOptions;
}

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

//////////////// THE MASSES /////////////////////
/*!
\brief Set the mass qualified using \a mass_type to the \a mass value.

\sa setMasses()
*/
void
Oligomer::setMass(Enums::MassType mass_type, double mass)
{
  if(mass_type == Enums::MassType::MONO)
    m_mono = mass;
  else if(mass_type == Enums::MassType::AVG)
    m_avg = mass;
  else
    qFatal() << "The mass_type needs to be either MONO or AVG.";
}

/*!
 \brief Sets \a mono and \a avg masses to this Oligomer.

 \sa setMass()
 */
void
Oligomer::setMasses(double mono, double avg)
{
  m_mono = mono;
  m_avg  = avg;
}

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

//////////////// THE CROSS-LINKS /////////////////////
/*!
\brief Returns a const reference to the CrossLink container.
*/
const std::vector<CrossLinkSPtr> &
Oligomer::getCrossLinksCstRef() const
{
  return m_crossLinks;
}

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

/*!
\brief Adds the \a cross_link_sp CrossLink to this Oligomer's container of
\l{CrossLink} instances.

Returns true if the CrossLink was added succesfully, or false \a
cross_link_sp was already in the container.
*/
bool
Oligomer::addCrossLink(CrossLinkSPtr cross_link_sp)
{
  if(cross_link_sp == nullptr || cross_link_sp.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  // Add the cross-link only if it does not exist already. Return true
  // only if the crossLink has been added.

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

  if(the_iterator_cst == m_crossLinks.end())
    {
      m_crossLinks.push_back(cross_link_sp);
      return true;
    }

  return false;
}

//////////////// THE PARTIAL CLEAVAGE /////////////////////
/*!
\brief Set the member \l m_partialCleavage to \a partial_cleavage.
*/
void
Oligomer::setPartialCleavage(std::size_t partial_cleavage)
{
  m_partialCleavage = partial_cleavage;
}

/*!
\brief Returns the m_partialCleavage member.
*/
std::size_t
Oligomer::getPartialCleavage() const
{
  return m_partialCleavage;
}

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

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

/*!
\brief Returns a constant reference to the formula.
*/
const Formula &
Oligomer::getFormulaCstRef() const
{
  return m_formula;
}

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

//////////////// THE MONOMERS /////////////////////
/*!
\brief Returns the Monomer in the member Polymer that is located at the index
that has the start value of the first IndexRange object in the member
IndexRangeCollection container.

If the Polymer is not avaialable, or if the start
index could not be obtained, nullptr is returned.

If the index is out of bounds, that is a fatal error.
*/
MonomerSPtr
Oligomer::getLeftEndMonomerCstSPtr() const
{
  if(mcsp_polymer == nullptr)
    {
      qDebug() << "The polymer is not available.";
      return nullptr;
    }

  bool ok           = false;
  std::size_t index = startIndex(ok);

  if(!ok)
    return nullptr;


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

  return mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(index);
}

/*!
\brief Returns the Monomer in the member Polymer that is located at the index
that has the stop value of the first IndexRange object in the member
IndexRangeCollection container.

If the Polymer is not avaialable, or if the stop
index could not be obtained, nullptr is returned.

If the index is out of bounds, that is a fatal error.
*/
MonomerSPtr
Oligomer::getRightEndMonomerCstSPtr() const
{
  if(mcsp_polymer == nullptr)
    {
      qDebug() << "The polymer is not available.";
      return nullptr;
    }

  bool ok           = false;
  std::size_t index = stopIndex(ok);

  if(!ok)
    return nullptr;

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

  return mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(index);
}

/*!
\brief Returns the Monomer in the member Polymer that is located at the \a
index.

If the Polymer is not available, nullptr is returned. If \a index is not
encompassed by the member IndexRangeCollection container, or if that container
is empty, nullptr is returned.

If \a index is out of bounds, that is a fatal error.
*/
MonomerSPtr
Oligomer::getMonomerCstSPtrAt(std::size_t index) const
{
  if(mcsp_polymer == nullptr)
    {
      qDebug() << "The polymer is not available.";
      return nullptr;
    }

  if(!m_calcOptions.getIndexRangeCollectionCstRef().size())
    return nullptr;

  if(!m_calcOptions.getIndexRangeCollectionCstRef().encompassIndex(index))
    {
      qDebug() << "Asking for Monomer at index not encompassed by member "
                  "IndexRangeCollection.";
    }

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

  return mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(index);
}

//////////////// OPERATORS /////////////////////
/*!
\brief Assigns \a other to this Oligomer instance.
*/
Oligomer &
Oligomer::operator=(const Oligomer &other)
{
  if(&other == this)
    return *this;

  PropListHolder::operator=(other);

  mcsp_polymer      = other.mcsp_polymer;
  m_name            = other.m_name;
  m_description     = other.m_description;
  m_isModified      = other.m_isModified;
  m_ionizer         = other.m_ionizer;
  m_partialCleavage = other.m_partialCleavage;
  m_calcOptions.initialize(other.m_calcOptions);
  m_mono    = other.m_mono;
  m_avg     = other.m_avg;
  m_formula = other.m_formula;
  m_crossLinks.assign(other.m_crossLinks.cbegin(), other.m_crossLinks.cend());

  return *this;
}

//////////////// ELEMENTAL CALCULATION FUNCTIONS /////////////////////

/*!
\brief Returns the elemental composition of this Oligomer instance.

The computation of the elemental composition is performed as configured in \a
calc_options and using the ionization described in \a ionizer.
*/
QString
Oligomer::elementalComposition(const CalcOptions &calc_options,
                               const Ionizer &ionizer) const
{
  qDebug().noquote()
    << "Calculating the elemental composition of Oligomer using "
       "CalcOptions:"
    << calc_options.toString() << "and Ionizer:" << ionizer.toString()
    << "that has internal formula:" << m_formula.getActionFormula();

  if(calc_options.getSelectionType() == Enums::SelectionType::RESIDUAL_CHAINS)
    qDebug() << "Enums::SelectionType::RESIDUAL_CHAINS";
  else
    qDebug() << "Enums::SelectionType::OLIGOMERS";

  qDebug() << "Going to call Polymer::elementalComposition() with CalcOptions:"
           << calc_options.toString() << "and ionizer:" << ionizer.toString();

  return mcsp_polymer->elementalComposition(calc_options, ionizer);
}

/*!
\brief Returns the elemental composition of this Oligomer instance.
*/
QString
Oligomer::elementalComposition() const
{
  return elementalComposition(m_calcOptions, m_ionizer);
}

//////////////// MASS CALCULATION FUNCTIONS /////////////////////
/*!
\brief Calculates the monoisotopic and average masses.

The calculation is performed by computing the mono and avg masses
of the sequence stretch as described by the set of start and top indices (member
IndexRangeCollection object) in the polymer sequence.

The member CalcOptions (m_calcOptions) and the member Ionizer
(m_ionizer) are used.

The m_mono and m_avg member objects are reset to 0.0 before the calculations.

Returns true if calculations were successful, false otherwise.
*/
bool
Oligomer::calculateMasses()
{
  return calculateMasses(m_calcOptions, m_ionizer);
}

/*!
\brief Calculates the monoisotopic and average masses.

The calculation is performed by computing the mono and avg masses
of the sequence stretch as described by the start and end indices in
the polymer sequence. The calculations are configured by \a calc_options
and the ionization is defined in \a ionizer.

The m_mono and m_avg member objects are reset to 0.0 before the calculations.

The member Ionizer is not set to \a ionizer. This is so that a whole set
of ionization computations can be performed without touching the internal
Ionizer.

Returns true if calculations were successful, false otherwise.
*/
bool
Oligomer::calculateMasses(const CalcOptions &calc_options,
                          const Ionizer &ionizer)
{
  qDebug().noquote() << "Going to calculate masses with calculation options:"
                     << calc_options.toString()
                     << "and ionizer:" << ionizer.toString();

  CalcOptions local_calc_options(calc_options);

  // The coordinates of the oligomer are the following:

  // MAMISGMSGRKAS

  // For a tryptic peptide obtained from protein above, we'd have

  // MAMISGMSGR, that is in the oligomer coordinates:

  // [0] MAMISGMSGR [9]

  // When computing the mass of the oligomer, we have to do a

  // for (iter == [0] ; iter < [9 + 1]; ++iter)

  // Which is why we increment add 1 to m_endIndex in the function below.

  // A polymer might be something made of more than one residual chain
  // in case it is a cross-linked oligomer. Compute the mass fore each
  // residual chain, without accounting for the cross-links...

  m_mono = 0;
  m_avg  = 0;

  // An oligomer is characterized by a reference polymer and a
  // IndexRangeCollection object that documents the ranges of that Polymer
  // Sequence. That IndexRangeCollection object is in the calc_options object
  // passed as reference. With it we can call the Polymer::accountMasses
  // function. This is why we need to first copy the member IndexRangeCollection
  // object in it.

  // local_calc_options.setIndexRanges(m_calcOptions.getIndexRangeCollectionCstRef());

  qDebug() << "Index ranges inside calculation options:"
           << local_calc_options.getIndexRangeCollectionRef().indicesAsText();

  // We do not want to take into account the cross-links because
  // we'll be doing this here and because it cannot work if the
  // cross-links are taken into account from the polymer.

  int flags = static_cast<int>(local_calc_options.getMonomerEntities());
  // qDebug() << "flags:" << flags
  //     << chemicalEntityMap[static_cast<Enums::ChemicalEntity>(flags)];

  flags &= ~static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER);
  // qDebug() << "flags:" << flags
  //     << chemicalEntityMap[static_cast<Enums::ChemicalEntity>(flags)];

  local_calc_options.setMonomerEntities(
    static_cast<Enums::ChemicalEntity>(flags));

  // flags = static_cast<int>(local_calc_options.getPolymerEntities());
  // qDebug() << "flags:" << flags
  //     << chemicalEntityMap[static_cast<Enums::ChemicalEntity>(flags)];

  if(!Polymer::accountMasses(
       mcsp_polymer.get(), local_calc_options, m_mono, m_avg))
    {
      qDebug() << "Failed accounting masses of Polymer.";
      return false;
    }

  qDebug() << "20250522 After the enclosing polymer accounted masses (prior to "
              "cross-links), mono mass is:"
           << m_mono << "avg mass is:" << m_avg;

  // At this point, we have added the mass of each constituent
  // oligomer's residual chain (with or without end caps, depending
  // on the configuration). Let's deal with the cross-links, if
  // so is required.

  if(static_cast<int>(calc_options.getMonomerEntities()) &
     static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
    {
      qDebug() << "As per configuration, now accounting "
                  "the cross-links.";

      for(const CrossLinkSPtr &cross_link_sp : m_crossLinks)
        {
          qDebug() << "Now iterating in one of the cross-links of the Oligomer:"
                   << cross_link_sp->toString();

          qDebug()
            << "Checking if Index ranges:"
            << local_calc_options.getIndexRangeCollectionRef().indicesAsText()
            << "fully encompass the cross-link.";

          // Only account the cross-link if it is entirely encompassed
          // by the index range.
          std::size_t in_count  = 0;
          std::size_t out_count = 0;
          Enums::CrossLinkEncompassed cross_link_encompassed =
            cross_link_sp->isEncompassedByIndexRangeCollection(
              local_calc_options.getIndexRangeCollectionCstRef(),
              in_count,
              out_count);

          if(cross_link_encompassed == Enums::CrossLinkEncompassed::FULLY)
            {
              cross_link_sp->accountMasses(m_mono, m_avg);

              qDebug() << "After accounting fully encompassed cross-link:"
                       << cross_link_sp->getCrossLinkerCstSPtr()->getName()
                       << "mono mass is:" << m_mono << "avg mass is:" << m_avg;
            }
        }
    }

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

  qDebug() << "Coming out from the calculateMasses function:"
           << "mono mass is:" << m_mono << "avg mass is:" << m_avg;

  return true;
}

/*!
\brief Returns the size of this Oligomer.

The size is computed by adding the length of all the regions of the
enclosing Polymer as documented in the Coordinates instances in the member
coordinateList.
*/
std::size_t
Oligomer::size()
{
  std::size_t sum = 0;

  foreach(const IndexRange *item,
          m_calcOptions.getIndexRangeCollectionCstRef().getRangesCstRef())
    sum += (item->m_stop - item->m_start + 1);

  return sum;
}

/*!
\brief Returns true if this Oligomer spans at least one region of the
enclosing polymer that contains a Monomer at \a index, false otherwise.
*/
bool
Oligomer::encompasses(std::size_t index) const
{
  if(mcsp_polymer == nullptr)
    {
      qDebug() << "The polymer is not available.";
      return false;
    }

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

  return m_calcOptions.getIndexRangeCollectionCstRef().encompassIndex(index);
}

/*!
\brief Returns true if this Oligomer spans at least one region of the
enclosing Polymer that contains the Monomer \a monomer_crp, false otherwise.

The search is performed by comparing pointers, thus the Monomer to be search \e
is \a monomer_crp.
*/
bool
Oligomer::encompasses(MonomerCstRPtr monomer_crp) const
{
  bool ok = false;
  std::size_t index =
    mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_crp, ok);

  return encompasses(index);
}

/*!
\brief Returns true if this Oligomer spans at least one region of the
enclosing Polymer that contains the Monomer \a monomer_sp, false otherwise.

The search is performed by comparing pointers, thus the Monomer to be search \e
is \a monomer_sp.
*/
bool
Oligomer::encompasses(MonomerSPtr monomer_sp) const
{

  bool ok = false;
  std::size_t index =
    mcsp_polymer->getSequenceCstRef().monomerIndex(monomer_sp, ok);

  return encompasses(index);
}

/*!
\brief Returns a string documenting this Oligomer instance.
*/
QString
Oligomer::toString() const
{
  QString text =
    QString(
      "name: %1 - desc.: %2 - index ranges: %3 - mono: %4 - avg: "
      "%5 - stored formula :%6 - ionizer: %7 - calc. elem. comp. : %8\n")
      .arg(m_name)
      .arg(m_description)
      .arg(m_calcOptions.getIndexRangeCollectionCstRef().positionsAsText())
      .arg(m_mono)
      .arg(m_avg)
      .arg(m_formula.getActionFormula())
      .arg(m_ionizer.toString())
      .arg(elementalComposition());

  return text;
}

} // namespace libXpertMassCore
} // namespace MsXpS
