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


/////////////////////// Local includes
#include <massxpert/nongui/Fragmenter.hpp>


namespace msXpSmassXpert
{

Fragmenter::Fragmenter(Polymer *polymer,
                       const PolChemDef *polChemDef,
                       const QList<FragOptions *> &fragOptionList,
                       const CalcOptions &calcOptions,
                       const msXpSlibmass::IonizeRule &ionizeRule)
  : mp_polymer(polymer),
    mp_polChemDef(polChemDef),
    m_calcOptions(calcOptions),
    m_ionizeRule(ionizeRule)
{
  Q_ASSERT(mp_polymer && mp_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),
    mp_polChemDef(other.mp_polChemDef),
    m_calcOptions(other.m_calcOptions),
    m_ionizeRule(other.m_ionizeRule)
{
  Q_ASSERT(mp_polymer && mp_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() << __FILE__ << __LINE__
               << "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() & 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() << __FILE__ << __LINE__
                 << "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.
      qSort(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<CrossLink *> crossLinkList;

              partials = 0;

              mp_polymer->crossLinkList(first, last, &crossLinkList, &partials);

              if(partials)
                qDebug() << __FILE__ << __LINE__
                         << "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<CrossLink *> crossLinkList;

      partials = 0;

      mp_polymer->crossLinkList(first, last, &crossLinkList, &partials);

      if(partials)
        qDebug() << __FILE__ << __LINE__
                 << "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() == FRAG_END_NONE)
        {
          if(fragmentEndNone(*fragOptions) == -1)
            return false;
        }
      else if(fragOptions->fragEnd() == FRAG_END_LEFT)
        {
          if(fragmentEndLeft(*fragOptions) == -1)
            return false;
        }
      else if(fragOptions->fragEnd() == 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.

  int count = 0;

  CalcOptions localOptions = m_calcOptions;

  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.

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

      Oligomer *oligomer1 =
        new Oligomer(mp_polymer,
                     "NOT_SET",
                     fragOptions.name() /*fragSpec.m_name*/,
                     mp_polymer->hasModifiedMonomer(iter, iter),
                     msXpSlibmass::Ponderable(),
                     ionizeRule,
                     m_calcOptions,
                     false /*isIonized*/,
                     iter /*startIndex*/,
                     iter /*endIndex*/);

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

      localOptions.setCapping(CAP_NONE);

      // Since version 3.6.0, 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).
      msXpSlibmass::IonizeRule rule;

      if(!oligomer1->calculateMasses(&localOptions, &rule))
        {
          delete oligomer1;

          return -1;
        }

      // qDebug() << __FILE__ << __LINE__
      //          << "right after calculateMasses with invalid ionizerule:"
      //          << oligomer1->mono();

      const QList<msXpSlibmass::Atom *> &refList = mp_polChemDef->atomList();

      if(!fragOptions.formula().isEmpty())
        if(!fragOptions.Formula::accountMasses(refList, oligomer1))
          {
            delete oligomer1;

            return -1;
          }

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

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

      int monomerContrib = fragOptions.monomerContribution();

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

          QString formula = monomer->formula();

          if(!monomer->accountMasses(
               &oligomer1->rmono(), &oligomer1->ravg(), monomerContrib))
            {
              delete oligomer1;

              return -1;
            }
        }

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

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

          if(!accountFragRule(fragRule, true, iter, FRAG_END_NONE, 0))
            continue;

          // Each fragrule triggers the creation of a new oligomer.

          Oligomer *oligomer2 = new Oligomer(*oligomer1);

          if(!accountFragRule(fragRule, false, iter, FRAG_END_NONE, oligomer2))
            {
              delete oligomer1;
              delete oligomer2;

              return -1;
            }

          // qDebug() << __FILE__ << __LINE__
          //          << "right after accountFragRule:"
          //          << oligomer1->mono();

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

          Oligomer *newOligomer = new Oligomer(*oligomer2);

          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));

          newOligomer->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(newOligomer);

          // 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.
          delete oligomer2;

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

              while(!formulaOligomerList->isEmpty())
                delete formulaOligomerList->takeFirst();

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

          Oligomer *newOligomer = new Oligomer(*oligomer1);

          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));

          newOligomer->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(newOligomer);

          // 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).
          delete oligomer1;

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

