/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright 2009--2026 by Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


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

namespace MsXpS
{
namespace libXpertMassCore
{


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

\brief The Cleaver class provides a model for performing aqueous cleavage
reactions involving \l{CleavageAgent} objects and
\l{Polymer} \l{Sequence}s.

\sa CleavageAgent, CleavageMotif, CleavageConfig, Ionizer
*/


/*!
\variable MsXpS::libXpertMassCore::Cleaver::mcqsp_polymer

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


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

\brief The \l PolChemDef polymer chemistry definition.
*/

/*!
\variable MsXpS::libXpertMassCore::Cleaver::m_cleavageConfig

\brief The CleavageConfig that configures the cleavage reaction.
*/

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

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

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

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

/*!
\variable MsXpS::libXpertMassCore::Cleaver::m_doCleaveIndices

\brief The container of indices in the Polymer sequence that are actual sites of
cleavage.
*/

/*!
\variable MsXpS::libXpertMassCore::Cleaver::m_doNotCleaveIndices

\brief The container of indices in the Polymer sequence that are \e{not} sites
of cleavage.
*/

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

\brief The container of Oligomer instances generated as a result of the
cleavage.
*/


/*!
\brief Constructs an uninitialized Cleaver instance.
*/
Cleaver::Cleaver()
{
}

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

\list

\li \a polymer_cqsp: initializes mcqsp_polymer, the Polymer instance to be
cleaved.

\li \a pol_chem_def_csp: initializes mcsp_polChemDef, the PolChemDef (polymer
chemistry definition) that is the context in which the Polymer exists.

\li \a cleavage_config: initializes : initializes, the CleavageConfig instance
that configures the cleavage.

\li \a calc_options: initializes m_calcOptions, the CalcOptions instance that
configures the mass and formula calculations.

\li \a ionizer: initializes m_ionizer, the Ionizer instance that drives the
ionization of the Oligomer instances generated by the cleavage.

\endlist

None of \a polymer_cqsp and \a pol_chem_def_csp can be nullptr or qFatal() is
called.
*/
Cleaver::Cleaver(PolymerCstQSPtr polymer_cqsp,
                 PolChemDefCstSPtr pol_chem_def_csp,
                 const CleavageConfig &cleavage_config,
                 const CalcOptions &calc_options,
                 const Ionizer &ionizer)
  : mcqsp_polymer(polymer_cqsp),
    mcsp_polChemDef(pol_chem_def_csp),
    m_cleavageConfig(cleavage_config),
    m_ionizer(ionizer)
{
  if(mcqsp_polymer == nullptr && mcqsp_polymer.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

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

  m_calcOptions.initialize(calc_options);

  // qDebug() << "Other calc options:" << calc_options.toString();
  // qDebug() << "And now *this calc options:" << m_calcOptions.toString();

  //  qDebug() << "Instantiating Cleave with Ionizer:" << m_ionizer.toString();
  // qDebug() << "Ionizer' isotopic data:"
  //   << m_ionizer.getIsotopicDataCstSPtr()->size();
}

/*!
\brief Constructs Cleaver instance as a copy of \a other.
*/
Cleaver::Cleaver(const Cleaver &other)
  : mcqsp_polymer(other.mcqsp_polymer),
    mcsp_polChemDef(other.mcsp_polChemDef),
    m_cleavageConfig(other.m_cleavageConfig),
    m_ionizer(other.m_ionizer)
{
  if(mcqsp_polymer == nullptr && mcqsp_polymer.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

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

  m_calcOptions.initialize(other.m_calcOptions);

  // qDebug() << "Other calc options:" << other.m_calcOptions.toString();
  // qDebug() << "And now *this calc options:" << m_calcOptions.toString();

  //  qDebug() << "Instantiating Cleave with Ionizer:" << m_ionizer.toString();
}

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

/*!
\brief Returns the CleavageAgent name (from the member
\l{CleavageConfig::m_name}).
*/
QString
Cleaver::getCleaveAgentName() const
{
  return m_cleavageConfig.getName();
}

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

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

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

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

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

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

  source.getOligomersRef().clear();

  return transferred_count;
}

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

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

/*!
\brief Performs the actual cleavage, thus generating Oligomer instances that are
added to the m_oligomers member OligomerCollection instance.

If \a reset is true, the member OligomerCollection is first cleared; otherwise
the newly generated Oligomer instances are simply added to it.

Returns true upon success,  false otherwise.
*/
bool
Cleaver::cleave(bool reset)
{
  // If the polymer sequence is empty, just return.
  if(!mcqsp_polymer->size())
    {
      qDebug() << "The polymer sequence is empty: nothing to cleave.";
      return true;
    }

  // Ensure that the cleavage pattern is parsed.

  if(!m_cleavageConfig.getMotifsCstRef().size())
    {
      if(!m_cleavageConfig.parse())
        {
          qDebug() << "Failed to parse the cleavage options";

          return false;
        }
    }

  // qDebug() << "Number of motifs:"
  //   << m_cleavageConfig.getMotifsCstRef().size();

  if(!fillCleavageIndices())
    {
      //      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())
    {
      qDebug() << "There are no cleavage indices left. Nothing to do.";

      return false;
    }

  if(!removeDuplicateCleavageIndices())
    {
      qDebug() << "There are no cleavage indices left. Nothing to do.";

      return false;
    }

  if(reset)
    m_oligomers.clear();

  for(int iter = 0; iter <= m_cleavageConfig.getPartials(); ++iter)
    {
      // qDebug() << "Now performing partial cleavage" << iter;

      if(cleavePartial(iter) == -1)
        {
          qDebug() << "Failed to perform partial cleavage" << iter;

          return false;
        }
    }

  // At this point we have the list of lists of oligomers, one list of
  // oligomers for each partial cleavage.
  m_doCleaveIndices.clear();
  m_doNotCleaveIndices.clear();

  return true;
}

/*!
\brief Fills-in all the index values that correspond to precise locations where
the cleavage reactions must occur. These indices represent locations in the
member \l{Polymer}'s \l{Sequence}.

If the pattern only contains cleaving sites, all the indices are added to the
member container of cleavage indices. If the pattern contains also no-cleaving
sites (like with Trypsin's "-Lys/Pro" pattern), then the corresponding indices
are set to the member container of no-cleavage indices.

Returns the sum of the two cleavage/no-cleavage containers sizes. Or 0 if not a
single cleavage site was found in the Polymer Sequence.
*/
int
Cleaver::fillCleavageIndices()
{
  const std::vector<CleavageMotifSPtr> &cleavage_motifs =
    m_cleavageConfig.getMotifsCstRef();

  m_doCleaveIndices.clear();
  m_doNotCleaveIndices.clear();

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

  // qDebug() << "The calculation options:" << m_calcOptions.toString();

  IndexRangeCollection index_range_collection(
    m_calcOptions.getIndexRangeCollectionCstRef());

  // qDebug() << "The index range collection:"
  //          << index_range_collection.indicesAsText();

  std::size_t index_start;
  std::size_t index_stop;

  if(!index_range_collection.size())
    {
      index_start = 0;
      index_stop  = mcqsp_polymer->size();
    }
  else
    {
      index_start = index_range_collection.getRangeCstRefAt(0).m_start;
      index_stop  = index_range_collection.getRangeCstRefAt(0).m_stop;
    }

  // qDebug() << "index_start:" << index_start << "index_stop:" << index_stop;

  for(const CleavageMotifSPtr &cleavage_motif_sp : cleavage_motifs)
    {
      int index = index_start;

      while(1)
        {
          ++index;
          index = findCleavageMotif(*cleavage_motif_sp, index, index_stop);

          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
          // cleavage agent 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 actual_cleave_index = index + cleavage_motif_sp->getOffset() - 1;

          if(actual_cleave_index < 0)
            continue;

          if(actual_cleave_index >= static_cast<int>(index_stop))
            break;

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

          if(cleavage_motif_sp->getCleavageAction() ==
             Enums::CleavageAction::CLEAVE)
            {
              m_doCleaveIndices.push_back(actual_cleave_index);

              // qDebug() << __FILE__ << __LINE__
              //   << "For cleavage, index:" << actual_cleave_index;
            }
          else if(cleavage_motif_sp->getCleavageAction() ==
                  Enums::CleavageAction::NO_CLEAVE)
            {
              m_doNotCleaveIndices.push_back(actual_cleave_index);

              // qDebug() << __FILE__ << __LINE__
              //   << "Not for cleavage";
            }
          else
            qFatal()
              << "Programming error. Enums::CleavageAction::NOT_SET is not "
                 "possible here.";
        }
      // End of
      // while (1)
    }
  // End of
  // for (int iter = 0; iter < cleavage_motifs->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_doCleaveIndices.size() + m_doNotCleaveIndices.size();
}

/*!
\brief Removes from the m_doCleaveIndices member container of cleavage indices
all the indices found in the m_doNotCleaveIndices member no-cleavage indices
container.

Returns the size of the container of cleavage indices.
*/
int
Cleaver::resolveCleavageNoCleavage()
{
  // Remove from the m_cleaveIndices container all the indices
  // that are found in the m_noCleaveIndices vector.

  for(const int &no_cleave_index : m_doNotCleaveIndices)
    {
      std::vector<int>::iterator the_iterator = m_doCleaveIndices.begin();

      // The erase() below works because in the while statement we
      // do test for the end() of the vector instead of determining
      // that end once and storing it in a variable.
      while(the_iterator != m_doCleaveIndices.end())
        {
          if((*the_iterator) == no_cleave_index)
            the_iterator = m_doCleaveIndices.erase(the_iterator);
          else
            ++the_iterator;
        }
    }

#if 0
#Old version
for(int iter = 0; iter < m_noCleaveIndices.size(); ++iter)
{
int noCleaveIndex = m_noCleaveIndices.at(iter);

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

if(noCleaveIndex == cleaveIndex)
m_cleaveIndices.removeAt(jter);
}
}
#endif

  return m_doCleaveIndices.size();
}

/*!
\brief Removes the duplicate cleavage indices from the container of cleavage
indices and returns the count of items in the member m_doCleaveIndices
container.
*/
int
Cleaver::removeDuplicateCleavageIndices()
{
  std::sort(m_doCleaveIndices.begin(), m_doCleaveIndices.end());
  auto last = std::unique(m_doCleaveIndices.begin(), m_doCleaveIndices.end());
  m_doCleaveIndices.erase(last, m_doCleaveIndices.end());

  return m_doCleaveIndices.size();
}

/*!
\brief Returns an index at which the \a cleavage_motif CleavageMotif is found in
the Polymer Sequence.

The search is started at index \a index_start and is stopped at index \a
index_stop.

If \a cleavage_motif is not found, returns -1.
*/
int
Cleaver::findCleavageMotif(CleavageMotif &cleavage_motif,
                           std::size_t index_start,
                           std::size_t index_stop)
{
  // qDebug() << "start:" << index_start << "stop:" << index_stop;

  bool search_failed = false;

  int first_index = 0;

  const std::vector<MonomerSPtr> &polymer_sequence_monomers =
    mcqsp_polymer->getSequenceCstRef().getMonomersCstRef();

  const std::vector<MonomerSPtr> &cleavage_motif_monomers =
    cleavage_motif.getMonomersCstRef();

  // We have to iterate in the polymer sequence starting at 'index', in
  // search for a sequence element identical to the sequence that is represented
  // in the cleavage_motif in the form of a container of MonomerCstSPtr.

  // This means that if

  // cleavage_motif_monomers[0]->getCode() = "Lys"
  // cleavage_motif_monomers[1]->getCode() = "Pro"

  // then, we want to search in the polymer_sequence_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(!mcqsp_polymer->size())
    return 0;

  if(!cleavage_motif_monomers.size())
    return -1;

  if(index_stop >= mcqsp_polymer->size())
    {
      qFatal() << "Programming error. Index is out of bounds:" << index_stop
               << "polymer size:" << mcqsp_polymer->size();
    }

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

  QString first_code = cleavage_motif_monomers.front()->getCode();

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

  std::size_t iter_index = index_start;


  while(iter_index < index_stop)
    {
      MonomerSPtr monomer_sp = polymer_sequence_monomers.at(iter_index);
      if(monomer_sp == nullptr)
        qFatal() << "Programming error.";

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

          ++iter_index;
          continue;
        }

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

      first_index   = iter_index;
      search_failed = false;

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

      for(std::size_t iter = 1; iter < cleavage_motif_monomers.size(); ++iter)
        {
          if(iter_index + iter >= index_stop)
            {
              search_failed = true;
              break;
            }

          QString next_code = cleavage_motif_monomers.at(iter)->getCode();
          monomer_sp        = polymer_sequence_monomers.at(iter_index + iter);

          if(monomer_sp == nullptr)
            qFatal() << "Programming error.";

          if(monomer_sp->getCode() == next_code)
            continue;
          else
            {
              search_failed = true;
              break;
            }
        }
      // End of
      // for (int iter = 1; iter < cleavage_motif_monomers.size(); ++iter)

      if(search_failed)
        {
          ++iter_index;
          continue;
        }
      else
        {
          return first_index;
        }
    }
  // End of
  // while (iter_index < polymer_sequence_monomers.size())

  // qDebug() << "At call with start:" << index_start << "stop:" << index_stop
  //          << "now returning -1, with cleavage motif"
  //          << cleavage_motif.getMotif();

  return -1;
}

/*!
\brief Accounts for the \l CleavageRule \a cleavage_rule_sp in the \l Oligomer
\a oligomer_sp .

Returns true if the accounting succeeded, false otherwise.
*/
bool
Cleaver::accountCleavageRule(CleavageRuleSPtr cleavage_rule_sp,
                             OligomerSPtr oligomer_sp)
{
  if(cleavage_rule_sp == nullptr || cleavage_rule_sp.get() == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

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

  IsotopicDataCstSPtr isotopic_data_csp = oligomer_sp->getPolymerCstSPtr()
                                            ->getPolChemDefCstSPtr()
                                            ->getIsotopicDataCstSPtr();

  // For each IndexRange element in the oligomer_sp, we have to
  // ensure we apply the formula(s) that is/are required.
  // The oligomer_sp might have multiple ranges if it has been crafted as
  // a cross-linked oligomer.

  // We need to check the validity of the CleavageRule for each range
  // individually because each range corresponds to an Oligomer (for example if
  // this oligomers_p is cross-linked to at least one other oligomer). And each
  // oligomer might have an end corresponding to the CleavageRule member data
  // (left or right end).

  foreach(const IndexRange *item,
          oligomer_sp->getIndexRangeCollectionCstRef().getRangesCstRef())
    {
      if(!cleavage_rule_sp->getLeftCode().isEmpty())
        {
          // The formula has to be there because the rule has a left code that
          // is not empty.
          Formula rule_formula = Formula(cleavage_rule_sp->getLeftFormula());

          if(rule_formula.getActionFormula().isEmpty())
            qFatal()
              << "Programming error. The cleavage rule's left code is "
                 "non-empty and the left formula thus cannot be empty.";

          // We are dealing with the cleavage rule's left code/formula, so
          // check what is the Monomer at the left end of oligomer_sp ?
          MonomerSPtr monomer_csp = oligomer_sp->getLeftEndMonomerCstSPtr();

          if(monomer_csp->getCode() == cleavage_rule_sp->getLeftCode())
            {
              // qDebug() << "Matched left code:" <<
              // cleavage_rule_sp->getLeftCode();

              // But, this is not going to be real true, if the
              // monomer_csp is actually the left-end monomer_csp of the
              // whole polymer sequence: if this oligomer_sp is actually
              // the Left-end oligomer (like N-terminal peptide) of the polymer
              // after having been digested, and the left end monomer of this
              // oligomer_sp has code equal to the cleavage rule's left code
              // (which we assessed above),
              // then the rule has not to be applied because there was no
              // cleavage at the left end monomer of the polymer!

              // Checking if the value of sequence_range.start == 0 tells us if
              // the
              // oligomer_sp is the left end oligomer of the polymer. If it is
              // == 0, then we do not apply the cleavage rule because being
              // the left end oligomer of the polymer, its left end has not
              // been cleaved.

              if(!item->m_start)
                {
                  // The monomer_csp is not the left-end monomer_csp, so the
                  // match is real. Account for the formula !

                  bool ok = false;
                  rule_formula.accountMasses(
                    ok,
                    isotopic_data_csp,
                    oligomer_sp->getMassRef(Enums::MassType::MONO),
                    oligomer_sp->getMassRef(Enums::MassType::AVG));

                  if(!ok)
                    return false;

                  oligomer_sp->getFormulaRef().accountFormula(
                    rule_formula.getActionFormula(), isotopic_data_csp, 1, ok);
                }
            }
        }

      if(!cleavage_rule_sp->getRightCode().isEmpty())
        {
          // The formula has to be there because the rule has a right code that
          // is not empty.
          Formula rule_formula = Formula(cleavage_rule_sp->getRightFormula());

          // qDebug() << "Right code formula:"
          //          << rule_formula.getActionFormula();

          if(rule_formula.getActionFormula().isEmpty())
            qFatal()
              << "Programming error. The cleavage rule's right code is "
                 "non-empty and the right formula thus cannot be empty.";

          // We are dealing with the cleavage rule's right code/formula, so
          // check what is the Monomer at the right end of oligomer_sp ?
          MonomerSPtr monomer_csp = oligomer_sp->getRightEndMonomerCstSPtr();

          if(monomer_csp->getCode() == cleavage_rule_sp->getRightCode())
            {
              // qDebug() << "Matched right code:"
              //          << cleavage_rule_sp->getRightCode();

              // See above for the left end code for detailed explanations.

              if(item->m_stop != (qsizetype)mcqsp_polymer->size() - 1)
                {
                  // The monomer_csp is not the right-end monomer_csp
                  // of the whole polymer sequence, so the match is real.
                  // Account for the formula !

                  // qDebug()
                  //   << "Before accouting rule formula, mono mass:"
                  //   << oligomer_sp->getMass(Enums::MassType::MONO);

                  bool ok = false;
                  rule_formula.accountMasses(
                    ok,
                    isotopic_data_csp,
                    oligomer_sp->getMassRef(Enums::MassType::MONO),
                    oligomer_sp->getMassRef(Enums::MassType::AVG));

                  if(!ok)
                    {
                      qDebug() << "Accounting masses set ok to false.";
                      return false;
                    }

                  // qDebug()
                  //   << "After accouting rule formula, mono mass:"
                  //   << oligomer_sp->getMass(Enums::MassType::MONO);

                  // This will modify the formula inside oligomer_sp.
                  // However, calling elementalComposition will not account
                  // for the cleavage rule !
                  oligomer_sp->getFormulaRef().accountFormula(
                    rule_formula.getActionFormula(), isotopic_data_csp, 1, ok);
                }
            }
        }
    }

  return true;
}

/*!
\brief Performs a cleavage operation for partial cleavage \a partial_cleavage.

Returns -1 if an error occurred, the count of generated \l{Oligomer}s otherwise.
*/
int
Cleaver::cleavePartial(int partial_cleavage)
{
  bool is_oligomer_the_polymer = false;

  std::size_t iter = 0;

  static int left_index  = 0;
  static int right_index = 0;

  Q_ASSERT(partial_cleavage >= 0);

  OligomerCollection partial_oligomers;

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

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

  const IndexRange *index_range_p =
    m_calcOptions.getIndexRangeCollectionCstRef()
      .mostInclusiveLeftRightIndexRange();

  // qDebug() << "Most inclusive left right index range:"
  //          << index_range_p->indicesAsText();

  qsizetype index_start = index_range_p->m_start;
  qsizetype index_stop  = index_range_p->m_stop;

  delete index_range_p;

  left_index  = index_start;
  right_index = 0;

  CalcOptions calc_options(m_calcOptions);

  // Iterate in the container of the indices where the cleavage should occur
  // in the polymer sequence.

  for(iter = 0; iter < m_doCleaveIndices.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 monomer index larger than its upper boundary.

      // Imagine cutting a polymer containing only one Met with
      // cyanogen bromide: m_cleaveIndices 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
      // 'partial_cleavage' of 2. The way we do it is that we fetch the
      // index in the list of cleavage indices (m_cleaveIndices) two
      // cleavage positions farther than the position we are iterating into:

      // int offset_partial_cleavage = iter + partial_cleavage;

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

      std::size_t offset_partial_cleavage = iter + partial_cleavage;

      if(offset_partial_cleavage >= m_doCleaveIndices.size())
        {
          if(iter == 0)
            is_oligomer_the_polymer = true;

          break;
        }

      right_index = m_doCleaveIndices.at(offset_partial_cleavage);

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

      // Note how we pass Ionizer() below so that it is invalid
      // because we are not ionizing Oligomer instances right now,
      // that will come later.

      calc_options.setIndexRange(left_index, right_index);

      // qDebug() << "After setting these values to calc_options:"
      //          << calc_options.getIndexRangeCollectionCstRef()
      //               .getRangesCstRef()
      //               .front()
      //               ->indicesAsText();

      OligomerSPtr oligomer_sp = std::make_shared<Oligomer>(
        mcqsp_polymer,
        name,
        m_cleavageConfig.getName(),
        mcqsp_polymer->modifiedMonomerCount(
          IndexRangeCollection(left_index, right_index)),
        Ionizer(),
        calc_options);

      // qDebug() << "Allocated new oligomer with index range:"
      //          << left_index << "--" << right_index << "calculation options:"
      //          << oligomer_sp->getCalcOptionsCstRef().toString()
      //          << "and default ionizer:"
      //          << oligomer_sp->getIonizerCstRef().toString();

      oligomer_sp->setPartialCleavage(partial_cleavage);

      QString elemental_composition = oligomer_sp->elementalComposition();

      // qDebug() << "The elemental composition of this new oligomer as "
      //             "calculated with its own calculation options:"
      //          << calc_options.toString() << "and with its own ionizer:"
      //          << oligomer_sp->getIonizerCstRef().toString()
      //          << "is:" << elemental_composition;

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

      bool ok = false;

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

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

          return -1;
        }

      // qDebug() << "The elemental composition above was used to set "
      //             "the oligomer's internal formula, which is now:"
      //          << oligomer_sp->getFormulaRef().getActionFormula();

      // At this point we can add the configured oligomer to the list.
      partial_oligomers.getOligomersRef().push_back(oligomer_sp);

      // Increment the index for next oligomer.
      left_index = m_doCleaveIndices.at(iter) + 1;
    }
  // End of
  // for (int iter = 0; iter < m_cleaveIndices.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 others 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(is_oligomer_the_polymer)
    left_index = index_start;
  else
    left_index = m_doCleaveIndices.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;

  right_index = index_stop;

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

  // Note how we pass Ionizer() below so that it is invalid
  // because we are not ionizing Oligomer instances right now,
  // that will come later.

  calc_options.setIndexRange(left_index, right_index);

  // qDebug() << "Now creating the last cooking cleavage Oligomer:"
  //          << name << "with indices:" << left_index << "-" << right_index
  //          << "and calculation options:" << calc_options.toString();

  OligomerSPtr oligomer_sp =
    std::make_shared<Oligomer>(mcqsp_polymer,
                               name,
                               m_cleavageConfig.getName(),
                               mcqsp_polymer->modifiedMonomerCount(
                                 IndexRangeCollection(left_index, right_index)),
                               Ionizer(),
                               calc_options);

  oligomer_sp->setPartialCleavage(partial_cleavage);

  // qDebug()
  //   << "After heap-allocation of Oligomer, its calculation options:"
  //   << oligomer_sp->getCalcOptionsRef().toString();

  QString elemental_composition = oligomer_sp->elementalComposition();

  // qDebug() << "Elemental composition:" << elemental_composition;

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

  bool ok = false;

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

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

  // qDebug() << "Oligomer has formula:"
  //          << oligomer_sp->getFormulaCstRef().getActionFormula();

  // At this point we can add the configured oligomer to the list.
  partial_oligomers.getOligomersRef().push_back(oligomer_sp);

  // At this point all the skeleton oligomers have been computed for
  // the given partial_cleavage. 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, partial_cleavage == 0).

  if(!partial_cleavage)
    {
      if(static_cast<int>(m_calcOptions.getMonomerEntities()) &
         static_cast<int>(Enums::ChemicalEntity::CROSS_LINKER))
        {
          if(analyzeCrossLinks(partial_oligomers) == -1)
            {
              return -1;
            }
        }
    }

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

  OligomerCollection oligomer_buffer_container;

  std::vector<OligomerSPtr>::iterator partial_oligomers_iterator =
    partial_oligomers.getOligomersRef().begin();

  std::size_t partial_oligomers_count =
    partial_oligomers.getOligomersRef().size();

  // qDebug() << "There are" << partial_oligomers_count
  //   << "items in the container";

  // The end iterator needs to be dynamic because we remove the
  // oligomer after iterating into it.
  while(partial_oligomers_iterator != partial_oligomers.getOligomersRef().end())
    {
      OligomerSPtr iter_oligomer_sp = (*partial_oligomers_iterator);

      // qDebug() << "Iterating partial oligomer:" <<
      // iter_oligomer_sp->toString()
      //   << "While there are still"
      //   << partial_oligomers.getOligomersRef().size()
      //   << "items in the container";

      // We do not ask that the oligomer be ionized yet, because we
      // have to first account for potential cleavage rules! Thus we
      // pass an invalid ionizer with Ionizer(), which is interpreted by
      // the mass calculation function that ionization should not be
      // performed. This was a bug in the release versions up
      // to 1.6.1.
      // iter_oligomer_sp->calculateMasses(calc_options, Ionizer());
      iter_oligomer_sp->calculateMasses();

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

      // qDebug() << "There are:"
      //   << m_cleavageConfig.getRulesCstRef().size() << "cleavage
      //   rules.";

      for(const CleavageRuleSPtr &cleavage_rule_sp :
          m_cleavageConfig.getRulesCstRef())
        {
          // qDebug() << "The CleavageRule name:"
          //          << cleavage_rule_sp->getName()
          //          << "Oligomer mono mass BEFORE accounting it:"
          //          << iter_oligomer_sp->getMass(Enums::MassType::MONO)
          //          << "and internal formula is:"
          //          <<
          //          iter_oligomer_sp->getFormulaCstRef().getActionFormula();

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

          if(!accountCleavageRule(cleavage_rule_sp, iter_oligomer_sp))
            return -1;

          // qDebug() << "The CleavageRule name:"
          //          << cleavage_rule_sp->getName()
          //          << "Oligomer mono mass AFTER accounting it:"
          //          << iter_oligomer_sp->getMass(Enums::MassType::MONO)
          //          << "and internal formula has become:"
          //          <<
          //          iter_oligomer_sp->getFormulaCstRef().getActionFormula();
        }

      // qDebug() << "Done iterating in the CleavageRule instances.";

      // 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 ionization_level      = m_cleavageConfig.getStartIonizeLevel();
      int ionization_stop_level = m_cleavageConfig.getStopIonizeLevel();

      qDebug() << "The Ionizer:" << m_ionizer.toString();

      // Sanity checks
      if(!m_ionizer.isValid())
        qFatal() << "Programming error. The Ionizer cannot be invalid.";
      if(m_ionizer.isIonized())
        qFatal()
          << "Programming error. The Ionizer cannot have ionized status.";

      // qDebug() << "BEFORE ionization:"
      //          << "Oligomer mono mass:"
      //          << iter_oligomer_sp->getMass(Enums::MassType::MONO)
      //          << "and internal formula is:"
      //          << iter_oligomer_sp->getFormulaCstRef().getActionFormula();

      while(ionization_level <= ionization_stop_level)
        {
          Ionizer temp_ionizer(m_ionizer);

          temp_ionizer.setLevel(ionization_level);

          OligomerSPtr new_oligomer_sp =
            std::make_shared<Oligomer>(*iter_oligomer_sp);

          new_oligomer_sp->setIonizer(temp_ionizer);

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

              new_oligomer_sp.reset();

              return -1;
            }

          bool ok = false;

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

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

              return -1;
            }

          // qDebug() << "AFTER ionization level:" << ionization_level
          //          << "Oligomer mono mass:"
          //          << iter_oligomer_sp->getMass(Enums::MassType::MONO)
          //          << "and internal formula has become:"
          //          <<
          //          iter_oligomer_sp->getFormulaCstRef().getActionFormula();

          // 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_sp->getName() +
                         QString("#z=%3").arg(temp_ionizer.charge());

          new_oligomer_sp->setName(name);

          oligomer_buffer_container.getOligomersRef().push_back(
            new_oligomer_sp);

          ++ionization_level;
        }

