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


/////////////////////// Qt includes
#include <QDebug>


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/OligomerCollection.hpp"
#include "MsXpS/libXpertMassCore/PolChemDef.hpp"
#include "MsXpS/libXpertMassCore/Fragmenter.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The Fragmenter class provides a model for performing gas phase
fragmentation reactions involving \l{FragmentationPathway} objects and
\l{Polymer} \l{Sequence}s.

The fragmentation process is configured by the member vector of
FragmentationConfig instances that may store,  for example,  all the
fragmentation pathways that need to be dealt with in the inner workings of this
Fragmenter class. For example,  the user might want to perform a series of
fragmentation involving pathways b and y for protein fragmentation.

\sa FragmentationPathway, FragmentationRule, FragmentationConfig, Ionizer
*/

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

\brief The \l Polymer polymer that is being cleaved (digested).
*/

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

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

/*!
\variable MsXpS::libXpertMassCore::Fragmenter::m_fragmentationConfigs
\brief The container of FragmentationConfig instances that collectively
configure the fragmentation pathways to implement during the fragmentation
process.
*/

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

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

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

\brief The Ionizer that directs the ionization of the Oligomer instances
obtained by cleaving the Polymer.
*/

/*!
\variable MsXpS::libXpertMassCore::Fragmenter::m_oligomers

\brief The vector of fragment Oligomer instances (product ions) generated as a
result of the fragmentation.
*/

/*!
\variable MsXpS::libXpertMassCore::Fragmenter::m_crossLinkedRegions

\brief The vector of CrossLinkedRegion that describe the way product ion
fragments might involved CrossLink instances.

\sa CrossLinkedRegion
*/


/*!
\brief Constructs a Fragmenter instance with a number of parameters.

\list
\li \a polymer_cqsp The Polymer instance to be fragmented.

\li \a pol_chem_def_csp The PolChemDef (polymer chemistry definition) that is
the context in which the Polymer exists.

\li \a fragmentation_configs The container of FragmentationConfig instances that
configure the fragmentation.

\li \a calc_options The CalcOptions instance that configures the mass and
formula calculations.

\li \a ionizer The Ionizer instance that drives the ionization of the Oligomer
instances generated by the cleavage.
\endlist

If polymer_cqsp or pol_chem_def_csp is nullptr, that is a fatal error.
*/
Fragmenter::Fragmenter(
  PolymerCstQSPtr polymer_cqsp,
  PolChemDefCstSPtr pol_chem_def_csp,
  const std::vector<FragmentationConfig> &fragmentation_configs,
  const CalcOptions &calc_options,
  const Ionizer &ionizer)
  : mcsp_polymer(polymer_cqsp),
    mcsp_polChemDef(pol_chem_def_csp),
    m_fragmentationConfigs(fragmentation_configs),
    m_calcOptions(calc_options),
    m_ionizer(ionizer)
{
  if(mcsp_polymer == nullptr && mcsp_polymer.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  if(mcsp_polChemDef == nullptr && mcsp_polChemDef.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  // qDebug() << "Constructing Fragmenter with CalcOptions:"
  //          << m_calcOptions.toString();
}

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

If polymer_cqsp or pol_chem_def_csp is nullptr, that is a fatal error.
*/
Fragmenter::Fragmenter(const Fragmenter &other)
  : mcsp_polymer(other.mcsp_polymer),
    mcsp_polChemDef(other.mcsp_polChemDef),
    m_fragmentationConfigs(other.m_fragmentationConfigs),
    m_calcOptions(other.m_calcOptions),
    m_ionizer(other.m_ionizer)
{
  if(mcsp_polymer == nullptr && mcsp_polymer.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  if(mcsp_polChemDef == nullptr && mcsp_polChemDef.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";
}

/*!
\brief Desstructs this Fragmenter instance
*/
Fragmenter::~Fragmenter()
{
}

/*!
\brief Adds \a fragmentation_config to the member container of
FragmentationConfig instances.
*/
void
Fragmenter::addFragmentationConfig(
  const FragmentationConfig &fragmentation_config)
{

  m_fragmentationConfigs.push_back(fragmentation_config);
}

/*!
\brief Returns a constant reference to the container of FragmentationConfig
instances.
*/
const std::vector<FragmentationConfig> &
Fragmenter::getFragmentationConfigsCstRef() const
{
  return m_fragmentationConfigs;
}

/*!
\brief Returns a reference to the container of FragmentationConfig instances.
*/
std::vector<FragmentationConfig> &
Fragmenter::getFragmentationConfigsRef()
{
  return m_fragmentationConfigs;
}

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

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

/*!
 \ b*rief Returns a constant reference to the CalcOptions instance.
 */
const CalcOptions &
Fragmenter::getCalcOptionsCstRef() const
{
  return m_calcOptions;
}

/*!
 \ b*rief Returns a reference to the CalcOptions instance.
 */
CalcOptions &
Fragmenter::getCalcOptionsRef()
{
  return m_calcOptions;
}

/*!
\brief Transfers (using std::move()) all the Oligomer instances from \a source
to \a dest.

After the transfer, the \a source Oligomer container is cleared since it
contains only nullptr items.
*/
std::size_t
Fragmenter::transferOligomers(OligomerCollection &source,
                              OligomerCollection &dest)
{
  std::size_t dest_oligomer_count_before = dest.getOligomersRef().size();

  // Move each element from source to dest
  dest.getOligomersRef().insert(
    dest.getOligomersRef().end(),
    std::make_move_iterator(source.getOligomersRef().begin()),
    std::make_move_iterator(source.getOligomersRef().end()));

  std::size_t dest_oligomer_count_after = dest.getOligomersRef().size();
  std::size_t transferred_count =
    dest_oligomer_count_after - dest_oligomer_count_before;

  // Sanity check
  if(transferred_count != source.getOligomersRef().size())
    qFatal()
      << "Programming error. Not all the Oligomers were transferred.";

  //  Now clear the source container which contains the same items as before but
  //  all the shared pointers are now nullptr.

  source.getOligomersRef().clear();

  return transferred_count;
}

/*!
\brief Transfers (using std::move()) \a source_oligomer_sp
to \a dest.
*/
void
Fragmenter::transferOligomer(OligomerSPtr &&source_oligomer_sp,
                             OligomerCollection &dest)
{
  dest.getOligomersRef().push_back(std::move(source_oligomer_sp));
}

/*!
\brief Returns a const reference to the member OligomerCollection instance.
*/
const OligomerCollection &
Fragmenter::getOligomerCollectionCstRef() const
{
  return m_oligomers;
}

/*!
\brief Returns a reference to the member OligomerCollection instance.
*/
OligomerCollection &
Fragmenter::getOligomerCollectionRef()
{
  return m_oligomers;
}

/*!
\brief Performs the actual fragmentation and returns true if successful,  false
otherwise.

All the FragmentationConfig instances in the member container are iterated into
and the fragmentation procedure is implemented, storing all the generated
fragment Oligomer instances in the member OligomerCollection instance.
*/
bool
Fragmenter::fragment()
{
  // If the polymer sequence is empty, just return.
  if(!mcsp_polymer->size())
    {
      qDebug() << "The polymer sequence is empty: nothing to fragment.";
      return true;
    }

  // Ensure that the list of fragmentation configs is not empty.

  if(!m_fragmentationConfigs.size())
    {
      qDebug() << "List of fragmentation configs is empty !";

      return false;
    }

  // qDebug() << "Number of fragmentation configurations:"
  //          << m_fragmentationConfigs.size();

  // Check if we have to account for monomer modifications. If so, DEEP-compute
  // the mass of all the monomers that are comprised in the sequence range.

  double mono;
  double avg;

  // if(static_cast<int>(m_calcOptions.getMonomerEntities()) &
  //    static_cast<int>(Enums::ChemicalEntity::MODIF))
  // Use the overload (see globals.hpp)
  if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
                       Enums::ChemicalEntity::MODIF))
    {
      // qDebug() << "Fragmentation calculations take "
      //             "into account the monomer modifications";

      m_calcOptions.setDeepCalculation(true);

      Polymer::calculateMasses(
        mcsp_polymer.get(), m_calcOptions, mono, avg, /*reset*/ false);
    }

  // Before starting the calculation we ought to know if there are
  // cross-links in the oligomer to be fragmented and if the user
  // has asked that these cross-linked be taken into account during
  // the fragmentation.

  // If there are cross-links, then we have to deal with
  // them. The strategy is to first get a list of all the
  // monomer indices for the monomers in the Oligomer (being
  // fragmented) that are involved in cross-links.

  FragmentationConfig fragmentation_config = m_fragmentationConfigs.at(0);
  std::vector<std::size_t> cross_link_indices;
  std::size_t partials = 0;

  // qDebug() << "Fragmentation config:"
  //          << fragmentation_config.toString();

  mcsp_polymer->crossLinkedMonomersIndicesInRange(
    fragmentation_config.getStartIndex(),
    fragmentation_config.getStopIndex(),
    cross_link_indices,
    partials);

  if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
                       Enums::ChemicalEntity::CROSS_LINKER) &&
     partials)
    {
      qDebug() << "Fragmentation calculations do not\n"
                  "take into account partial cross-links.\n"
                  "These partial cross-links are ignored.";
    }

  // We do run into the if block below only if the CrossLinks
  // should be taken into account (if any) and if there are
  // at least one CrossLink that is not partial (that is, that is
  // fully encompassed by the startIndex--stopIndex region).

  if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
                       Enums::ChemicalEntity::CROSS_LINKER) &&
     cross_link_indices.size())
    {
      qDebug() << "Fragmentation calculations take "
                  "into account the cross-links and there are "
               << cross_link_indices.size()
               << "cross-links in the selected Oligomer.";

      // Let's put one of the fragmentation configs (the first) so that
      // we can get the indices of the oligomer to fragment.

      FragmentationConfig fragmentation_config = m_fragmentationConfigs.at(0);

      if(partials)
        qDebug() << "Fragmentation calculations do not\n"
                    "take into account partial cross-links.\n"
                    "These partial cross-links are ignored.";

      // Now that we have a container with all of the indices of all the
      // monomers that are cross-linked, we can iterate in it and create a list
      // of CrossLinkedRegion instances that will allow us to "segment" the
      // to-fragment oligomer so as to ease the calculation of product ion
      // masses.

      // Sort the indices.
      std::sort(cross_link_indices.begin(), cross_link_indices.end());

#ifdef QT_DEBUG

      QString indices_text;

      for(std::size_t index : cross_link_indices)
        indices_text += QString("%1\n").arg(index);

      // qDebug().noquote()
      //   << "Indices of all the Monomer instances involved in "
      //      "cross-linked sequences: \n"
      //   << indices_text;

#endif

      // Now find continuous regions and create a new region each
      // time we find one.

      std::size_t first = 0;
      std::size_t last  = 0;

      std::size_t prev = 0;
      std::size_t next = 0;

      std::size_t cross_link_indices_size       = cross_link_indices.size();
      std::size_t last_cross_link_indices_index = cross_link_indices_size - 1;
      bool iterated_in_loop                     = false;

      for(std::size_t iter = 0; iter < last_cross_link_indices_index; ++iter)
        {
          iterated_in_loop = true;

          // Seed the system only at the first iteration.
          if(!iter)
            {
              first = cross_link_indices.at(iter);
              last  = cross_link_indices.at(iter + 1);

              // qDebug() << __FILE__ << __LINE__
              //          << "Seeding with first and last:" << first << "--"
              //          << last;
            }

          prev = cross_link_indices.at(iter);
          next = cross_link_indices.at(iter + 1);

          if(next - prev == 1)
            {
              // We are going on with a continuum. Fine.
              last = next;

              // qDebug() << "Elongating continuum:"
              //          << "[" << first << "-" << last << "]";

              continue;
            }
          else
            {
              // There is a gap. Close the previous continuum and
              // start another one.

              last = prev;

              // qDebug() << "Closing continuum:"
              //          << "[" << first << "-" << last << "]";

              CrossLinkedRegion region(first, last);

              // Get the cross-links for the region.
              std::vector<CrossLinkSPtr> cross_links;

              partials = 0;

              mcsp_polymer->crossLinksInRange(
                first, last, cross_links, partials);

              if(partials)
                qDebug() << "Fragmentation calculations do not\n"
                            "take into account partial cross-links.\n"
                            "These partial cross-links are ignored.";

              // Append the obtained cross-links to the region so
              // that we finalize its construction. Finally append
              // the new region to the list.

              region.appendCrossLinks(cross_links);

              m_crossLinkedRegions.push_back(region);

              // Now that we have closed a continuum, start seeding
              // a new one.
              first = next;
            }
        }
      // End of
      // for(std::size_t iter = 0; iter < cross_link_indices_size; ++iter)

      // qDebug() << "Did iterate in loop ?" << iterated_in_loop;

      // Only close the pending CrossLinkedRegion if we actually did
      // iterate in the loop above.
      if(iterated_in_loop)
        {
          // We have to close the last continuum that we could not close
          // because we ended off the for loop.

          last = next;

          // qDebug() << "Closing continuum:"
          //          << "[" << first << "-" << last << "]";

          CrossLinkedRegion region(first, last);

          // Get the cross-links for the region.
          std::vector<CrossLinkSPtr> cross_links;

          partials = 0;

          mcsp_polymer->crossLinksInRange(first, last, cross_links, partials);

          if(partials)
            qDebug() << "Fragmentation calculations do not\n"
                        "take into account partial cross-links.\n"
                        "These partial cross-links are ignored.";

          // Append the obtained cross-links to the region so
          // that we finalize its construction. Finally append
          // the new region to the list.

          region.appendCrossLinks(cross_links);

          m_crossLinkedRegions.push_back(region);

          // qDebug() << "Determined" << m_crossLinkedRegions.size() <<
          // "regions";

          // for(CrossLinkedRegion &cross_linked_region : m_crossLinkedRegions)
          //   qDebug() << "Region [" << cross_linked_region.getStartIndex() <<
          //   "-"
          //            << cross_linked_region.getStopIndex() << "]";
        }
    }
  // End of
  // if(static_cast<bool>(m_calcOptions.getMonomerEntities() &
  // Enums::ChemicalEntity::CROSS_LINKER) &&
  // cross_link_indices.size())

  // At this point we have a list of regions that we'll be able to
  // use to compute the fragment masses (if cross-links apply).

  // For each fragmentation configs instance in the list, perform the
  // required fragmentation.

  for(const FragmentationConfig &fragmentation_config : m_fragmentationConfigs)
    {
      if(fragmentation_config.getFragEnd() == Enums::FragEnd::NE)
        {
          if(fragmentEndNone(fragmentation_config) == -1)
            return false;
        }
      else if(fragmentation_config.getFragEnd() == Enums::FragEnd::LE)
        {
          if(fragmentEndLeft(fragmentation_config) == -1)
            return false;
        }
      else if(fragmentation_config.getFragEnd() == Enums::FragEnd::RE)
        {
          if(fragmentEndRight(fragmentation_config) == -1)
            return false;
        }
      else
        qFatal() << "Programming error. Erroneous "
                          "Fragmentation End.";
    }

  return true;
}

