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


/////////////////////// Local includes
#include "Oligomer.hpp"
#include "Polymer.hpp"
#include "IonizeRule.hpp"


namespace MsXpS
{

namespace libXpertMass
{


/*!
\class MsXpS::libXpertMass::Oligomer
\inmodule libXpertMass
\ingroup PolChemDefBuildingdBlocks
\inheaderfile Oligomer.hpp

\brief The Oligomer class provides abstractions to work with
an oligomer molecule (peptide , for example).

The notion of an oligomer is that it is part of a \l Polymer and is thus
defined by a range of \l Monomer indices in this Polymer (the \l Coordinates).

The start index cannot be less than 0 nor greater than the size of the polymer
minus one, and the end index follows the same rule.

Derived from \l Ionizable (itself derived from \l Ponderable), an oligomer is
also characterized by a monoisotopic
mass and an average mass.

All computations about an oligomer (fragmentation, composition, for
example, isoelectric point, ...) can only be performed by referring
to the sequence of its "enclosing" \l Polymer. Therefore, an Oligomer
should never exist after the destruction of its "enclosing" polymer.
*/

/*!
\variable MsXpS::libXpertMass::Oligomer::mp_polymer

\brief The \l Polymer instance of which this Oligomer is part.
*/

/*!
\variable MsXpS::libXpertMass::Oligomer::m_description

\brief The description of the Oligomer.
*/


/*!
\variable MsXpS::libXpertMass::Oligomer::m_isModified

\brief Tell if the Oligomer is modified.
*/

/*!
\variable MsXpS::libXpertMass::Oligomer::m_crossLinkList

\brief The list of CrossLink events in the oligomer sequence.
*/

/*!
\variable MsXpS::libXpertMass::Oligomer::m_calcOptions

\brief The calculation options definining how calculations are
performed.
*/

/*!
    \typedef OligomerSPtr
    \relates Oligomer

    Synonym for std::shared_ptr<Oligomer> OligomerSPtr.
*/

/*!
    \typedef OligomerCstSPtr
    \relates Oligomer

    Synonym for std::shared_ptr<const Oligomer> OligomerSPtr.
*/

/*!
\brief Constructs an oligomer.

The Oligomer instance is constructed with these arguments:

\a polymer: The polymer instance that encloses this Oligomer. Used to intialize
the member m_polymer and the Ionizable base class' polymer
chemistry definition

\a name: The name of this Oligomer, used to intialize the Ionizable base class

\a description: The description of this Oligomer (m_description)

\a modified: Tells if the Oligomer is modified

\a ponderable: Used to initialize the Ionizable base class

\a ionizeRule: Used to initialize the Ionizable  base class

\a calcOptions: Used to initialize the m_calcOptions member

\a isIonized: Tells if this Oligomer instance is ionized

\a startIndex: The oligomer's start index coordinate in the enclosing Polymer

\a endIndex: The oligomer's end index coordinate in the enclosing Polymer
*/
Oligomer::Oligomer(Polymer *polymer,
                   const QString &name,
                   const QString &description,
                   bool modified,
                   const Ponderable &ponderable,
                   const IonizeRule &ionizeRule,
                   const CalcOptions &calcOptions,
                   bool isIonized,
                   int startIndex,
                   int endIndex)
  : Ionizable(
      polymer->getPolChemDefCstSPtr(), name, ponderable, ionizeRule, isIonized),
    mp_polymer(polymer),
    m_description(description),
    m_isModified{modified},
    m_calcOptions(calcOptions)
{
  setStartEndIndices(startIndex, endIndex);
}


/*!
\brief Constructs an oligomer.

The Oligomer instance is constructed with these arguments:

\a pol_chem_def_csp: The polymer chemistry definition used to initialize the
Ionizable base class

\a name: The name of this Oligomer, used to intialize the Ionizable base class

\a description: The description of this Oligomer (m_description)

\a modified: Tells if the Oligomer is modified

\a ponderable: Used to initialize the Ionizable base class

\a ionizeRule: Used to initialize the Ionizable  base class

\a calcOptions: Used to initialize the m_calcOptions member

\a isIonized: Tells if this Oligomer instance is ionized

\a startIndex: The oligomer's start index coordinate in the enclosing Polymer

\a endIndex: The oligomer's end index coordinate in the enclosing Polymer
*/
Oligomer::Oligomer(PolChemDefCstSPtr pol_chem_def_csp,
                   const QString &name,
                   const QString &description,
                   bool modified,
                   const Ponderable &ponderable,
                   const IonizeRule &ionizeRule,
                   const CalcOptions &calcOptions,
                   bool isIonized,
                   int startIndex,
                   int endIndex)
  : Ionizable(pol_chem_def_csp, name, ponderable, ionizeRule, isIonized),
    mp_polymer(nullptr),
    m_description(description),
    m_isModified{modified},
    m_calcOptions(calcOptions)
{
  setStartEndIndices(startIndex, endIndex);
  // if (startIndex < 0)
  //   qDebug() << __FILE__ << __LINE__
  //            << "Construct with startIndex:" << startIndex;
}


/*!
\brief Constructs an oligomer.

The Oligomer instance is constructed with these arguments:

\a polymer: The polymer instance that encloses this Oligomer. Used to intialize
the member m_polymer and the Ionizable base class' polymer

\a name: The name of this Oligomer, used to intialize the Ionizable base class

\a description: The description of this Oligomer (m_description)

\a modified: Tells if the Oligomer is modified

\a ponderable: Used to initialize the Ionizable base class

\a calcOptions: Used to initialize the m_calcOptions member

\a startIndex: The oligomer's start index coordinate in the enclosing Polymer

\a endIndex: The oligomer's end index coordinate in the enclosing Polymer
*/
Oligomer::Oligomer(Polymer *polymer,
                   const QString &name,
                   const QString &description,
                   bool modified,
                   const Ponderable &ponderable,
                   int startIndex,
                   int endIndex,
                   const CalcOptions &calcOptions)
  : Ionizable(polymer->getPolChemDefCstSPtr(), name, ponderable),
    mp_polymer(polymer),
    m_description(description),
    m_isModified(modified),
    m_calcOptions(calcOptions)
{
  setStartEndIndices(startIndex, endIndex);
  if(startIndex < 0)
    qDebug() << __FILE__ << __LINE__
             << "Construct with startIndex:" << startIndex;
}


/*!
\brief Constructs an oligomer.

The Oligomer instance is constructed with these arguments:

\a pol_chem_def_csp: The polymer chemistry definition used to initialize the
Ionizable base class

\a name: The name of this Oligomer, used to intialize the Ionizable base class

\a description: The description of this Oligomer (m_description)

\a modified: Tells if the Oligomer is modified

\a ponderable: Used to initialize the Ionizable base class

\a calcOptions: Used to initialize the m_calcOptions member

\a startIndex: The oligomer's start index coordinate in the enclosing Polymer

\a endIndex: The oligomer's end index coordinate in the enclosing Polymer
*/
Oligomer::Oligomer(PolChemDefCstSPtr pol_chem_def_csp,
                   const QString &name,
                   const QString &description,
                   bool modified,
                   const Ponderable &ponderable,
                   const CalcOptions &calcOptions,
                   int startIndex,
                   int endIndex)
  : Ionizable(pol_chem_def_csp, name, ponderable),
    mp_polymer(nullptr),
    m_description(description),
    m_isModified{modified},
    m_calcOptions(calcOptions)
{
  setStartEndIndices(startIndex, endIndex);
  if(startIndex < 0)
    qDebug() << __FILE__ << __LINE__
             << "Construct with startIndex:" << startIndex;
}


/*!
\brief Constructs an oligomer.

The Oligomer instance is constructed with these arguments:

\a ionizable: Used to initialize the Ionizable  base class

\a calcOptions: Used to initialize the m_calcOptions member

\a startIndex: The oligomer's start index coordinate in the enclosing Polymer

\a endIndex: The oligomer's end index coordinate in the enclosing Polymer
*/
Oligomer::Oligomer(const Ionizable &ionizable,
                   const CalcOptions &calcOptions,
                   int startIndex,
                   int endIndex)
  : Ionizable(ionizable),
    mp_polymer(nullptr),
    m_description("NOT_SET"),
    m_calcOptions(calcOptions)
{
  setStartEndIndices(startIndex, endIndex);
  if(startIndex < 0)
    qDebug() << __FILE__ << __LINE__
             << "Construct with startIndex:" << startIndex;
}


/*!
\brief Constructs an oligomer.

The Oligomer instance is constructed with these arguments:

\a polymer: Use to initialize the member mp_polymer and also to
initialize the Ionizable base class (by using its polymer chemistry definition
member).

\a name: The name of this Oligomer, used to intialize the Ionizable base class

\a description: The description of this Oligomer (m_description)

\a modified: Tells if the Oligomer is modified

\a mono and \a avg: Used to initialize the Ionizable::Ponderable base class

\a calcOptions: Used to initialize the m_calcOptions member

\a startIndex: The oligomer's start index coordinate in the enclosing Polymer

\a endIndex: The oligomer's end index coordinate in the enclosing Polymer
*/
Oligomer::Oligomer(Polymer *polymer,
                   const QString &name,
                   const QString &description,
                   bool modified,
                   double mono,
                   double avg,
                   int startIndex,
                   int endIndex,
                   const CalcOptions &calcOptions)
  : Ionizable(polymer->getPolChemDefCstSPtr(), name, Ponderable(mono, avg)),
    mp_polymer(polymer),
    m_description(description),
    m_isModified{modified},
    m_calcOptions(calcOptions)
{
  setStartEndIndices(startIndex, endIndex);
  if(startIndex < 0)
    qDebug() << __FILE__ << __LINE__
             << "Construct with startIndex:" << startIndex;
}


/*!
\brief Constructs an oligomer.

The Oligomer instance is constructed with these arguments:

\a pol_chem_def_csp: The polymer chemistry definition used to initialize the
Ionizable base class

\a name: The name of this Oligomer, used to intialize the Ionizable base class

\a description: The description of this Oligomer (m_description)

\a modified: Tells if the Oligomer is modified

\a calcOptions: Used to initialize the m_calcOptions member

\a mono and \a avg: Used to initialize the Ionizable::Ponderable base class

\a startIndex: The oligomer's start index coordinate in the enclosing Polymer

\a endIndex: The oligomer's end index coordinate in the enclosing Polymer
*/
Oligomer::Oligomer(PolChemDefCstSPtr pol_chem_def_csp,
                   const QString &name,
                   const QString &description,
                   bool modified,
                   const CalcOptions &calcOptions,
                   double mono,
                   double avg,
                   int startIndex,
                   int endIndex)
  : Ionizable(pol_chem_def_csp, name, Ponderable(mono, avg)),
    mp_polymer(0),
    m_description(description),
    m_isModified{modified},
    m_calcOptions(calcOptions)
{
  setStartEndIndices(startIndex, endIndex);
}


/*!
\brief Constructs the Oligomer as a copy of \a other.
  */
Oligomer::Oligomer(const Oligomer &other)
  : Sequence(other),
    CoordinateList(other),
    Ionizable(other),
    PropListHolder(other),
    mp_polymer(other.mp_polymer),
    m_description(other.m_description),
    m_isModified{other.m_isModified},
    m_calcOptions(other.m_calcOptions)
{
}


/*!
\brief Destructs this Oligomer.
*/
Oligomer::~Oligomer()
{
  //   qDebug() << "~Oligomer()";
}


/*!
\brief Returns the polymer.
 */
const Polymer *
Oligomer::polymer() const
{
  return mp_polymer;
}


/*!
\brief Sets the start and end indices to \a value1 and \a value2 respectively.

The values are used to construct a Coordinates instance  that is allocated on
the heap and added to member CoordinateList if that list is empty. If the
CoordinateList is not empty, then the values are set to the first Coordinates
instance in that list.

\sa setStartIndex(), setEndIndex()
*/
void
Oligomer::setStartEndIndices(int value1, int value2)
{
  if(!CoordinateList::size())
    {
      Coordinates *coordinates = new Coordinates(value1, value2);
      append(coordinates);

      //       qDebug() << __FILE__ << __LINE__
      // 		<< "[start--end]:" << startIndex() << endIndex();
    }
  else
    {
      Coordinates *coordinates = first();
      coordinates->setStart(value1);
      coordinates->setEnd(value2);

      //       qDebug() << __FILE__ << __LINE__
      // 		<< "[start--end]:" << startIndex() << endIndex();
    }
}


/*!
\brief Sets the start index to \a value.

The value is used to construct a Coordinates instance that is allocated on
the heap and added to member CoordinateList if that list is empty. If the
CoordinateList is not empty, then the value is set to the first Coordinates
instance in that list.
*/
void
Oligomer::setStartIndex(int value)
{
  if(value < 0)
    qDebug() << __FILE__ << __LINE__ << "setStartIndex with value:" << value;

  if(!CoordinateList::size())
    {
      Coordinates *coordinates = new Coordinates();
      coordinates->setStart(value);
      append(coordinates);
    }
  else
    {
      Coordinates *coordinates = first();
      coordinates->setStart(value);
    }
}


/*!
\brief Returns the start index, or -1 if no Coordinates instance is found in
the CoordinateList member.

The start index that is returned is the start index of the first Coordinates
instance of the CoordinateList member.
 */
int
Oligomer::startIndex() const
{
  if(!CoordinateList::size())
    return -1;

  Coordinates *coordinates = first();
  return coordinates->start();
}


/*!
\brief Sets the end index to \a value.

The value is used to construct a Coordinates instance that is allocated on
the heap and added to member CoordinateList if that list is empty. If the
CoordinateList is not empty, then the value is set to the first Coordinates
instance in that list.
*/
void
Oligomer::setEndIndex(int value)
{
  if(!CoordinateList::size())
    {
      Coordinates *coordinates = new Coordinates();
      coordinates->setEnd(value);
      append(coordinates);
    }
  else
    {
      Coordinates *coordinates = first();
      coordinates->setEnd(value);
    }
}


/*!
\brief Returns the end index.

The end index that is returned is the end index of the first Coordinates
instance of the CoordinateList member.
 */
int
Oligomer::endIndex() const
{
  if(!CoordinateList::size())
    return -1;

  Coordinates *coordinates = first();
  return coordinates->end();
}

/*!
\brief Set the description to \a description.
*/
void
Oligomer::setDescription(const QString &description)
{
  m_description = description;
}

/*!
\brief Returns the description.
*/
QString
Oligomer::description() const
{
  return m_description;
}

/*!
\brief Add to the member CoordinateList all the Coordinates instances found in
\a list.

The added Coordinates instances are copies of the instances found in \a list.

Returns the count of added Coordinates instances allocated on the heap.
*/
int
Oligomer::appendCoordinates(CoordinateList *list)
{
  Q_ASSERT(list);

  int count = 0;

  for(int iter = 0; iter < list->size(); ++iter)
    {
      Coordinates *iterCoordinates = list->at(iter);

      Coordinates *coordinates = new Coordinates(*iterCoordinates);

      append(coordinates);

      ++count;
    }

  return count;
}

/*!
\brief Set the IonizeRule member to \a ionize_rule.

\sa MsXpS::libXpertMass::Ionizable::m_ionizeRule
*/
void
Oligomer::setIonizeRule(IonizeRule &ionize_rule)
{
  m_ionizeRule = ionize_rule;
}


/*!
\brief Returns a reference to the IonizeRule member.
*/
IonizeRule &
Oligomer::ionizeRule()
{
  return m_ionizeRule;
}

/*!
\brief Set the calculation options to \a calc_options.

\sa m_calcOptions
*/
void
Oligomer::setCalcOptions(const CalcOptions &calc_options)
{
  m_calcOptions = calc_options;
}

/*!
\brief Returns the calculation options.
*/
const CalcOptions &
Oligomer::calcOptions() const
{
  return m_calcOptions;
}


/*!
\brief Updates the member calculation options with this Oligomer's
CoordinateList.

The data in m_calcOptions that need updating are the CoordinateList elements.
*/
void
Oligomer::updateCalcOptions()
{
  // The data that need update are the CoordinateList elements
  // depending on the internal ::OligomerList data.

  m_calcOptions.setCoordinateList(*(dynamic_cast<CoordinateList *>(this)));
}

/*!
\brief Returns the Monomer in the enclosing Polymer that is located at the
start index of this oligomer.

\sa atRightEnd(), startIndex()
*/
const Monomer &
Oligomer::atLeftEnd() const
{
  //     qDebug() << __FILE__ << __LINE__ << "Going to call at() with value"
  // 	   << startIndex();

  return *(mp_polymer->at(startIndex()));
}


/*!
\brief Returns the Monomer in the enclosing Polymer that is located at the
end index of this oligomer.

\sa atLeftEnd(), endIndex()
*/
const Monomer &
Oligomer::atRightEnd() const
{
  //     qDebug() << __FILE__ << __LINE__ << "Going to call at() with value"
  // 	   << endIndex();

  return *(mp_polymer->at(endIndex()));
}

/*!
\brief Return the Monomer that is located in the enclosing Polymer at \a index.
*/
const Monomer *
Oligomer::monomerAt(int index) const
{
  Q_ASSERT(index >= 0);
  Q_ASSERT(index < mp_polymer->size() - 1);

  //     qDebug() << __FILE__ << __LINE__ << "Going to call at() with value"
  // 	      << index;

  return mp_polymer->at(index);
}

/*!
\brief Returns the member crossLinkList.
*/
QList<CrossLink *> *
Oligomer::crossLinkList()
{
  return &m_crossLinkList;
}

/*!
\brief Add the \a cross_link CrossLink to this Oligomer's list of \l{CrossLink}s

Returns true if the cross-link was added succesfully, or false if \a cross_link
was already in the list.
*/
bool
Oligomer::addCrossLink(CrossLink *cross_link)
{
  // Add the cross-link only if it does not exist already. Return true
  // only if the crossLink has been added.

  if(!m_crossLinkList.contains(cross_link))
    {
      m_crossLinkList.append(cross_link);

      return true;
    }

  return false;
}


// ELEMENTAL CALCULATION FUNCTION
/////////////////////////////////

/*!
\brief Returns the elemental composition of this Oligomer instance.
*/
QString
Oligomer::elementalComposition()
{
  return elementalComposition(m_calcOptions, m_ionizeRule);
}


/*!
\brief Returns the elemental composition of this Oligomer instance.

The computation of the elemental composition is performed as configured in \a
calc_options and using the ionization described in \a ionize_rule.
*/
QString
Oligomer::elementalComposition(const CalcOptions &calc_options,
                               const IonizeRule &ionize_rule)
{
  int times = 0;

  if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS)
    {
      times = 1;

      // qDebug() << __FILE__ << __LINE__
      //          << "SELECTION_TYPE_RESIDUAL_CHAINS ; times:" << times;
    }
  else
    {
      // 	times = CoordinateList::size();

      // Use the version whereby we can perform a sanity check that
      // calc_options was updated with the proper CoordinateList
      // data.

      times = calc_options.coordinateList().size();
      if(times != CoordinateList::size())
        qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

      // qDebug() << __FILE__ << __LINE__
      //          << "SELECTION_TYPE_OLIGOMERS ; times:" << times;
    }