      // qDebug() << "Going to erase partial oligomer:"
      //   << (*partial_oligomers_iterator)->toString();

      // We can now remove the template oligomer.
      partial_oligomers_iterator =
        partial_oligomers.getOligomersRef().erase(partial_oligomers_iterator);

      // No need to increment the iterator because we got new iterator from the
      // erase() call
      // above. Since we call erase() at each iteration, the iterator gets
      // updated at each iteration.
    }

  // qDebug() << "At this point, we had" << partial_oligomers_count
  //     << "partial (uncharged) oligomers and we now have"
  //     << oligomer_buffer_container.getOligomersCstRef().size()
  //     << "buffer (charged) oligomers";

  // Sanity check
  // There should be as many times more Oligomer in the buffer container
  // as there were charge levels to be performed, with respect to
  // the uncharged oligomers in the partial container.
  std::size_t buffer_oligomers_count =
    oligomer_buffer_container.getOligomersRef().size();

  if(buffer_oligomers_count !=
     (partial_oligomers_count * (m_cleavageConfig.getStopIonizeLevel() -
                                 m_cleavageConfig.getStartIonizeLevel() + 1)))
    qFatal()
      << "Programming error. The counts of Oligomer instances does not match.";

  // At this point we should transfer all the
  // oligomers from the oligomer_buffer_container to the initial
  // partial_oligomers.

