/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 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>


/////////////////////// libmass includes
#include <libXpertMass/PolChemDef.hpp>
#include <libXpertMass/PolChemDefEntity.hpp>


/////////////////////// Local includes
#include "globals.hpp"
#include "Fragmenter.hpp"
#include "FragmentOligomer.hpp"


namespace MsXpS
{

namespace massxpert
{


Fragmenter::Fragmenter(libXpertMass::Polymer *polymer,
                       libXpertMass::PolChemDefCstSPtr polChemDefCstSPtr,
                       const QList<FragOptions *> &fragOptionList,
                       const libXpertMass::CalcOptions &calcOptions,
                       const libXpertMass::IonizeRule &ionizeRule)
  : mp_polymer(polymer),
    mcsp_polChemDef(polChemDefCstSPtr),
    m_calcOptions(calcOptions),
    m_ionizeRule(ionizeRule)
{
  Q_ASSERT(mp_polymer && mcsp_polChemDef);

  for(int iter = 0; iter < fragOptionList.size(); ++iter)
    {
      FragOptions *fragOptions    = fragOptionList.at(iter);
      FragOptions *newFragOptions = new FragOptions(*fragOptions);

      m_fragOptionList.append(newFragOptions);
    }

  mp_oligomerList = 0;
}


Fragmenter::Fragmenter(const Fragmenter &other)
  : mp_polymer(other.mp_polymer),
    mcsp_polChemDef(other.mcsp_polChemDef),
    m_calcOptions(other.m_calcOptions),
    m_ionizeRule(other.m_ionizeRule)
{
  Q_ASSERT(mp_polymer && mcsp_polChemDef);

  for(int iter = 0; iter < other.m_fragOptionList.size(); ++iter)
    {
      FragOptions *fragOptions    = other.m_fragOptionList.at(iter);
      FragOptions *newFragOptions = new FragOptions(*fragOptions);

      m_fragOptionList.append(newFragOptions);
    }

  mp_oligomerList = other.mp_oligomerList;
}


Fragmenter::~Fragmenter()
{
  // We are not owner of the oligomer list, do not free it!

  qDeleteAll(m_fragOptionList.begin(), m_fragOptionList.end());
  m_fragOptionList.clear();
}


// Takes ownership of the parameter.
void
Fragmenter::addFragOptions(FragOptions *fragOptions)
{
  Q_ASSERT(fragOptions);

  m_fragOptionList.append(fragOptions);
}


void
Fragmenter::setOligomerList(OligomerList *oligomerList)
{
  Q_ASSERT(oligomerList);

  mp_oligomerList = oligomerList;
}


OligomerList *
Fragmenter::oligomerList()
{
  return mp_oligomerList;
}


bool
Fragmenter::fragment()
{
  // If the polymer sequence is empty, just return.
  if(!mp_polymer->size())
    return true;

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

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

      return false;
    }

  //   qDebug() << __FILE__ << __LINE__
  //  	    << "number of fragmentation specifications:"
  //  	    << m_fragOptionList.size();