/*!
\brief Performs the actual fragmentation in the specific case that \a
fragmentation_config indicates the fragment Oligomers to be generated contain no
Polymer end (like immonium ions in protein gas phase chemistry).

Returns the count of produced fragment Oliogomer instances.
*/
int
Fragmenter::fragmentEndNone(const FragmentationConfig &fragmentation_config)
{
  int count = 0;

  // qDebug().noquote()
  //   << "Now fragmenting according to fragmentation_config:"
  //   << fragmentation_config.toString();

  // We are generating fragments that are made of a single monomer,
  // like in the proteinaceous world we have the immonium ions.

  // We will need the isotopic data throughout all of this function.
  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr)
    qFatal() << "Programming error. The isotopic data must be available.";

  CalcOptions calc_options(m_calcOptions);
  calc_options.setCapType(Enums::CapType::NONE);

  // qDebug() << "Now fragmenting END::NONE with calculation options:"
  //          << calc_options.toString();

  for(std::size_t iter = fragmentation_config.getStartIndex();
      iter < fragmentation_config.getStopIndex() + 1;
      ++iter)
    {
      bool frag_rule_applied = false;

      // We create an oligomer which is not ionized(false) but that
      // bears the default ionization rule, because this oligomer
      // might be later used in places where the ionization rule has
      // to be valid. For example, one drag and drop operation might
      // copy this oligomer into a mzLab dialog window where its
      // ionization rule validity might be challenged. Because this
      // fragmentation oligomer will be a neutral species, we should
      // set the level member of the ionization to 0.

      // The general idea is that we will later create as many different
      // oligomers as there are requested levels of ionization. So, for the
      // moment we try to create a "template" oligomer with the ionization rule
      // set but with the ionization level set to 0.

      //  Old version,  see below for Ionizer() in the construction of the
      //  Oligomer.
      // Ionizer temp_ionizer(m_ionizer);
      // temp_ionizer.setLevel(0);

      // Allocate an Oligomer in which we'll update each time the formula
      // that mirrors the masses of it.

      CalcOptions fragment_calc_options(calc_options);
      fragment_calc_options.setIndexRange(iter, iter);

      OligomerSPtr oligomer1_sp = std::make_shared<Oligomer>(
        mcsp_polymer,
        "NOT_SET",
        fragmentation_config.getName() /*fragmentationPathway.m_name*/,
        mcsp_polymer->modifiedMonomerCount(IndexRangeCollection(iter, iter)),
        Ionizer(),
        fragment_calc_options);

      // The formula of the fragmentation specification should yield a neutral
      // molecular species, which is then ionized according to the current
      // Ionizer in the caller context. The levels of this Ionizer are set by
      // the user and default to a single level of ionization.

      // The first step is to calculate the masses of the fragment
      // oligomer without taking into account the ionization,
      // because we still have other things to account for that
      // might interfere with the mass of the fragment. So we pass
      // an invalid temp_ionizer object(upon creation, an Ionizer
      // is invalid).
      Ionizer temp_ionizer;

      if(!oligomer1_sp->calculateMasses(fragment_calc_options, temp_ionizer))
        {
          oligomer1_sp.reset();
          return -1;
        }

      // qDebug() << "Right after creation with Ionizer() at index:" << iter
      //          << "mass calculation:"
      //          << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
      //          << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG);

      // Account in the oligomer's formula for the formula of the monomer.

      // Ask the monomer to actually compute its formula either by taking into
      // account the modifications or not depending on the calculation options
      // below.
      QString monomer_formula_string =
        mcsp_polymer->getSequenceCstRef()
          .getMonomerCstSPtrAt(iter)
          ->calculateFormula(m_calcOptions.getMonomerEntities());

      bool ok = false;

      oligomer1_sp->getFormulaRef().accountFormula(
        monomer_formula_string, isotopic_data_csp, 1, ok);

      if(!ok)
        {
          qDebug() << "Failed to account formula:" << monomer_formula_string;
          return -1;
        }

      // Now start getting deep inside the chemistry of the fragmentation
      // oligomer.
      if(!fragmentation_config.getFormulaCstRef().getActionFormula().isEmpty())
        {
          Formula temp_formula(
            fragmentation_config.getFormulaCstRef().getActionFormula());

          bool ok = false;

          //  First account the masses,  that is MONO and AVG.
          temp_formula.accountMasses(
            ok,
            isotopic_data_csp,
            oligomer1_sp->getMassRef(Enums::MassType::MONO),
            oligomer1_sp->getMassRef(Enums::MassType::AVG));

          if(!ok)
            {
              qDebug() << "Failed to account masses for:"
                       << temp_formula.getActionFormula();
              return -1;
            }

          // qDebug() << "After accounting fragmentation pathway formula:"
          //          << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG);

          // Second,  account the formula.
          oligomer1_sp->getFormulaRef().accountFormula(
            temp_formula.getActionFormula(), isotopic_data_csp, 1, ok);

          if(!ok)
            {
              qDebug() << "Failed to account formula:"
                       << temp_formula.getActionFormula();
              return -1;
            }
        }

      // qDebug() << "After accounting formula, Oligomer:"
      // << oligomer1_sp->toString();

      // At this moment, the new fragment might be challenged for
      // the fragmented monomer's contribution. For example, in
      // nucleic acids, it happens that during a fragmentation, the
      // base of the fragmented monomer is decomposed and goes
      // away. This is implemented here with the ability to
      // tell the fragmenter that upon fragmentation the mass of the
      // monomer is to be removed. The skeleton mass is then added
      // to the formula of the fragmentation pattern (FragmentationPathway).

      int monomer_contribution = fragmentation_config.getMonomerContribution();

      if(monomer_contribution)
        {
          MonomerSPtr monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

          // First the masses.
          monomer_csp->accountMasses(
            oligomer1_sp->getMassRef(Enums::MassType::MONO),
            oligomer1_sp->getMassRef(Enums::MassType::AVG),
            monomer_contribution);

          // qDebug() << "After accounting monomer contribution:"
          //          << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG);

          bool ok = false;

          //  Next the formula.
          oligomer1_sp->getFormulaRef().accountFormula(
            monomer_csp->getFormula(),
            isotopic_data_csp,
            monomer_contribution,
            ok);

          if(!ok)
            {
              qDebug() << "Failed to account formula:"
                       << monomer_csp->getFormula();
              oligomer1_sp.reset();

              return -1;
            }
        }

      // At this point we should check if the fragmentation
      // specification includes fragmentation rules that apply to this
      // fragment. FragmentationConfig is derived from FragmentationPathway.

      for(const FragmentationRuleSPtr &fragmentation_rule_sp :
          fragmentation_config.getRulesCstRef())
        {
          // The accounting of the fragmentationrule is performed on a
          // neutral oligomer, as defined by the fragmentation
          // formula. Later, we'll have to take into account the
          // fact that the user might want to calculate fragment m/z values
          // with z>1.

          // This round is not for real accounting, but only to check if the
          // currently iterated fragmentation rule should be accounted for (see
          // the true that indicates this call is only for checking).

          double mono;
          double avg;

          if(!accountFragmentationRule(fragmentation_rule_sp,
                                       /*only for checking*/ true,
                                       iter,
                                       Enums::FragEnd::NE,
                                       mono,
                                       avg))
            continue;

          // This is why we first check above if the fragmentation rule was to
          // be accounted for: each fragmentationrule triggers the creation of a
          // new oligomer.

          OligomerSPtr oligomer2_sp = std::make_shared<Oligomer>(*oligomer1_sp);

          // qDebug() << "After copying oligomer1_sp into oligomer2_sp:"
          //          << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG);

          accountFragmentationRule(
            fragmentation_rule_sp,
            false,
            iter,
            Enums::FragEnd::NE,
            oligomer2_sp->getMassRef(Enums::MassType::MONO),
            oligomer2_sp->getMassRef(Enums::MassType::AVG));

          // qDebug() << "After accounting fragmentation rule:"
          //          << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG);

          bool ok = false;

          oligomer2_sp->getFormulaRef().accountFormula(
            fragmentation_rule_sp->getFormulaCstRef().getActionFormula(),
            isotopic_data_csp,
            1,
            ok);

          if(!ok)
            {
              qDebug()
                << "Failed to account formula:"
                << fragmentation_rule_sp->getFormulaCstRef().getActionFormula();
              oligomer1_sp.reset();
              oligomer2_sp.reset();

              return -1;
            }

          // Let the following steps know that we actually succeeded in
          // preparing a fragment oligomer with a fragmentation rule applied.
          frag_rule_applied = true;

          // At this point we have the fragment oligomer in a
          // neutral state (the fragmentation specification specifies a formula
          // to create a neutral fragmentation product). This is so that we can
          // later charge the ions as many times as requested by the user.

          // We still have to account for potential formulas set to the
          // FragmentationConfig, like when the user asks that for each product
          // ion -H2O or -NH3 be accounted for,
          //  which happens all the time when doing peptide fragmentations.
          //  These formulas are stored in the m_formulas member of
          //  FragmentationConfig.

          // And once we have done this, we'll still have to actually charge the
          // fragments according to the FragmentationConfig's
          // [m_charge_level_start--m_stopIonizeLevel] range.

          // Each supplementary step above demultiplies the "versions" of the
          // fragment Oligomer currently created (oligomer2_sp). All the newly
          // created "versions"
          //  will need to be stored in a container
          //  (formula_variant_oligomers). So,  for the moment copy the current
          //  oligomer into a template oligomer that will be used as a template
          //  to create all the "variant" oligomers.

          OligomerSPtr template_oligomer_for_formula_variants_sp =
            std::make_shared<Oligomer>(*oligomer2_sp);

          // We can immediately set the name of template oligomer on which
          // to base the creation of the derivative formula-based
          // oligomers.
          QString name = QString("%1#%2#(%3)")
                           .arg(fragmentation_config.getName())
                           .arg(mcsp_polymer->getSequenceCstRef()
                                  .getMonomerCstSPtrAt(iter)
                                  ->getCode())
                           .arg(fragmentation_rule_sp->getName());

          // qDebug() << "Short name of oligomer:" << name;

          int charge = 0;

          // Set the name of this template oligomer, but with the
          // charge in the form of "#z=1".
          QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);

          // qDebug() << "Long name of oligomer:" << name_with_charge;

          template_oligomer_for_formula_variants_sp->setName(name_with_charge);

          // We have not yet finished characterizing fully the oligomer, for
          // example, there might be formulas to be applied to the oligomer.
          // Also, the user might have asked for a number of charge states. To
          // store all these variants, create an oligomer container:

          OligomerCollection formula_variant_oligomers(
            fragmentation_config.getName(), mcsp_polymer);

          // Ask that this new container be filled-in with the variants obtained
          // by applying the Formula instances onto the template oligomer.
          // Indeed, the user may have asked in FragmentationConfig that
          // formulas like -H2O or -NH3 be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          // Note that we use std::move when passing
          // template_oligomer_for_formula_variants_sp to the function because
          // the function will move it as the first item in the
          // formula_variant_oligomers container.

          //            int accountedFormulas =
          accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
                          formula_variant_oligomers,
                          fragmentation_config,
                          name,
                          charge);

          // qDebug() << "There are now"
          //          << formula_variant_oligomers.getOligomersCstRef().size()
          //          << "formula variant oligomers";

          // We now have a container of oligomers (or only one if there
          // was no formula to take into account). For each
          // oligomer, we have to account for the charge levels
          // asked by the user.

          OligomerCollection ionization_variant_oligomers =
            accountIonizationLevels(formula_variant_oligomers,
                                    fragmentation_config);

          //  At this point we do not need the initial *uncharged* Oligomer
          //  instances anymore.
          formula_variant_oligomers.clear();

          // First off, we can finally delete the grand template oligomer.
          oligomer2_sp.reset();

          if(!ionization_variant_oligomers.size())
            {
              qDebug() << "Failed to generate ionized fragment oligomers.";
              return -1;
            }

          // Finally transfer all the oligomers generated for this fragmentation
          // to the container of ALL the oligomers. But before making
          // the transfer, compute the elemental composition and store it
          // as a property object.

          // Involves a std::move operation.
          // std::size_t transferred_count =
          transferOligomers(ionization_variant_oligomers, m_oligomers);

          // qDebug() << "The number of transferred Oligomers:"
          //          << transferred_count;
        }
      // End of
      // for(const FragmentationRuleSPtr &fragmentation_rule_sp :
      //   fragmentation_config.getRulesCstRef())

      // We are here because of two reasons:

      // 1. because the container of fragmentation_rule_sp was empty, in which
      // case we still have to validate and terminate the oligomer1
      // (frag_rule_applied is false);

      // 2. because we finished dealing with the container of
      // fragmentation_rule_sp, in which case we ONLY add oligomer1 to the list
      // of fragments if none of the fragmentationrules analyzed above gave a
      // successfully generated fragment(frag_rule_applied is false).

      if(!frag_rule_applied)
        {
          // At this point we have a single fragment oligomer because we did not
          //  had to apply any fragmentation rule,  and thus have generated no
          //  variant of it. We may have formulas to apply,  as there might be
          //  formulas in FragmentationConfig.

          // So, first create an oligomer with the "default"
          // fragmentation specification-driven neutral state (that
          // is, charge = 0).

          OligomerSPtr template_oligomer_for_formula_variants_sp =
            std::make_shared<Oligomer>(*oligomer1_sp);

          // We can immediately set the name of template oligomer on which
          // to base the creation of the derivative formula-based
          // oligomers.
          QString name = QString("%1#%2")
                           .arg(fragmentation_config.getName())
                           .arg(mcsp_polymer->getSequenceCstRef()
                                  .getMonomerCstSPtrAt(iter)
                                  ->getCode());

          // qDebug() << "Short name of oligomer:" << name;

          int charge = 0;

          // Set the name of this template oligomer, but with the
          // charge in the form of "#z=1".
          QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);

          // qDebug() << "Long name of oligomer:" << name_with_charge;

          template_oligomer_for_formula_variants_sp->setName(name_with_charge);

          // We have not yet finished characterizing fully the oligomer, for
          // example, there might be formulas to be applied to the oligomer.
          // Also, the user might have asked for a number of charge states. To
          // store all these variants, create an oligomer container:

          OligomerCollection formula_variant_oligomers(
            fragmentation_config.getName(), mcsp_polymer);

          // Ask that this new container be filled-in with the variants obtained
          // by applying the Formula instances onto the template oligomer.
          // Indeed, the user may have asked in FragmentationConfig that
          // formulas like -H2O or -NH3 be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          // Note that we use std::move when passing
          // template_oligomer_for_formula_variants_sp to the function because
          // the function will move it as the first item in the
          // formula_variant_oligomers container.

          //            int accountedFormulas =
          accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
                          formula_variant_oligomers,
                          fragmentation_config,
                          name,
                          charge);

          // qDebug() << "There are now"
          //          << formula_variant_oligomers.getOligomersCstRef().size()
          //          << "formula variant oligomers";

          // We now have a container of oligomers (or only one if there
          // was no formula to take into account). For each
          // oligomer, we have to account for the charge levels
          // asked by the user.

          OligomerCollection ionization_variant_oligomers =
            accountIonizationLevels(formula_variant_oligomers,
                                    fragmentation_config);

          //  At this point we do not need the initial *uncharged* Oligomer
          //  instances anymore.
          formula_variant_oligomers.clear();

          // First off, we can finally delete the grand template
          // oligomer (oligomer with no fragmentation rules applied).
          oligomer1_sp.reset();

          if(!ionization_variant_oligomers.size())
            {
              qDebug() << "Failed to generate ionized fragment oligomers.";
              return -1;
            }

          // Finally transfer all the oligomers generated for this fragmentation
          // to the container of ALL the oligomers. But before making
          // the transfer, compute the elemental composition and store it
          // as a property object.

          // Involves a std::move operation.
          std::size_t transferred_count =
            transferOligomers(ionization_variant_oligomers, m_oligomers);

          // qDebug() << "The number of transferred Oligomers:"
          //          << transferred_count;

          count += transferred_count;
        }
      // End of
      // if(!frag_rule_applied)
      else // (frag_rule_applied == true)
        {
          // There were fragmentation rule(s) that could be
          // successfully applied. Thus we already have created the
          // appropriate oligomers. Simply delete the template
          // oligomer.
          oligomer1_sp.reset();
        }
    }
  // End of
  //   for (int iter = fragmentation_config.getStartIndex();
  //   fragmentation_config.getStopIndex() + 1; ++iter)

  return count;
}