  // Version involving much copying...
  // for(const OligomerSPtr &iter_oligomer_sp :
  //  oligomer_buffer_container.getOligomersRef())
  // partial_oligomers.getOligomersRef().push_back(iter_oligomer_sp);
  // oligomer_buffer_container.clear();

  // Version involving a std::move operation.
  // std::size_t transferred_count =
  transferOligomers(oligomer_buffer_container, partial_oligomers);

  // qDebug() << "The number of transferred Oligomers:" << transferred_count;
  // qDebug() << "Count of oligomers in the source container:"
  //   << oligomer_buffer_container.getOligomersCstRef().size();

  // Sanity check
  if(oligomer_buffer_container.size())
    qFatal()
      << "Programming error. The container cannot contain Oligomers anymore.";

  std::size_t generated_oligomers_count = partial_oligomers.size();

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

  // Old version involving much copying...
  // while(partial_oligomers.size())
  // {
  //  // Crucial to make this pointer cast so that we transfer actual
  //  // Oligomers!
  //
  //  OligomerSPtr iter_oligomer_sp =
  //  std::dynamic_pointer_cast<Oligomer>(
  //   partial_oligomers.takeFirst());
  //
  //  //// Elemental formula
  //  // QString *text = new
  //  QString(iter_oligomer_sp->elementalComposition());
  //  // StringProp *prop =
  //  // new StringProp("ELEMENTAL_COMPOSITION", text);
  //  // iter_oligomer_sp->appendProp(static_cast<Prop *>(prop));
  //
  //  mp_oligomerList->append(iter_oligomer_sp);
  // }

