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


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


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



namespace MsXpS
{

namespace massxpert
{


Cleaver::Cleaver(libXpertMass::Polymer *polymer,
                 const libXpertMass::PolChemDefCstSPtr polChemDefCstSPtr,
                 const CleaveOptions &cleaveOptions,
                 const libXpertMass::CalcOptions &calcOptions,
                 const libXpertMass::IonizeRule &ionizeRule)
  : mp_polymer(polymer),
    mcsp_polChemDef(polChemDefCstSPtr),
    m_cleaveOptions(cleaveOptions),
    m_calcOptions(calcOptions),
    m_ionizeRule(ionizeRule)
{
  Q_ASSERT(mp_polymer && mcsp_polChemDef);
  mp_oligomerList = nullptr;
}


Cleaver::Cleaver(const Cleaver &other)
  : mp_polymer(other.mp_polymer),
    mcsp_polChemDef(other.mcsp_polChemDef),
    m_cleaveOptions(other.m_cleaveOptions),
    m_calcOptions(other.m_calcOptions),
    m_ionizeRule(other.m_ionizeRule)
{
  Q_ASSERT(mp_polymer && mcsp_polChemDef);
  mp_oligomerList = nullptr;
}


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


QString
Cleaver::cleaveAgentName() const
{
  return m_cleaveOptions.name();
}


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