/*!
\brief Performs the actual fragmentation in the specific case that \a
fragmentation_config indicates the fragment Oligomers to be generated contain
the left Polymer end (like b ions in protein gas phase chemistry).

Returns the count of produced fragment Oliogomer instances.
*/
int
Fragmenter::fragmentEndLeft(const FragmentationConfig &fragmentation_config)
{
  int count          = 0;
  std::size_t number = 0;
  bool ok;

  double mono = 0;
  double avg  = 0;

  // qDebug().noquote() << "Now fragmenting according to fragmentation_config:"
  //                    << fragmentation_config.toString();

  // Formula to hold the summative result of accounting all of the residues
  // making the residual chain of the fragmentation ion product. Thus, this
  // formula is to be incremented at the beginning of each for loop iteration
  // below.
  Formula residue_chain_formula;
  residue_chain_formula.clear();

  // We will need the isotopic data throughout all of this function.
  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // If the crosslinks are to be taken into account, then make a local copy of
  // the m_crossLinkedRegions because we are going to remove items from it
  // during the calculation of the fragments and we do not want to modify the
  // contents of the original list (remember that this fragmenter might be
  // created not to perform a single fragmentation, but a set of
  // fragmentations).

  std::vector<CrossLinkedRegion> local_cross_linked_regions =
    m_crossLinkedRegions;

  // At this point we can start making the calculations of the
  // fragments. Because we are generating fragments that contain the
  // left part of the oligomer, we iterate in the
  // fragmentation_config.getStartIndex() -->
  // fragmentation_config.getStopIndex() direction.

  // The general idea of the for loop below is that we iterate in the
  // [startIndex-stopIndex) range of the polymer that describes the oligomer to
  // be fragmented. Each iteration adds one monomer. The code below is thus
  // sectioned into parts that are additive between one iteration and another
  // and then parts that are renewed each time.

  // The for loop below is designed to construct product ion oligomers that
  // start at the left end of the precursor ion (a ions,  for example,  in
  // protein chemistry).

  // If the peptide precursor ion is MAMISGMSGR,  for example,

  // The first pass over the loop below, creates fragment

  // M

  // That fragment will have its masses (and chemical formula) computed like so:

  // 1. the mass/formula of the monomer 'M'
  // 2. the mass/formula of the CrossLink instances found being
  //    encompassed by the startIndex--stopIndex length of the fragment.
  //    Obviously,  for this first M,  that is not going to be happening.

  // Points 1 and 2 above produce data that are then incremented during
  //  the next loop iteration,  so that we add the masses for monomer 'A'
  //  in the example sequence at the second iteration and then of 'M' at the
  //  third...

  // But then, at each iteration (first 'M',  then 'A',  then 'M' and so
  // on...), there are calculations that will not apply at each iteration.
  //  For example, the chemical formula that describes the fragmentation
  //  chemistry (for a fragments, -CHO) should be applied only once for all of
  //  the fragments that are elongating through the iteration in the loop.

  // So, at each iteration, we perform the summed calculations 1. and 2. above,
  //  then we make a copy of the obtained results and we apply the
  //  only-once-per-oligomer calculations to these copies.

  // One very important concept here: the Oligomer instances that represent the
  // fragmentation product-ions cannot be represented fully: in particular,
  // the elementalComposition() function cannot be used to compute the formula
  // of the fragmentation oligomer just using CalcOptions (index range data)
  // because the Oligomer object does not know anything of the fragmentation
  // rules or added formula (+H2O or -NH3, for example). So we rely on the
  // member Oligomer::m_formula of Oligomer for this.

  for(std::size_t iter = fragmentation_config.getStartIndex();
      iter < fragmentation_config.getStopIndex();
      ++iter, ++number)
    {
      // This is the common part that will be incremented at each iteration,
      // thus first creating a fragment of one single monomer (index
      // startIndex), then at the second iteration creating an oligomer of 2
      // monomers, [startIndex, startIndex+1] and so on. The formula that gets
      // incremented at each iteration with the monomer's formula is
      // residue_chain_formula.

      bool frag_rule_applied = false;

      // Get a pointer to the polymer's monomer at index iter.
      MonomerSPtr monomer_csp =
        mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

      // The accountMasses function below automatically accounts for the monomer
      // modification(s), if any and if the calculation options do require that
      // these be accounted for.
      monomer_csp->accountMasses(mono, avg, 1);

      // Ask the monomer to actually compute its formula either by taking into
      // account the modifications or not depending on the calculation options
      // below.
      QString monomer_formula_string =
        monomer_csp->calculateFormula(m_calcOptions.getMonomerEntities());

      residue_chain_formula.accountFormula(
        monomer_formula_string, isotopic_data_csp, 1, ok);
      if(!ok)
        {
          qDebug() << "Failed to account formula:" << monomer_formula_string;

          return -1;
        }

      // qDebug() << "Accounted masses for monomer:" << monomer_csp->getCode()
      //          << "at index:" << iter << "mono:" << mono << "avg:" << avg
      //          << "with formula:" <<
      //          residue_chain_formula.getActionFormula();

      // If we are to take into account the cross-links, we ought to
      // take them into account here *once* and then remove them
      // from the local_cross_linked_regions so that we do not take them
      // into account more than once.


      //            [3] [4] [5] [6]        [9]    [11]
      // o---o---o---o---o--o---o---o---o---o---o---o---o---o---o
      //             |   |__|   |       |   |       |       |
      //             |          +-----------+       +-------+
      //             |                  |
      //             +------------------+
      //
      //
      // In the example above, there are two cross-linked regions: [3--9]
      // and [11--13].

      std::vector<CrossLinkedRegion>::iterator the_iterator =
        local_cross_linked_regions.begin();

      // Note below how we need to recompute the end iterator at each loop
      // iteration because we erase items in the loop and thus the
      // end iterator gets invalidated.

      while(the_iterator != local_cross_linked_regions.end())
        {
          CrossLinkedRegion &cross_linked_region = *the_iterator;

          // Because we are generating fragments that increase in size at each
          // iteration from the left end, we know a given fragment encompasses
          // fully a region if its right end (that is the current iteration
          // 'iter') is at least equal to the right end (stop index) of a given
          // region.

          if(cross_linked_region.getStopIndex() == iter)
            {
              // We are iterating in a fragment monomer index (iter) that
              // corresponds to the right end side of a region. Since we already
              // have gone through all the monomer positions at the left of
              // 'iter',  by essence we know we are constructing a fragment that
              // encompasses fully the region.

              //  In this case,  we have to take into account the cross-links
              //  that are referenced in the region.

              // qDebug() << "There are"
              //          << cross_linked_region.getCrossLinksCstRef().size()
              //          << "cross-links involved in the current region.";

              std::size_t iteration_count_for_debugging = 0;

              for(const CrossLinkSPtr &cross_link_sp :
                  cross_linked_region.getCrossLinksCstRef())
                {
                  ++iteration_count_for_debugging;

                  // qDebug() << "At iteration count"
                  //          << iteration_count_for_debugging
                  //          << "going to account for masses for cross-link:"
                  //          <<
                  //          cross_link_sp->getCrossLinkerCstSPtr()->getName();

                  cross_link_sp->accountMasses(mono, avg, 1);

                  // qDebug() << "Accounted masses for cross-link:"
                  //          <<
                  //          cross_link_sp->getCrossLinkerCstSPtr()->getName()
                  //          << "mono:" << mono << "avg:" << avg;

                  // The cross-linker formula might be empty.
                  if(!cross_link_sp->getCrossLinkerCstSPtr()
                        ->getFormula()
                        .isEmpty())
                    {
                      // qDebug()
                      //   << "Now accounting for the cross-link formula:"
                      //   <<
                      //   cross_link_sp->getCrossLinkerCstSPtr()->getFormula();

                      residue_chain_formula.accountFormula(
                        cross_link_sp->getCrossLinkerCstSPtr()->getFormula(),
                        isotopic_data_csp,
                        1,
                        ok);

                      if(!ok)
                        {
                          qDebug() << "Failed to account formula:"
                                   << cross_link_sp->getCrossLinkerCstSPtr()
                                        ->getFormula();

                          return -1;
                        }

                      // qDebug()
                      //   << "Accounted formula for cross-link:"
                      //   << cross_link_sp->getCrossLinkerCstSPtr()->getName()
                      //   << residue_chain_formula.getActionFormula();
                    }
                }

              the_iterator = local_cross_linked_regions.erase(the_iterator);
            }
          // End of
          // if(cross_linked_region.getStopIndex() == iter)
          else
            ++the_iterator;
        }
        // End of
        // while (the_iterator !=  the_end_iterator)

#if 0

      //  Old QList-based version

      // Iterate in the local_cross_linked_regions (do that in reverse
      // order because we'll have at some point to have to remove
      // items) and...

      int jter = local_cross_linked_regions.size() - 1;

      while(jter >= 0)
        {
          // ... for each item in it ask if the region encompasses
          // the current monomer index (value of iter)....

          CrossLinkedRegion region = local_cross_linked_regions.at(jter);

          if(region.getStopIndex() == iter)
            {
              // ... if so, iterate in the list of cross-links that
              // is stored in the CrossLinkedRegion...

              const QList<CrossLink *> &crossLinkList = region.crossLinkList();

              for(int kter = 0; kter < crossLinkList.size(); ++kter)
                {
                  // ... and for each cross-link, account its mass
                  // in the fragment (that is, ponderable)...

                  CrossLink *crossLink = crossLinkList.at(kter);

                  crossLink->accountMasses(&ponderable, 1);

                  residue_chain_formula.accountFormula(
                    crossLink->formula(), isotopic_data_csp, ok, 1);

                  if(!ok)
                    {
                      qDebug()
                        << "Failed to account formula:" << crossLink->formula();

                      return -1;
                    }
                }

              // ... and remove+delete the CrossLinkedRegion from
              // the list so that we are sure we do not take that
              // cross-link into account more than once.

              delete local_cross_linked_regions.takeAt(jter);
            }

          --jter;
        }
#endif

      // Now start a new section that will be specific to this iteration in the
      // for loop. We have summed the masses/formulas of the monomers up to this
      // iteration and we need to finalize the current fragment with
      // calculations that apply only once for each fragment. We thus create a
      // copy of the initial residual-chain-only formula and of the masses.
      // These data are updated each time a new aspect of the fragmentation
      // chemistry will be accounted for.

      double frag_chemistry_mono = mono;
      double frag_chemistry_avg  = avg;

      // Make a copy of the residual chain-only formula so that we can aggregate
      // specific formula components later.
      Formula frag_chemistry_formula(residue_chain_formula);

      // qDebug() << "Now starting fragment position-specific calculations with
      // "
      //             "masses mono:"
      //          << frag_chemistry_mono << "avg:" << frag_chemistry_avg
      //          << "with formula:" <<
      //          frag_chemistry_formula.getActionFormula();

      // FragmentationConfig is a FragmentationPathway that holds the formula
      // that makes a fragment out of an oligomer.
      if(!fragmentation_config.getFormulaCstRef().getActionFormula().isEmpty())
        {
          Formula frag_specific_formula(
            fragmentation_config.getFormulaCstRef().getActionFormula());

          bool ok = false;

          //  First account the masses,  that is MONO and AVG.
          frag_specific_formula.accountMasses(
            ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);

          if(!ok)
            {
              qDebug() << "Failed to account masses for:"
                       << frag_specific_formula.getActionFormula();
              return -1;
            }

          // Second,  account the formula.
          frag_chemistry_formula.accountFormula(
            frag_specific_formula.getActionFormula(), isotopic_data_csp, 1, ok);

          if(!ok)
            {
              qDebug() << "Failed to account formula:"
                       << frag_specific_formula.getActionFormula();

              return -1;
            }

          // qDebug() << "Accounted masses for fragmentation formula:"
          //          << frag_specific_formula.getActionFormula()
          //          << "mono:" << frag_chemistry_mono
          //          << "avg:" << frag_chemistry_avg << "and with formula:"
          //          << frag_chemistry_formula.getActionFormula();
        }

      // Account for the left cap since we are dealing with fragments that
      // retain the left end.
      Formula left_cap_formula = Formula(mcsp_polChemDef->getLeftCap());

      left_cap_formula.accountMasses(
        ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);

      if(!ok)
        {
          qDebug() << "Failed to account masses for left cap:"
                   << left_cap_formula.getActionFormula();
          return -1;
        }

      frag_chemistry_formula.accountFormula(
        left_cap_formula.getActionFormula(), isotopic_data_csp, 1, ok);

      if(!ok)
        {
          qDebug() << "Failed to account left cap formula:"
                   << left_cap_formula.getActionFormula();

          return -1;
        }

      // qDebug() << "Accounted masses for left cap formula:"
      //          << left_cap_formula.getActionFormula()
      //          << "mono:" << frag_chemistry_mono << "avg:" <<
      //          frag_chemistry_avg
      //          << "and with formula:"
      //          << frag_chemistry_formula.getActionFormula();

      //  Same of left end chemical modification only if the left end of this
      //  fragment oligomer actually encompasses the real left end monomer
      //  of the polymer (that is the start index is actually 0).

      if(static_cast<bool>(m_calcOptions.getPolymerEntities() &
                           Enums::ChemicalEntity::LEFT_END_MODIF) &&
         !fragmentation_config.getStartIndex())
        {
          Polymer::accountEndModifMasses(mcsp_polymer.get(),
                                         Enums::ChemicalEntity::LEFT_END_MODIF,
                                         frag_chemistry_mono,
                                         frag_chemistry_avg);

          frag_chemistry_formula.accountFormula(
            mcsp_polymer->getLeftEndModifCstRef().getFormula(),
            isotopic_data_csp,
            1,
            ok);

          if(!ok)
            {
              qDebug() << "Failed to account left end formula:"
                       << mcsp_polymer->getLeftEndModifCstRef().getFormula();

              return -1;
            }

          // qDebug() << "Accounted masses for left end formula:"
          //          << mcsp_polymer->getLeftEndModifCstRef().getFormula()
          //          << "mono:" << frag_chemistry_mono
          //          << "avg:" << frag_chemistry_avg << "and with formula:"
          //          << frag_chemistry_formula.getActionFormula();
        }

      // At this moment, we have crafted the skeleton of the fragment oligomer
      // but we still potentially need to account for a number of chemical
      // entities.

      // We will account for a number of formulas later, for the moment make the
      // oligomer as naked as possible. We'll dress it with formulas as we go.
      // Naked here means just account for the monomers (that is, the monomers
      // and not the monomers along with the end caps, the charge and so on).

      CalcOptions local_calc_options(m_calcOptions);
      local_calc_options.setCapType(Enums::CapType::NONE);

      // qDebug() << "Going to use CalcOptions:" <<
      // local_calc_options.toString()
      //          << "to initialize Oligomer.";

      // We need to set the proper IndexRange for the current iteration in the
      // sequence of the oligomer being fragmented. We know the start index is
      // certainly not changing because we are doing LE fragmentation. The
      // stop index, instead, changes with each iteration of this loop.
      local_calc_options.setIndexRange(fragmentation_config.getStartIndex(),
                                       iter);

      // Create a fragmentation oligomer by constructing it with the
      // data that signal what oligomer it is,  but for the moment
      // empty as far as the formula and the masses are concerned.

      OligomerSPtr oligomer1_sp = std::make_shared<Oligomer>(
        mcsp_polymer,
        "NOT_SET",
        fragmentation_config.getName() /*fragmentationPathway.m_name*/,
        mcsp_polymer->modifiedMonomerCount(
          IndexRangeCollection(fragmentation_config.getStartIndex(), iter)),
        Ionizer(),
        local_calc_options);

      // Now set the masses that were additively crafted along the iterations
      //  in this loop:

      oligomer1_sp->setMasses(frag_chemistry_mono, frag_chemistry_avg);

      // Now  set the formula of the oligomer: set its constructed-empty
      // formula to the formula that resulted from all the previous steps.
      oligomer1_sp->getFormulaRef().accountFormula(
        frag_chemistry_formula.getActionFormula(), isotopic_data_csp, 1, ok);

      if(!ok)
        {
          qDebug() << "Failed to account formula:"
                   << frag_chemistry_formula.getActionFormula();
          oligomer1_sp.reset();
          return -1;
        }

      // qDebug() << "The oligomer1_sp has been created using "
      //             "data crafted up to now and has masses:"
      //          << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
      //          << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
      //          << "and formula:"
      //          << oligomer1_sp->getFormulaRef().getActionFormula()
      //          << "Now accounting (if set) for monomer contribution.";

      // At this moment, the new fragment might be challenged for the
      // fragmented monomer's side chain contribution. For example, in
      // nucleic acids, it happens that during a fragmentation, the
      // base of the fragmented monomer is decomposed and goes
      // away. This is implemented in massXpert with the ability to
      // tell the fragmenter that upon fragmentation the mass of the
      // monomer is to be removed. The skeleton mass is then added to
      // the formula of the fragmentation pattern.

      int monomer_contribution = fragmentation_config.getMonomerContribution();

      if(monomer_contribution)
        {
          MonomerSPtr monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

          // First the masses.
          monomer_csp->accountMasses(
            oligomer1_sp->getMassRef(Enums::MassType::MONO),
            oligomer1_sp->getMassRef(Enums::MassType::AVG),
            monomer_contribution);

          bool ok = false;

          //  Next the formula.
          oligomer1_sp->getFormulaRef().accountFormula(
            monomer_csp->getFormula(),
            isotopic_data_csp,
            monomer_contribution,
            ok);

          if(!ok)
            {
              qDebug() << "Failed to account formula:"
                       << monomer_csp->getFormula();
              oligomer1_sp.reset();

              return -1;
            }

          // qDebug() << "The oligomer1_sp after side chain contribution "
          //             "has masses:"
          //          << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
          //          << "updated formula:"
          //          << oligomer1_sp->getFormulaRef().getActionFormula();
        }

      // At this point we should check if the fragmentation
      // pathway includes fragmentation rules that apply to this
      // fragment. FragmentationConfig is derived from FragmentationPathway.

      for(const FragmentationRuleSPtr &fragmentation_rule_sp :
          fragmentation_config.getRulesCstRef())
        {
          // The accounting of the fragmentationrule is performed on a
          // neutral oligomer, as defined by the fragmentation
          // formula. Later, we'll have to take into account the
          // fact that the user might want to calculate fragment m/z values
          // with z>1.

          // This round is not for real accounting, but only to check of the
          // currently iterated fragmentation rule should be accounted for (see
          // the true that indicates this call is only for checking).

          double mono;
          double avg;

          if(!accountFragmentationRule(fragmentation_rule_sp,
                                       /*only for checking*/ true,
                                       iter,
                                       Enums::FragEnd::LE,
                                       mono,
                                       avg))
            continue;

          // This is why we first check above if the fragmentation rule was to
          // be accounted for: each fragmentationrule triggers the creation of a
          // new oligomer.

          OligomerSPtr oligomer2_sp = std::make_shared<Oligomer>(*oligomer1_sp);

          // qDebug() << "The oligomer2_sp has been copied from "
          //             "oligomer1_sp and has masses:"
          //          << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
          //          << "and formula:"
          //          << oligomer2_sp->getFormulaRef().getActionFormula();

          accountFragmentationRule(
            fragmentation_rule_sp,
            false,
            iter,
            Enums::FragEnd::LE,
            oligomer2_sp->getMassRef(Enums::MassType::MONO),
            oligomer2_sp->getMassRef(Enums::MassType::AVG));

          bool ok = false;

          oligomer2_sp->getFormulaRef().accountFormula(
            fragmentation_rule_sp->getFormulaCstRef().getActionFormula(),
            isotopic_data_csp,
            1,
            ok);

          if(!ok)
            {
              qDebug()
                << "Failed to account formula:"
                << fragmentation_rule_sp->getFormulaCstRef().getActionFormula();
              oligomer1_sp.reset();
              oligomer2_sp.reset();

              return -1;
            }

          // qDebug() << "The oligomer2_sp after accounting for "
          //             "fragmentation rule"
          //          << fragmentation_rule_sp->getName() << "has masses:"
          //          << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
          //          << "updated formula:"
          //          << oligomer2_sp->getFormulaRef().getActionFormula();

          // Let the following steps know that we actually succeeded in
          // preparing an oligomer (oligonucleotide, for example) with a
          // fragmentation rule applied.

          frag_rule_applied = true;

          // At this point we have the fragment oligomer in a
          // neutral state (the fragmentation specification specifies a formula
          // to create a neutral fragmentation product). This is so that we can
          // later charge the ions as many times as requested by the user.

          // We still have to account for potential formulas set to the
          // FragmentationConfig, like when the user asks that for each product
          // ion -H2O or -NH3 be accounted for,
          //  which happens all the time when doing peptide fragmentations.
          //  These formulas are stored in the m_formulas member of
          //  FragmentationConfig.

          // And once we have done this, we'll still have to actually charge the
          // fragments according to the FragmentationConfig's
          // [m_startIonizeLevel--m_stopIonizeLevel] range.

          // Each supplementary step above demultiplies the "versions" of the
          // fragment Oligomer currently created (oligomer2_sp). All the newly
          // created "versions"
          //  will need to be stored in a container
          //  (formula_variant_oligomers). So,  for the moment copy the current
          //  oligomer into a template oligomer that will be used as a template
          //  to create all the "variant" oligomers.

          OligomerSPtr template_oligomer_for_formula_variants_sp =
            std::make_shared<Oligomer>(*oligomer2_sp);

          // We can immediately set the name of template oligomer on which
          // to base the creation of the derivative formula-based
          // oligomers.
          QString name = QString("%1#%2#(%3)")
                           .arg(fragmentation_config.getName())
                           .arg(number + 1)
                           .arg(fragmentation_rule_sp->getName());

          int charge = 0;

          // Set the name of this template oligomer, but with the
          // charge in the form of "#z=1".
          QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);

          template_oligomer_for_formula_variants_sp->setName(name_with_charge);

          // qDebug() << "The template_oligomer_for_formula_variants_sp is
          // created"
          //             "by copying over oligomer2_sp. It now has a new name:"
          //          << fragmentation_rule_sp->getName() << " and has masses:"
          //          << "mono:"
          //          << template_oligomer_for_formula_variants_sp->getMass(
          //               Enums::MassType::MONO)
          //          << "avg:"
          //          << template_oligomer_for_formula_variants_sp->getMass(
          //               Enums::MassType::AVG)
          //          << "updated formula:"
          //          <<
          //          template_oligomer_for_formula_variants_sp->getFormulaRef()
          //               .getActionFormula();

          // We have not yet finished characterizing fully the oligomer, for
          // example, there might be formulas to be applied to the oligomer.
          // Also, the user might have asked for a number of charge states. To
          // store all these variants, create an oligomer container:

          OligomerCollection formula_variant_oligomers(
            fragmentation_config.getName(), mcsp_polymer);

          // Ask that this new container be filled-in with the variants obtained
          // by applying the Formula instances onto the template oligomer.
          // Indeed, the user may have asked in FragmentationConfig that
          // formulas like -H2O or -NH3 be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          // Note that we use std::move when passing
          // template_oligomer_for_formula_variants_sp to the function because
          // the function will move it as the first item in the
          // formula_variant_oligomers container.

          //            int accountedFormulas =
          accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
                          formula_variant_oligomers,
                          fragmentation_config,
                          name,
                          charge);

          // qDebug() << "There are now"
          //          << formula_variant_oligomers.getOligomersCstRef().size()
          //          << "formula variant oligomers";

          // We now have a container of oligomers (or only one if there
          // was no formula to take into account). For each
          // oligomer, we have to account for the charge levels
          // asked by the user.

          OligomerCollection ionization_variant_oligomers =
            accountIonizationLevels(formula_variant_oligomers,
                                    fragmentation_config);

          // qDebug() << "We now got"
          //          <<
          //          ionization_variant_oligomers.getOligomersCstRef().size()
          //          << "ionization variant oligomers:\n"
          //          << ionization_variant_oligomers.toString();

          //  At this point we do not need the initial *uncharged* Oligomer
          //  instances anymore.
          formula_variant_oligomers.clear();

          // qDebug() << "We still have"
          //          <<
          //          ionization_variant_oligomers.getOligomersCstRef().size()
          //          << "ionization variant oligomers";

          // First off, we can finally delete the grand template oligomer.
          oligomer2_sp.reset();

          if(!ionization_variant_oligomers.size())
            {
              qDebug() << "Failed to generate ionized fragment oligomers.";
              return -1;
            }

          // Finally transfer all the oligomers generated for this fragmentation
          // to the container of ALL the oligomers. But before making
          // the transfer, compute the elemental composition and store it
          // as a property object.

          // Involves a std::move operation.
          std::size_t transferred_count =
            transferOligomers(ionization_variant_oligomers, m_oligomers);

          // qDebug() << "The number of transferred Oligomers:"
          //          << transferred_count;

          // qDebug().noquote()
          //   << "And now we have" << m_oligomers.getOligomersCstRef().size()
          //   << "oligomers in total, listed below:\n"
          //   << m_oligomers.toString();

          count += transferred_count;
        }
      // End of
      // for(const FragmentationRuleSPtr &fragmentation_rule_sp :
      //   fragmentation_config.getRulesCstRef())

      // We are here because of two possible reasons:

      // 1. because the container of fragmentation_rule_sp was empty, in which
      // case we still have to validate and terminate the oligomer1
      // (frag_rule_applied is false);

      // 2. because we finished dealing with the container of
      // fragmentation_rule_sp, in which case we ONLY add oligomer1 to the list
      // of fragments if none of the fragmentationrules analyzed above gave a
      // successfully generated fragment(frag_rule_applied is false).

      if(!frag_rule_applied)
        {
          // At this point we have a single fragment oligomer because we did not
          //  had to apply any fragmentation rule,  and thus have generated no
          //  variant of it. We may have formulas to apply,  as there might be
          //  formulas in FragmentationConfig.

          // qDebug() << "No FragmentationRule had to be applied. We thus have "
          //             "a single oligomer at the moment.";

          // So, first create an oligomer with the "default"
          // fragmentation specification-driven neutral state (that
          // is, charge = 0).

          OligomerSPtr template_oligomer_for_formula_variants_sp =
            std::make_shared<Oligomer>(*oligomer1_sp);

          // qDebug()
          //   << "Created copy of oligomer1_sp as "
          //      "template_oligomer_for_formula_variants_sp for accounting "
          //      "formulas, like +H2O or -NH3, for example, with masses:"
          //   << "mono:"
          //   << template_oligomer_for_formula_variants_sp->getMass(
          //        Enums::MassType::MONO)
          //   << "avg:"
          //   <<
          //   template_oligomer_for_formula_variants_sp->getMass(Enums::MassType::AVG)
          //   << "and formula:"
          //   << template_oligomer_for_formula_variants_sp->getFormulaCstRef()
          //        .getActionFormula();

          // We can immediately set the name of template oligomer on which
          // to base the creation of the derivative formula-based
          // oligomers.
          QString name = QString("%1#%2")
                           .arg(fragmentation_config.getName())
                           .arg(number + 1);

          int charge = 0;

          // Set the name of this template oligomer, but with the
          // charge in the form of "#z=1".
          QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);

          template_oligomer_for_formula_variants_sp->setName(name_with_charge);

          // We have not yet finished characterizing fully the oligomer, for
          // example, there might be formulas to be applied to the oligomer.
          // Also, the user might have asked for a number of charge states. To
          // store all these variants, create an oligomer container:

          OligomerCollection formula_variant_oligomers(
            fragmentation_config.getName(), mcsp_polymer);

          // Ask that this new container be filled-in with the variants obtained
          // by applying the Formula instances onto the template oligomer.
          // Indeed, the user may have asked in FragmentationConfig that
          // formulas like -H2O or -NH3 be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          // Note that we use std::move when passing
          // template_oligomer_for_formula_variants_sp to the function because
          // the function will move it as the first item in the
          // formula_variant_oligomers container.

          // qDebug() << "Now accounting (if any) for added formulas, like +H2O
          // "
          //             "or -NH3 or any user-defined formula.";

          //            int accountedFormulas =
          accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
                          formula_variant_oligomers,
                          fragmentation_config,
                          name,
                          charge);

          // qDebug() << "After accounting (if any) for formulas, there are"
          //          << formula_variant_oligomers.getOligomersCstRef().size()
          //          << "formula variant oligomers.";

          // We now have a list of oligomers (or only one if there
          // was no formula to take into account). For each
          // oligomer, we have to account for the charge levels
          // asked by the user.

          // qDebug() << "Now accounting for the ionization level(s).";

          OligomerCollection ionization_variant_oligomers =
            accountIonizationLevels(formula_variant_oligomers,
                                    fragmentation_config);

          // qDebug().noquote()
          //   << "We now got"
          //   << ionization_variant_oligomers.getOligomersCstRef().size()
          //   << "ionization variant oligomers:\n"
          //   << ionization_variant_oligomers.toString();

          //  At this point we do not need the initial *uncharged* Oligomer
          //  instances anymore.
          formula_variant_oligomers.clear();

          // qDebug() << "We still have"
          //          <<
          //          ionization_variant_oligomers.getOligomersCstRef().size()
          //          << "ionization variant oligomers";

          if(!ionization_variant_oligomers.size())
            {
              qDebug() << "Failed to generate ionized fragment oligomers.";
              return -1;
            }

          // First off, we can finally delete the grand template
          // oligomer (oligomer with no fragmentation rules applied).
          oligomer1_sp.reset();

          // Finally transfer all the oligomers generated for this fragmentation
          // to the container of ALL the oligomers. But before making
          // the transfer, compute the elemental composition and store it
          // as a property object.

          // Involves a std::move operation.
          std::size_t transferred_count =
            transferOligomers(ionization_variant_oligomers, m_oligomers);

          // qDebug() << "The number of transferred Oligomers:"
          //          << transferred_count;

          // qDebug().noquote()
          //   << "And now we have" << m_oligomers.getOligomersCstRef().size()
          //   << "oligomers in total, listed below:\n"
          //   << m_oligomers.toString();

          count += transferred_count;
        }
      // End of
      // if(!frag_rule_applied)
      else // (frag_rule_applied == true)
        {
          // There were fragmentation rule(s) that could be
          // successfully applied. Thus we already have created the
          // appropriate oligomers. Simply delete the template
          // oligomer.
          oligomer1_sp.reset();
        }
    }
  // End of
  //   for (int iter = fragmentation_config.getStartIndex();
  //   iter < fragmentation_config.getStopIndex() + 1; ++iter, ++count)

  return count;
}