  return mp_polymer->elementalComposition(
    ionize_rule, *(static_cast<CoordinateList *>(this)), calc_options);
}

/////////////////////////////////
// ELEMENTAL CALCULATION FUNCTION

/*!
\brief Calculates the sequence of this Oligomer and sets it to the Sequence
base class member m_monomerText.

Returns the size of m_monomerText. If m_polymer is nullptr, return 0.

\sa Sequence
*/
int
Oligomer::makeMonomerText()
{
  // Prepare the text version of the oligomer's sequence by basing
  // it on the coordinates of *this oligomer and set that text to
  // m_monomerText.

  if(mp_polymer == nullptr)
    return 0;

  QString *text = monomerText();

  m_monomerText = *text;

  delete(text);

  return m_monomerText.size();
}

/*!
\brief Returns a string with the sequence of this Oligomer.

If mp_polymer is non-nullptr, the sequence is calculated by looking
into the Polymer using member Coordinates.

If mp_polymer is nullptr and if Sequence::m_monomerText is not empty, that is
returned.

If mp_polymer is nullptr and if Sequence::m_monomerText is emtpy, then the
sequence is crafted by looking into Sequence::m_monomerList.

\sa makeMonomerText()
*/
QString *
Oligomer::monomerText()
{
  // Allocate a new string to hold the text version of *this
  // oligomer's sequence.

  // There are two situations:

  // 1. The mp_polymer member is non-0, we can get access to it. Ask
  // the polymer to do the work. This is the most faithful
  // situation. But the caller must first ensure that the polymer
  // actually exists.

  // 2. The mp_polymer member is 0, we may have the oligomer
  // sequence stored in *this oligomer. Test that.

  QString *text = new QString();

  if(mp_polymer)
    {
      // For each oligomer(if more than one, which is the case when the
      // oligomer is actually a cross-linked oligomer), create a string...

      int oligomerCount = CoordinateList::size();

      for(int iter = 0; iter < oligomerCount; ++iter)
        {
          Coordinates *coordinates = CoordinateList::at(iter);

          QString *local = mp_polymer->monomerText(
            coordinates->start(), coordinates->end(), true);
          text->append(*local);

          // If this is a cross-linked oligomer and we are not appending
          // text for the __last__ oligomer, then append "~" to let the
          // user know that the sequences are cross-linked.
          if(oligomerCount > 1 && iter < oligomerCount - 1)
            text->append("~");

          delete(local);
        }
    }
  else
    {
      if(m_monomerText.size())
        {
          *text = m_monomerText;

          return text;
        }
      else
        {
          if(m_monomerList.size())
            return Sequence::monomerText(0, m_monomerList.size(), true);
        }
    }

  return text;
}


/*!
\brief Calculates the monoisotopic and average masses.

The calculation is performed by computing the mono and avg masses
of the sequence stretch as described by the start and end indices in
the polymer sequence. Default calculation options are used.

Returns true if calculations were successful, false otherwise.
*/
bool
Oligomer::calculateMasses()
{
  CalcOptions calcOptions;

  calcOptions.setCapping(CAP_NONE);

  IonizeRule rule;

  return calculateMasses(&calcOptions, &rule);
}

/*!
\brief Calculates the monoisotopic and average masses.

The calculation is performed by computing the mono and avg masses
of the sequence stretch as described by the start and end indices in
the polymer sequence. The calculations are configured by \a calc_options
and the ionization is defined in \a ionize_rule.

Returns true if calculations were successful, false otherwise.
*/
bool
Oligomer::calculateMasses(const CalcOptions *calc_options,
                          const IonizeRule *ionize_rule)
{
  Q_ASSERT(calc_options);

  CalcOptions localOptions(*calc_options);

  // The coordinates of the oligomer are the following:

  // MAMISGMSGRKAS

  // For a tryptic peptide obtained from protein above, we'd have

  // MAMISGMSGR, that is in the oligomer coordinates:

  // [0] MAMISGMSGR [9]

  // When computing the mass of the oligomer, we have to do a

  // for (iter == [0] ; iter < [9 + 1]; ++iter)

  // Which is why we increment add 1 to m_endIndex in the function below.

  // A polymer might be something made of more than one residual chain
  // in case it is a cross-linked oligomer. Compute the mass fore each
  // residual chain, without accounting for the cross-links...

  m_mono = 0;
  m_avg  = 0;

  // An oligomer _is_ a CoordinateList, and we need that list in the
  // calcOptions so that we can call the Polymer::accountMasses
  // function.

  localOptions.setCoordinateList(*this);

  // We do not want to take into account the cross-links because
  // we'll be doing this here and because it cannot work if the
  // cross-links are taken into account from the polymer.

  int flags = localOptions.monomerEntities();
  flags &= ~MONOMER_CHEMENT_CROSS_LINK;
  localOptions.setMonomerEntities(flags);

  Polymer::accountMasses(mp_polymer, localOptions, &m_mono, &m_avg);

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "After accounting masses(prior to cross-links):"
  // 	      << "mono mass is:" << m_mono;

  // At this point, we have added the mass of each constituent
  // oligomer's residual chain. Let's deal with the cross-links.

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

      if(!crossLink->accountMasses(&m_mono, &m_avg))
        return false;

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "After accounting cross-link:"
      // 		  << crossLink->name()
      // 		  << "mono mass is:" << m_mono;
    }