              while(!formulaOligomerList->isEmpty())
                delete formulaOligomerList->takeFirst();

              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())
            {
              Oligomer *iterOligomer = ionizeLevelOligomerList->takeFirst();

              mp_oligomerList->append(iterOligomer);
              ++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.
          delete oligomer1;
        }
    }
  // End of
  //   for (int iter = fragOptions.startIndex();
  //   fragOptions.endIndex() + 1; ++iter)

  return count;
}


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

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

  const QList<msXpSlibmass::Atom *> &refList = mp_polChemDef->atomList();

  // 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
  // left part of the oligomer, we iterate in the
  // fragOptions.startIndex() --> fragOptions.endIndex() direction.

  for(int iter = fragOptions.startIndex(); iter < fragOptions.endIndex();
      ++iter, ++number)
    {
      bool fragRuleApplied = false;

      const Monomer *monomer = mp_polymer->at(iter);

      monomer->accountMasses(&ponderable, 1);


      // 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<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);
                }

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


      msXpSlibmass::Ponderable ponderableTemp(ponderable);

      if(!fragOptions.formula().isEmpty())
        if(!fragOptions.Formula::accountMasses(refList, &ponderableTemp))
          {
            return -1;
          }

      msXpSlibmass::Formula formula = mp_polChemDef->leftCap();

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

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

      // As of version 3.6.0, the polymer chemistry definition
      // should define a formula for the 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.

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

      Oligomer *oligomer1 =
        new Oligomer(mp_polymer,
                     "NOT_SET",
                     fragOptions.name() /*fragSpec.m_name*/,
                     mp_polymer->hasModifiedMonomer(iter, iter),
                     ponderableTemp,
                     ionizeRule,
                     m_calcOptions,
                     false /*isIonized*/,
                     fragOptions.startIndex(),
                     iter /*endIndex*/);

      // 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 Monomer *monomer = mp_polymer->at(iter);

          QString formula = monomer->formula();

          if(!monomer->accountMasses(
               &oligomer1->rmono(), &oligomer1->ravg(), monomerContrib))
            {
              delete oligomer1;

              return -1;
            }
        }


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

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

          if(!accountFragRule(fragRule, true, iter, FRAG_END_LEFT, 0))
            continue;

          // Each fragrule triggers the creation of a new oligomer.

          Oligomer *oligomer2 = new Oligomer(*oligomer1);

          if(!accountFragRule(fragRule, false, iter, FRAG_END_LEFT, oligomer2))
            {
              delete oligomer1;
              delete oligomer2;

              return -1;
            }

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

          Oligomer *newOligomer = new Oligomer(*oligomer2);

          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));

          newOligomer->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(newOligomer);

          // 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.
          delete oligomer2;

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

              while(!formulaOligomerList->isEmpty())
                delete formulaOligomerList->takeFirst();

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

          Oligomer *newOligomer = new Oligomer(*oligomer1);

          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));

          newOligomer->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(newOligomer);

          // 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).
          delete oligomer1;

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

              while(!formulaOligomerList->isEmpty())
                delete formulaOligomerList->takeFirst();

              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())
            {
              Oligomer *iterOligomer = ionizeLevelOligomerList->takeFirst();

              mp_oligomerList->append(iterOligomer);
              ++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.
          delete oligomer1;
        }
    }
  // End of
  //   for (int iter = fragOptions.startIndex();
  //   iter < fragOptions.endIndex() + 1; ++iter, ++count)


  return count;
}


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

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

  const QList<msXpSlibmass::Atom *> &refList = mp_polChemDef->atomList();

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

  for(int iter = fragOptions.endIndex(); iter > fragOptions.startIndex();
      --iter, ++number)
    {
      bool fragRuleApplied = false;

      const Monomer *monomer = mp_polymer->at(iter);

      monomer->accountMasses(&ponderable, 1);

      // 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<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);
                }

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


      msXpSlibmass::Ponderable ponderableTemp(ponderable);

      if(!fragOptions.formula().isEmpty())
        if(!fragOptions.Formula::accountMasses(refList, &ponderableTemp))
          {
            return -1;
          }

      msXpSlibmass::Formula formula = mp_polChemDef->rightCap();

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

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

      // As of version 3.6.0, the polymer chemistry definition
      // should define a formula for the 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.

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

      Oligomer *oligomer1 =
        new Oligomer(mp_polymer,
                     "NOT_SET",
                     fragOptions.name() /*fragSpec.m_name*/,
                     mp_polymer->hasModifiedMonomer(iter, iter),
                     ponderableTemp,
                     ionizeRule,
                     m_calcOptions,
                     false /*isIonized*/,
                     iter /*startIndex*/,
                     fragOptions.endIndex() /*endIndex*/);

      // 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 Monomer *monomer = mp_polymer->at(iter);

          QString formula = monomer->formula();

          if(!monomer->accountMasses(
               &oligomer1->rmono(), &oligomer1->ravg(), monomerContrib))
            {
              delete oligomer1;

              return -1;
            }
        }

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

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

          if(!accountFragRule(fragRule, true, iter, FRAG_END_RIGHT, 0))
            continue;

          // Each fragrule triggers the creation of a new oligomer.

          Oligomer *oligomer2 = new Oligomer(*oligomer1);

          if(!accountFragRule(fragRule, false, iter, FRAG_END_RIGHT, oligomer2))
            {
              delete oligomer1;
              delete oligomer2;

              return -1;
            }

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

          Oligomer *newOligomer = new Oligomer(*oligomer2);

          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));

          newOligomer->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(newOligomer);

          // 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.
          delete oligomer2;

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

              while(!formulaOligomerList->isEmpty())
                delete formulaOligomerList->takeFirst();

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

          Oligomer *newOligomer = new Oligomer(*oligomer1);

          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));

          newOligomer->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(newOligomer);

          // 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).
          delete oligomer1;

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

              while(!formulaOligomerList->isEmpty())
                delete formulaOligomerList->takeFirst();

              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())
            {
              Oligomer *iterOligomer = ionizeLevelOligomerList->takeFirst();

              mp_oligomerList->append(iterOligomer);
              ++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.
          delete oligomer1;
        }
    }
  // End of
  //  for (int iter = fragOptions.endIndex();
  //  iter > fragOptions.endIndex() - 1; --iter, ++number)

  return count;
}


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

  QList<msXpSlibmass::Atom *> refList = mp_polChemDef->atomList();

  Q_ASSERT(fragRule);

  if(!onlyCheck)
    Q_ASSERT(ponderable);


  const 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 & FRAG_END_LEFT || fragEnd & 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 & 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(refList, ponderable))
            {
              qDebug() << __FILE__ << __LINE__
                       << "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 & FRAG_END_LEFT || fragEnd & 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 & 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(refList, ponderable))
            {
              qDebug() << __FILE__ << __LINE__
                       << "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 & FRAG_END_LEFT || fragEnd & 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 & 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(refList, ponderable))
            {
              qDebug() << __FILE__ << __LINE__
                       << "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(refList, ponderable))
        {
          qDebug() << __FILE__ << __LINE__
                   << "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)
{
  Q_ASSERT(oligomerList);

  const QList<msXpSlibmass::Atom *> &atomRefList =
    fragOptions.polChemDef()->atomList();
  int count = 0;

  // The oligomer that we get as parameter is the template on which
  // to base the derivatives on the basis of the formulas.
  Oligomer *templateOligomer = oligomerList->first();

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

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

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

      // We will apply the formula to a copy of the template oligomer
      Oligomer *newOligomer = new Oligomer(*templateOligomer);

      if(!formula->accountMasses(atomRefList, newOligomer))
        {
          qDebug() << __FILE__ << __LINE__ << "Failed to account formula";

          delete newOligomer;
          continue;
        }

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

      // 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->text()).arg(charge));

      newOligomer->setName(newName);

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

      ++count;
    }

  return count;
}


OligomerList *
Fragmenter::accountIonizationLevels(OligomerList *oligomerList,
                                    FragOptions &fragOptions)
{
  Q_ASSERT(oligomerList);

  bool wasFailure = false;

  // 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())
    {
      Oligomer *curOligomer = 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)
        {
          msXpSlibmass::IonizeRule ionizeRule(m_ionizeRule);
          ionizeRule.setLevel(kter);

          Oligomer *newOligomer = new Oligomer(*curOligomer);

          // 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 = newOligomer->ionize(ionizeRule);

          if(res == -1)
            {
              delete newOligomer;

              wasFailure = true;

              break;
            }
          else if(res == 0)
            {
              delete newOligomer;

              continue;
            }

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

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

          newOligomer->setName(name);

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

          newOligomerList->append(newOligomer);
        }
      // 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.
      delete(curOligomer);

      // 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())
            delete newOligomerList->takeFirst();

          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())
            delete oligomerList->takeFirst();

          // 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()
{
  while(mp_oligomerList->size())
    {
      delete mp_oligomerList->takeFirst();
    }
}

} // namespace msXpSmassXpert
