/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   

   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


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


/////////////////////// Local includes
#include "fragmenter.hpp"


namespace massXpert
{

  Fragmenter::Fragmenter(Polymer *polymer,
			  const PolChemDef *polChemDef, 
			  const QList<FragOptions *> &fragOptionList,
			  const CalcOptions &calcOptions, 
			  const 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() & MXT_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() & MXT_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() == MXT_FRAG_END_NONE)
	  {
	    if (fragmentEndNone(*fragOptions) == -1)
	      return false;
	  }
	else if (fragOptions->fragEnd() == MXT_FRAG_END_LEFT)
	  {
	    if (fragmentEndLeft(*fragOptions) == -1)
	      return false;
	  }
	else if (fragOptions->fragEnd() == MXT_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 bear only its intrinsic 1
	// charge, we should set the level member of the ionization to
	// 0.
	
	IonizeRule ionizeRule(m_ionizeRule);
	ionizeRule.setLevel(0);
	
	FragmentOligomer *oligomer1 = 
	  new FragmentOligomer(mp_polymer, "NOT_SET", fragOptions.name(),
                               Ponderable(), 
                               ionizeRule, false,
                               iter, iter, m_calcOptions);
	
	localOptions.setCapping(MXT_CAP_NONE);
      

	// The calculation of the masses is not performed by applying
	// any ionization rule, as the fragmentation rule itself will
	// yield an ionized species(it should be singly-charged). Thus
	// we pass an invalid ionizeRule object(upon creation, an
	// ionize rule is invalid).
	IonizeRule rule;

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

	    return -1;
	  }

	const QList<Atom *> &refList = mp_polChemDef->atomList();
      