  // If an ionizeRule is provided, use it. Otherwise, ionize
  // automatically using the ::Ionizable IonizeRule.
  if(ionize_rule)
    {
      // Note that if ionizeRule is invalid, then the ionization is
      // not performed.

      if(ionize_rule->isValid())
        {
          /*
             if (ionize(mp_polymer, *ionizeRule) == -1)
             The line above is a huge bug. While we should be
             ionizing this oligomer, we end up ionizing the polymer !
             */

          if(ionize(*ionize_rule) == -1)
            return false;
        }
    }
  else
    {
      if(ionize() == -1)
        return false;
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Coming out from the calculateMasses function:"
  // 	      << "mono mass is:" << m_mono;

  return true;
}

/*!
\brief Set the m_isModified status of this Oligomer to \a modified.
*/
void
Oligomer::setModified(bool modified)
{
  m_isModified = modified;
}

/*!
\brief Returns the chemical modification status of this Oligomer.

If \a deep is true, the enclosing Polymer must exist (m_polymer cannot be
nullptr) because each monomer of the Polymer is probed for a potential
modification. This function returns true as soon as such a modified monomer is
encountered.

If \a deep is false, the value of m_isModified is returned.

\sa m_isModified, Monomer::isModified()
*/
bool
Oligomer::isModified(bool deep /*false*/)
{
  // Either we truly go to the polymer instance and check if the oligomer is
  // modified or we just ask for the member datum, that might have been set,
  // for example, during creation of the oligomer in the Cleaver::cleave()
  // function. We need the possibility to ask for the member datum because
  // there are circumstances where the oligomer exists and not the original
  // polymer (for example if the polymer sequence is edited while a set of
  // cleavage oligomers is displayed in the cleavage dialog. When the
  // tableviewmodel needs to refresh the contents of the cells, it crashes
  // because the polymer has been edited and one monomer is missing from the
  // sequence of the oligomer as it had been configured in the first place.

  if(deep)
    {
      if(mp_polymer == nullptr)
        qFatal("Programming error.");

      int oligomerCount = CoordinateList::size();

      for(int iter = 0; iter < oligomerCount; ++iter)
        {
          Coordinates *coordinates = CoordinateList::at(iter);

          for(int jter = coordinates->start(); jter < coordinates->end() + 1;
              ++jter)
            {
              // 	    qDebug() << __FILE__ << __LINE__ << "Going to call at()
              // with value"
              // 		      << iter;

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

              if(monomer->isModified())
                return true;
            }
        }

      return false;
    }
  else
    {
      return m_isModified;
    }
}

/*!
\brief Returns the size of this Oligomer.

The size is computed by adding the length of all the regions of the
enclosing Polymer as documented in the Coordinates instances in the member
coordinateList.
*/
int
Oligomer::size()
{
  int sum = 0;

  int oligomerCount = CoordinateList::size();

  // The size of an oligomer is the sum of all its oligomeric
  // components as described by the various coordinates.

  for(int iter = 0; iter < oligomerCount; ++iter)
    {
      Coordinates *coordinates = CoordinateList::at(iter);

      sum += coordinates->length();
    }

  return sum;
}

/*!
\brief Returns true if this Oligomer spans at least one region of the
enclosing polymer that contains a Monomer at \a index, false otherwise.
*/
bool
Oligomer::encompasses(int index) const
{
  int oligomerCount = CoordinateList::size();

  for(int iter = 0; iter < oligomerCount; ++iter)
    {
      Coordinates *coordinates = CoordinateList::at(iter);

      if(index <= coordinates->start() && index >= coordinates->end())
        return true;
    }

  return false;
}


/*!
\brief Returns true if this Oligomer spans at least one region of the
enclosing polymer that contains the Monomer \a monomer, false otherwise.

The search is performed by comparing pointers, thus the Monomer to be search \e
is \a monomer.
*/
bool
Oligomer::encompasses(const Monomer *monomer) const
{
  int oligomerCount = CoordinateList::size();

  for(int iter = 0; iter < oligomerCount; ++iter)
    {
      Coordinates *coordinates = CoordinateList::at(iter);

      for(int jter = coordinates->start(); jter < coordinates->end() + 1;
          ++jter)
        {
          // 	    qDebug() << __FILE__ << __LINE__ << "Going to call at() with
          // value"
          // 		      << jter;

          if(mp_polymer->at(jter) == monomer)
            return true;
        }
    }

  return false;
}

} // namespace libXpertMass

} // namespace MsXpS