/*!
\briefPerforms the actual fragmentation in the specific case that \a
fragmentation_config indicates the fragment Oligomers to be generated contain
the right Polymer end (like y ions in protein gas phase chemistry).

Returns the count of produced fragment Oliogomer instances.
*/
int
Fragmenter::fragmentEndRight(const FragmentationConfig &fragmentation_config)
{
  int count          = 0;
  std::size_t number = 0;
  bool ok;

  double mono = 0;
  double avg  = 0;

  // qDebug().noquote() << "Now fragmenting according to fragmentation_config:"
  //                    << fragmentation_config.toString();

  // Formula to hold the summative result of accounting all of the residues
  // making the residual chain of the fragmentation ion product. Thus, this
  // formula is to be incremented at the beginning of each for loop iteration
  // below.
  Formula residue_chain_formula;
  residue_chain_formula.clear();

  // We will need the isotopic data throughout all of this function.
  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // If the crosslinks are to be taken into account, then make a local copy of
  // the m_crossLinkedRegions because we are going to remove items from it
  // during the calculation of the fragments and we do not want to modify the
  // contents of the original list (remember that this fragmenter might be
  // created not to perform a single fragmentation, but a set of
  // fragmentations).

  std::vector<CrossLinkedRegion> local_cross_linked_regions =
    m_crossLinkedRegions;

  // See the fragmentEndLeft() very detailed explanations above.

  for(std::size_t iter = fragmentation_config.getStopIndex();
      iter > fragmentation_config.getStartIndex();
      --iter, ++number)
    {
      // This is the common part that will be incremented at each iteration,
      // thus first creating a fragment of one single monomer (index
      // stopIndex), then at the second run creating an oligomer of 2 monomers,
      // [stopIndex -1, stopIndex] and so on. The formula that gets incremented
      // at each iteration with the monomer's formula is residue_chain_formula.

      bool frag_rule_applied = false;

      // Get a pointer to the polymer's monomer at index iter.
      MonomerSPtr monomer_csp =
        mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

      // The accountMasses function below automatically accounts for the monomer
      // modification(s), if any and if the calculation options do required that
      // these be accounted for.
      monomer_csp->accountMasses(mono, avg, 1);

      // Ask the monomer to actually compute its formula either by taking into
      // account the modifications or not depending on the calculation options
      // below.
      QString monomer_formula_string =
        monomer_csp->calculateFormula(m_calcOptions.getMonomerEntities());

      residue_chain_formula.accountFormula(
        monomer_formula_string, isotopic_data_csp, 1, ok);
      if(!ok)
        {
          qDebug() << "Failed to account formula:" << monomer_formula_string;

          return -1;
        }

      // qDebug() << "Accounted masses for monomer:" << monomer_csp->getCode()
      //          << "at index:" << iter << "mono:" << mono << "avg:" << avg
      //          << "with formula:" <<
      //          residue_chain_formula.getActionFormula();

      // qDebug() << "Accounted formula for monomer:"
      //          << residue_chain_formula.getActionFormula();

      // If we are to take into account the cross-links, we ought to
      // take them into account here *once* and then remove them
      // from the local_cross_linked_regions so that we do not take them
      // into account more than once.


      //            [3] [4] [5] [6]        [9]    [11]
      // o---o---o---o---o--o---o---o---o---o---o---o---o---o---o
      //             |   |__|   |       |   |       |       |
      //             |          +-----------+       +-------+
      //             |                  |
      //             +------------------+
      //
      //
      // In the example above, there are two cross-linked regions: [3--9]
      // and [11--13].

      // Iterate in the crossLinkedRegionList (do that in reverse
      // order because we'll have at some point to have to remove
      // items) and...

      std::vector<CrossLinkedRegion>::iterator the_iterator =
        local_cross_linked_regions.begin();

      // Note below how we need to recompute the end iterator at each loop
      // iteration because we erase items in the loop and thus the
      // end iterator gets invalidated.

      while(the_iterator != local_cross_linked_regions.end())
        {
          CrossLinkedRegion &cross_linked_region = *the_iterator;

          // Because we are generating fragments that increase in size at each
          // iteration from the right end, we know a given fragment encompasses
          // fully a region if its left end (that is the current iteration
          // 'iter') is at least equal to the left end (start index) of a given
          // region.

          if(cross_linked_region.getStartIndex() == iter)
            {
              // We are iterating in a fragment monomer index (iter) that
              // corresponds to the left end side of a region. Since we already
              // have gone through all the monomer positions at the right of
              // 'iter', by essence we know we are constructing a fragment that
              // encompasses fully the region.

              //  In this case,  we have to take into account the cross-links
              //  that are referenced in the region.

              // qDebug() << "There are"
              //          << cross_linked_region.getCrossLinksCstRef().size()
              //          << "cross-links involved in the current region.";

              std::size_t iteration_count_for_debugging = 0;

              for(const CrossLinkSPtr &cross_link_sp :
                  cross_linked_region.getCrossLinksCstRef())
                {
                  ++iteration_count_for_debugging;

                  // qDebug() << "At iteration count"
                  //          << iteration_count_for_debugging
                  //          << "going to account for masses for cross-link:"
                  //          <<
                  //          cross_link_sp->getCrossLinkerCstSPtr()->getName();

                  cross_link_sp->accountMasses(mono, avg, 1);

                  // qDebug() << "Accounted masses for cross-link:"
                  //          <<
                  //          cross_link_sp->getCrossLinkerCstSPtr()->getName()
                  //          << "mono:" << mono << "avg:" << avg;

                  // The cross-linker formula might be empty.
                  if(!cross_link_sp->getCrossLinkerCstSPtr()
                        ->getFormula()
                        .isEmpty())
                    {
                      // qDebug()
                      //   << "Now accounting for the cross-link formula:"
                      //   <<
                      //   cross_link_sp->getCrossLinkerCstSPtr()->getFormula();

                      residue_chain_formula.accountFormula(
                        cross_link_sp->getCrossLinkerCstSPtr()->getFormula(),
                        isotopic_data_csp,
                        1,
                        ok);

                      if(!ok)
                        {
                          qDebug() << "Failed to account formula:"
                                   << cross_link_sp->getCrossLinkerCstSPtr()
                                        ->getFormula();

                          return -1;
                        }

                      // qDebug()
                      //   << "Accounted formula for cross-link:"
                      //   << cross_link_sp->getCrossLinkerCstSPtr()->getName()
                      //   << residue_chain_formula.getActionFormula();
                    }
                }

              the_iterator = local_cross_linked_regions.erase(the_iterator);
            }
          // End of
          // if(cross_linked_region.getStopIndex() == iter)
          else
            ++the_iterator;
        }
      // End of
      // while (the_iterator !=  the_end_iterator)

      // Now start a new section that will be specific to this iteration in the
      // for loop. We have summed the masses/formulas of the monomers up to this
      // iteration and we need to finalize the current fragment with
      // calculations that apply only once for each fragment. We thus create a
      // copy of the initial residual-chain-only formula and of the masses.
      // These data are updated each time a new aspect of the fragmentation
      // chemistry will be accounted for.

      double frag_chemistry_mono = mono;
      double frag_chemistry_avg  = avg;

      Formula frag_chemistry_formula(residue_chain_formula);

      // FragmentationConfig is a FragmentationPathway that holds the formula
      // that makes a fragment out of an oligomer.
      if(!fragmentation_config.getFormulaCstRef().getActionFormula().isEmpty())
        {
          Formula frag_specific_formula(
            fragmentation_config.getFormulaCstRef().getActionFormula());

          bool ok = false;

          //  First account the masses,  that is MONO and AVG.
          frag_specific_formula.accountMasses(
            ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);

          if(!ok)
            {
              qDebug() << "Failed to account masses for:"
                       << frag_specific_formula.getActionFormula();
              return -1;
            }

          // Second,  account the formula.
          frag_chemistry_formula.accountFormula(
            frag_specific_formula.getActionFormula(), isotopic_data_csp, 1, ok);

          if(!ok)
            {
              qDebug() << "Failed to account formula:"
                       << frag_specific_formula.getActionFormula();

              return -1;
            }

          // qDebug() << "Accounted masses for fragmentation formula:"
          //          << frag_specific_formula.getActionFormula()
          //          << "mono:" << frag_chemistry_mono
          //          << "avg:" << frag_chemistry_avg << "and with formula:"
          //          << frag_chemistry_formula.getActionFormula();
        }

      // Account for the right cap since we are dealing with fragments that
      // retain the right end.
      Formula right_cap_formula = Formula(mcsp_polChemDef->getRightCap());

      right_cap_formula.accountMasses(
        ok, isotopic_data_csp, frag_chemistry_mono, frag_chemistry_avg);

      if(!ok)
        {
          qDebug() << "Failed to account masses for right cap:"
                   << right_cap_formula.getActionFormula();
          return -1;
        }

      frag_chemistry_formula.accountFormula(
        right_cap_formula.getActionFormula(), isotopic_data_csp, 1, ok);

      if(!ok)
        {
          qDebug() << "Failed to account right cap formula:"
                   << right_cap_formula.getActionFormula();

          return -1;
        }

      // qDebug() << "Accounted masses for right cap formula:"
      //          << right_cap_formula.getActionFormula()
      //          << "mono:" << frag_chemistry_mono << "avg:" <<
      //          frag_chemistry_avg
      //          << "and with formula:"
      //          << frag_chemistry_formula.getActionFormula();

      //  Same of right end chemical modification only if the right end of this
      //  fragment oligomer actually encompasses the real left end monomer
      //  of the polymer (that is the end index is actually size() - 1).

      if(static_cast<bool>(m_calcOptions.getPolymerEntities() &
                           Enums::ChemicalEntity::RIGHT_END_MODIF) &&
         fragmentation_config.getStopIndex() == mcsp_polymer->size() - 1)
        {
          Polymer::accountEndModifMasses(mcsp_polymer.get(),
                                         Enums::ChemicalEntity::RIGHT_END_MODIF,
                                         frag_chemistry_mono,
                                         frag_chemistry_avg);

          frag_chemistry_formula.accountFormula(
            mcsp_polymer->getRightEndModifCstRef().getFormula(),
            isotopic_data_csp,
            1,
            ok);

          if(!ok)
            {
              qDebug() << "Failed to account right end formula:"
                       << mcsp_polymer->getRightEndModifCstRef().getFormula();

              return -1;
            }

          // qDebug() << "Accounted masses for right end formula:"
          //          << mcsp_polymer->getRightEndModifCstRef().getFormula()
          //          << "mono:" << frag_chemistry_mono
          //          << "avg:" << frag_chemistry_avg << "and with formula:"
          //          << frag_chemistry_formula.getActionFormula();
        }

      // The polymer chemistry definition defines a formula for the
      // FragmentationPathway that yields a fragment oligomer having no
      // charge: in a neutral state.

      // The general idea is that we will later create as many different
      // oligomers as there are requested levels of ionization. So, for the
      // moment we try to create a "template" oligomer with the ionization rule
      // set but with the ionization level set to 0 and false for the ionization
      // status.

      // We will account for a number of formulas later, for the moment make the
      // oligomer as naked as possible. We'll dress it with formulas as we go.
      // Naked here means just account for the monomers (that its the monomers
      // and not the monomers along with the end caps, the charge and so on).
      CalcOptions local_calc_options(m_calcOptions);
      local_calc_options.setCapType(Enums::CapType::NONE);

      // qDebug() << "Going to use CalcOptions:" <<
      // local_calc_options.toString()
      //          << "to initialize Oligomer.";

      // We need to set the proper IndexRange for the current iteration in the
      // sequence of the oligomer being fragmented. We know the stop index is
      // certainly not changing because we are doing RE fragmentation. The
      // start index, instead, changes with each iteration of this loop.
      local_calc_options.setIndexRange(iter,
                                       fragmentation_config.getStopIndex());

      // Create a fragmentation oligomer by constructing it with the
      // data that signal what oligomer it is,  but for the moment
      // empty as far as the formula and the masses are concerned.

      OligomerSPtr oligomer1_sp = std::make_shared<Oligomer>(
        mcsp_polymer,
        "NOT_SET",
        fragmentation_config.getName() /*fragmentationPathway.m_name*/,
        mcsp_polymer->modifiedMonomerCount(
          IndexRangeCollection(iter, fragmentation_config.getStopIndex())),
        Ionizer(),
        local_calc_options);

      // Now set the masses that were additively crafted along the iterations
      //  in this loop:

      oligomer1_sp->setMasses(frag_chemistry_mono, frag_chemistry_avg);

      // Now  set the formula of the oligomer: set its constructed-empty
      // formula to the formula that resulted from all the previous steps.
      oligomer1_sp->getFormulaRef().accountFormula(
        frag_chemistry_formula.getActionFormula(), isotopic_data_csp, 1, ok);

      if(!ok)
        {
          qDebug() << "Failed to account formula:"
                   << frag_chemistry_formula.getActionFormula();
          oligomer1_sp.reset();
          return -1;
        }

      // qDebug() << "The oligomer1_sp before side chain contribution "
      //             "has masses:"
      //          << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
      //          << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
      //          << "and formula:"
      //          << oligomer1_sp->getFormulaCstRef().getActionFormula();

      // At this moment, the new fragment might be challenged for the
      // fragmented monomer's side chain contribution. For example, in
      // nucleic acids, it happens that during a fragmentation, the
      // base of the fragmented monomer is decomposed and goes
      // away. This is implemented in massXpert with the ability to
      // tell the fragmenter that upon fragmentation the mass of the
      // monomer is to be removed. The skeleton mass is then added to
      // the formula of the fragmentation pattern.

      int monomer_contribution = fragmentation_config.getMonomerContribution();

      if(monomer_contribution)
        {
          MonomerSPtr monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

          // First the masses.
          monomer_csp->accountMasses(
            oligomer1_sp->getMassRef(Enums::MassType::MONO),
            oligomer1_sp->getMassRef(Enums::MassType::AVG),
            monomer_contribution);

          bool ok = false;

          //  Next the formula.
          oligomer1_sp->getFormulaRef().accountFormula(
            monomer_csp->getFormula(),
            isotopic_data_csp,
            monomer_contribution,
            ok);

          if(!ok)
            {
              qDebug() << "Failed to account formula:"
                       << monomer_csp->getFormula();
              oligomer1_sp.reset();

              return -1;
            }

          // qDebug() << "The oligomer1_sp after side chain contribution "
          //             "has masses:"
          //          << "mono:" << oligomer1_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer1_sp->getMass(Enums::MassType::AVG)
          //          << "updated formula:"
          //          << oligomer1_sp->getFormulaRef().getActionFormula();
        }

      // At this point we should check if the fragmentation
      // specification includes fragmentation rules that apply to this
      // fragment.

      for(const FragmentationRuleSPtr &fragmentation_rule_sp :
          fragmentation_config.getRulesCstRef())
        {
          // The accounting of the fragmentationrule is performed on a
          // neutral oligomer, as defined by the fragmentation
          // formula. Later, we'll have to take into account the
          // fact that the user might want to calculate fragment m/z values
          // with z>1.

          // This round is not for real accounting, but only to check of the
          // currently iterated fragmentation rule should be accounted for (see
          // the true that indicates this call is only for checking).

          double mono;
          double avg;

          if(!accountFragmentationRule(fragmentation_rule_sp,
                                       /*only for checking*/ true,
                                       iter,
                                       Enums::FragEnd::RE,
                                       mono,
                                       avg))
            continue;

          // This is why we first check above if the fragmentation rule was to
          // be accounted for: each fragmentationrule triggers the creation of a
          // new oligomer.

          OligomerSPtr oligomer2_sp = std::make_shared<Oligomer>(*oligomer1_sp);

          // qDebug() << "The oligomer2_sp has been copied from "
          //             "oligomer1_sp and "
          //             "has masses:"
          //          << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
          //          << "and formula:"
          //          << oligomer2_sp->getFormulaRef().getActionFormula();


          accountFragmentationRule(
            fragmentation_rule_sp,
            false,
            iter,
            Enums::FragEnd::RE,
            oligomer2_sp->getMassRef(Enums::MassType::MONO),
            oligomer2_sp->getMassRef(Enums::MassType::AVG));

          bool ok = false;

          oligomer2_sp->getFormulaRef().accountFormula(
            fragmentation_rule_sp->getFormulaCstRef().getActionFormula(),
            isotopic_data_csp,
            1,
            ok);

          if(!ok)
            {
              qDebug()
                << "Failed to account formula:"
                << fragmentation_rule_sp->getFormulaCstRef().getActionFormula();
              oligomer1_sp.reset();
              oligomer2_sp.reset();

              return -1;
            }

          // qDebug() << "The oligomer2_sp after accounting for "
          //             "fragmentation rule"
          //          << fragmentation_rule_sp->getName() << "has masses:"
          //          << "mono:" << oligomer2_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" << oligomer2_sp->getMass(Enums::MassType::AVG)
          //          << "updated formula:"
          //          << oligomer2_sp->getFormulaRef().getActionFormula();

          // Let the following steps know that we actually succeeded
          // in preparing an oligonucleotide with a fragmentation
          // rule applied.

          frag_rule_applied = true;

          // At this point we have the fragment oligomer in a
          // neutral state (the fragmentation specification specifies a formula
          // to create a neutral fragmentation product). This is so that we can
          // later charge the ions as many times as requested by the user.

          // We still have to account for potential formulas set to the
          // FragmentationConfig, like when the user asks that for each product
          // ion -H2O or -NH3 be accounted for,
          //  which happens all the time when doing peptide fragmentations.
          //  These formulas are stored in the m_formulas member of
          //  FragmentationConfig.

          // And once we have done this, we'll still have to actually charge the
          // fragments according to the FragmentationConfig's
          // [m_charge_level_start--m_stopIonizeLevel] range.

          // Each supplementary step above demultiplies the "versions" of the
          // fragment Oligomer currently created (oligomer2_sp). All the newly
          // created "versions"
          //  will need to be stored in a container
          //  (formula_variant_oligomers). So,  for the moment copy the current
          //  oligomer into a template oligomer that will be used as a template
          //  to create all the "variant" oligomers.

          OligomerSPtr template_oligomer_for_formula_variants_sp =
            std::make_shared<Oligomer>(*oligomer2_sp);

          // We can immediately set the name of template oligomer on which
          // to base the creation of the derivative formula-based
          // oligomers.
          QString name = QString("%1#%2#(%3)")
                           .arg(fragmentation_config.getName())
                           .arg(number + 1)
                           .arg(fragmentation_rule_sp->getName());

          int charge = 0;

          // Set the name of this template oligomer, but with the
          // charge in the form of "#z=1".
          QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);

          template_oligomer_for_formula_variants_sp->setName(name_with_charge);

          // We have not yet finished characterizing fully the oligomer, for
          // example, there might be formulas to be applied to the oligomer.
          // Also, the user might have asked for a number of charge states. To
          // store all these variants, create an oligomer container:

          OligomerCollection formula_variant_oligomers(
            fragmentation_config.getName(), mcsp_polymer);

          // Ask that this new container be filled-in with the variants obtained
          // by applying the Formula instances onto the template oligomer.
          // Indeed, the user may have asked in FragmentationConfig that
          // formulas like -H2O or -NH3 be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          // Note that we use std::move when passing
          // template_oligomer_for_formula_variants_sp to the function because
          // the function will move it as the first item in the
          // formula_variant_oligomers container.

          //            int accountedFormulas =
          accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
                          formula_variant_oligomers,
                          fragmentation_config,
                          name,
                          charge);

          // qDebug() << "There are now"
          //          << formula_variant_oligomers.getOligomersCstRef().size()
          //          << "formula variant oligomers";

          // We now have a container of oligomers (or only one if there
          // was no formula to take into account). For each
          // oligomer, we have to account for the charge levels
          // asked by the user.

          OligomerCollection ionization_variant_oligomers =
            accountIonizationLevels(formula_variant_oligomers,
                                    fragmentation_config);

          // qDebug() << "We now got"
          //          <<
          //          ionization_variant_oligomers.getOligomersCstRef().size()
          //          << "ionization variant oligomers";

          //  At this point we do not need the initial *uncharged* Oligomer
          //  instances anymore.
          formula_variant_oligomers.clear();

          // qDebug() << "We still have"
          //          <<
          //          ionization_variant_oligomers.getOligomersCstRef().size()
          //          << "ionization variant oligomers";

          // First off, we can finally delete the grand template oligomer.
          oligomer2_sp.reset();

          if(!ionization_variant_oligomers.size())
            {
              qDebug() << "Failed to generate ionized fragment oligomers.";
              return -1;
            }

          // Finally transfer all the oligomers generated for this fragmentation
          // to the container of ALL the oligomers. But before making
          // the transfer, compute the elemental composition and store it
          // as a property object.

          // Involves a std::move operation.
          std::size_t transferred_count =
            transferOligomers(ionization_variant_oligomers, m_oligomers);

          // qDebug() << "The number of transferred Oligomers:"
          //          << transferred_count;

          // qDebug() << "And now we have"
          //          << m_oligomers.getOligomersCstRef().size()
          //          << "oligomers in total";

          count += transferred_count;
        }
      // End of
      // for(const FragmentationRuleSPtr &fragmentation_rule_sp :
      //   fragmentation_config.getRulesCstRef())

      // We are here because of two possible reasons:

      // 1. because the container of fragmentation_rule_sp was empty, in which
      // case we still have to validate and terminate the oligomer1
      // (frag_rule_applied is false);

      // 2. because we finished dealing with the container of
      // fragmentation_rule_sp, in which case we ONLY add oligomer1 to the list
      // of fragments if none of the fragmentationrules analyzed above gave a
      // successfully generated fragment(frag_rule_applied is false).

      if(!frag_rule_applied)
        {
          // At this point we have a single fragment oligomer because we did not
          //  had to apply any fragmentation rule,  and thus have generated no
          //  variant of it. We may have formulas to apply,  as there might be
          //  formulas in FragmentationConfig.

          // So, first create an oligomer with the "default"
          // fragmentation specification-driven neutral state (that
          // is, charge = 0).

          OligomerSPtr template_oligomer_for_formula_variants_sp =
            std::make_shared<Oligomer>(*oligomer1_sp);

          // We can immediately set the name of template oligomer on which
          // to base the creation of the derivative formula-based
          // oligomers.
          QString name = QString("%1#%2")
                           .arg(fragmentation_config.getName())
                           .arg(number + 1);

          int charge = 0;

          // Set the name of this template oligomer, but with the
          // charge in the form of "#z=1".
          QString name_with_charge = QString("%1#z=%2").arg(name).arg(charge);

          template_oligomer_for_formula_variants_sp->setName(name_with_charge);

          // We have not yet finished characterizing fully the oligomer, for
          // example, there might be formulas to be applied to the oligomer.
          // Also, the user might have asked for a number of charge states. To
          // store all these variants, create an oligomer container:

          OligomerCollection formula_variant_oligomers(
            fragmentation_config.getName(), mcsp_polymer);

          // Ask that this new container be filled-in with the variants obtained
          // by applying the Formula instances onto the template oligomer.
          // Indeed, the user may have asked in FragmentationConfig that
          // formulas like -H2O or -NH3 be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          // Note that we use std::move when passing
          // template_oligomer_for_formula_variants_sp to the function because
          // the function will move it as the first item in the
          // formula_variant_oligomers container.

          //            int accountedFormulas =
          accountFormulas(std::move(template_oligomer_for_formula_variants_sp),
                          formula_variant_oligomers,
                          fragmentation_config,
                          name,
                          charge);

          // qDebug() << "There are now"
          //          << formula_variant_oligomers.getOligomersCstRef().size()
          //          << "formula variant oligomers";

          // We now have a list of oligomers (or only one if there
          // was no formula to take into account). For each
          // oligomer, we have to account for the charge levels
          // asked by the user.

          OligomerCollection ionization_variant_oligomers =
            accountIonizationLevels(formula_variant_oligomers,
                                    fragmentation_config);

          // qDebug() << "We now got"
          //          <<
          //          ionization_variant_oligomers.getOligomersCstRef().size()
          //          << "ionization variant oligomers";

          //  At this point we do not need the initial *uncharged* Oligomer
          //  instances anymore.
          formula_variant_oligomers.clear();

          // qDebug() << "We still have"
          //          <<
          //          ionization_variant_oligomers.getOligomersCstRef().size()
          //          << "ionization variant oligomers";

          // First off, we can finally delete the grand template
          // oligomer (oligomer with no fragmentation rules applied).
          oligomer1_sp.reset();

          if(!ionization_variant_oligomers.size())
            {
              qDebug() << "Failed to generate ionized fragment oligomers.";
              return -1;
            }

          // Finally transfer all the oligomers generated for this fragmentation
          // to the container of ALL the oligomers. But before making
          // the transfer, compute the elemental composition and store it
          // as a property object.

          // Involves a std::move operation.
          std::size_t transferred_count =
            transferOligomers(ionization_variant_oligomers, m_oligomers);

          // qDebug() << "The number of transferred Oligomers:"
          //          << transferred_count;

          // qDebug() << "And now we have"
          //          << m_oligomers.getOligomersCstRef().size()
          //          << "oligomers in total";

          count += transferred_count;
        }
      // End of
      // if(!frag_rule_applied)
      else // (frag_rule_applied == true)
        {
          // There were fragmentation rule(s) that could be
          // successfully applied. Thus we already have created the
          // appropriate oligomers. Simply delete the template
          // oligomer.
          oligomer1_sp.reset();
        }
    }
  // End of
  //  for (int iter = fragmentation_config.getStopIndex();
  //  iter > fragmentation_config.getStopIndex() - 1; --iter, ++number)

  return count;
}

