/* 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 "Polymer.hpp"
#include "CrossLink.hpp"


namespace MsXpS
{

namespace libXpertMass
{

///////////////////////////// CrossLink ////////////////////////
///////////////////////////// CrossLink ////////////////////////
///////////////////////////// CrossLink ////////////////////////

///// Look down for CrossLinkList



/*!
\class MsXpS::libXpertMass::CrossLink
\inmodule libXpertMass
\ingroup PolChemDefAqueousChemicalReactions
\inheaderfile CrossLink.hpp

\brief The CrossLink class provides abstractions to work with
a cross-link entity between \l Monomer instances.

The notion of a cross-link is that it is a chemical reaction that involves at
least two monomers in a \l Polymer or in an \l Oligomer sequence.

Polymer sequences might contain more than one CrossLink and these are stored as
a \l CrossLinkList.
*/

/*!
    \enum MsXpS::libXpertMass::CrossLinkEncompassed

    This enum type specifies the manner in which a sequence region in a \l
Polymer or in an \l Oligomer contains fully, or partially not does not contain
all the monomers involved in a CrossLink:

    \value CROSS_LINK_ENCOMPASSED_NO
           The region does not contain any \l Monomer involved in a CrossLink.
    \value CROSS_LINK_ENCOMPASSED_PARTIAL
           The region contains one or more \l{Monomer}s involved in a
CrossLink but not all.
    \value CROSS_LINK_ENCOMPASSED_FULL
           All the \l{Monomer}s involved in the CrossLink are contained in
the \l Polymer or \l Oligomer region.
*/


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

\brief The \l Polymer instance of which this CrossLink is part. This CrossLink
does not own the Polymer.
*/

/*!
\variable MsXpS::libXpertMass::CrossLink::m_monomerList

\brief The list of \l Monomer instances that are involved in the formation of
this CrossLink.

The monomers are the \l Monomer instances found in the mp_polymer \l Polymer
instance. This CrossLink does not own these \l{Monomer}s.

The reason the CrossLink lists the involved \l Monomer instances as pointers to
these very monomers in the polymer sequence is that in this way, even if the
sequence is edited the cross-link does not loose the relation to the monomers.
The only way that the sequence editing modifies a CrossLink instance is by
removing a \l Monomer instance involved in the CrossLink. If that occurs, the
CrossLink is informed and its destructed.
*/

/*!
\variable MsXpS::libXpertMass::CrossLink::m_comment

\brief The comment that might be associated to this CrossLink.
*/

/*!
\brief Constructs a CrossLink instance

\list
\li \a pol_chem_def_csp: the polymer chemistry definition (\l PolChemDef).
\li \a polymer: the \l Polymer instance in which this CrossLink was formed.
\li \a name: the name of this CrossLink instance.
\li \a formula: the \l Formula that describes the reaction that is the basis of
the chemical reaction leading to the formation of this CrossLink.
\li \a comment: a comment that might be associated to this CrossLink.
\endlist
*/
CrossLink::CrossLink(PolChemDefCstSPtr pol_chem_def_csp,
                     Polymer *polymer,
                     const QString &name,
                     const QString &formula,
                     const QString &comment)
  : CrossLinker(pol_chem_def_csp, name, formula),
    mp_polymer(polymer),
    m_comment(comment)
{
}


/*!
\brief Constructs a CrossLink instance

\list
\li \a cross_linker: CrossLinker instance used to initialize this CrossLink.
\li \a polymer: the \l Polymer instance in which this CrossLink was formed
\li \a comment: a comment that might be associated to this CrossLink
\endlist
*/
CrossLink::CrossLink(const CrossLinker &cross_linker,
                     Polymer *polymer,
                     const QString &comment)
  : CrossLinker(cross_linker), mp_polymer(polymer), m_comment(comment)
{
}