  // New version still involving much copying...
  // for(const OligomerSPtr &iter_oligomer_sp :
  //  partial_oligomers.getOligomersRef())
  // m_oligomers.getOligomersRef().push_back(iter_oligomer_sp);
  // partial_oligomers.clear();

  // Version involving a std::move operation.
  // size_t transferred_count =
  transferOligomers(partial_oligomers, m_oligomers);

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

  // Sanity check
  if(partial_oligomers.size())
    qFatal()
      << "Programming error. The container cannot contain Oligomers anymore.";

  return generated_oligomers_count;
}

/*!
\brief Analyzes the \l{CrossLink} instances that might be involved in the
\l{Oligomer} instances in the \a oligomers collection.

The Oligomers that are found to be cross-linked are set in \a oligomers and the
size of this collection is returned.
*/
int
Cleaver::analyzeCrossLinks(OligomerCollection &oligomers)
{
  OligomerCollection cross_linked_oligomers;

  // General overview:

  // At the moment this function is called only with oligomers that
  // were obtained with a full cleavage (no partial cleavages).
  // Thus, any given Monomer of the Polymer sequence is necessarily
  // contained only one Oligomer (see below).

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

  const std::vector<CrossLinkSPtr> &polymer_cross_links =
    mcqsp_polymer->getCrossLinksCstRef();

  std::vector<CrossLinkSPtr>::const_iterator cross_link_iterator_cst =
    polymer_cross_links.cbegin();
  std::vector<CrossLinkSPtr>::const_iterator cross_link_end_iterator_cst =
    polymer_cross_links.cend();

  while(cross_link_iterator_cst != cross_link_end_iterator_cst)
    {
      // Get the first monomer that is involved in the CrossLink.
      CrossLinkCstSPtr cross_link_sp = *cross_link_iterator_cst;

      MonomerCstSPtr first_cross_linked_monomer_csp =
        cross_link_sp->getFirstMonomer();

      if(first_cross_linked_monomer_csp == nullptr)
        qFatal()
          << "Programming error. Cannot be that the CrossLink has no "
             "Monomer in its container.";

      // In the whole set of Oligomer instances passed as argument, find the ONE
      // oligomer that encompasses that first Monomer involved in the CrossLink
      // currently iterated into. That is, the question is: "what is the
      // Oligomer that happens to contain that Monomer that is involved in the
      // CrossLink ? ".

      std::size_t oligomer_index_that_encompasses_monomer = 0;

      OligomerSPtr first_cross_linked_oligomer_sp =
        oligomers.findOligomerEncompassing(
          first_cross_linked_monomer_csp,
          oligomer_index_that_encompasses_monomer);

      if(first_cross_linked_oligomer_sp != nullptr)
        {
          // 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.

          // Set the cross-linked oligomer apart.
          cross_linked_oligomers.getOligomersRef().push_back(
            first_cross_linked_oligomer_sp);

          // Remove the cross-link from the main list of oligomers so
          // that we do not stumble upon it in the next analysis
          // steps.
          oligomers.getOligomersRef().erase(
            oligomers.getOligomersRef().begin() +
            oligomer_index_that_encompasses_monomer);

          // Finally, deeply scrutinize that oligomer that is used as a bait
          // to pull down all the Oligomers that are cross-linked to it.
          analyzeCrossLinkedOligomer(first_cross_linked_oligomer_sp, oligomers);
        }
      else
        {
          // qDebug() << __FILE__ << __LINE__
          // << "Cross-link at index" << iter
          // << "did not find any oligomer for its first monomer "
          //  "partner";
        }

      ++cross_link_iterator_cst;
    }

  // 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 cross_linked_oligomers to
  // oligomers. While doing so make sure that the m_calcOptions
  // datum has correct IndexRangeCollection data, as these data will be
  // required later, typically to calculate the elemental formula of
  // the oligomer.

  // Old version involving much copying.

  // while(cross_linked_oligomers.size())
  //  {
  //  OligomerSPtr oligomer_sp = cross_linked_oligomers.takeAt(0);
  //  oligomer_sp->updateCalcOptions();
  //  oligomers->append(oligomer_sp);
  //  }
  // cross_linked_oligomers.clear();
  //

  // New more C++ modern version.
  for(OligomerSPtr &oligomer_sp : cross_linked_oligomers.getOligomersRef())
    {
      oligomers.getOligomersRef().push_back(std::move(oligomer_sp));
    }

  // Sanity check:
  for(OligomerSPtr &oligomer_sp : cross_linked_oligomers.getOligomersRef())
    {
      if(oligomer_sp != nullptr)
        qFatal() << "The oligomer was not moved.";
    }

  cross_linked_oligomers.clear();

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

  return oligomers.size();
}