/*!
\brief Accounts for the \a fragmentation_rule_sp FragmentationRule if the
Monomer instance at \a monomer_index matches the requirement of the rule along
with \a frag_end.

The masses are accounted to \a mono and \a avg.

If \a only_for_checking is true,  then the computation is only a verification
that the FragmentationRule is to be accounted for. If \a only_for_checking is
false, the fragmentation rule is actually accounted for.

Returns true if the FragmentationRule should be accounted for (if \a
only_for_checking is true) or if it was effectively accounted for (if \a
only_for_checking is false), false otherwise.
*/
bool
Fragmenter::accountFragmentationRule(
  FragmentationRuleSPtr fragmentation_rule_sp,
  bool only_for_checking,
  std::size_t monomer_index,
  Enums::FragEnd frag_end,
  double &mono,
  double &avg)
{
  MonomerSPtr prev_monomer_csp = 0;
  MonomerSPtr next_monomer_csp = 0;

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  if(fragmentation_rule_sp == nullptr || fragmentation_rule_sp.get() == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

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

  MonomerSPtr monomer_csp =
    mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(monomer_index);

  if(!fragmentation_rule_sp->getCurrCode().isEmpty())
    if(fragmentation_rule_sp->getCurrCode() != monomer_csp->getCode())
      return false;

  if(!fragmentation_rule_sp->getPrevCode().isEmpty() &&
     !fragmentation_rule_sp->getNextCode().isEmpty())
    {
      // Use the overload (see globals.hpp)
      if(static_cast<bool>(frag_end & Enums::FragEnd::LE) ||
         static_cast<bool>(frag_end & Enums::FragEnd::NE))
        {
          if(!monomer_index)
            // There cannot be any getPrevCode since we are at monomer_index ==
            // 0, at the first monomer_csp of the fragmentation
            // series. That means that we can return immediately.
            return false;

          // Since we know that we are either in LEFT or NONE end
          // mode, we know that previous is at monomer_index 'monomer_index'
          // - 1. Thus get the monomer_csp out of the sequence for this
          // monomer_index.

          prev_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index - 1);

          if(monomer_index == mcsp_polymer->size() - 1)
            // There cannot be any next code since we are already at
            // the last monomer_csp in the fragmentation series.
            return false;

          next_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index + 1);
        }
      else if(static_cast<bool>(frag_end & Enums::FragEnd::RE))
        {
          if(!monomer_index)
            // There cannot be any getNextCode since getCurrCode is the last
            // monomer_csp in the fragmentation series.
            return false;

          next_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index - 1);

          if(monomer_index == mcsp_polymer->size() - 1)
            // There cannot be any previous code since getCurrCode is the
            // first in the fragmentation series.
            return false;

          prev_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index + 1);
        }
      else
        return false;

      // Now that the getPrevCode and getNextCode have been correctly
      // identified, we can go on and check if some conditions are
      // met.

      if(fragmentation_rule_sp->getPrevCode() == prev_monomer_csp->getCode() &&
         fragmentation_rule_sp->getNextCode() == next_monomer_csp->getCode())
        {
          if(only_for_checking)
            return true;

          // The fragmentation rule condition is met, we can apply its
          // formula.

          bool ok;

          Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
          temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);

          if(!ok)
            {
              qDebug() << "Failed to account fragmentation rule";

              return false;
            }

          return true;
        }
      else
        {
          if(only_for_checking)
            return false;
          else
            return true;
        }
    }
  // End of
  //   if (!fragmentation_rule_sp->getPrevCode().isEmpty() &&
  //   !fragmentation_rule_sp->getNextCode().isEmpty())
  else if(!fragmentation_rule_sp->getPrevCode().isEmpty())
    {
      if(static_cast<bool>(frag_end & Enums::FragEnd::LE) ||
         static_cast<bool>(frag_end & Enums::FragEnd::NE))
        {
          if(!monomer_index)
            // There cannot be any getPrevCode since getCurrCode is already
            // the first of the fragmentation series.
            return false;

          // Since we know that fragEnd is either LEFT or NONE end, we
          // know what monomer_index has the getPrevCode:

          prev_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index - 1);
        }
      else if(static_cast<bool>(frag_end & Enums::FragEnd::RE))
        {
          if(monomer_index == mcsp_polymer->size() - 1)
            // There cannot be any getPrevCode since getCurrCode is already
            // the first of the fragmentation series.
            return false;

          prev_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index + 1);
        }
      else
        return false;

      // Now that we have correctly identified the getPrevCode, we can go
      // on and check if some conditions are met.

      if(fragmentation_rule_sp->getPrevCode() == prev_monomer_csp->getCode())
        {
          if(only_for_checking)
            return true;

          // The fragmentation rule condition is met, we can apply its
          // formula.

          bool ok;

          Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
          temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);

          if(!ok)
            {
              qDebug() << "Failed to account fragmentation rule";

              return false;
            }

          return true;
        }
      else
        {
          if(only_for_checking)
            return false;
          else
            return true;
        }
    }
  // End of
  // else if (!fragmentation_rule_sp->getPrevCode().isEmpty())
  else if(!fragmentation_rule_sp->getNextCode().isEmpty())
    {
      if(static_cast<bool>(frag_end & Enums::FragEnd::LE) ||
         static_cast<bool>(frag_end & Enums::FragEnd::NE))
        {
          if(monomer_index == mcsp_polymer->size() - 1)
            // There cannot be any getNextCode since getCurrCode is already
            // the last of the fragmentation series.
            return false;

          // Since we know that fragEnd is either LEFT or NONE end, we
          // know what monomer_index has the getPrevCode:

          next_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index + 1);
        }
      else if(static_cast<bool>(frag_end & Enums::FragEnd::RE))
        {
          if(!monomer_index)
            // There cannot be any getPrevCode since getCurrCode is already
            // the last of the fragmentation series.
            return false;

          next_monomer_csp =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(
              monomer_index - 1);
        }
      else
        return false;

      // Now that we have correctly identified the getNextCode, we can go
      // on and check if some conditions are met.

      if(fragmentation_rule_sp->getNextCode() == next_monomer_csp->getCode())
        {
          if(only_for_checking)
            return true;

          // The fragmentation rule condition is met, we can apply its
          // formula.

          bool ok;

          Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
          temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);

          if(!ok)
            {
              qDebug() << "Failed to account fragmentation rule";

              return false;
            }

          return true;
        }
      else
        {
          if(only_for_checking)
            return false;
          else
            return true;
        }
    }
  // End of
  // else if (!fragmentation_rule_sp->getNextCode().isEmpty())
  else
    {
      // All the prev and next codes are empty, which means that we
      // consider the conditions verified.
      if(only_for_checking)
        return true;

      bool ok;

      Formula temp_formula(fragmentation_rule_sp->getFormulaCstRef());
      temp_formula.accountMasses(ok, isotopic_data_csp, mono, avg, 1);

      if(!ok)
        {
          qDebug() << "Failed to account fragmentation rule";

          return false;
        }

      return true;
    }

  // We should never reach this point !
  Q_ASSERT(0);

  return false;
}