  // 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(m_calcOptions.monomerEntities() & libXpertMass::MONOMER_CHEMENT_CROSS_LINK)
    {
      // qDebug() << __FILE__ << __LINE__
      //          << "Fragmentation calculations take "
      //   "into account the cross-links";

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

      FragOptions *fragOptions = m_fragOptionList.at(0);

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

      QList<int> crossLinkedMonomerIndexList;
      int partials = 0;

      // qDebug() << __FILE__ << __LINE__
      //          << "Fragmentation options:"
      //          << fragOptions->startIndex() << fragOptions->endIndex();

      mp_polymer->crossLinkedMonomerIndexList(fragOptions->startIndex(),
                                              fragOptions->endIndex(),
                                              &crossLinkedMonomerIndexList,
                                              &partials);

      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 list 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(crossLinkedMonomerIndexList.begin(),
                crossLinkedMonomerIndexList.end());

      // qDebug() << __FILE__ << __LINE__
      //          << "Indices:" << crossLinkedMonomerIndexList;


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

      int first = 0;
      int last  = 0;

      int prev = 0;
      int next = 0;

      for(int iter = 0, size = crossLinkedMonomerIndexList.size() - 1;
          iter < size;
          ++iter)
        {
          // Seed the system only at the first iteration.
          if(!iter)
            {
              first = crossLinkedMonomerIndexList.at(iter);
              last  = crossLinkedMonomerIndexList.at(iter + 1);

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

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

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

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

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

              last = prev;

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

              CrossLinkedRegion *region = new CrossLinkedRegion(first, last);

              // Get the cross-links for the region.
              QList<libXpertMass::CrossLink *> crossLinkList;

              partials = 0;

              mp_polymer->crossLinkList(first, last, &crossLinkList, &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(crossLinkList);

              m_crossLinkedRegionList.append(region);

              // Now that we have closed a continuum, start seeding
              // a new one.
              first = next;
            }
        }
      // End of
      // for(int iter = 0, size = crossLinkedMonomerIndexList.size() - 1;
      //    iter < size; ++iter)

      // We have to close the last continuum that we could not close
      // because we ended off the for loop.

      last = next;

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

      CrossLinkedRegion *region = new CrossLinkedRegion(first, last);

      // Get the cross-links for the region.
      QList<libXpertMass::CrossLink *> crossLinkList;

      partials = 0;

      mp_polymer->crossLinkList(first, last, &crossLinkList, &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(crossLinkList);

      m_crossLinkedRegionList.append(region);

      // qDebug() << __FILE__ << __LINE__
      //          << "Having" << m_crossLinkedRegionList.size() << "regions";

      // for(int iter = 0; iter < m_crossLinkedRegionList.size(); ++iter)
      //   qDebug() << __FILE__ << __LINE__
      //            << "Start:" <<
      //            m_crossLinkedRegionList.at(iter)->startIndex()
      //            << "End:" << m_crossLinkedRegionList.at(iter)->endIndex();
    }
  // End of
  // if (m_calcOptions.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)

  // At this point we have a list of regions that we'll be able to
  // use to compute the fragment masses.

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

  for(int iter = 0; iter < m_fragOptionList.size(); ++iter)
    {
      FragOptions *fragOptions = m_fragOptionList.at(iter);

      if(fragOptions->fragEnd() == libXpertMass::FRAG_END_NONE)
        {
          if(fragmentEndNone(*fragOptions) == -1)
            return false;
        }
      else if(fragOptions->fragEnd() == libXpertMass::FRAG_END_LEFT)
        {
          if(fragmentEndLeft(*fragOptions) == -1)
            return false;
        }
      else if(fragOptions->fragEnd() == libXpertMass::FRAG_END_RIGHT)
        {
          if(fragmentEndRight(*fragOptions) == -1)
            return false;
        }
      else
        Q_ASSERT(0);
    }

  return true;
}


int
Fragmenter::fragmentEndNone(FragOptions &fragOptions)
{
  // 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.
  libXpertMass::IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  int count = 0;

  libXpertMass::CalcOptions localOptions = m_calcOptions;
  localOptions.setCapping(libXpertMass::CAP_NONE);

  for(int iter = fragOptions.startIndex(); iter < fragOptions.endIndex() + 1;
      ++iter)
    {
      bool fragRuleApplied = 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.

      libXpertMass::IonizeRule ionizeRule(m_ionizeRule);
      ionizeRule.setLevel(0);

      // Allocate a FragmentOligomer in which we'll update each time the formulj
      // that mirrors the masses of it.
      FragmentOligomerSPtr oligomer1_sp = std::make_shared<FragmentOligomer>(
        mp_polymer,
        "NOT_SET",
        fragOptions.name() /*fragSpec.m_name*/,
        mp_polymer->hasModifiedMonomer(iter, iter),
        libXpertMass::Ponderable(),
        ionizeRule,
        localOptions,
        false /*isIonized*/,
        iter /*startIndex*/,
        iter /*endIndex*/);

      // qDebug() << __FILE__ << __LINE__
      //          << "right after creation with ionizerule but no charge:"
      //          << oligomer1->mono();

      // The formula of the fragmentation specification should yield a neutral
      // molecular species, which is then ionized according to the current
      // ionization rule in the editor window. The levels of this ionization
      // rule are set by the user in the fragmentation dialog window, which
      // 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 ionizeRule object(upon creation, an ionize rule
      // is invalid).
      libXpertMass::IonizeRule rule;

      if(!oligomer1_sp->calculateMasses(&localOptions, &rule))
        {
          oligomer1_sp.reset();
          return -1;
        }

      // 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.
      libXpertMass::Formula monomer_formula =
        mp_polymer->at(iter)->calculateFormula(m_calcOptions.monomerEntities());

      oligomer1_sp->m_formula.accountFormula(monomer_formula.toString(),
                                           isotopic_data_csp);

      // Now start getting deep inside the chemistry of the fragmentation
      // oligomer.
      if(!fragOptions.formula().isEmpty())
        {
          // qDebug() << "The formula for the fragmentation pattern:"
          //<< fragOptions.formula();

          if(!fragOptions.Formula::accountMasses(isotopic_data_csp,
                                                 oligomer1_sp.get()))
            {
              oligomer1_sp.reset();

              return -1;
            }

          // qDebug() << __FILE__ << __LINE__
          //          << "right after accountMasses for fragSpec formula:"
          //          << oligomer1->mono();

          // Now the fomula:
          oligomer1_sp->m_formula.accountFormula(
            fragOptions.formula(), isotopic_data_csp, 1);
        }

      // 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 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 (libXpertMass::FragSpec).

      int monomerContrib = fragOptions.monomerContribution();

      if(monomerContrib)
        {
          const libXpertMass::Monomer *monomer = mp_polymer->at(iter);

          QString formula = monomer->formula();

          if(!monomer->accountMasses(
               &oligomer1_sp->rmono(), &oligomer1_sp->ravg(), monomerContrib))
            {
              oligomer1_sp.reset();

              return -1;
            }

          oligomer1_sp->m_formula.accountFormula(
            monomer->formula(), isotopic_data_csp, monomerContrib);
        }

      // qDebug() << __FILE__ << __LINE__
      //          << "right after accountMasses for monomer contribution:"
      //          << oligomer1->mono();

      // At this point we should check if the fragmentation
      // specification includes fragmentation rules that apply to this
      // fragment. FragOptions is derived from libXpertMass::FragSpec.

      for(int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
        {
          // The accounting of the fragrule 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
          // with z>1.

          libXpertMass::FragRule *fragRule = fragOptions.ruleList().at(jter);

          // This round is not for real accounting, but only to check of the
          // currently iterated frag rule should be accounted for (see the true
          // that indicates this call is only for checking).
          if(!accountFragRule(
               fragRule, true, iter, libXpertMass::FRAG_END_NONE, nullptr))
            continue;

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

          FragmentOligomerSPtr oligomer2_sp =
            std::make_shared<FragmentOligomer>(*oligomer1_sp);

          if(!accountFragRule(fragRule,
                              false,
                              iter,
                              libXpertMass::FRAG_END_NONE,
                              oligomer2_sp.get()))
            {
              oligomer1_sp.reset();
              oligomer2_sp.reset();

              return -1;
            }

          oligomer2_sp->m_formula.accountFormula(fragRule->formula(),
                                                 isotopic_data_csp);

          // qDebug()
          //<< "Allocated new oligomer for accounting fragrule:"
          //<< fragRule->formula()
          //<< "and after accounting oligomer's formula that has now become:"
          //<< oligomer2_sp->m_formula.toString();

          // At this point we have the fragment oligomer within 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.

          FragmentOligomerSPtr new_oligomer_sp =
            std::make_shared<FragmentOligomer>(*oligomer2_sp);

          int charge = 0;

          // 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(fragOptions.name())
                           .arg(mp_polymer->at(iter)->code())
                           .arg(fragRule->name());

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

          new_oligomer_sp->setName(nameWithCharge);

          // 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 list object.

          OligomerList *formulaOligomerList =
            new OligomerList(fragOptions.name(), mp_polymer);

          // Append the template oligomer to the temporary list so
          // that the called function has it to base new oligomers
          // on it.
          formulaOligomerList->append(new_oligomer_sp);

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

          fragRuleApplied = true;

          // Now that the list has ONE template item, we can use
          // that list to stuff in it all the other ones depending
          // on the presence of any formula in fragOptions. Indeed,
          // it might be that the user has checked the -H20 or -NH3
          // checkboxes, asking that such formulas be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          //            int accountedFormulas =
          accountFormulas(formulaOligomerList, fragOptions, name, charge);

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

          OligomerList *ionizeLevelOligomerList =
            accountIonizationLevels(formulaOligomerList, fragOptions);

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

          if(!ionizeLevelOligomerList)
            {
              qDebug() << QObject::tr(
                "massxpert - Fragmentation : "
                "Failed to generate ionized "
                "fragment oligomers.");

              while(!formulaOligomerList->isEmpty())
                formulaOligomerList->takeFirst().reset();

              delete formulaOligomerList;

              return -1;
            }

          // Note that during the work on ionizeLevels, the list
          // of oligomers that was passed to that function as a
          // parameter has gotten emptied. It is thus now time to
          // delete it.
          Q_ASSERT(formulaOligomerList->isEmpty());
          delete formulaOligomerList;

          // At this point, we have to remove all the oligomers
          // from the lastOligomerList and put them into the
          // oligomerList.

          while(!ionizeLevelOligomerList->isEmpty())
            {
              mp_oligomerList->append(ionizeLevelOligomerList->takeFirst());
              ++count;
            }

          delete ionizeLevelOligomerList;
        }
      // End of
      // for (int jter = 0; jter < fragOptions.ruleList().size(); ++jter)

      // We are here because of two reasons:

      // 1. because the array of fragRules did not contain any
      // 1. fragRule, in which case we still have to validate and
      // 1. terminate the oligomer1 (fragRuleApplied is false);

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

      if(!fragRuleApplied)
        {
          // At this point we have the fragment oligomer. However, do
          // not forget that the user might ask for fragments that
          // bear more than the single charge that was intrinsically
          // computed within the formula of the fragmentation
          // specification.

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

          FragmentOligomerSPtr new_oligomer_sp =
            std::make_shared<FragmentOligomer>(*oligomer1_sp);

          int charge = 0;

          // 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(fragOptions.name())
                           .arg(mp_polymer->at(iter)->code());

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

          new_oligomer_sp->setName(nameWithCharge);

          // We should make a temporary list of oligomers to handle
          // both formulas and charge state.

          OligomerList *formulaOligomerList =
            new OligomerList(fragOptions.name(), mp_polymer);

          // Append the template oligomer to the temporary list so
          // that the called function has it to base new oligomers
          // on it.
          formulaOligomerList->append(new_oligomer_sp);

          // Now that the list has ONE template item, we can use
          // that list to stuff in it all the other ones depending
          // on the presence of any formula in fragOptions. Indeed,
          // it might be that the user has checked the -H20 or -NH3
          // checkboxes, asking that such formulas be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          //            int accountedFormulas =
          accountFormulas(formulaOligomerList, fragOptions, name, charge);

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

          OligomerList *ionizeLevelOligomerList =
            accountIonizationLevels(formulaOligomerList, fragOptions);

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

          if(!ionizeLevelOligomerList)
            {
              qDebug() << QObject::tr(
                "massxpert - Fragmentation : "
                "Failed to generate ionized "
                "fragment oligomers.");

              while(!formulaOligomerList->isEmpty())
                formulaOligomerList->takeFirst().reset();

              delete formulaOligomerList;

              return -1;
            }

          // At this point, we have to remove all the oligomers
          // from the lastOligomerList and put them into the
          // oligomerList.

          while(!ionizeLevelOligomerList->isEmpty())
            {
              libXpertMass::OligomerSPtr iter_oligomer_sp =
                ionizeLevelOligomerList->takeFirst();

              mp_oligomerList->append(iter_oligomer_sp);
              ++count;
            }

          delete ionizeLevelOligomerList;
        }
      // End of
      // if(!fragRuleApplied)
      else // (fragRuleApplied == 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 = fragOptions.startIndex();
  //   fragOptions.endIndex() + 1; ++iter)

  // FragmentOligomerSPtr oligomer_sp =
  // std::dynamic_pointer_cast<FragmentOligomer>(mp_oligomerList->first());
  // qDebug() << "First oligomer has formula:" <<
  // oligomer_sp->m_formula.toString()
  //<< "as reported:" << oligomer_sp->elementalComposition();

  return count;
}


int
Fragmenter::fragmentEndLeft(FragOptions &fragOptions)
{
  int count  = 0;
  int number = 0;

  // qDebug() << "FragOptions:" << fragOptions.toString();

  static libXpertMass::Ponderable ponderable;
  ponderable.clearMasses();

  // 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 increment at the beginning of each for loop iteration
  // below.
  static libXpertMass::Formula residue_chain_formula;
  residue_chain_formula.clear();

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


  // If the crosslinks are to be taken into account, then make a local copy of
  // the m_crossLinkedRegionList 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).

  QList<CrossLinkedRegion *> crossLinkedRegionList;

  for(int iter = 0; iter < m_crossLinkedRegionList.size(); ++iter)
    {
      CrossLinkedRegion *region =
        new CrossLinkedRegion(*m_crossLinkedRegionList.at(iter));

      crossLinkedRegionList.append(region);
    }

  // 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
  // fragOptions.startIndex() --> fragOptions.endIndex() direction.

  // The general idea of the for loop below is that we iterate in the
  // [startIndex-endindex) range of the oligomer that describes the oligomer to
  // be fragmented. Each iteration adds one monomer. The code below is thus
  // sectioned into part that are additive between one iteration and another and
  // then section that are renewed each time.
  for(int iter = fragOptions.startIndex(); iter < fragOptions.endIndex();
      ++iter, ++number)
    {
      // qDebug() << "Iterating in fragoptions index:" << iter;

      // 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 fragRuleApplied = false;

      // Get a pointer to the polymer's monomer at index iter.
      const libXpertMass::Monomer *monomer = mp_polymer->at(iter);

      // qDebug() << "Before accounting for monomer" << monomer->code()
      //<< "of formula, the residue_chain_formula: "
      //<< residue_chain_formula.toString();

      // qDebug() << "Ponderable'mono before accounting " << monomer->code()
      //<< "masses:" << ponderable.mono();

      // 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->accountMasses(&ponderable, 1);

      // qDebug() << "Ponderable'mono after accounting " << monomer->code()
      //<< "masses:" << ponderable.mono();

      // Ask the monomer to actually compute its formula either by taking into
      // account the modifications or not depending on the calculation options
      // below.
      libXpertMass::Formula monomer_formula =
        monomer->calculateFormula(m_calcOptions.monomerEntities());

      //qDebug() << "Before accounting for monomer formula:"
               //<< monomer_formula.toString() << "the residue_chain_formula: "
               //<< residue_chain_formula.toString();

      residue_chain_formula.accountFormula(monomer_formula.toString(),
                                           isotopic_data_csp);

      //qDebug() << "At this iteration, after accounting for monomer formula, "
                  //"the residue_chain_formula: "
               //<< residue_chain_formula.toString();

      // 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 crossLinkedRegionList so that we do not take them
      // into account more than once.

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

      int jter = crossLinkedRegionList.size() - 1;

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

          CrossLinkedRegion *region = crossLinkedRegionList.at(jter);

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

              const QList<libXpertMass::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)...

                  libXpertMass::CrossLink *crossLink = crossLinkList.at(kter);

                  crossLink->accountMasses(&ponderable, 1);

                  residue_chain_formula.accountFormula(
                    crossLink->formula(), isotopic_data_csp, 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 crossLinkedRegionList.takeAt(jter);
            }

          --jter;
        }

      // Now starts a new section that will be specific to this iteration in the
      // for loop, which is why we now create a copy of the initial
      // residual-chain-only formula. The new formula will get updated each time
      // a new aspect of the fragmentation chemistry will be accounted for.

      libXpertMass::Ponderable ponderableTemp(ponderable);

      libXpertMass::Formula frag_chemistry_formula(residue_chain_formula);

      // FragOptions is a FragSpec that holds the formula that makes a fragment
      // out of an oligomer.
      if(!fragOptions.formula().isEmpty())
        {
          // qDebug() << "The fragoptions formula:" << fragOptions.formula();

          if(!fragOptions.Formula::accountMasses(isotopic_data_csp,
                                                 &ponderableTemp))
            return -1;

          if(!frag_chemistry_formula.accountFormula(fragOptions.formula(),
                                                    isotopic_data_csp))
            return -1;

          // qDebug() << "frag_chemistry_formula after accouting for the "
          //"fragoptions formula:"
          //<< frag_chemistry_formula.toString();
        }

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

      formula.accountMasses(isotopic_data_csp, &ponderableTemp, 1);

      frag_chemistry_formula.accountFormula(
        formula.toString(), isotopic_data_csp, 1);

      // qDebug()
      //<< "frag_chemistry_formula after having accounted for the leftcap:"
      //<< frag_chemistry_formula.toString();

      if(m_calcOptions.polymerEntities() &
           libXpertMass::POLYMER_CHEMENT_LEFT_END_MODIF &&
         !fragOptions.startIndex())
        {
          libXpertMass::Polymer::accountEndModifMasses(
            mp_polymer,
            libXpertMass::POLYMER_CHEMENT_LEFT_END_MODIF,
            &ponderableTemp);

          frag_chemistry_formula.accountFormula(
            mp_polymer->leftEndModif().toString(), isotopic_data_csp);

          // qDebug() << "frag_chemistry_formula after having accounted for "
          //"the left end modif:"
          //<< frag_chemistry_formula.toString();
        }

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

      // 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 bear only its intrinsic 1
      // charge, 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 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).
      libXpertMass::CalcOptions localOptions = m_calcOptions;
      localOptions.setCapping(libXpertMass::CAP_NONE);

      libXpertMass::IonizeRule ionizeRule(m_ionizeRule);
      ionizeRule.setLevel(0);

      // Create a fragmentation oligomer by constructing it with the
      // ponderableTemp values that were calculated above.
      FragmentOligomerSPtr oligomer1_sp = std::make_shared<FragmentOligomer>(
        mp_polymer,
        "NOT_SET",
        fragOptions.name() /*fragSpec.m_name*/,
        mp_polymer->hasModifiedMonomer(iter, iter),
        ponderableTemp,
        ionizeRule,
        localOptions,
        false /*isIonized*/,
        fragOptions.startIndex(),
        iter /*endIndex*/);


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

      // qDebug() << "Right after  accounting for the various formulas until
      // now:"
      //<< oligomer1_sp->m_formula.toString();

      // 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 monomerContrib = fragOptions.monomerContribution();

      if(monomerContrib)
        {
          const libXpertMass::Monomer *monomer = mp_polymer->at(iter);

          if(!monomer->accountMasses(
               &oligomer1_sp->rmono(), &oligomer1_sp->ravg(), monomerContrib))
            {
              oligomer1_sp.reset();
              return -1;
            }

          oligomer1_sp->m_formula.accountFormula(
            monomer->formula(), isotopic_data_csp, monomerContrib);

          // qDebug() << "oligomer's formula right after accounting for monomer
          // " "contribution:"
          //<< oligomer1_sp->m_formula.toString();
        }

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

      for(int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
        {
          // The accounting of the fragrule is performed on a
          // singly-charged 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 with
          // z>1.

          libXpertMass::FragRule *fragRule = fragOptions.ruleList().at(jter);

          // This round is not for real accounting, but only to check of the
          // currently iterated frag rule should be accounted for (see the true
          // that indicates this call is only for checking).
          if(!accountFragRule(
               fragRule, true, iter, libXpertMass::FRAG_END_LEFT, nullptr))
            continue;

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

          FragmentOligomerSPtr oligomer2_sp =
            std::make_shared<FragmentOligomer>(*oligomer1_sp);

          if(!accountFragRule(fragRule,
                              false,
                              iter,
                              libXpertMass::FRAG_END_LEFT,
                              oligomer2_sp.get()))
            {
              oligomer1_sp.reset();
              oligomer2_sp.reset();

              return -1;
            }

          oligomer2_sp->m_formula.accountFormula(fragRule->formula(),
                                                 isotopic_data_csp);

          // qDebug()
          //<< "Allocated new oligomer for accounting fragrule:"
          //<< fragRule->formula()
          //<< "and after accounting oligomer's formula that has now become:"
          //<< oligomer2_sp->m_formula.toString();

          // At this point we have the fragment oligomer within 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.

          FragmentOligomerSPtr new_oligomer_sp =
            std::make_shared<FragmentOligomer>(*oligomer2_sp);

          int charge = 0;

          // 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(fragOptions.name())
                           .arg(number + 1)
                           .arg(fragRule->name());

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

          new_oligomer_sp->setName(nameWithCharge);

          // We will need to demultiply that new oligomer because we will
          // account for formula (-NH3 or -H20 if requested by the user) and
          // also the charge range that might have been set by the user. This is
          // why we seed an oligomer list with that new oligomer that will
          // behave as a template for all the other newly generated oligomers.

          OligomerList *formulaOligomerList =
            new OligomerList(fragOptions.name(), mp_polymer);

          // Append the template oligomer to the temporary list so
          // that the called function has it to base new oligomers
          // on it.
          formulaOligomerList->append(new_oligomer_sp);

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

          fragRuleApplied = true;

          // Now that the list has ONE template item, we can use
          // that list to stuff in it all the other ones depending
          // on the presence of any formula in fragOptions. Indeed,
          // it might be that the user has checked the -H20 or -NH3
          // checkboxes, asking that such formulas be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          //            int accountedFormulas =
          accountFormulas(formulaOligomerList, fragOptions, name, charge);

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

          OligomerList *ionizeLevelOligomerList =
            accountIonizationLevels(formulaOligomerList, fragOptions);

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

          if(!ionizeLevelOligomerList)
            {
              qDebug() << QObject::tr(
                "massxpert - Fragmentation : "
                "Failed to generate ionized "
                "fragment oligomers.");

              while(!formulaOligomerList->isEmpty())
                formulaOligomerList->takeFirst().reset();

              delete formulaOligomerList;

              return -1;
            }

          // Note that during the work on ionizeLevels, the list
          // of oligomers that was passed to that function as a
          // parameter has gotten emptied. It is thus now time to
          // delete it.
          Q_ASSERT(formulaOligomerList->isEmpty());
          delete formulaOligomerList;

          // At this point, we have to remove all the oligomers
          // from the lastOligomerList and put them into the
          // oligomerList.

          while(!ionizeLevelOligomerList->isEmpty())
            {
              mp_oligomerList->append(ionizeLevelOligomerList->takeFirst());
              ++count;
            }

          delete ionizeLevelOligomerList;
        }
      // End of
      // for (int jter = 0; jter < fragOptions.ruleList().size(); ++jter)

      // We are here because of two reasons:

      // 1. because the array of fragRules did not contain any
      // 1. fragRule, in which case we still have to validate and
      // 1. terminate the oligomer1 (fragRuleApplied is false);

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

      if(!fragRuleApplied)
        {
          // qDebug() << "There were no fragmentation rules to be applied for.
          // We " "now make sure we abide by the potential formulas and "
          //"ionization levels requests.";

          // At this point we have the fragment oligomer. However, do
          // not forget that the user might ask for fragments that
          // bear more than the single charge that was intrinsically
          // computed within the formula of the fragmentation
          // specification.

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

          FragmentOligomerSPtr new_oligomer_sp =
            std::make_shared<FragmentOligomer>(*oligomer1_sp);

          int charge = 0;

          // 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(fragOptions.name()).arg(number + 1);

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

          new_oligomer_sp->setName(nameWithCharge);

          // We should make a temporary list of oligomers to handle
          // both formulas and charge state.

          OligomerList *formulaOligomerList =
            new OligomerList(fragOptions.name(), mp_polymer);

          // Append the template oligomer to the temporary list so
          // that the called function has it to base new oligomers
          // on it.
          formulaOligomerList->append(new_oligomer_sp);

          // Now that the list has ONE template item, we can use
          // that list to stuff in it all the other ones depending
          // on the presence of any formula in fragOptions. Indeed,
          // it might be that the user has checked the -H20 or -NH3
          // checkboxes, asking that such formulas be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          //            int accountedFormulas =
          accountFormulas(formulaOligomerList, fragOptions, name, charge);

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

          OligomerList *ionizeLevelOligomerList =
            accountIonizationLevels(formulaOligomerList, fragOptions);

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

          if(!ionizeLevelOligomerList)
            {
              qDebug() << QObject::tr(
                "massxpert - Fragmentation : "
                "Failed to generate ionized "
                "fragment oligomers.");

              while(!formulaOligomerList->isEmpty())
                formulaOligomerList->takeFirst().reset();

              delete formulaOligomerList;

              return -1;
            }

          // At this point, we have to remove all the oligomers
          // from the lastOligomerList and put them into the
          // oligomerList.

          while(!ionizeLevelOligomerList->isEmpty())
            {
              libXpertMass::OligomerSPtr iter_oligomer_sp =
                ionizeLevelOligomerList->takeFirst();

              mp_oligomerList->append(iter_oligomer_sp);
              ++count;
            }

          delete ionizeLevelOligomerList;
        }
      // End of
      // if(!fragRuleApplied)
      else // (fragRuleApplied == 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 = fragOptions.startIndex();
  //   iter < fragOptions.endIndex() + 1; ++iter, ++count)

  // FragmentOligomerSPtr oligomer_sp =
  // std::dynamic_pointer_cast<FragmentOligomer>(mp_oligomerList->first());
  // qDebug() << "First oligomer has formula:" <<
  // oligomer_sp->m_formula.toString()
  //<< "as reported:" << oligomer_sp->elementalComposition();

  return count;
}


int
Fragmenter::fragmentEndRight(FragOptions &fragOptions)
{
  int count  = 0;
  int number = 0;

  // qDebug() << "FragOptions:" << fragOptions.toString();

  static libXpertMass::Ponderable ponderable;
  ponderable.clearMasses();

  // Formula to hold the summative result of accounting all of the chemical
  // entities making the oligomers. This formula is used for the moment, while
  // no FragmentOligomer has been allocated yet.
  static libXpertMass::Formula residue_chain_formula;
  residue_chain_formula.clear();

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


  // If the crosslinks are to be taken into account, then make a
  // local copy of the m_crossLinkedRegionList 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 to perform more
  // than one single fragmentation but a set of fragmentations).

  QList<CrossLinkedRegion *> crossLinkedRegionList;

  for(int iter = 0; iter < m_crossLinkedRegionList.size(); ++iter)
    {
      CrossLinkedRegion *region =
        new CrossLinkedRegion(*m_crossLinkedRegionList.at(iter));

      crossLinkedRegionList.append(region);
    }

  // At this point we can start making the calculations of the
  // fragments. Because we are generating fragments that contain the
  // right part of the oligomer, we iterate in the
  // fragOptions.endIndex() --> fragOptions.startIndex() direction.

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

  for(int iter = fragOptions.endIndex(); iter > fragOptions.startIndex();
      --iter, ++number)
    {
      // qDebug() << "Iterating in fragoptions index:" << iter;

      // 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 run 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 fragRuleApplied = false;

      // Get a pointer to the polymer's monomer at index iter.
      const libXpertMass::Monomer *monomer = mp_polymer->at(iter);

      // qDebug() << "Before accounting for monomer" << monomer->code()
      //<< "of formula, the residue_chain_formula: "
      //<< residue_chain_formula.toString();

      // qDebug() << "Ponderable'mono before accounting " << monomer->code()
      //<< "masses:" << ponderable.mono();

      // 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->accountMasses(&ponderable, 1);

      // qDebug() << "Ponderable'mono after accounting " << monomer->code()
      //<< "masses:" << ponderable.mono();

      // Ask the monomer to actually compute its formula either by taking into
      // account the modifications or not depending on the calculation options
      // below.
      libXpertMass::Formula monomer_formula =
        monomer->calculateFormula(m_calcOptions.monomerEntities());

      //qDebug() << "Before accounting for monomer formula:"
               //<< monomer_formula.toString() << "the residue_chain_formula: "
               //<< residue_chain_formula.toString();

      residue_chain_formula.accountFormula(monomer_formula.toString(),
                                           isotopic_data_csp);

      //qDebug() << "At this iteration, after accounting for monomer formula, "
                  //"the residue_chain_formula: "
               //<< residue_chain_formula.toString();

      // 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 crossLinkedRegionList so that we do not take them
      // into account more than once.

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

      int jter = crossLinkedRegionList.size() - 1;

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

          CrossLinkedRegion *region = crossLinkedRegionList.at(jter);

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

              const QList<libXpertMass::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)...

                  libXpertMass::CrossLink *crossLink = crossLinkList.at(kter);

                  crossLink->accountMasses(&ponderable, 1);

                  residue_chain_formula.accountFormula(
                    crossLink->formula(), isotopic_data_csp, 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 crossLinkedRegionList.takeAt(jter);
            }

          --jter;
        }

      // Now starts a new section that will be specific to this iteration in the
      // for loop, which is why we now create a copy of the initial
      // residual-chain-only formula. The new formula will get updated each time
      // a new aspect of the fragmentation chemistry will be accounted for.

      libXpertMass::Ponderable ponderableTemp(ponderable);

      libXpertMass::Formula frag_chemistry_formula(residue_chain_formula);

      // FragOptions is a FragSpec that holds the formula that makes a fragment
      // out of an oligomer.
      if(!fragOptions.formula().isEmpty())
        {
          // qDebug() << "FragOptions has at least one formula.";

          if(!fragOptions.Formula::accountMasses(isotopic_data_csp,
                                                 &ponderableTemp))
            return -1;

          if(!frag_chemistry_formula.accountFormula(fragOptions.formula(),
                                                    isotopic_data_csp))
            return -1;

          // qDebug() << "frag_chemistry_formula after accouting for the "
          //"fragoptions formula:"
          //<< frag_chemistry_formula.toString();
        }

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

      formula.accountMasses(isotopic_data_csp, &ponderableTemp, 1);

      frag_chemistry_formula.accountFormula(
        formula.toString(), isotopic_data_csp, 1);

      // qDebug()
      //<< "frag_chemistry_formula after having accounted for the leftcap:"
      //<< frag_chemistry_formula.toString();

      if(m_calcOptions.polymerEntities() &
           libXpertMass::POLYMER_CHEMENT_RIGHT_END_MODIF &&
         fragOptions.endIndex() == mp_polymer->size() - 1)
        {
          libXpertMass::Polymer::accountEndModifMasses(
            mp_polymer,
            libXpertMass::POLYMER_CHEMENT_RIGHT_END_MODIF,
            &ponderableTemp);

          frag_chemistry_formula.accountFormula(
            mp_polymer->leftEndModif().toString(), isotopic_data_csp);

          // qDebug() << "frag_chemistry_formula after having accounted for "
          //"the left end modif:"
          //<< frag_chemistry_formula.toString();
        }

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

      // 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 bear only its intrinsic 1
      // charge, 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 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).
      libXpertMass::CalcOptions localOptions = m_calcOptions;
      localOptions.setCapping(libXpertMass::CAP_NONE);

      libXpertMass::IonizeRule ionizeRule(m_ionizeRule);
      ionizeRule.setLevel(0);

      // Create a fragmentation oligomer by constructing it with the
      // ponderableTemp values that were calculated above.
      FragmentOligomerSPtr oligomer1_sp = std::make_shared<FragmentOligomer>(
        mp_polymer,
        "NOT_SET",
        fragOptions.name() /*fragSpec.m_name*/,
        mp_polymer->hasModifiedMonomer(iter, iter),
        ponderableTemp,
        ionizeRule,
        localOptions,
        false /*isIonized*/,
        iter /*startIndex*/,
        fragOptions.endIndex() /*endIndex*/);


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

      // qDebug() << "Right after  accounting for the various formulas until
      // now:"
      //<< oligomer1_sp->m_formula.toString();

      // 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 monomerContrib = fragOptions.monomerContribution();

      if(monomerContrib)
        {
          const libXpertMass::Monomer *monomer = mp_polymer->at(iter);

          if(!monomer->accountMasses(
               &oligomer1_sp->rmono(), &oligomer1_sp->ravg(), monomerContrib))
            {
              oligomer1_sp.reset();
              return -1;
            }

          oligomer1_sp->m_formula.accountFormula(
            monomer->formula(), isotopic_data_csp, monomerContrib);

          // qDebug() << "oligomer's formula right after accounting for monomer
          // " "contribution:"
          //<< oligomer1_sp->m_formula.toString();
        }

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

      for(int jter = 0; jter < fragOptions.ruleList().size(); ++jter)
        {
          // The accounting of the fragrule is performed on a
          // singly-charged 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 with
          // z>1.

          libXpertMass::FragRule *fragRule = fragOptions.ruleList().at(jter);

          // This round is not for real accounting, but only to check of the
          // currently iterated frag rule should be accounted for (see the true
          // that indicates this call is only for checking).
          if(!accountFragRule(fragRule, true, iter, libXpertMass::FRAG_END_RIGHT, 0))
            continue;

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

          FragmentOligomerSPtr oligomer2_sp =
            std::make_shared<FragmentOligomer>(*oligomer1_sp);

          if(!accountFragRule(fragRule,
                              false,
                              iter,
                              libXpertMass::FRAG_END_RIGHT,
                              oligomer2_sp.get()))
            {
              oligomer1_sp.reset();
              oligomer2_sp.reset();

              return -1;
            }

          oligomer2_sp->m_formula.accountFormula(fragRule->formula(),
                                                 isotopic_data_csp);

          // qDebug()
          //<< "Allocated new oligomer for accounting fragrule:"
          //<< fragRule->formula()
          //<< "and after accounting oligomer's formula that has now become:"
          //<< oligomer2_sp->m_formula.toString();

          // At this point we have the fragment oligomer within a
          // neutral state, because starting with version 3.6.0, the
          // fragmentation specification should yield a neutral
          // molecular species.

          FragmentOligomerSPtr new_oligomer_sp =
            std::make_shared<FragmentOligomer>(*oligomer2_sp);

          int charge = 0;

          // 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(fragOptions.name())
                           .arg(number + 1)
                           .arg(fragRule->name());

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

          new_oligomer_sp->setName(nameWithCharge);

          // We will need to demultiply that new oligomer because we will
          // account for formula (-NH3 or -H20 if requested by the user) and
          // also the charge range that might have been set by the user. This is
          // why we seed an oligomer list with that new oligomer that will
          // behave as a template for all the other newly generated oligomers.

          OligomerList *formulaOligomerList =
            new OligomerList(fragOptions.name(), mp_polymer);

          // Append the template oligomer to the temporary list so
          // that the called function has it to base new oligomers
          // on it.
          formulaOligomerList->append(new_oligomer_sp);

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

          fragRuleApplied = true;

          // Now that the list has ONE template item, we can use
          // that list to stuff in it all the other ones depending
          // on the presence of any formula in fragOptions. Indeed,
          // it might be that the user has checked the -H20 or -NH3
          // checkboxes, asking that such formulas be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          //            int accountedFormulas =
          accountFormulas(formulaOligomerList, fragOptions, name, charge);

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

          OligomerList *ionizeLevelOligomerList =
            accountIonizationLevels(formulaOligomerList, fragOptions);

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

          if(!ionizeLevelOligomerList)
            {
              qDebug() << QObject::tr(
                "massxpert - Fragmentation : "
                "Failed to generate ionized "
                "fragment oligomers.");

              while(!formulaOligomerList->isEmpty())
                formulaOligomerList->takeFirst().reset();

              delete formulaOligomerList;

              return -1;
            }

          // Note that during the work on ionizeLevels, the list
          // of oligomers that was passed to that function as a
          // parameter has gotten emptied. It is thus now time to
          // delete it.
          Q_ASSERT(formulaOligomerList->isEmpty());
          delete formulaOligomerList;

          // At this point, we have to remove all the oligomers
          // from the lastOligomerList and put them into the
          // oligomerList.

          while(!ionizeLevelOligomerList->isEmpty())
            {
              mp_oligomerList->append(ionizeLevelOligomerList->takeFirst());
              ++count;
            }

          delete ionizeLevelOligomerList;
        }
      // End of
      // for (int jter = 0; jter < fragOptions.ruleList().size(); ++jter)

      // We are here because of two reasons:

      // 1. because the array of fragRules did not contain any
      // 1. fragRule, in which case we still have to validate and
      // 1. terminate the oligomer1 (fragRuleApplied is false);

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

      if(!fragRuleApplied)
        {
          // qDebug() << "There were no fragmentation rules to be applied for.
          // We " "now make sure we abide by the potential formulas and "
          //"ionization levels requests.";

          // At this point we have the fragment oligomer. However, do
          // not forget that the user might ask for fragments that
          // bear more than the single charge that was intrinsically
          // computed within the formula of the fragmentation
          // specification.

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

          FragmentOligomerSPtr new_oligomer_sp =
            std::make_shared<FragmentOligomer>(*oligomer1_sp);

          int charge = 0;

          // 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(fragOptions.name()).arg(number + 1);

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

          new_oligomer_sp->setName(nameWithCharge);

          // We should make a temporary list of oligomers to handle
          // both formulas and charge state.

          OligomerList *formulaOligomerList =
            new OligomerList(fragOptions.name(), mp_polymer);

          // Append the template oligomer to the temporary list so
          // that the called function has it to base new oligomers
          // on it.
          formulaOligomerList->append(new_oligomer_sp);

          // Now that the list has ONE template item, we can use
          // that list to stuff in it all the other ones depending
          // on the presence of any formula in fragOptions. Indeed,
          // it might be that the user has checked the -H20 or -NH3
          // checkboxes, asking that such formulas be accounted for
          // in the generation of the fragment oligomers. Account
          // for these potential formulas...

          //            int accountedFormulas =
          accountFormulas(formulaOligomerList, fragOptions, name, charge);

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

          OligomerList *ionizeLevelOligomerList =
            accountIonizationLevels(formulaOligomerList, fragOptions);

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

          if(!ionizeLevelOligomerList)
            {
              qDebug() << QObject::tr(
                "massxpert - Fragmentation : "
                "Failed to generate ionized "
                "fragment oligomers.");

              while(!formulaOligomerList->isEmpty())
                formulaOligomerList->takeFirst().reset();

              delete formulaOligomerList;

              return -1;
            }

          // At this point, we have to remove all the oligomers
          // from the lastOligomerList and put them into the
          // oligomerList.

          while(!ionizeLevelOligomerList->isEmpty())
            {
              libXpertMass::OligomerSPtr iter_oligomer_sp =
                ionizeLevelOligomerList->takeFirst();

              mp_oligomerList->append(iter_oligomer_sp);
              ++count;
            }

          delete ionizeLevelOligomerList;
        }
      // End of
      // if(!fragRuleApplied)
      else // (fragRuleApplied == 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 = fragOptions.endIndex();
  //  iter > fragOptions.endIndex() - 1; --iter, ++number)

  // FragmentOligomerSPtr oligomer_sp =
  // std::dynamic_pointer_cast<FragmentOligomer>(mp_oligomerList->first());
  // qDebug() << "First oligomer has formula:" <<
  // oligomer_sp->m_formula.toString()
  //<< "as reported:" << oligomer_sp->elementalComposition();

  return count;
}


bool
Fragmenter::accountFragRule(libXpertMass::FragRule *fragRule,
                            bool onlyCheck,
                            int index,
                            int fragEnd,
                            libXpertMass::Ponderable *ponderable)
{
  const libXpertMass::Monomer *prevMonomer = 0;
  const libXpertMass::Monomer *nextMonomer = 0;

  libXpertMass::IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  Q_ASSERT(fragRule);

  if(!onlyCheck)
    Q_ASSERT(ponderable);


  const libXpertMass::Monomer *monomer = mp_polymer->at(index);

  if(!fragRule->currCode().isEmpty())
    if(fragRule->currCode() != monomer->code())
      return false;

  if(!fragRule->prevCode().isEmpty() && !fragRule->nextCode().isEmpty())
    {
      if(fragEnd & libXpertMass::FRAG_END_LEFT || fragEnd & libXpertMass::FRAG_END_NONE)
        {
          if(!index)
            // There cannot be any prevCode since we are at index ==
            // 0, at the first monomer 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 index 'index' - 1. Thus
          // get the monomer out of the sequence for this index.

          prevMonomer = mp_polymer->at(index - 1);

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

          nextMonomer = mp_polymer->at(index + 1);
        }
      else if(fragEnd & libXpertMass::FRAG_END_RIGHT)
        {
          if(!index)
            // There cannot be any nextCode since currCode is the last
            // monomer in the fragmentation series.
            return false;

          nextMonomer = mp_polymer->at(index - 1);

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

          prevMonomer = mp_polymer->at(index + 1);
        }
      else
        return false;

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

      if(fragRule->prevCode() == prevMonomer->code() &&
         fragRule->nextCode() == nextMonomer->code())
        {
          if(onlyCheck)
            return true;

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

          if(!fragRule->Formula::accountMasses(isotopic_data_csp, ponderable))
            {
              qDebug() << "Failed to account fragmentation rule";

              return false;
            }

          return true;
        }
      else
        {
          if(onlyCheck)
            return false;
          else
            return true;
        }
    }
  // End of
  //   if (!fragRule->prevCode().isEmpty() &&
  //   !fragRule->nextCode().isEmpty())
  else if(!fragRule->prevCode().isEmpty())
    {
      if(fragEnd & libXpertMass::FRAG_END_LEFT || fragEnd & libXpertMass::FRAG_END_NONE)
        {
          if(!index)
            // There cannot be any prevCode since currCode is already
            // the first of the fragmentation series.
            return false;

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

          prevMonomer = mp_polymer->at(index - 1);
        }
      else if(fragEnd & libXpertMass::FRAG_END_RIGHT)
        {
          if(index == mp_polymer->size() - 1)
            // There cannot be any prevCode since currCode is already
            // the first of the fragmentation series.
            return false;

          prevMonomer = mp_polymer->at(index + 1);
        }
      else
        return false;

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

      if(fragRule->prevCode() == prevMonomer->code())
        {
          if(onlyCheck)
            return true;

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

          if(!fragRule->Formula::accountMasses(isotopic_data_csp, ponderable))
            {
              qDebug() << "Failed to account fragmentation rule";

              return false;
            }

          return true;
        }
      else
        {
          if(onlyCheck)
            return false;
          else
            return true;
        }
    }
  // End of
  // else if (!fragRule->prevCode().isEmpty())
  else if(!fragRule->nextCode().isEmpty())
    {
      if(fragEnd & libXpertMass::FRAG_END_LEFT || fragEnd & libXpertMass::FRAG_END_NONE)
        {
          if(index == mp_polymer->size() - 1)
            // There cannot be any nextCode since currCode is already
            // the last of the fragmentation series.
            return false;

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

          nextMonomer = mp_polymer->at(index + 1);
        }
      else if(fragEnd & libXpertMass::FRAG_END_RIGHT)
        {
          if(!index)
            // There cannot be any prevCode since currCode is already
            // the last of the fragmentation series.
            return false;

          nextMonomer = mp_polymer->at(index - 1);
        }
      else
        return false;

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

      if(fragRule->nextCode() == nextMonomer->code())
        {
          if(onlyCheck)
            return true;

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

          if(!fragRule->Formula::accountMasses(isotopic_data_csp, ponderable))
            {
              qDebug() << "Failed to account fragmentation rule";

              return false;
            }

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

      if(!fragRule->Formula::accountMasses(isotopic_data_csp, ponderable))
        {
          qDebug() << "Failed to account fragmentation rule";

          return false;
        }

      return true;
    }

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

  return false;
}


int
Fragmenter::accountFormulas(OligomerList *oligomerList,
                            FragOptions &fragOptions,
                            QString name,
                            int charge)
{
  // qDebug() << "Now accounting for formulas checked by the user (-NH3, -H20, "
  //"for example.)";

  Q_ASSERT(oligomerList);

  if(!oligomerList->size())
    qDebug() << "The oligomer list is empty.";

  libXpertMass::IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  int count = 0;

  // The single oligomer in the oligomerlist that we get as parameter is the
  // template on which to base the derivatives on the basis of the formulas.
  FragmentOligomerSPtr template_oligomer_sp =
    std::dynamic_pointer_cast<FragmentOligomer>(oligomerList->first());

  if(template_oligomer_sp == nullptr)
    qFatal("Programming error.");

  // At this point check if the fragOptions.m_formulaList has items
  // in it.

  const QList<libXpertMass::Formula *> &formulaList = fragOptions.formulaList();

  for(int iter = 0; iter < formulaList.size(); ++iter)
    {
      libXpertMass::Formula *formula = formulaList.at(iter);

      // qDebug() << "Handling supplementary formula:" << formula->toString();

      // We will apply the formula to a copy of the template oligomer
      FragmentOligomerSPtr new_oligomer_sp =
        std::make_shared<FragmentOligomer>(*template_oligomer_sp);

      if(new_oligomer_sp == nullptr)
        qFatal("Programming error.");

      if(!formula->accountMasses(isotopic_data_csp, new_oligomer_sp.get()))
        {
          qDebug() << "Failed to account formula";

          new_oligomer_sp.reset();
          continue;
        }

      new_oligomer_sp->m_formula.accountFormula(formula->toString(),
                                                isotopic_data_csp);

      // qDebug() << __FILE__ << __LINE__
      //          << "right after accountFormulaIteration:"
      //          << new_oligomer_sp->mono();

      // qDebug() << "Right after account formula iteration:"
      //<< new_oligomer_sp->m_formula.toString();

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

      QString newName = name;

      newName.append(QString("#%1#z=%2").arg(formula->toString()).arg(charge));

      new_oligomer_sp->setName(newName);

      // At this point append the new oligomer to the list.
      oligomerList->append(new_oligomer_sp);

      ++count;
    }

  return count;
}


OligomerList *
Fragmenter::accountIonizationLevels(OligomerList *oligomerList,
                                    FragOptions &fragOptions)
{
  // qDebug() << "Now accounting for the ionization levels requested by the
  // user.";

  Q_ASSERT(oligomerList);

  bool wasFailure = false;

  libXpertMass::IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // We ge a list of oligomers (or only one, in fact, if no
  // -H2O/-NH3 formulas were checked by the user in the graphical
  // user interface), and we have for each to compute the required
  // ionisation levels. Indeed, the user might ask for fragments
  // that bear more than the single charge that was intrinsically
  // computed within the formula of the fragmentation
  // specification. Thus create as many new oligomers as needed for
  // the different charge levels asked by the user.  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 startIonizeLevel = fragOptions.startIonizeLevel();
  int endIonizeLevel   = fragOptions.endIonizeLevel();

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

  OligomerList *newOligomerList =
    new OligomerList(fragOptions.name(), mp_polymer);

  while(!oligomerList->isEmpty())
    {
      FragmentOligomerSPtr cur_oligomer_sp =
        std::dynamic_pointer_cast<FragmentOligomer>(oligomerList->takeFirst());

      // First of remove the currently iterated oligomer from the
      // list, we won't need it as it is charged 0. But we need to
      // make a copy of it before deleting it at the end of the
      // ionization loop below.

      // At this point use that oligomer as a template for the
      // ionization level stuff.

      for(int kter = startIonizeLevel; kter < endIonizeLevel + 1; ++kter)
        {
          libXpertMass::IonizeRule ionizeRule(m_ionizeRule);
          ionizeRule.setLevel(kter);

          FragmentOligomerSPtr new_oligomer_sp =
            std::make_shared<FragmentOligomer>(*cur_oligomer_sp);

          // qDebug() << __FILE__ << __LINE__
          //          << "right before ionizing with ionizerule:"
          //          << newOligomer->mono()
          //          << "with ionize level =" << kter
          //          << " charge of the oligomer: " << newOligomer->charge();

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

          int res = new_oligomer_sp->ionize(ionizeRule);

          if(res == -1)
            {
              new_oligomer_sp.get();

              wasFailure = true;

              break;
            }
          // The ionization did not change anything in the oligomer's masses.
          else if(res == 0)
            {
              new_oligomer_sp.reset();

              continue;
            }

          new_oligomer_sp->m_formula.accountFormula(
            ionizeRule.formula(), isotopic_data_csp, ionizeRule.level());

          // qDebug() << "After ionization level" << kter
          //<< "with formula:" << ionizeRule.formula()
          //<< ", the new oligomer formula is:"
          //<< new_oligomer_sp->m_formula.toString();

          // qDebug() << __FILE__ << __LINE__
          //          << "right after ionizing with ionizerule:"
          //          << newOligomer->mono()
          //          << "with ionize level =" << kter
          //          << " charge of the oligomer: " << newOligomer->charge();

          // 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->name();
          QString chargeLevel = QString("z=%1").arg(new_oligomer_sp->charge());

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

          new_oligomer_sp->setName(name);

          // qDebug() << __FILE__ << __LINE__
          //          << "new_oligomer_sp charge: " << new_oligomer_sp->charge()
          //          << "name:" << new_oligomer_sp->name();

          newOligomerList->append(new_oligomer_sp);
        }
      // End of
      // for (int kter = startIonizeLevel; kter < endIonizeLevel; ++kter)

      // We can now delete the oligomer that was used as a template,
      // and which had a charge of 0.
      cur_oligomer_sp.reset();

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

      if(wasFailure)
        {
          // Empty the new oligomer list and delete it.
          while(!newOligomerList->isEmpty())
            newOligomerList->takeFirst().reset();

          delete newOligomerList;

          // Also empty the oligomer list passed as parameter, as
          // the caller expects all of its item to be transferred to
          // the new oligomer list and will delete the initial list.

          while(!oligomerList->isEmpty())
            oligomerList->takeFirst().reset();

          // At this point we freed all the allocated data, we can return.
          return NULL;
        }
    }
  // End of
  // while(!oligomerList.isEmpty())

  // At this point, we can Q_ASSERT that oligomerList is empty !
  Q_ASSERT(oligomerList->isEmpty());

  return newOligomerList;
}


void
Fragmenter::emptyOligomerList()
{
  if(mp_oligomerList != nullptr)
    mp_oligomerList->clear();
}

} // namespace massxpert

} // namespace MsXpS