  mp_oligomerList = oligomerList;
}


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


bool
Cleaver::cleave(bool reset)
{
  if(!mp_oligomerList)
    qFatal("%s@%d -- The oligomer list is 0.", __FILE__, __LINE__);

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

  // Ensure that the cleavage pattern was already parsed.

  if(!m_cleaveOptions.motifList()->size())
    {
      if(!m_cleaveOptions.parse())
        {
          qDebug() << "Failed to parse the cleavage options";

          return false;
        }
    }

  //   qDebug() << __FILE__ << __LINE__
  // 	    << "number of motifs:"
  // 	    << mp_cleaveOptions->motifList()->size();

  if(!fillIndexLists())
    {
      qDebug() << "Index lists(cleave/nocleave) are empty."
                  "No oligomer generated.";

      // We can return true, as no error condition was found but not
      // oligomers were generated.

      return true;
    }

  if(resolveCleavageNoCleavage() == -1)
    {
      qDebug() << "Failed to resolve cleavage/nocleavage";

      return false;
    }

  removeDuplicatesCleavage();

  std::sort(m_cleaveIndexList.begin(), m_cleaveIndexList.end());

  //     for (int debugIter = 0; debugIter < m_cleaveIndexList.size();
  // 	 ++debugIter)
  //       {
  // 	qDebug() << __FILE__ << __LINE__
  // 		 << "Index:" << m_cleaveIndexList.at(debugIter);
  //       }

  if(reset)
    emptyOligomerList();

  for(int iter = 0; iter <= m_cleaveOptions.partials(); ++iter)
    {
      if(cleavePartial(iter) == -1)
        {
          qDebug() << "Failed to perform partial cleavage at index:" << iter;

          return false;
        }
    }

  // At this point we have the list of lists of oligomers, one list of
  // oligomers for each partial cleavage.

  while(m_cleaveIndexList.size())
    m_cleaveIndexList.removeFirst();

  while(m_noCleaveIndexList.size())
    m_noCleaveIndexList.removeFirst();

  return true;
}


int
Cleaver::fillIndexLists()
{
  QList<libXpertMass::CleaveMotif *> *motifList = m_cleaveOptions.motifList();

  while(m_cleaveIndexList.size())
    m_cleaveIndexList.removeFirst();

  while(m_noCleaveIndexList.size())
    m_noCleaveIndexList.removeFirst();

  // The cleavage might be performed on a selected portion of a sequence only,
  // not necessarily on the whole polymer sequence.

  libXpertMass::CoordinateList coordinateList = m_calcOptions.coordinateList();
  libXpertMass::Coordinates *coordinates      = coordinateList.first();

  int startIndex = coordinates->start();
  int endIndex   = coordinates->end();

  for(int iter = 0; iter < motifList->size(); ++iter)
    {
      libXpertMass::CleaveMotif *cleaveMotif = motifList->at(iter);
      Q_ASSERT(cleaveMotif);

      int index = startIndex - 1;

      while(1)
        {
          index = findCleaveMotif(*cleaveMotif, index + 1, endIndex);

          if(index == -1)
            break;

          // Do not forget: The position at which the motif is found
          // in the polymer sequence is not necessarily the position
          // at which the cleavage will effectively occur. Indeed,
          // let's say that we found such motif in the polymer
          // sequence: "KKRKGP". This motif was extracted from a
          // cleave spec that had a pattern like this: "KKRK/GP". What
          // we see here is that the cleavage occurs after the fourth
          // monomer! And we must realize that the 'index' returned
          // above corresponds to the index of the first 'K' in
          // "KKRKGP" motif that was found in the polymer
          // sequence. Thus we have to take into account the offset
          //(+4, in our example, WHICH IS A POSITION and not an
          // index, which is why we need to remove 1 below) of the
          // cleavage:

          int cleavageIndex = index + cleaveMotif->offset() - 1;

          if(cleavageIndex < 0)
            continue;

          if(cleavageIndex >= endIndex)
            break;

          // qDebug() << __FILE__ << __LINE__
          //          << "Found new cleavage index:"
          //          << cleavageIndex;

          if(cleaveMotif->isForCleave())
            {
              m_cleaveIndexList.append(cleavageIndex);

              // qDebug() << __FILE__ << __LINE__
              //          << "For cleavage, index:" << cleavageIndex;
            }
          else
            {
              m_noCleaveIndexList.append(cleavageIndex);

              // qDebug() << __FILE__ << __LINE__
              //          << "Not for cleavage";
            }
        }
      // End of
      // while (1)
    }
  // End of
  // for (int iter = 0; iter < motifList->size(); ++iter)

  // Note that returning 0 is not an error condition, because a
  // sequence where no site is found whatsoever will result in 0.
  return m_cleaveIndexList.size() + m_noCleaveIndexList.size();
}


int
Cleaver::resolveCleavageNoCleavage()
{
  for(int iter = 0; iter < m_noCleaveIndexList.size(); ++iter)
    {
      int noCleaveIndex = m_noCleaveIndexList.at(iter);

      for(int jter = 0; jter < m_cleaveIndexList.size(); ++jter)
        {
          int cleaveIndex = m_cleaveIndexList.at(jter);

          if(noCleaveIndex == cleaveIndex)
            m_cleaveIndexList.removeAt(jter);
        }
    }

  return m_cleaveIndexList.size();
}


int
Cleaver::removeDuplicatesCleavage()
{
  for(int iter = 0; iter < m_cleaveIndexList.size(); ++iter)
    {
      int index = m_cleaveIndexList.at(iter);

      int foundItemIndex = m_cleaveIndexList.indexOf(index, iter + 1);

      if(foundItemIndex != -1)
        {
          m_cleaveIndexList.removeAt(foundItemIndex);
          --iter;
        }
    }

  return m_cleaveIndexList.size();
}


int
Cleaver::findCleaveMotif(libXpertMass::CleaveMotif &cleaveMotif,
                         int startIndex,
                         int endIndex)
{
  bool noGood = false;

  int firstIndex = 0;

  QList<const libXpertMass::Monomer *> monomerList = mp_polymer->monomerList();
  const QStringList &codeList                 = cleaveMotif.codeList();


  // We have to iterate in the polymer sequence starting at 'index', in
  // search for a sequence element identical to the sequence in the
  //'cleaveMotif'.

  // This means that if

  // cleavemotif->m_motifList [0] = "Lys"

  // cleavemotif->m_motifList [1] = "Pro"

  // the, we want to search in the polymer list of monomers the same
  // sequence by iterating in this list from index 'index' onwards,
  // and we stop searching when the list's end is found or if

  // list [n] = "Lys" and

  // list [n+1] = "Pro".


  if(mp_polymer->size() == 0)
    return 0;

  if(codeList.size() == 0)
    return -1;

  if(startIndex < 0)
    return -1;

  if(endIndex >= mp_polymer->size())
    return -1;

  // Seed the routine by setting 'first' to the first motif in the
  // codeList (in our example this is "Lys").

  QString firstCode = codeList.first();

  // And now iterate (starting from 'index') in the polymer
  // sequence's list of monomers in search for a monomer having the
  // proper code ("Lys").

  int iterIndex = startIndex;

  while(iterIndex < endIndex)
    {
      const libXpertMass::Monomer *monomer = monomerList.at(iterIndex);

      if(monomer->code() != firstCode)
        {
          // The currently iterated code is not the one we
          // search. So go one code further in the sequence.

          ++iterIndex;
          continue;
        }

      // If we are here, then that means that we actually found on
      // monomer code in the sequence that matches the one we are
      // looking for.

      firstIndex = iterIndex;
      noGood     = false;

      // Now that we have anchored our search at firstIndex in the
      // polymer sequence, continue with next monomer and check if
      // it matches the next monomer in the motif we are looking
      // for.

      for(int iter = 1; iter < codeList.size(); ++iter)
        {
          if(iterIndex + iter >= endIndex)
            {
              noGood = true;
              break;
            }

          QString nextCode = codeList.at(iter);

          monomer = monomerList.at(iterIndex + iter);

          if(monomer->code() == nextCode)
            continue;
          else
            {
              noGood = true;
              break;
            }
        }
      // End of
      // for (int iter = 1; iter < codeList.size(); ++iter)

      if(noGood)
        {
          ++iterIndex;
          continue;
        }
      else
        {
          return firstIndex;
        }
    }
  // End of
  // while (iterIndex < monomerList.size())

  return -1;
}


bool
Cleaver::accountCleaveRule(libXpertMass::CleaveRule *cleaveRule,
                           CleaveOligomer *oligomer)
{
  Q_ASSERT(cleaveRule);
  Q_ASSERT(oligomer);

  libXpertMass::IsotopicDataCstSPtr isotopic_data_csp =
    oligomer->polymer()->getPolChemDefCstSPtr()->getIsotopicDataCstSPtr();

  // For each libXpertMass::Coordinates element in the oligomer, we have to ensure
  // we apply the formula(s) that is/are required.

  int coordinatesCount =
    static_cast<libXpertMass::CoordinateList *>(oligomer)->size();

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Coordinates count:" << coordinatesCount;

  for(int iter = 0; iter < coordinatesCount; ++iter)
    {
      libXpertMass::Coordinates *coordinates =
        static_cast<libXpertMass::CoordinateList *>(oligomer)->at(iter);

      if(!cleaveRule->leftCode().isEmpty())
        {
          // The formula has to be there.

          libXpertMass::Formula ruleFormula = cleaveRule->leftFormula();
          Q_ASSERT(!ruleFormula.toString().isEmpty());

          // What is the monomer at the left end of current oligomer ?
          const libXpertMass::Monomer &monomer = oligomer->atLeftEnd();

          if(monomer.code() == cleaveRule->leftCode())
            {
              // 		qDebug() << __FILE__ << __LINE__
              // 			  << "Matched left code:" << cleaveRule->leftCode();

              // But, this is not going to be real true, if the
              // monomer is acutally the left-end monomer of the
              // polymer sequence, because then that would mean that
              // there was no cleavage on this monomer, thus no rule
              // to apply.

              if(!coordinates->start())
                {
                  // The monomer is not the left-end monomer, so the
                  // match is real. Account for the formula !

                  if(!ruleFormula.accountMasses(isotopic_data_csp, oligomer))
                    return false;

                  oligomer->m_formula.accountFormula(ruleFormula.toString(),
                                                     isotopic_data_csp);
                }
            }
        }

      if(!cleaveRule->rightCode().isEmpty())
        {
          // The formula has to be there.

          libXpertMass::Formula ruleFormula = cleaveRule->rightFormula();
          Q_ASSERT(!ruleFormula.toString().isEmpty());

          // What is the monomer at the right end of current oligomer ?
          const libXpertMass::Monomer &monomer = oligomer->atRightEnd();

          if(monomer.code() == cleaveRule->rightCode())
            {
              // 		qDebug() << __FILE__ << __LINE__
              // 			  << "Matched right code:" << cleaveRule->rightCode();

              // But, this is not going to be real true, if the
              // monomer is acutally the right-end monomer of the
              // polymer sequence, because then that would mean that
              // there was no cleavage on this monomer, thus no rule
              // to apply.

              if(coordinates->end() != mp_polymer->size() - 1)
                {
                  // The monomer is not the right-end monomer, so
                  // the match is real. Account for the formula !

                  if(!ruleFormula.accountMasses(isotopic_data_csp, oligomer))
                    return false;

                  oligomer->m_formula.accountFormula(ruleFormula.toString(),
                                                     isotopic_data_csp);
                }
            }
        }
    }

  return true;
}


int
Cleaver::cleavePartial(int partialCleavageValue)
{
  bool oligomerIsPolymer = false;

  int iter = 0;

  static int leftIndex  = 0;
  static int rightIndex = 0;

  Q_ASSERT(partialCleavageValue >= 0);

  OligomerList partialOligomerList;

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

  // The cleavage might be performed on a selected portion of a sequence only,
  // not necessarily on the whole polymer sequence. We have to know these values
  // because when we create oligomer we'll have to set the coordinates of the
  // monomers correctly.

  libXpertMass::CoordinateList coordinateList = m_calcOptions.coordinateList();
  libXpertMass::Coordinates *coordinates      = coordinateList.first();

  int startIndex = coordinates->start();
  int endIndex   = coordinates->end();

  leftIndex  = startIndex;
  rightIndex = 0;

  // Iterate in the array of indices where the cleavages should occur.

  for(iter = 0; iter < m_cleaveIndexList.size(); ++iter)
    {
      // Make sure, if the partial cleavage is very large, for
      // example, that it will not lead us to access the polymer
      // sequence at a position larger than its upper boundary.

      // Imagine cutting a polymer with only one Met residue with
      // cyanogen bromide: m_cleaveIndexList will contain a single
      // element: the index at which the methionine occurs in the
      // polymer sequence(and there is a single one). Now, Imagine
      // that we are asked to perform a cleavage with
      // 'partialCleavageValue' of 2. The way we do it is that we fetch the
      // index in the list of cleavage indices(m_cleaveIndexList) two
      // positions farther than the position we are iterating:

      // int partCleave = iter + partialCleavageValue;

      // Now, if m_cleaveIndexList contains a single element, asking
      // for this m_cleaveIndexList.at(iter + partialCleavageValue) will
      // go out of the boundaries of the list, since it has a single
      // item and partialCleavageValue is 2. This is what we are willing to
      // avoid.

      int partCleave = iter + partialCleavageValue;

      if(partCleave >= m_cleaveIndexList.size())
        {
          if(iter == 0)
            oligomerIsPolymer = true;

          break;
        }

      rightIndex = m_cleaveIndexList.at(partCleave);

      QString name = QString("%1#%2").arg(partialCleavageValue).arg(iter + 1);

      CleaveOligomerSPtr oligomer_sp = std::make_shared<CleaveOligomer>(
        mp_polymer,
        name,
        m_cleaveOptions.name(),
        mp_polymer->hasModifiedMonomer(leftIndex, rightIndex),
        libXpertMass::Ponderable(),
        leftIndex,
        rightIndex,
        partialCleavageValue,
        m_calcOptions);

      // The CleaveOligomer cannot compute its own elemental composition,
      // contrary to libXpertMass::Oligomer.

      // Use the shared pointer to the CleaveOligomer to access the base class
      // function that works by using calcoptions to compute the formula of the
      // oligomer.
      QString elemental_composition =
        oligomer_sp.get()->libXpertMass::Oligomer::elementalComposition();

      // And now use that elemental composition to set it in the CleaveOligomer.

      oligomer_sp->m_formula.accountFormula(
        elemental_composition, isotopic_data_csp, 1);

      // qDebug() << "Oligomer has formula:" <<
      // oligomer_sp->elementalComposition();

      // At this point we can add the configured oligomer to the list.
      partialOligomerList.append(oligomer_sp);

      // Increment the indx for next oligomer.
      leftIndex = m_cleaveIndexList.at(iter) + 1;
    }
  // End of
  // for (int iter = 0; iter < m_cleaveIndexList.size(); iter=+)

  // At this point we have finished iterating in the cleave index list, but
  // there was an oligomer cooking when we ended the looping. We should handle
  // that stray oligomer exactly the same way we did for the other inside the
  // loop.

  // Indeed, this last oligomer that was cooking is the right-end oligomer ! And
  // be sure to determine what's its real left end index !

  if(oligomerIsPolymer)
    leftIndex = startIndex;
  else
    leftIndex = m_cleaveIndexList.at(--iter) + 1;

  // 'iter' is used to construct the name of the oligomer, so we have
  // to increment it once because we did not have the opportunity to
  // increment it between the last but one oligomer and this one.

  ++iter;

  rightIndex = endIndex;

  QString name = QString("%1#%2").arg(partialCleavageValue).arg(iter + 1);

  CleaveOligomerSPtr oligomer_sp = std::make_shared<CleaveOligomer>(
    mp_polymer,
    name,
    m_cleaveOptions.name(),
    mp_polymer->hasModifiedMonomer(leftIndex, rightIndex),
    libXpertMass::Ponderable(),
    leftIndex,
    rightIndex,
    partialCleavageValue,
    m_calcOptions);

  // The CleaveOligomer cannot compute its own elemental composition,
  // contrary to libXpertMass::Oligomer.

  // Use the shared pointer to the CleaveOligomer to access the base class
  // function that works by using calcoptions to compute the formula of the
  // oligomer.
  QString elemental_composition =
    oligomer_sp.get()->libXpertMass::Oligomer::elementalComposition();

  // And now use that elemental composition to set it in the CleaveOligomer.

  oligomer_sp->m_formula.accountFormula(
    elemental_composition, isotopic_data_csp, 1);

  // qDebug() << "Oligomer has formula:" << oligomer_sp->elementalComposition();

  // At this point we can add the configured oligomer to the list.
  partialOligomerList.append(oligomer_sp);

  // At this point all the skeleton oligomers have been computed for
  // the given partialCleavageValue. We still have to perform the
  // cross-link analysis prior to both calculate the masses and
  // perform the ionization of all the generated oligomers. Note that
  // making cross-link analysis is only useful in case the cleavage is
  // full (that is, partialCleavageValue == 0).

  if(!partialCleavageValue)
    {
      if(m_calcOptions.monomerEntities() & libXpertMass::MONOMER_CHEMENT_CROSS_LINK)
        {
          if(analyzeCrossLinks(&partialOligomerList) == -1)
            {
              return false;
            }
        }
    }

  // Finally, we can now perform the mass calculations and the
  // ionization. We will use each oligomer in the oligomerList as a
  // template for creating new oligomers(with different z values) and
  // all the new oligomers will be appended to waitOligomerList. Each
  // time a template oligomer will have been used, it will be removed
  // from oligomerList. Once all the oligomers in oligomerList will
  // have been used, and thus removed, all the newly allocated
  // oligomers in waitOligomerList will be moved to oligomerList.

  OligomerList waitOligomerList;

  while(partialOligomerList.size())
    {
      libXpertMass::OligomerSPtr iter_oligomer_sp = partialOligomerList.takeFirst();

      CleaveOligomer *iter_oligomer_p =
        dynamic_cast<CleaveOligomer *>(iter_oligomer_sp.get());

      // We do not ask that the oligomer be ionized yet, because we
      // have to first account for potential cleavage rules! Thus we
      // pass an uninitialized ionization rule with IonizeRule().
      // iterOligomer->calculateMasses(*mp_calcOptions,
      // *mp_ionizeRule); This was a bug in the release versions up
      // to 1.6.1.
      iter_oligomer_p->calculateMasses(&m_calcOptions);

      // At this point we should test if the oligomer has to be
      // processed using cleavage rules.

      for(int jter = 0; jter < m_cleaveOptions.ruleList()->size(); ++jter)
        {
          //  Note that the accounting of the cleavage rule is
          //  performed as if the oligomer was charged 1. This is why
          //  we have to ionize the oligomer only after we have
          //  completed the determination of its atomic composition.

          libXpertMass::CleaveRule *cleaveRule =
            m_cleaveOptions.ruleList()->at(jter);

          // qDebug() << __FILE__ << __LINE__
          //           << "Accounting for cleaverule:"
          //           << cleaveRule->name();

          // qDebug() << __FILE__ << __LINE__
          //           << "Oligomer mono mass before:" <<
          //           iterOligomer->mono();

          if(!accountCleaveRule(cleaveRule, iter_oligomer_p))
            return -1;

          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << "Oligomer mono mass after:" << iterOligomer->mono();
        }

      // At this point we can finally ionize the oligomer ! Remember
      // that we have to ionize the oligomer as expected in the
      // cleavage options. 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 = m_cleaveOptions.startIonizeLevel();
      int endIonizeLevel   = m_cleaveOptions.endIonizeLevel() + 1;
      libXpertMass::IonizeRule ionizeRule(m_ionizeRule);

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

          CleaveOligomerSPtr new_oligomer_sp =
            std::make_shared<CleaveOligomer>(*iter_oligomer_p);

          if(new_oligomer_sp->ionize(ionizeRule) == -1)
            return -1;

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

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

          // The name was set already during the creation of the
          // template oligomer. All we have to add to the name is the
          // ionization level.

          QString name = iter_oligomer_p->name() +
                         QString("#z=%3").arg(new_oligomer_sp->charge());

          new_oligomer_sp->setName(name);

          waitOligomerList.append(new_oligomer_sp);
        }

      // We can delete the template oligomer that was already removed
      // from the oligomerList(use of QList::takeFirst()).
      iter_oligomer_sp.reset();
    }