/*!
\brief Constructs a CrossLink instance as a copy of \a other.
*/
CrossLink::CrossLink(const CrossLink &other)
  : CrossLinker(other),
    mp_polymer(other.mp_polymer),
    m_comment(other.m_comment)
{
}


/*!
\brief Destructs this CrossLink instance.

No entity needs to be destructed, since the \l Monomer instances in the list
are not owned by this CrossLinker instance.
*/
CrossLink::~CrossLink()
{
}

/*!
\brief Sets \a monomer to this CrossLink at \a index of the member list of
Monomer instances, effectively replacing the \a monomer instance currently at
that index.

\a monomer cannot be nullptr and \a index cannot be out-of-bounds.

This CrossLinker instance does not take ownership of \a monomer.

Returns true.
*/
bool
CrossLink::setMonomerAt(Monomer *monomer, int index)
{
  Q_ASSERT(monomer);
  Q_ASSERT(index >= 0 && index < m_monomerList.size());

  m_monomerList.replace(index, monomer);

  return true;
}

/*!
\brief Adds \a monomer to the member list of Monomer instances.

\a monomer cannot be nullptr.

Returns true.
*/
bool
CrossLink::appendMonomer(const Monomer *monomer)
{
  Q_ASSERT(monomer);

  m_monomerList.append(monomer);

  return true;
}

/*!
\brief Returns the monomer at \a index in the member list of \l Monomer
instances.

\a index cannot be out-of-bounds.
*/
const Monomer *
CrossLink::monomerAt(int index)
{
  Q_ASSERT(index >= 0 && index < m_monomerList.size());

  return m_monomerList.at(index);
}

/*!
\brief Removes the monomer at \a index in the member list of \l Monomer
instances.

\a index cannot be out-of-bounds.

Returns true.
*/
bool
CrossLink::removeMonomerAt(int index)
{
  Q_ASSERT(index < m_monomerList.size());

  m_monomerList.removeAt(index);

  return true;
}

/*!
\brief Set \a polymer as the \l Polymer sequence in which this CrossLink has
been formed.

\a polymer cannot be nullptr.
*/
void
CrossLink::setPolymer(Polymer *polymer)
{
  Q_ASSERT(polymer);

  mp_polymer = polymer;
}

/*!
\brief Returns the \l Polymer instance in which this CrossLink has
been formed.
*/
Polymer *
CrossLink::polymer()
{
  return mp_polymer;
}

/*!
\brief Set the member comment to \a comment.
*/
void
CrossLink::setComment(const QString &comment)
{
  m_comment = comment;
}

/*!
 \brief Returns the member comment.
 */
const QString &
CrossLink::comment() const
{
  return m_comment;
}

/*!
\brief Returns true if this CrossLink instance and \a other are identical,
false otherwise.
*/
bool
CrossLink::operator==(const CrossLink &other) const
{
  int tests = 0;

  tests += CrossLinker::operator==(other);
  tests += mp_polymer == other.mp_polymer;

  if(m_monomerList.size() != other.m_monomerList.size())
    return false;

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      if(m_monomerList.at(iter) != other.m_monomerList.at(iter))
        return false;
    }

  tests += m_comment == other.m_comment;

  if(tests < 3)
    return false;
  else
    return true;
}

/*!
\brief Sets to the member monomer list all the \l Monomer instances referenced
in \a text as indices to monomer in the \l Polymer sequence.

\a text contains a list of \l Monomer instance indices separated by ';'
characters. The corresponding monomers found in the member Polymer are copied
to the member list of \l Monomer instances, effectively documenting a CrossLink
in that member Polymer sequence.

This CrossLink instance does not own the \l Monomer instances pointers, as they
effectively point to monomer that belong to the member \l Polymer.
*/
int
CrossLink::populateMonomerList(QString text)
{
  // We do not own the monomers pointed to by the pointers in
  // m_monomerList.

  while(m_monomerList.size())
    m_monomerList.removeFirst();

  QStringList stringList =
    text.split(';', Qt::SkipEmptyParts, Qt::CaseSensitive);

  // There must be at least 2 monomers to make a crossLink !

  if(stringList.size() < 2)
    return -1;

  for(int iter = 0; iter < stringList.size(); ++iter)
    {
      QString value = stringList.at(iter);

      bool ok = false;

      int index = value.toInt(&ok);

      if(!index && !ok)
        return -1;

      Q_ASSERT(index >= 0 && index < mp_polymer->size());

      m_monomerList.append(mp_polymer->at(index));
    }

  return m_monomerList.size();
}