/*!
\brief Pulls down all the Oligomer instances in \a oligomers that are found to
be cross-linked to \a oligomer_sp.

Returns the count of IndexRange instances found in \a oligomer_sp, which is a
reflection of the number of oligomers that are found to be cross-linked to it.
*/
int
Cleaver::analyzeCrossLinkedOligomer(OligomerSPtr oligomer_sp,
                                    OligomerCollection &oligomers)
{
  // We get a cross-linked oligomer, previously found to contain the first
  // Monomer involved in a Polymer CrossLink. We want to
  // use that oligomer_sp as a bait to pull down all the other
  // oligomers that are cross-linked to it.

  // For that, we iterate in the oligomer_sp's Monomer
  // instances one by one and for each Monomer we ask the
  // mcsp_polymer to fill-in a container of CrossLink indices
  // in that Polymer that involve the Monomer being iterated into.

  // For each CrossLink at the indices reported above:
  // 1. its shared pointer is added to the oligomer_sp container of
  // cross-links.
  // 2. for each Monomer involved in the CrossLink, the list of
  // oligomers that is passed as argument is asked to return
  // an Oligomer that encompasses that Monomer.

  // The Oligomer returned at point 2 above (found_oligomer_sp)
  // (if non-nullptr) is pushed back to
  // a container of Oligomer instances (clearance_oligomers). Then that returned
  // Oligomer is removed from the initial container of Oligomers passed as
  // argument to this function (oligomers).

  // Finally, the original Oligomer (oligomer_sp) has its name chanaged to
  // indicate the cross-link between itself the the found oligomer.

  // 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
  // (oligomers_sp) to the clearance list of oligomers
  // (clearance_oligomers). 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 oligomers
  // (this is what I call "pull-down" stuff). Found oligomers are
  // appended to the clearance_oligomers.

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

  OligomerCollection clearance_oligomers;

  // '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->getName();
  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.

  bool ok = false;

  std::size_t index_start = oligomer_sp->startIndex(ok);
  if(!ok)
    return -1;

  std::size_t index_stop = oligomer_sp->stopIndex(ok);
  if(!ok)
    return -1;

  // qDebug() << "Oligomer start:" << index_start << "stop:" << index_stop;

  const std::vector<CrossLinkSPtr> &polymer_cross_links =
    mcqsp_polymer->getCrossLinksCstRef();

  for(std::size_t iter = index_start; iter < index_stop + 1; ++iter)
    {
      // qDebug() << "iter:" << iter;

      MonomerSPtr monomer_csp =
        mcqsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

      // What crossLinks do involve this monomer ?

      std::vector<std::size_t> cross_link_indices =
        mcqsp_polymer->crossLinkIndicesInvolvingMonomer(monomer_csp.get());

      // At least one cross-link involves the monomer currently
      // iterated into inside the oligomer being analysed.

      for(const std::size_t &index : cross_link_indices)
        {
          CrossLinkSPtr cross_link_sp = polymer_cross_links.at(index);

          if(cross_link_sp == nullptr)
            qFatal() << "Programming error.";

          //  qDebug() << __FILE__ << __LINE__
          // << cross_link_sp->getName();

          // 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(cross_link_sp))
            {
              // qDebug() << "The cross-link:"
              //  << cross_link_sp->getName()
              //  << "was already in the"
              //  << oligomer
              //  << "oligomer's list of cross-links: "
              //  "not duplicated.";
            }
          else
            {
              // qDebug() << "The cross-link:"
              //  << cross_link_sp->getName()
              //  << "was added to the"
              //  << oligomer
              //  << "oligomer's list of cross-links.";
            }

          for(const MonomerCstSPtr &monomer_csp :
              cross_link_sp->getMonomersCstRef())
            {
              // qDebug() << monomer_csp->getName();

              std::size_t found_index = 0;

              OligomerSPtr found_oligomer_sp =
                oligomers.findOligomerEncompassing(monomer_csp, found_index);

              if(found_oligomer_sp != nullptr)
                {
                  // qDebug() << found_oligomer_sp->getName() <<
                  // found_index;

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

                  // Old version
                  // oligomers.removeAt(found_index);

                  clearance_oligomers.getOligomersRef().push_back(
                    found_oligomer_sp);

                  oligomers.getOligomersRef().erase(
                    oligomers.getOligomersRef().begin() + found_index);

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

                  QString name = QString("%1+%2")
                                   .arg(oligomer_sp->getName())
                                   .arg(found_oligomer_sp->getName());

                  oligomer_sp->setName(name);
                }
            }
        }
      //  End of
      // for(const std::size_t &index : cross_link_indices)
    }
  //  End of
  // for(std::size_t iter = index_start; iter < index_stop + 1; ++iter)

  // 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
  // (oligomers_sp) to the clearance list of oligomers
  // (clearance_oligomers). 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 oligomers
  // (this is what I call "pull-down" stuff). Found oligomers are
  // appended to the clearance_oligomers.

  while(clearance_oligomers.size())
    {
      OligomerSPtr iter_oligomer_sp =
        clearance_oligomers.getOligomersRef().front();

      bool ok                 = false;
      std::size_t index_start = iter_oligomer_sp->startIndex(ok);
      if(!ok)
        return -1;

      std::size_t index_stop = iter_oligomer_sp->stopIndex(ok);
      if(!ok)
        return -1;

      for(std::size_t iter = index_start; iter <= index_stop; ++iter)
        {
          MonomerSPtr monomer_csp =
            mcqsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

          // qDebug() << __FILE__ << __LINE__
          //  << monomer_sp->getName();

          // What crossLinks do involve this monomer ?

          std::vector<std::size_t> cross_link_indices =
            mcqsp_polymer->crossLinkIndicesInvolvingMonomer(monomer_csp.get());

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

              for(const std::size_t &index : cross_link_indices)
                {
                  CrossLinkSPtr cross_link_sp = polymer_cross_links.at(index);

                  if(cross_link_sp == nullptr)
                    qFatal() << "Programming error.";

                  //  qDebug() << __FILE__ << __LINE__
                  // << cross_link_sp->getName();


                  // 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(cross_link_sp))
                    {
                      // qDebug() << __FILE__ << __LINE__
                      // << "The cross-link:"
                      // << cross_link_sp->getName()
                      // << "was already in the"
                      // << oligomer
                      // << "oligomer's list of cross-links: "
                      // "not duplicated.";
                    }
                  else
                    {
                      // qDebug() << __FILE__ << __LINE__
                      // << "The cross-link:"
                      // << cross_link_sp->getName()
                      // << "was added to the"
                      // << oligomer
                      // << "oligomer's list of cross-links.";
                    }

                  for(const MonomerCstSPtr &monomer_csp :
                      cross_link_sp->getMonomersCstRef())
                    {
                      // qDebug() << monomer_csp->getName();

                      std::size_t found_index = 0;

                      OligomerSPtr found_oligomer_sp =
                        oligomers.findOligomerEncompassing(monomer_csp,
                                                           found_index);

                      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.

                          // Old version
                          // oligomers.removeAt(foundIndex);

                          clearance_oligomers.getOligomersRef().push_back(
                            found_oligomer_sp);

                          oligomers.getOligomersRef().erase(
                            oligomers.getOligomersRef().begin() + found_index);

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

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

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

      oligomer_sp->getIndexRangeCollectionRef().appendIndexRanges(
        iter_oligomer_sp->getIndexRangeCollectionRef());

      // That oligomer was gotten with front(), so it is the
      // first Oligomer in the vector. The corresponding iterator
      // is thus begin().
      clearance_oligomers.getOligomersRef().erase(
        clearance_oligomers.getOligomersRef().begin());
    }

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

  return oligomer_sp->getIndexRangeCollectionRef().size();
}

//////////////// OPERATORS /////////////////////

/*!
\brief Assigns \a other to this instance and returns a reference to this
instance.
*/
Cleaver &
Cleaver::operator=(const Cleaver &other)
{
  if(this == &other)
    return *this;

  mcqsp_polymer   = other.mcqsp_polymer;
  mcsp_polChemDef = other.mcsp_polChemDef;

  m_cleavageConfig.initialize(other.m_cleavageConfig);
  m_calcOptions.initialize(other.m_calcOptions);
  m_ionizer = other.m_ionizer;

  m_doCleaveIndices.assign(other.m_doCleaveIndices.begin(),
                           other.m_doCleaveIndices.end());
  m_doNotCleaveIndices.assign(other.m_doNotCleaveIndices.begin(),
                              other.m_doNotCleaveIndices.end());

  m_oligomers = other.m_oligomers;

  return *this;
}

/*!
\brief Returns true if this instance is identical to \a other, false otherwise.

\sa operator!=()
*/
bool
Cleaver::operator==(const Cleaver &other) const
{
  if(this == &other)
    return true;

  if(mcqsp_polymer != other.mcqsp_polymer ||
     mcsp_polChemDef != other.mcsp_polChemDef ||
     m_cleavageConfig != other.m_cleavageConfig ||
     m_calcOptions != other.m_calcOptions || m_ionizer != other.m_ionizer ||
     m_oligomers != other.m_oligomers)
    return false;

  return true;
}

/*!
\brief Returns true if this instance is different than \a other, false
otherwise.

Returns the negated value of \l{operator==()}.

\sa operator==()
*/
bool
Cleaver::operator!=(const Cleaver &other) const
{
  return !operator==(other);
}

} // namespace libXpertMassCore
} // namespace MsXpS