	// It is at this precise moment that the generated oligomer will
	// have a single charge onto it: by the accounting of the
	// fragmentation rule's formula.

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

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

	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, MXT_FRAG_END_NONE, 0))
	      continue;
	  
	    // Each fragrule triggers the creation of a new oligomer.

	    FragmentOligomer *oligomer2 = 
	      new FragmentOligomer(*oligomer1);

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

		return -1;
	      }

	    // At this point we have the fragment oligomer that bears
	    // the "default" fragmentation specification
	    // formula-driven charge, that is z=1.
	    
	    FragmentOligomer *newOligomer = 
	      new FragmentOligomer(*oligomer2);

            int charge = 1;
            
            // 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 1+ charge.  

	    FragmentOligomer *newOligomer = 
	      new FragmentOligomer(*oligomer1);

            int charge = 1;
            
            // 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())
                {
                  FragmentOligomer *iterOligomer = 
                    static_cast<FragmentOligomer *>(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 Ponderable ponderable;
    ponderable.clearMasses();

    const QList<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;
          }
        
      
	Ponderable ponderableTemp(ponderable);
      
	if(!fragOptions.formula().isEmpty())    
	  if (!fragOptions.Formula::accountMasses(refList, &ponderableTemp))
	    {
	      return -1;
	    }
      
	Formula formula = mp_polChemDef->leftCap();
      
	formula.accountMasses(refList, &ponderableTemp, 1);
      
	if(m_calcOptions.polymerEntities() & 
	    MXT_POLYMER_CHEMENT_LEFT_END_MODIF && !fragOptions.startIndex())
	  Polymer::accountEndModifMasses 
	   (mp_polymer,
	     MXT_POLYMER_CHEMENT_LEFT_END_MODIF,
	     &ponderableTemp);
      
	// It is at this precise moment that the generated oligomer will
	// have a single charge onto it: by the accounting of the
	// fragmentation rule's formula as computed above in the
	// ponderableTemp instance used to construct the oligomer.

	// 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.
	
	IonizeRule ionizeRule(m_ionizeRule);
	ionizeRule.setLevel(0);
	
	FragmentOligomer *oligomer1 = 
	  new FragmentOligomer(mp_polymer, "NOT_SET", fragOptions.name(),
                               ponderableTemp, 
                               ionizeRule, false,
                               fragOptions.startIndex(), iter, m_calcOptions);
	
	// 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, MXT_FRAG_END_LEFT, 0))
	      continue;
	  
	    // Each fragrule triggers the creation of a new oligomer.

	    FragmentOligomer *oligomer2 = 
	      new FragmentOligomer(*oligomer1);

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

		return -1;
	      }

	    // At this point we have the fragment oligomer that bears
	    // the "default" fragmentation specification formula-driven
	    // charge. Let's create one such fragment oligomer for the
	    // record(see the 1 charge for the oligomer).

	    FragmentOligomer *newOligomer = 
	      new FragmentOligomer(*oligomer2);

            int charge = 1;
            
            // 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 1+ charge.  

	    FragmentOligomer *newOligomer = 
	      new FragmentOligomer(*oligomer1);
	  
            int charge = 1;
            
            // 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())
                {
                  FragmentOligomer *iterOligomer = 
                    static_cast<FragmentOligomer *>
                    (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 Ponderable ponderable;
    ponderable.clearMasses();
  
    const QList<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;
          }
        

	Ponderable ponderableTemp(ponderable);
      
	if(!fragOptions.formula().isEmpty())    
	  if (!fragOptions.Formula::accountMasses(refList, &ponderableTemp))
	    {
	      return -1;
	    }
      
	Formula formula = mp_polChemDef->rightCap();
      
	formula.accountMasses(refList, &ponderableTemp, 1);
      
	if(m_calcOptions.polymerEntities() & 
	    MXT_POLYMER_CHEMENT_RIGHT_END_MODIF && 
	    fragOptions.endIndex() == mp_polymer->size() - 1)
	  Polymer::accountEndModifMasses 
	   (mp_polymer, 
	     MXT_POLYMER_CHEMENT_RIGHT_END_MODIF,
	     &ponderableTemp);
      
	// It is at this precise moment that the generated oligomer will
	// have a single charge onto it: by the accounting of the
	// fragmentation rule's formula as computed above in the
	// ponderableTemp instance used to construct the oligomer.

	// 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.
	
	IonizeRule ionizeRule(m_ionizeRule);
	ionizeRule.setLevel(0);
	
	FragmentOligomer *oligomer1 = 
	  new FragmentOligomer(mp_polymer, "NOT_SET", fragOptions.name(),
				ponderableTemp, 
				ionizeRule, false, 
				iter, fragOptions.endIndex(), m_calcOptions);

	// 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, MXT_FRAG_END_RIGHT, 0))
	      continue;
	  
	    // Each fragrule triggers the creation of a new oligomer.

	    FragmentOligomer *oligomer2 = 
	      new FragmentOligomer(*oligomer1);

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

		return -1;
	      }

	    // At this point we have the fragment oligomer that bears
	    // the "default" fragmentation specification formula-driven
	    // charge. Let's create one such fragment oligomer for the
	    // record(see the 1 charge for the oligomer).

	    FragmentOligomer *newOligomer = 
	      new FragmentOligomer(*oligomer2);

            int charge = 1;
            
            // 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 1+ charge.  

	    FragmentOligomer *newOligomer = 
	      new FragmentOligomer(*oligomer1);
	  
            int charge = 1;
            
            // 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())
                {
                  FragmentOligomer *iterOligomer = 
                    static_cast<FragmentOligomer *>(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,
			       Ponderable *ponderable)
  {
    const Monomer *prevMonomer = 0;
    const Monomer *nextMonomer = 0;
  
    QList<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 & MXT_FRAG_END_LEFT || fragEnd & MXT_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 & MXT_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 & MXT_FRAG_END_LEFT || fragEnd & MXT_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 & MXT_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 & MXT_FRAG_END_LEFT || fragEnd & MXT_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 & MXT_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 <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.
    FragmentOligomer *templateOligomer = 
      static_cast<FragmentOligomer *>(oligomerList->first());
    
    // At this point check if the fragOptions.m_formulaList has items
    // in it.

    const QList<Formula *>& formulaList = fragOptions.formulaList();
    
    for(int iter = 0; iter < formulaList.size(); ++iter)
      {
        Formula *formula = formulaList.at(iter);
    
        // We will apply the formula to a copy of the template oligomer
        FragmentOligomer *newOligomer = 
          new FragmentOligomer(static_cast<FragmentOligomer &>(*templateOligomer));
        
        if(!formula->accountMasses(atomRefList, newOligomer))
          {
            qDebug() << __FILE__ << __LINE__
                     << "Failed to account formula";
            
            delete newOligomer;
            continue;
          }
        
        // 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())
      {
        FragmentOligomer *curOligomer = 
          static_cast<FragmentOligomer *>(oligomerList->takeFirst());

        // First of remove the currently iterated oligomer from the
        // list and append it right away to the new oligomerList.
        newOligomerList->append(curOligomer);

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

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

            FragmentOligomer *newOligomer = new FragmentOligomer(*curOligomer);
	  
            // 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;
              }
	      
            // 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)

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