/*!
\brief Returns the member list of \l Monomer instances.
*/
QList<const Monomer *> *
CrossLink::monomerList()
{
  return &m_monomerList;
}

/*!
\brief Lists in \a list the indices of all the monomers involved in this
CrossLink instance.

Returns the count of indices set to \a list.
*/
int
CrossLink::monomerIndexList(QList<int> *list)
{
  if(!list)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  int count = 0;

  QList<int> localIndexList;

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);
      int index              = mp_polymer->monomerIndex(monomer);

      // qDebug() << __FILE__ << __LINE__
      //          << "Other monomer index:" << index;

      localIndexList.append(index);

      ++count;
    }

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

  // Now that we have the minIndex and the maxIndex of the region
  // involved by the cross-link, we can fill-in the integer list
  // with all the values contained between these two borders.i

  for(int iter = localIndexList.first(); iter <= localIndexList.last(); ++iter)
    {
      // If we had a cross-link between monomers [4] and [10] of the
      // polymer, then the indices appended to the list would be 4,
      // 5, 6, 7, 8, 9 and 10.
      list->append(iter);
    }

  return count;
}

/*!
\brief Returns a string containing a ';'-separated list of the indices of all
the \l Monomer instances involved in this CrossLink.
*/
QString
CrossLink::monomerIndexText()
{
  QString text = ";";

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);
      int index              = mp_polymer->monomerIndex(monomer);

      text += QString("%1;").arg(index);
    }

  return text;
}

/*!
\brief Returns a string containing a ';'-separated list of the positions
of all the \l Monomer instances involved in this CrossLink.
*/
QString
CrossLink::monomerPosText()
{
  QString text = ";";

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);
      int index              = mp_polymer->monomerIndex(monomer);

      text += QString("%1;").arg(index + 1);
    }

  return text;
}

/*!
\brief Returns the index of \a monomer found in the member \l Polymer sequence
that is involved in this CrossLink, or -1 if that is not found.
*/
int
CrossLink::involvesMonomer(const Monomer *monomer) const
{
  Q_ASSERT(monomer);

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      if(m_monomerList.at(iter) == monomer)
        {
          return iter;
        }
    }

  return -1;
}

/*!
\brief Returns the first \l Monomer instance listed in the member list of
monomers. If the list is empty; returns nullptr.
*/
const Monomer *
CrossLink::firstMonomer() const
{
  if(!m_monomerList.size())
    return nullptr;

  return m_monomerList.first();
}

/*!
\brief Tells if this CrossLink instance is encompassed (partially or fully) or
not at all by the Polymer sequence region defined by the [\a start - \a end ]
\l Monomer index range.

The count of monomers involved in this cross-link that:

\list
\li Are contained in the range is stored in \a in.
\li Are not contained in the range is stored in \a out.
\endlist

If all the monomers are listed as \a in, then this CrossLink is fully
encompassed by the Polymer sequence region defined by the [\a start - \a end ]
\l Monomer index range and CROSS_LINK_ENCOMPASSED_FULL is returned.

If all the monomers are listed as \a out, then this CrossLink is not at all
encompassed by the Polymer sequence region and CROSS_LINK_ENCOMPASSED_NO is
returned.

If both kinds of monmers were found, then CROSS_LINK_ENCOMPASSED_PARTIAL is
returned.
*/
int
CrossLink::encompassedBy(int start, int end, int *in, int *out)
{
  // Iterate in the list of monomers, and for each monomer check if
  // their index is contained in the stretch.

  int countIn  = 0;
  int countOut = 0;

  int localStart = std::min(start, end);
  int localEnd   = std::max(start, end);

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);

      int index = mp_polymer->monomerIndex(monomer);

      if(index >= localStart && index <= localEnd)
        ++countIn;
      else
        ++countOut;
    }

  Q_ASSERT((countIn + countOut) == m_monomerList.size());

  if(in)
    *in = countIn;
  if(out)
    *out = countOut;

  if(countOut && countIn)
    return CROSS_LINK_ENCOMPASSED_PARTIAL;

  if(countOut)
    return CROSS_LINK_ENCOMPASSED_NO;

  if(countIn)
    return CROSS_LINK_ENCOMPASSED_FULL;

  return CROSS_LINK_ENCOMPASSED_NO;
}


