/* 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 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::mcsp_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 vector of indices in the Polymer Sequence that are \e{indeed} the
site of cleavage.
*/

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

\brief The vector of indices in the Polymer Sequence that are \e{not} the site
of cleavage.
*/

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

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


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

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

\list
\li \a polymer_cqsp The Polymer instance to be cleaved.

\li \a pol_chem_def_csp The PolChemDef (polymer chemistry definition) that is
the context in which the Polymer exists.

\li \a cleavage_config The CleavageConfig instance that configures the cleavage.

\li \a calc_options The CalcOptions instance that configures the mass and
formula calculations.

\li \a ionizer The Ionizer instance that drives the ionization of the Oligomer
instances generated by the cleavage.
\endlist

If polymer_cqsp or pol_chem_def_csp is nullptr, that is a fatal error.
*/
Cleaver::Cleaver(PolymerCstQSPtr polymer_cqsp,
                 PolChemDefCstSPtr pol_chem_def_csp,
                 const CleavageConfig &cleavage_config,
                 const CalcOptions &calc_options,
                 const Ionizer &ionizer)
  : mcsp_polymer(polymer_cqsp),
    mcsp_polChemDef(pol_chem_def_csp),
    m_cleavageConfig(cleavage_config),
    m_ionizer(ionizer)
{
  if(mcsp_polymer == nullptr && mcsp_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.

If polymer_cqsp or pol_chem_def_csp is nullptr, that is a fatal error.
*/
Cleaver::Cleaver(const Cleaver &other)
  : mcsp_polymer(other.mcsp_polymer),
    mcsp_polChemDef(other.mcsp_polChemDef),
    m_cleavageConfig(other.m_cleavageConfig),
    m_ionizer(other.m_ionizer)
{
  if(mcsp_polymer == nullptr && mcsp_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 CleavageConfig instance's
base class).
*/
QString
Cleaver::getCleaveAgentName() const
{
  return m_cleavageConfig.getName();
}

/*!
\brief Transfers (using std::move()) all the 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 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(!mcsp_polymer->size())
    {
      qDebug() << "The polymer sequence is empty: nothing to cleave.";
      return true;
    }

  // Ensure that the cleavage pattern was already 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 location in the
member \l{Polymer} \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  = mcsp_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 form the container of cleavage indices all the indices that were
found to be no-cleavage indices in the corresponding 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.
*/
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 =
    mcsp_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(!mcsp_polymer->size())
    return 0;

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

  if(index_stop >= mcsp_polymer->size())
    {
      qFatal() << "Programming error. Index is out of bounds:" << index_stop
               << "polymer size:" << mcsp_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 into the \a oligomer_sp for the \a cleavage_rule_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)mcsp_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 oligomers 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>(
        mcsp_polymer,
        name,
        m_cleavageConfig.getName(),
        mcsp_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>(mcsp_polymer,
                               name,
                               m_cleavageConfig.getName(),
                               mcsp_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 CrossLink instances that might be involved in the Oligomer
instances in the \a oligomers collection.

The Oligomers that are found 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 =
    mcsp_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 =
    mcsp_polymer->getCrossLinksCstRef();

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

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

      // What crossLinks do involve this monomer ?

      std::vector<std::size_t> cross_link_indices =
        mcsp_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 =
            mcsp_polymer->getSequenceCstRef().getMonomerCstSPtrAt(iter);

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

          // What crossLinks do involve this monomer ?

          std::vector<std::size_t> cross_link_indices =
            mcsp_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 Returns a reference to this Cleaver instance after initialization using
\a other.
*/
Cleaver &
Cleaver::operator=(const Cleaver &other)
{
  if(this == &other)
    return *this;

  mcsp_polymer    = other.mcsp_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.
*/
bool
Cleaver::operator==(const Cleaver &other) const
{
  if(this == &other)
    return true;

  if(mcsp_polymer != other.mcsp_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.
*/
bool
Cleaver::operator!=(const Cleaver &other) const
{
  return !operator==(other);
}

} // namespace libXpertMassCore
} // namespace MsXpS