/*!
\brief Generates as many more Oligomer fragments are there are Formula instances
to be accounted for.

\a template_oligomer_sp is the Oligomer instance that serves as a template to
generate as many variants as there are Formula instances in the \a
fragmentation_config's member container of Formula instances. For example,  if
the caller wants to generate for each Oligomer frament a variant with -H2O and
-NH3 formulas applied,  then each fragment Oligomer in the \a oligomers
collection will generate two variants: one with water-production decomposition
and one with ammonia-producing decomposition. Thus, in the end,  there will be
as many times more fragment Oligomer instances in \a oligomers as there are
Formula instances to be accounted for.

\a name and \a charge are the name and charge onto which base the creation of
the variant fragment Oligomer new name so that the fragment Oligomer has a name
that documents the kind of fragmentation pathway is was generated from along
with both the Formula that was accounted for and finally its charge.

Returns the count of variant fragment Oligomer instances that were added into
the \a oligomers collection.
*/
std::size_t
Fragmenter::accountFormulas(OligomerSPtr &&template_oligomer_sp,
                            OligomerCollection &oligomers,
                            const FragmentationConfig &fragmentation_config,
                            const QString &name,
                            int charge)
{
  // qDebug() << "Now accounting for formulas checked by the user (-NH3, -H20, "
  //"for example.)";

  if(template_oligomer_sp == nullptr || template_oligomer_sp.get() == nullptr)
    qFatal() << "Programming error. Pointer cannot be nullptr.";

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  std::size_t count = 0;

  // The  oligomer that we get as parameter is the
  // template on which to base the derivatives made on the basis of the
  // formulas. However, we must use it also as a mere fragmentation oligomer.
  // We get it as a pointer to be moved into the container as the very first
  //  item in it.

  // qDebug() << "Template we got to base the formula variants on:"
  //          << template_oligomer_sp->toString();

  oligomers.getOligomersRef().push_back(std::move(template_oligomer_sp));

  // template_oligomer_sp is now nullptr. We'll base the copies below
  // on the first item of the container.

  for(const Formula &formula : fragmentation_config.getFormulasCstRef())
    {
      // We will apply the formula to a copy of the template oligomer
      OligomerSPtr formula_variant_oligomer_sp =
        std::make_shared<Oligomer>(*oligomers.getOligomersRef().front());

      // qDebug() << "Created new oligomer as a copy of the template:"
      //          << formula_variant_oligomer_sp->toString();

      // First, account the masses
      Formula temp_formula(formula);

      // qDebug() << "Accounting of fragmentation config's formula:"
      //          << temp_formula.getActionFormula();

      bool ok;

      temp_formula.accountMasses(
        ok,
        isotopic_data_csp,
        formula_variant_oligomer_sp->getMassRef(Enums::MassType::MONO),
        formula_variant_oligomer_sp->getMassRef(Enums::MassType::AVG));

      if(!ok)
        {
          qDebug() << "Failed to account formula' masses:"
                   << formula.getActionFormula();

          formula_variant_oligomer_sp.reset();
          continue;
        }

      // qDebug() << "Right after accounting the formula's masses"
      //          << temp_formula.getActionFormula() << "the oligomer becomes:"
      //          << formula_variant_oligomer_sp->toString();

      //  Second, account the formula.
      formula_variant_oligomer_sp->getFormulaRef().accountFormula(
        formula.getActionFormula(), isotopic_data_csp, 1, ok);

      if(!ok)
        {
          qDebug() << "Failed to account formula:"
                   << formula.getActionFormula();

          formula_variant_oligomer_sp.reset();
          continue;
        }

      // qDebug() << "Right after accounting the formula proper"
      //          << temp_formula.getActionFormula() << "the oligomer becomes:"
      //          << formula_variant_oligomer_sp->toString();

      // The new oligomer could be generated correctly. Append the
      // formula to its name, so that we'll be able to recognize it.

      QString name_with_formula = QString("%1#%2#z=%3")
                                    .arg(name)
                                    .arg(formula.getActionFormula())
                                    .arg(charge);

      formula_variant_oligomer_sp->setName(name_with_formula);

      // At this point append the new oligomer to the list.
      // qDebug() << "Pushing back to oligomers, the new variant:"
      //          << formula_variant_oligomer_sp->toString();

      oligomers.getOligomersRef().push_back(formula_variant_oligomer_sp);

      ++count;
    }

  // qDebug() << "At the time of returning,  the number of oligomers:"
  //          << oligomers.getOligomersRef().size();

  return count;
}