/*!
\brief Tells if this CrossLink instance is encompassed (partially or fully) or
not at all by various \l Coordinates instances in the \a coordinate_list \l
CoordinateList.

The count of monomers involved in this cross-link that:

\list
\li Are contained in the regions defined in \a coordinate_list is stored in \a
in.
\li Are not contained in the range is stored in \a out.
\endlist

If all the monomers are listed as \a in, then this CrossLink is fully
encompassed by the Polymer sequence regions defined in \a coordinate_list and
CROSS_LINK_ENCOMPASSED_FULL is returned.

If all the monomers are listed as \a out, then this CrossLink is not at all
encompassed by the Polymer sequence region and CROSS_LINK_ENCOMPASSED_NO is
returned.

If both kinds of monmers were found, then CROSS_LINK_ENCOMPASSED_PARTIAL is
returned.
*/
int
CrossLink::encompassedBy(const CoordinateList &coordinate_list,
                         int *in,
                         int *out)
{
  // Iterate in the list of monomers involved in *this crossLink,
  // and for each monomer check if their index is contained in any
  // of the Coordinates [start--end] of the coordinateList passed as
  // parameter.

  int countIn  = 0;
  int countOut = 0;

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);

      int index = mp_polymer->monomerIndex(monomer);

      // 	qDebug() << __FILE__ <<  __LINE__
      // 		  << "           monomer at index:" << index
      // 		  << "tested:";

      // Is the index encompassed by any of the Coordinates of the
      // CoordinateList ?

      bool countedIn = false;

      for(int jter = 0; jter < coordinate_list.size(); ++jter)
        {
          Coordinates *coordinates = coordinate_list.at(jter);

          if(index >= coordinates->start() && index <= coordinates->end())
            {
              ++countIn;
              countedIn = true;

              // 		qDebug() << __FILE__ <<  __LINE__
              // 			  << "           encompassedBy YES by coordinates"
              // 			  << "coordinates->start()" << coordinates->start()
              // 			  << "coordinates->end()" << coordinates->end();

              break;
            }
          else
            {
              // 		qDebug() << __FILE__ <<  __LINE__
              // 			  << "           encompassedBy NO by coordinates"
              // 			  << "coordinates->start()" << coordinates->start()
              // 			  << "coordinates->end()" << coordinates->end();
            }
        }

      if(!countedIn)
        ++countOut;
    }

  Q_ASSERT((countIn + countOut) == m_monomerList.size());

  if(in)
    *in = countIn;
  if(out)
    *out = countOut;

  if(countOut && countIn)
    return CROSS_LINK_ENCOMPASSED_PARTIAL;

  if(countOut)
    return CROSS_LINK_ENCOMPASSED_NO;

  if(countIn)
    return CROSS_LINK_ENCOMPASSED_FULL;

  return CROSS_LINK_ENCOMPASSED_NO;
}