  // At this point we should transfer all the oligomers from the
  // waitOligomerList to the initial oligomerList.

  while(waitOligomerList.size())
    {
      // Crucial to make this pointer cast so that we transfer actual
      // CleaveOligomers!

      CleaveOligomerSPtr iter_oligomer_sp =
        std::dynamic_pointer_cast<CleaveOligomer>(waitOligomerList.takeFirst());

      partialOligomerList.append(iter_oligomer_sp);
    }

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

  int oligomerCount = partialOligomerList.size();

  while(partialOligomerList.size())
    {
      // Crucial to make this pointer cast so that we transfer actual
      // CleaveOligomers!

      CleaveOligomerSPtr iter_oligomer_sp =
        std::dynamic_pointer_cast<CleaveOligomer>(
          partialOligomerList.takeFirst());

      //// Elemental formula
      // QString *text = new QString(iter_oligomer_sp->elementalComposition());
      // libXpertMass::StringProp *prop =
      // new libXpertMass::StringProp("ELEMENTAL_COMPOSITION", text);
      // iter_oligomer_sp->appendProp(static_cast<libXpertMass::Prop *>(prop));

      mp_oligomerList->append(iter_oligomer_sp);
    }

  return oligomerCount;
}


int
Cleaver::analyzeCrossLinks(OligomerList *oligomerList)
{
  Q_ASSERT(oligomerList);

  OligomerList crossLinkedOligomerList;

  // General overview:

  // Iterate in the polymer's list of cross-links and for each
  // cross-link find the oligomer that contains the first monomer
  // involved in the cross-link. This first found oligomer should
  // serve as a seed to pull-down all the oligomers cross-linked to
  // it.

  const libXpertMass::CrossLinkList &crossLinkList = mp_polymer->crossLinkList();

  for(int iter = 0; iter < crossLinkList.size(); ++iter)
    {
      libXpertMass::CrossLink *crossLink = crossLinkList.at(iter);

      // With that crossLink, find an oligomer that encompasses the
      // first monomer of the cross-link.

      const libXpertMass::Monomer *firstMonomer = crossLink->firstMonomer();

      Q_ASSERT(firstMonomer);

      // What oligomer does encompass that monomer ?

      int foundIndex = 0;

      libXpertMass::OligomerSPtr first_oligomer_sp =
        oligomerList->findOligomerEncompassing(firstMonomer, &foundIndex);

      if(first_oligomer_sp)
        {
          // At this point we should turn this oligomer into a
          // cross-linked oligomer, so that we can continue performing its
          // cross-link analysis. To do that we allocate a list of
          // oligomers for this cross-linked oligomer, were we'll store
          // this first oligomer and then all the "pulled-down" oligomers.

          // Remove the cross-link from the main list of oligomers so
          // that we do not stumble upon it in the next analysis
          // steps.
          oligomerList->removeAt(foundIndex);

          // Set the cross-linked oligomer apart.
          crossLinkedOligomerList.append(first_oligomer_sp);

          // Finally deeply scrutinize the oligomer.
          analyzeCrossLinkedOligomer(first_oligomer_sp, oligomerList);
        }
      else
        {
          // qDebug() << __FILE__ << __LINE__
          //    << "Cross-link at index" << iter
          //    << "did not find any oligomer for its first monomer "
          // 	    "partner";
        }
    }

  // At this point we have terminated analyzing all the oligomers
  // for the partial cleavage. All we have to do is move all the
  // crossLinked oligomers from the crossLinkedOligomerList to
  // oligomerList. While doing so make sure that the m_calcOptions
  // datum has correct libXpertMass::CoordinateList data, as these data will be
  // required later, typically to calculate the elemental formula of
  // the oligomer.

  while(crossLinkedOligomerList.size())
    {
      libXpertMass::OligomerSPtr oligomer_sp = crossLinkedOligomerList.takeAt(0);

      oligomer_sp->updateCalcOptions();

      oligomerList->append(oligomer_sp);
    }

  crossLinkedOligomerList.clear();

  // Return the number of cross-linked/non-cross-linked oligomers
  // alltogether.

  return oligomerList->size();
}