/*!
\brief For each ionization level contained in the \a fragmentation_config
start and stop ionization level members,  create fragment Oligomer variants of
all the Oligomer instances present in \a oligomers.

The generated fragment Oligomer instances are stored in a OligomerCollection
that is returned.
*/
OligomerCollection
Fragmenter::accountIonizationLevels(
  OligomerCollection &oligomers,
  const FragmentationConfig &fragmentation_config)
{
  bool failed = false;

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // qDebug() << "Handling the ionization levels for a container of"
  //          << oligomers.getOligomersCstRef().size() << "oligomers";

  // We get an Oligomer container (or only one, in fact, if no
  // -H2O/-NH3 formulas,  for example, were asked for
  // (accountFormulas()), and we have for each one to compute the required
  // ionisation levels. Indeed, the user might ask for fragments
  // that are charged with different charge levels. Thus we need to create as
  // many new oligomers as needed for the different charge levels.  Because the
  // ionization changes the values in the oligomer, and we need a new oligomer
  // each time, we duplicate the oligomer each time we need it.

  int ionization_level      = fragmentation_config.getStartIonizeLevel();
  int ionization_stop_level = fragmentation_config.getStopIonizeLevel();

  // qDebug() << "Requested ionization level range:"
  //          << "[" << ionization_level << "-" << ionization_stop_level << "]";

  // We have to perform the operation for each oligomer in
  // oligomers. We populate a new Oligomers container that we will return
  // filled with at least the same Oligomer instances that were in
  // the Oligomer container passed as parameter.

  OligomerCollection charge_state_oligomers(fragmentation_config.getName(),
                                            mcsp_polymer);

  for(const OligomerSPtr &iter_oligomer_sp : oligomers.getOligomersRef())
    {
      // The oligomer being iterated into is a non-ionized oligomer and
      // we will create as many ionized variants as there are ionization levels
      // requested.

      // qDebug() << "Now handling non-ionized oligomer with masses:"
      //          << "mono:" << iter_oligomer_sp->getMass(Enums::MassType::MONO)
      //          << "avg:" << iter_oligomer_sp->getMass(Enums::MassType::AVG)
      //          << "and formula:"
      //          << iter_oligomer_sp->getFormulaCstRef().getActionFormula();

      // For this new oligomer, reset the initial ionization level.
      ionization_level = fragmentation_config.getStartIonizeLevel();

      while(ionization_level <= ionization_stop_level)
        {
          // qDebug() << "Current ionization level:" << ionization_level;

          Ionizer temp_ionizer(m_ionizer);
          temp_ionizer.setLevel(ionization_level);

          //  Make a copy of the non-ionized oligomer.
          OligomerSPtr new_oligomer_sp =
            std::make_shared<Oligomer>(*iter_oligomer_sp);

          new_oligomer_sp->setIonizer(temp_ionizer);

          // If the result of the call below is
          // Enums::IonizationOutcome::FAILED, then that means that there was an
          // error and we should return immediately. If it is
          // Enums::IonizationOutcome::UNCHANGED, then that means that no error
          // was encountered, but that no actual ionization took place, so we
          // need not take into account the oligomer.

          Enums::IonizationOutcome ionization_outcome =
            new_oligomer_sp->ionize();

          if(ionization_outcome == Enums::IonizationOutcome::FAILED)
            {
              qDebug() << "Failed to ionize the oligomer.";

              new_oligomer_sp.reset();
              failed = true;
              break;
            }
          else if(ionization_outcome == Enums::IonizationOutcome::UNCHANGED)
            {
              qDebug() << "The Oligomer ionization changed nothing.";
              new_oligomer_sp.reset();
              continue;
            }

          // qDebug() << "After ionization with level:"
          //          << ionization_level << ", oligomer has masses:"
          //          << "mono:" <<
          //          new_oligomer_sp->getMass(Enums::MassType::MONO)
          //          << "avg:" <<
          //          new_oligomer_sp->getMass(Enums::MassType::AVG);

          //  Go on with effectively ionized oligomer variant.

          bool ok = false;

          new_oligomer_sp->getFormulaRef().accountFormula(
            temp_ionizer.getFormulaCstRef().getActionFormula(),
            isotopic_data_csp,
            temp_ionizer.getLevel(),
            ok);

          if(!ok)
            {
              qDebug() << "Failed to account the ionizer formula:"
                       << temp_ionizer.getFormulaCstRef().getActionFormula();
              new_oligomer_sp.reset();

              continue;
            }

          // qDebug() << "After ionization with level:"
          //          << ionization_level << ", oligomer has formula:"
          //          << new_oligomer_sp->getFormulaCstRef().getActionFormula();

          // At this point the ionization did indeed perform
          // something interesting, craft the name of the resulting
          // oligomer and set it. We must of the name of the
          // oligomer, but simply replace the value substring
          // "#z=xx" with "z=yy".

          QString name          = new_oligomer_sp->getName();
          QString charge_string = QString("z=%1").arg(temp_ionizer.charge());

          name.replace(QRegularExpression("z=\\d+$"), charge_string);

          new_oligomer_sp->setName(name);

          // qDebug() << "After ionization, oligomer has name:"
          //          << new_oligomer_sp->getName();

          charge_state_oligomers.getOligomersRef().push_back(new_oligomer_sp);

          ++ionization_level;
        }
      // End of
      // while(ionization_level <= ionization_stop_level)
      // for (int iter = charge_level_start; iter < charge_level_stop;
      // ++iter)

      // If there was a single failure, we get here with failed
      // set to true. In that case, free the charge_state_oligomers and
      // return NULL.

      if(failed)
        {
          charge_state_oligomers.clear();
          return charge_state_oligomers;
        }
    }
  // End of
  // for(const OligomerSPtr &iter_oligomer_sp : oligomers.getOligomersRef())

  return charge_state_oligomers;
}


} // namespace libXpertMassCore
} // namespace MsXpS