/*!
\brief Returns true if this CrossLink instance validates successfully, false
otherwise.

The validation is successful if:

\list
\li The count of \l Monomer instances listed in the member list is > 1.
\li The member \l Polymer instance exists.
\li The count of \l Monomer instances listed in the member list is equal to
the number of \l Modif instance in the \l CrossLinker base class.
\li If the list of \l Modif
instances in the \l CrossLinker base class is not empty, there must be a
colinearity between the order in which these instances are listed and the
order in which the \l Monomer instances are listed in the member list. The
monomer instance must be a target of the modification.
\endlist
*/
bool
CrossLink::validate()
{
  if(m_monomerList.size() <= 1)
    {
      qDebug() << __FILE__ << __LINE__
               << "A crossLink with a single monomer "
                  "cannot be created.";

      return false;
    }

  if(mp_polymer == nullptr)
    return false;

  // If the crossLinker has at least one modif in its definition, then
  // we have to make sure that the modification has as one of its
  // targets the corresponding monomer in the list of monomer
  // pointers.

  // Indeed, there is some logic here. Either the m_modifList in the
  // parent class CrossLinker contains no item, in which case
  // everything is fine, or it does contain items. In the latter case,
  // then, the number of items must match the number of monomers being
  // crosslinked, and then we get to know which modif is attributable
  // to which monomer, hence the possibility of a check.

  if(m_modifList.size())
    {
      if(m_modifList.size() != m_monomerList.size())
        {
          qDebug() << __FILE__ << __LINE__
                   << QObject::tr(
                        "The number of modification items "
                        "does not match the number of "
                        "monomers. This is an error.");
          return false;
        }

      // At this point, we can make the check for each modif:

      for(int iter = 0; iter < m_modifList.size(); ++iter)
        {
          Modif *modif           = m_modifList.at(iter);
          const Monomer *monomer = m_monomerList.at(iter);

          if(!monomer->isModifTarget(*modif))
            {
              // The monomer is not target for the modification that is
              // implied by the crossLinking.

              qDebug() << __FILE__ << __LINE__
                       << QObject::tr(
                            "The monomer '%1' is not target of "
                            "crossLinker modif '%2'")
                            .arg(monomer->name())
                            .arg(modif->name());

              return false;
            }
        }
    }

  return true;
}

/*!
\brief Calculates the masses of this CrossLink and set them anew to the member
masses (\l Ponderable base class).

Returns true.
*/
bool
CrossLink::calculateMasses()
{
  return CrossLinker::calculateMasses();
}

/*!
\brief Adds the member m_mono and m_avg masses to \a mono and \a
avg, as compounded by the \a times factor.

Returns true.
*/
bool
CrossLink::accountMasses(double *mono, double *avg, int times)
{
  return CrossLinker::accountMasses(mono, avg, times);
}


/*!
\brief Adds the member m_mono and m_avg masses to \a ponderable, as compounded
by the \a times factor.

Returns true.
*/
bool
CrossLink::accountMasses(Ponderable *ponderable, int times)
{
  return CrossLinker::accountMasses(ponderable, times);
}

/*!
\brief Returns an allocated string describing this CrossLink instance.
*/
QString *
CrossLink::prepareResultsTxtString()
{
  QString *text = new QString();

  *text += QObject::tr(
             "\nCross-link:\n"
             "===============\n"
             "Cross-linker name: %1\n"
             "Cross-link comment: %2\n")
             .arg(m_name)
             .arg(m_comment);

  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      const Monomer *monomer = m_monomerList.at(iter);

      int index = mp_polymer->monomerIndex(monomer);

      *text += QObject::tr("Partner %1: %2 at position: %3\n")
                 .arg(iter + 1)
                 .arg(monomer->code())
                 .arg(index + 1);
    }

  return text;
}

///////////////////////////// CrossLinkList ////////////////////////
///////////////////////////// CrossLinkList ////////////////////////
///////////////////////////// CrossLinkList ////////////////////////


/*!
\class MsXpS::libXpertMass::CrossLinkList
\inmodule libXpertMass
\ingroup PolChemDefAqueousChemicalReactions

\brief The CrossLinkList class provides a list of CrossLink instances.
*/