int
Cleaver::analyzeCrossLinkedOligomer(libXpertMass::OligomerSPtr oligomer_sp,
                                    OligomerList *oligomerList)
{
  Q_ASSERT(oligomer_sp);
  Q_ASSERT(oligomerList);

  const libXpertMass::CrossLinkList &crossLinkList = mp_polymer->crossLinkList();

  OligomerList clearanceOligomerList;

  // 'oligomer_sp' is the first oligomer in the cross-link series of
  // oligomers. It is the "seeding" oligomer with which to pull-down
  // all the others. Prepend to its name the "cl-" string to let it
  // know it is cross-linked.

  QString name = oligomer_sp->name();
  name.prepend("cl-");
  oligomer_sp->setName(name);

  // Iterate in the 'oligomer_sp' and for each monomer get any
  // cross-linked oligomer out of the list of cross-links.

  for(int iter = oligomer_sp->startIndex(); iter < oligomer_sp->endIndex() + 1;
      ++iter)
    {
      const libXpertMass::Monomer *monomer = mp_polymer->at(iter);

      // What crossLinks do involve this monomer ?

      QList<int> crossLinkIndices;

      int ret =
        crossLinkList.crossLinksInvolvingMonomer(monomer, &crossLinkIndices);

      if(ret)
        {
          // At least one cross-link involves the monomer currently
          // iterated in the oligomer being analysed.

          int index = 0;

          foreach(index, crossLinkIndices)
            {
              libXpertMass::CrossLink *crossLink = crossLinkList.at(index);

              // 	      qDebug() << __FILE__ << __LINE__
              // 			<< crossLink->name();

              // First off, we can add the cross-link to the list of
              // cross-links of the oligomer(we'll need them to be
              // able to perform mass calculations). Note that this is
              // only copying the pointer to the actual cross-link in
              // the polymer's list of cross-links. Note also that a
              // cross-link might not be found more than once(the
              // call below first checks that the cross-link is not
              // already in the list).

              if(!oligomer_sp->addCrossLink(crossLink))
                {
                  //  qDebug() << __FILE__ << __LINE__
                  //	    << "The cross-link:"
                  //	    << crossLink->name()
                  //	    << "was already in the"
                  //	    << oligomer
                  //	    << "oligomer's list of cross-links: "
                  // 		    "not duplicated.";
                }
              else
                {
                  //  qDebug() << __FILE__ << __LINE__
                  //	    << "The cross-link:"
                  //	    << crossLink->name()
                  //	    << "was added to the"
                  //	    << oligomer
                  //	    << "oligomer's list of cross-links.";
                }

              const libXpertMass::Monomer *iterMonomer = 0;

              foreach(iterMonomer, *(crossLink->monomerList()))
                {
                  // 	 qDebug() << __FILE__ << __LINE__
                  // 	    << iterMonomer->name();

                  int foundIndex = 0;

                  libXpertMass::OligomerSPtr found_oligomer_sp =
                    oligomerList->findOligomerEncompassing(iterMonomer,
                                                           &foundIndex);

                  if(found_oligomer_sp)
                    {
                      // qDebug() << __FILE__ << __LINE__
                      // 	<< foundOligomer->name() << foundIndex;

                      // One oligomer in the original oligomer list
                      // encompasses a monomer that seems to be
                      // cross-linked to the 'monomer' being iterated
                      // in in the currently analyzed oligomer. Move
                      // that oligomer to the clearance list of
                      // oligomer that will need to be further
                      // analyzed later.

                      oligomerList->removeAt(foundIndex);

                      clearanceOligomerList.append(found_oligomer_sp);

                      // Update the name of the oligomer with the name
                      // of the new foundOligomer.

                      QString name = QString("%1+%2")
                                       .arg(oligomer_sp->name())
                                       .arg(found_oligomer_sp->name());
                      oligomer_sp->setName(name);
                    }
                }
            }
          // End of
          // foreach(index, crossLinkIndices)
        }
    }

  // At this point we have one oligomer which we know is cross-linked
  // at least once(with another oligomer or the cross-link is between
  // two or more monomers in the same oligomer, think cyan fluorescent
  // protein). If monomers in that same oligomer were cross-linked to
  // other monomers in other oligomers, then these oligomers should by
  // now have been moved from the original list of oligomers
  //(oligomerList) to the clearance list of oligomers
  //(clearanceOligomerList). We have to iterate in each oligomer of that
  // clearance list and for each of its monomers, check if it has a
  // cross-link to any oligomer still in the original oligomerList
  //(this is what I call "pull-down" stuff). Found oligomers are
  // appended to the clearanceOligomerList.

  while(clearanceOligomerList.size())
    {
      libXpertMass::OligomerSPtr iter_oligomer_sp = clearanceOligomerList.first();

      for(int iter = iter_oligomer_sp->startIndex();
          iter < iter_oligomer_sp->endIndex() + 1;
          ++iter)
        {
          const libXpertMass::Monomer *monomer = mp_polymer->at(iter);

          // 	  qDebug() << __FILE__ << __LINE__
          // 		    << monomer->name();

          // What crossLinks do involve this monomer ?

          QList<int> crossLinkIndices;

          int ret = crossLinkList.crossLinksInvolvingMonomer(monomer,
                                                             &crossLinkIndices);

          if(ret)
            {
              // At least one cross-link involves the monomer currently
              // iterated in the iter_oligomer_sp being analysed.

              int index = 0;

              foreach(index, crossLinkIndices)
                {
                  libXpertMass::CrossLink *crossLink = crossLinkList.at(index);

                  // 		  qDebug() << __FILE__ << __LINE__
                  // 			    << crossLink->name();

                  // First off, we can add the cross-link to the list of
                  // cross-links of the oligomer(we'll need them to be
                  // able to perform mass calculations). Note that this is
                  // only copying the pointer to the actual cross-link in
                  // the polymer's list of cross-links. Note also that a
                  // cross-link might not be found more than once(the
                  // call below first checks that the cross-link is not
                  // already in the list).

                  if(!oligomer_sp->addCrossLink(crossLink))
                    {
                      // qDebug() << __FILE__ << __LINE__
                      // << "The cross-link:"
                      // << crossLink->name()
                      // << "was already in the"
                      // << oligomer
                      // << "oligomer's list of cross-links: "
                      // "not duplicated.";
                    }
                  else
                    {
                      // qDebug() << __FILE__ << __LINE__
                      // << "The cross-link:"
                      // << crossLink->name()
                      // << "was added to the"
                      // << oligomer
                      // << "oligomer's list of cross-links.";
                    }

                  const libXpertMass::Monomer *iterMonomer = 0;

                  foreach(iterMonomer, *(crossLink->monomerList()))
                    {
                      // qDebug() << __FILE__ << __LINE__
                      //	<< iterMonomer->name();

                      int foundIndex = 0;

                      libXpertMass::OligomerSPtr found_oligomer_sp =
                        oligomerList->findOligomerEncompassing(iterMonomer,
                                                               &foundIndex);

                      if(found_oligomer_sp)
                        {
                          // qDebug() << __FILE__ << __LINE__
                          //          << foundOligomer->name() << foundIndex;

                          // One oligomer in the original oligomer list
                          // encompasses a monomer that seems to be
                          // cross-linked to the 'monomer' being iterated
                          // in in the currently analyzed oligomer. Move
                          // that oligomer to the clearance list of
                          // oligomer that will need to be further
                          // analyzed later.

                          oligomerList->removeAt(foundIndex);

                          clearanceOligomerList.append(found_oligomer_sp);

                          // Update the name of the oligomer with the name
                          // of the new foundOligomer.

                          QString name = QString("%1+%2")
                                           .arg(oligomer_sp->name())
                                           .arg(found_oligomer_sp->name());
                          oligomer_sp->setName(name);
                        }
                    }
                }
              // End of
              // foreach(index, crossLinkIndices)
            }
          // End of(ret) ie cross-links involved monomer
        }
      // End of
      //   for (int iter = iter_oligomer_sp->startIndex();
      //   iter < iter_oligomer_sp->endIndex() + 1; ++iter)

      // At this point this quarantinized oligomer might be removed
      // from the clearance clearanceOligomerList and its coordinates
      // be appended to the 'oligomer' list of coordinates. Then, the
      // quanrantinized oligomer might be destroyed.

      clearanceOligomerList.removeFirst();

      oligomer_sp->appendCoordinates(iter_oligomer_sp.get());

      iter_oligomer_sp.reset();
    }

  // At this point, all the oligomers in the clearance oligomer list
  // have all been dealt with, return the number of cross-linked
  // oligomers in this oligomer.

  //   for (int iter = 0; iter < oligomer->crossLinkList()->size(); ++iter)
  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Finished for oligomer:" <<
  //       oligomer->crossLinkList()->at(iter)->name();


  return static_cast<libXpertMass::CoordinateList *>(oligomer_sp.get())->size();
}


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

} // namespace massxpert

} // namespace MsXpS