/*!
\variable MsXpS::libXpertMass::CrossLinkList::m_name

\brief The name of the list of \l CrossLink instances.
*/

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

\brief The \l Polymer instance of which this CrossLinkList is part. This
CrossLinkList does not own the Polymer.
*/

/*!
\variable MsXpS::libXpertMass::CrossLinkList::m_comment

\brief The comment that might be associated to this CrossLinkList.
*/

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

/*!
\brief Constructs a CrossLinkList instance.

\list
\li \a name: the name of this CrossLinkList instance.
\li \a polymer: the \l Polymer instance in which this CrossLinkList was formed.
\li \a comment: a comment that might be associated to this CrossLinkList.
\endlist
*/
CrossLinkList::CrossLinkList(const QString &name,
                             Polymer *polymer,
                             const QString &comment)
  : m_name(name), mp_polymer(polymer), m_comment(comment)
{
}


/*!
\brief Constructs a CrossLinkList instance as a copy of \a other.
*/
CrossLinkList::CrossLinkList(const CrossLinkList *other)
  : QList<CrossLink *>(*other),
    m_name(other->m_name),
    mp_polymer(other->mp_polymer),
    m_comment(other->m_comment)
{
  // And now copy all the items from other in here:
  for(int iter = 0; iter < other->size(); ++iter)
    {
      CrossLink *crossLink = other->at(iter);

      CrossLink *crossLinkNew = new CrossLink(crossLink->getPolChemDefCstSPtr(),
                                              crossLink->polymer(),
                                              crossLink->name(),
                                              crossLink->formula(),
                                              crossLink->comment());

      append(crossLinkNew);
    }
}

/*!
\brief Destructs thisCrossLinkList instance.

Deletes all the CrossLink instances in this list.
*/
CrossLinkList::~CrossLinkList()
{
  qDeleteAll(begin(), end());
  clear();
}

/*!
\brief Assigns \a other to this CrossLinkList instance.

Returns a reference to this CrossLinkList instance.
*/
CrossLinkList &
CrossLinkList::operator=(const CrossLinkList &other)
{
  if(&other == this)
    return *this;

  m_name     = other.m_name;
  mp_polymer = other.mp_polymer;
  m_comment  = other.m_comment;

  // Now empty and clear this list.
  qDeleteAll(begin(), end());
  clear();

  // And now copy all the items from other in here:
  for(int iter = 0; iter < other.size(); ++iter)
    {
      CrossLink *crossLink = other.at(iter);

      CrossLink *crossLinkNew = new CrossLink(*crossLink);

      append(crossLinkNew);
    }

  return *this;
}


/*!
\brief Sets the \a name.
*/
void
CrossLinkList::setName(QString name)
{
  if(!name.isEmpty())
    m_name = name;
}


/*!
\brief Returns the name.
*/
QString
CrossLinkList::name()
{
  return m_name;
}

/*!
\brief Sets the \a comment.
*/
void
CrossLinkList::setComment(QString comment)
{
  if(!comment.isEmpty())
    m_comment = comment;
}


/*!
\brief Returns the comment.
*/
const QString &
CrossLinkList::comment() const
{
  return m_comment;
}

/*!
\brief Sets the \a polymer.
*/
void
CrossLinkList::setPolymer(Polymer *polymer)
{
  mp_polymer = polymer;
}


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


int
CrossLinkList::crossLinksInvolvingMonomer(const Monomer *monomer,
                                          QList<int> *list) const
{
  int count = 0;

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

      for(int jter = 0; jter < crossLink->monomerList()->size(); ++jter)
        {
          const Monomer *iterMonomer = crossLink->monomerList()->at(jter);

          if(monomer == iterMonomer)
            {
              count++;

              if(list)
                list->append(iter);
            }
        }
    }

  return count;
}


} // namespace libXpertMass

} // namespace MsXpS
