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


namespace MsXpS
{

namespace libXpertMass
{


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

\brief The Monomer class provides abstractions to work with monomers.

In libmass, a momomer is an entity that is part of a polymer chemistry
definition. A monomer models a chemical entity that is part of a polymer.

In protein chemistry, that would be a \e{residue}, that is, an amino-acid that
has been polymerized into a residue chain (that is, a protein, or a peptide).
The chemical reaction that polymerizez an amino acid into an elongating protein
structure is a condensation, with loss of H2O from the amino acid to actually
lead to a what is called a \e{residue} of a monomer, or for short a \e{residue}.

\note The monomer, that is partly defined by its formula, has the formula of the
\e{residue}, not of the amino acid. This is always true, whatever the polymer
chemistry definition at hand: protein, saccharide, nucleic acid.
*/


/*!
   \variable int MsXpS::libXpertMass::Monomer::m_code

   \brief The code of the monomer, like K for lysine, A for adenine.
 */

/*!
   \variable int MsXpS::libXpertMass::Monomer::mpa_modifList

   \brief Allocated list of MsXpS::libXpertMass::Modif objects.
 */


/*!
  \brief Constructs a monomer with its member data set to \a name, \a code, \a
formula.

  The \a pol_chem_def_csp polymer chemistry definition is set to the
PolChemDefEntity base class' member.
  */
Monomer::Monomer(PolChemDefCstSPtr pol_chem_def_csp,
                 QString name,
                 QString code,
                 QString formula)
  : PolChemDefEntity(pol_chem_def_csp, name),
    Formula(formula),
    Ponderable(0, 0),
    m_code(code)
{
  mpa_modifList = 0;
}

/*!
  \brief Constructs a monomer as a copy of \a other.
  */
Monomer::Monomer(const Monomer &other)
  : PolChemDefEntity(other),
    Formula(other.m_formula),
    Ponderable(other),
    PropListHolder(other)
{
}


/*!
 \brief Destroys the monomer.
 */
Monomer::~Monomer()
{
}

/*!
 \brief Assigns \a other's member data to this monomer.

 The copy is deep, in particular with the mpa_modifList being copied.

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

  PolChemDefEntity::operator=(other);
  Formula::operator=(other);
  Ponderable::operator=(other);
  PropListHolder::operator=(other);

  m_code = other.m_code;

  if(mpa_modifList != nullptr)
    qDeleteAll(*mpa_modifList);

  if(other.mpa_modifList != nullptr)
    {
      if(mpa_modifList == nullptr)
        mpa_modifList = new QList<Modif *>();

      for(int iter = 0; iter < other.mpa_modifList->size(); ++iter)
        {
          Modif *modif = new Modif(*other.mpa_modifList->at(iter));
          mpa_modifList->append(modif);
        }
    }
  else
    {
      delete(mpa_modifList);

      mpa_modifList = nullptr;
    }

  return *this;
}


/*!
  \brief Sets the code to \a code

 */
void
Monomer::setCode(const QString &code)
{
  m_code = code;
}


/*!
  \brief Returns the code
 */
QString
Monomer::code() const
{
  return m_code;
}


/*!
  \brief Returns true if this monomer and \a other are identical, false
otherwise.

    The comparison involves also the comparison of the Modif objects in
mpa_modifList.
  */
bool
Monomer::operator==(const Monomer &other) const
{
  int tests = 0;

  tests += PolChemDefEntity::operator==(other);
  tests += Formula::operator==(other);
  tests += Ponderable::operator==(other);

  tests += (m_code == other.m_code);

  if(mpa_modifList->size() != other.mpa_modifList->size())
    return false;

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

  if(tests < 4)
    return false;

  return true;
}


/*!
  \brief Returns true if this monomer and \a other differ, false
otherwise.

    The comparison involves also the comparison of the Modif objects in
mpa_modifList.
  */
bool
Monomer::operator!=(const Monomer &other) const
{
  int tests = 0;

  tests += PolChemDefEntity::operator!=(other);
  tests += Formula::operator!=(other);
  tests += Ponderable::operator!=(other);

  tests += (m_code != other.m_code);

  if(mpa_modifList->size() != other.mpa_modifList->size())
    return true;

  for(int iter = 0; iter < mpa_modifList->size(); ++iter)
    {
      if(mpa_modifList->at(iter) != other.mpa_modifList->at(iter))
        return true;
    }

  if(tests > 0)
    return true;

  return false;
}


/*!
  \brief Checks the code's syntactic validity.

  The monomer code is verified and has to verify these criteria:

  \list
  \li It must be non-empty
  \li Its character length has to be less or equal to the code length parameter
in the polymer chemistry definition (see PolChemDef::m_codeLength)
  \li The first character is uppercase
  \li All the remaining characters are lowercase
  \endlist

  Returns true if the code syntax checked successfully, false otherwise.

  \sa validate()
  */
bool
Monomer::checkCodeSyntax() const
{
  // The code has to be at least one character long.
  // The first letter in the code has to be uppercase.
  // All the remaining authorized characters have to be
  // lowercase.
  int codeLength = mcsp_polChemDef->codeLength();

  if(m_code.length() < 1 || m_code.length() > codeLength)
    return false;

  // Note that the actual monomer code length might be less than the
  // codeLength member datum in the polymer chemistry
  // definition. Which is why we have to make sure we test that before
  // risking to access a character ouf of bonds of the m_code string.

  for(int iter = 0; iter < m_code.size(); ++iter)
    {
      // Test that the m_code length is not greater than codeLength.
      if(iter + 1 > codeLength)
        return false;

      // And now check the character syntax.
      QChar curChar = m_code.at(iter);

      if(iter == 0)
        {
          if(curChar.category() != QChar::Letter_Uppercase)
            return false;
        }
      else if(curChar.category() == QChar::Letter_Uppercase)
        return false;
    }

  return true;
}

/*!
 \brief Tells if a monomer by this monomer's code is known in the polymer
chemistry definition.

  The monomers in the list of monomers of the polymer chemistry definition
(mcsp_polChemDef) are searched through for this monomer's code (m_code).

  Returns the index of the found monomer or -1 either if m_code is empty or if
the monomer was not found.
  */
int
Monomer::isCodeKnown() const
{
  const QList<Monomer *> &refList = mcsp_polChemDef->monomerList();

  if(m_code.isEmpty())
    return -1;

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      if(refList.at(iter)->m_code == m_code)
        return iter;
    }

  return -1;
}


/*!
 \brief Tells if a monomer by the monomer \a code is in \a refList.

  The monomers in  \a refList are searched through for the monomer \a code.

  If a monomer is found and \a monomer_p is non-nullptr, the monomer pointed
to by \a monomer_p is set to the contents of the found monomer.

  Returns the index of the found monomer or -1 either if m_code is empty or if
the monomer was not found.
  */
int
Monomer::isCodeInList(const QString &code,
                      const QList<Monomer *> &refList,
                      Monomer *monomer_p)
{
  Monomer *iter_monomer_p = nullptr;

  if(code.isEmpty())
    return -1;

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      iter_monomer_p = refList.at(iter);
      Q_ASSERT(iter_monomer_p);

      if(iter_monomer_p->m_code == code)
        {
          // qDebug() << "Found the monomer in the reference list by code:" <<
          // str;

          if(monomer_p)
            *monomer_p = *iter_monomer_p;

          return iter;
        }
    }

  return -1;
}


/*!
 \brief Tells if a monomer by this monomer's name is known in the polymer
chemistry definition.

  The monomers in the list of monomers of the polymer chemistry definition
(mcsp_polChemDef) are searched through for this monomer's name (m_name).

  Returns the index of the found monomer or -1 either if m_name is empty or if
the monomer was not found.
  */
int
Monomer::isNameKnown() const
{
  const QList<Monomer *> &refList = mcsp_polChemDef->monomerList();

  if(m_name.isEmpty())
    return -1;

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      if(refList.at(iter)->m_name == m_name)
        return iter;
    }

  return -1;
}


/*!
 \brief Tells if a monomer by the monomer \a name is in \a refList.

  The monomers in  \a refList are searched through for the monomer \a name.

  If a monomer is found and \a monomer_p is non-nullptr, the monomer pointed
to by \a monomer_p is set to the contents of the found monomer.

  Returns the index of the found monomer or -1 either if m_name is empty or if
the monomer was not found.
  */
int
Monomer::isNameInList(const QString &name,
                      const QList<Monomer *> &refList,
                      Monomer *monomer_p)
{
  Monomer *monomer = 0;

  if(name.isEmpty())
    return -1;

  for(int iter = 0; iter < refList.size(); ++iter)
    {
      monomer = refList.at(iter);
      Q_ASSERT(monomer != 0);

      if(monomer->m_name == name)
        {
          if(monomer_p != 0)
            *monomer_p = *monomer;

          return iter;
        }
    }

  return -1;
}

/*!
  \brief Returns the modification list

*/
QList<Modif *> *
Monomer::modifList() const
{
  return mpa_modifList;
}

/*
  \brief Return the formula of this monomer as a string.
  */
QString
Monomer::formula() const
{
  return Formula::toString();
}


/*
  \brief Returns true if this monomer is a target of Modif \a modif, false
otherwise.
  */
bool
Monomer::isModifTarget(const Modif &modif) const
{
  // Pure convenience function.

  return modif.hasMonomerTarget(m_code);
}

/*!
  \brief Modifies this monomer using \a modif.

  The two verifications that are done:

  \list
  \li This monomer must be a \a modif target;
  \li The count of \a modif modifications in this monomer must be at most the
authorized count - 1, to accomodate this new modification [see
Modif::maxCount()].
  \endlist

  The two restriction above can be overridden by setting \a override to true.

  If errors are encountered, these are reported as strings in \a errorList.

  The \a{modif}'s ownership is taken by this monomer.

  Returns true on success, false otherwise.
  */
bool
Monomer::modify(Modif *modif, bool override, QStringList &errorList)
{
  // Will take ownership of the modif.

  // We have to check two things:

  // 1. That *this monomer is actually a target of the modification
  // at hand(or that we can override limitations);

  // 2. That *this monomer can still accommodate one such 'modif'
  // more (that is the count of 'modif' for *this mononomer is
  // correct for adding one more.

  Q_ASSERT(modif);

  if(!isModifTarget(*modif) && !override)
    {
      // This monomer is not a target for the modif, and no override
      // is allowed.

      errorList << QObject::tr(
                     "\t%1 not target of modif %2 "
                     "(no overriding allowed)")
                     .arg(m_name)
                     .arg(modif->name());

      return false;
    }

  int count = modifCount(modif->name());

  if(count >= modif->maxCount() && !override)
    {
      // This monomer has already the maximum count of 'modif' objects.

      errorList << QObject::tr(
                     "\t%1 already modified %2 times "
                     "(no overriding allowed)")
                     .arg(m_name)
                     .arg(count);

      return false;
    }

  // We are going to add one modification to the list of
  // modifications. Note however, that if the monomer had never been
  // modified(or all of its modifications had been removed), then its
  // modifList should be 0. We must allocate it.

  if(!mpa_modifList)
    mpa_modifList = new QList<Modif *>();

  mpa_modifList->append(modif);

  return true;
}


/*!
  \brief Removes \a modif from this monomer.

  The member list of modifications must exist (it is a heap-allocated list that
is allocated upon the first modification of the monomer) and must be non-empty.

  Returns true.
*/
bool
Monomer::unmodify(Modif *modif)
{
  // The unmodification pertains to the single 'modif' object.

  // We are given the address of a specific modif to remove, thus it
  // cannot be that the list of modifs be 0 or empty.
  Q_ASSERT(mpa_modifList);
  Q_ASSERT(mpa_modifList->size());

  // Will remove only one item, even if we call removeAll() because
  // there is only one 'modif' pointer to Modif.
  int ret = mpa_modifList->removeAll(modif);

  // Only one item should have been found in the list.
  if(ret != 1)
    qFatal("Programming error");

  // If we removed the last item, free the list.
  if(!mpa_modifList->size())
    {
      delete mpa_modifList;

      mpa_modifList = 0;
    }

  return true;
}


/*!
  \brief Removes \e{all} the modification from this monomer.

  Returns true.
  */
bool
Monomer::unmodify()
{
  if(mpa_modifList)
    {
      qDeleteAll(*mpa_modifList);

      delete mpa_modifList;
    }

  return true;
}


/*!
  \brief Returns true if this monomer has at least one modification, false
otherwise.
  */
bool
Monomer::isModified() const
{
  if(mpa_modifList && mpa_modifList->size())
    return true;

  return false;
}

/*!
  \brief Returns the count of modifications by name \a modif_name in this
monomer.
  */
int
Monomer::modifCount(const QString &modif_name)
{
  int count = 0;

  if(!mpa_modifList)
    return 0;

  for(int iter = 0; iter < mpa_modifList->size(); ++iter)
    {
      Modif *modif = mpa_modifList->at(iter);

      if(modif_name == modif->name())
        ++count;
    }

  return count;
}


/*!
  \brief Returns true if this monomer is valid, false otherwise.

  Validation of the monomer occurs if:

  \list
  \li Its name is not empty
  \li Its code is not empty and its syntax is correct
  \li Its formula validates
  \li Its modifications (if any) validate
  \endlist

  \sa checkCodeSyntax()
   */
bool
Monomer::validate()
{
  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  if(m_name.isEmpty())
    return false;

  if(!checkCodeSyntax())
    return false;

  if(!Formula::validate(isotopic_data_csp))
    return false;

  if(mpa_modifList)
    {
      for(int iter = 0; iter < mpa_modifList->size(); ++iter)
        {
          if(!mpa_modifList->at(iter)->validate())
            return false;
        }
    }

  return true;
}


/*!
  \brief Calculates this monomer's monoisotopic and avg masses.

    The calculation is performed by computing the masses
    of this monomer's formula.

    If \a monomer_chemical_entities & MONOMER_CHEMENT_MODIF is true, then the
masses are updated to account for the mass of modifications.

  Returns true if the calculations were successful, false otherwise.

  \sa Formula::accountMasses()
  */
bool
Monomer::calculateMasses(int monomer_chemical_entities)
{
  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  m_mono = 0;
  m_avg  = 0;

  // qDebug() << "Accounting masses for monomer: " << m_name;

  if(!Formula::accountMasses(isotopic_data_csp, &m_mono, &m_avg))
    return false;

  if(monomer_chemical_entities & MONOMER_CHEMENT_MODIF)
    {
      if(mpa_modifList)
        {
          for(int iter = 0; iter < mpa_modifList->size(); ++iter)
            {
              Modif *modif = mpa_modifList->at(iter);

              modif->accountMasses(&m_mono, &m_avg);
            }
        }
    }

  return true;
}

/*
  \brief Calculates a Formula representing this monomer .

  The calculated formula accounts for this monomer's formula and for its
modifications formulas if any and if \a (monomer_chemical_entities &
MONOMER_CHEMENT_MODIF) is true.

  This monomer's formula must validate using Modif::validate.

  Returns the Formula.

  \sa Modif::accountFormula()
  */
Formula
Monomer::calculateFormula(int monomer_chemical_entities) const
{
  // We want to return the formula of this monomer that accounts for its
  // modifications if so is requested.

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // qDebug() << "Calculating formula for monomer: " << m_name
  //<< "with chemical entities:" << monomer_chemical_entities;

  Formula formula(m_formula);

  // qDebug() << "Basic formula:" << formula.toString();

  // The Formula object above is only a text string. We need to convert that
  // into the symbol/count pair by validating it with true true params.
  // The formula is asked to validate with storage of the found symbol/count
  // pairs and with resetting of the previous contents of the symbol/count map.

  // We need to seed the symbol/count pairs because the following call
  // (accountFormula()) will update the pairs' values.
  if(!formula.validate(isotopic_data_csp, true, true))
    {
      qDebug() << "Formula:" << formula.toString() << "failed to validate.";
      return Formula();
    }

  if(monomer_chemical_entities & MONOMER_CHEMENT_MODIF)
    {
      if(mpa_modifList)
        {
          for(int iter = 0; iter < mpa_modifList->size(); ++iter)
            {
              // qDebug() << "Before accounting modif:"
              //<< mpa_modifList->at(iter)->name()
              //<< "with formula:" << mpa_modifList->at(iter)->formula()
              //<< "the global formula:" << formula.toString();

              formula.accountFormula(mpa_modifList->at(iter)->formula(),
                                     mcsp_polChemDef->getIsotopicDataCstSPtr());

              // qDebug() << "After accounting modif:"
              //<< mpa_modifList->at(iter)->name()
              //<< "with formula:" << mpa_modifList->at(iter)->formula()
              //<< "the new global formula:" << formula.toString();
            }
        }
    }

  // qDebug() << "Returning the formula as string:" << formula.toString();

  return formula;
}

/*!
  \brief Accounts this monomer's masses in \a mono and \a avg.

  The \a mono and \a avg masses are updated only if they are non-nullptr.

  This monomer's masses are stored in member data \c m_mono and \c m_avg. This
monomer's masses multiplied by \a times before setting the values to \a mono
and \a avg.

  Returns true.
  */
bool
Monomer::accountMasses(double *mono, double *avg, int times) const
{
  if(mono)
    *mono += m_mono * times;

  if(avg)
    *avg += m_avg * times;

  return true;
}


/*!
  \brief Accounts this monomer's masses in \a ponderable's mono and avg
masses.

  This monomer's masses are stored in member data \c m_mono and \c m_avg. These
 masses are multiplied by \a times before setting the values to \a
ponderable. \a ponderable cannot be nullptr.

  Returns true.
  */
bool
Monomer::accountMasses(Ponderable *ponderable, int times) const
{
  Q_ASSERT(ponderable != nullptr);

  ponderable->rmono() += m_mono * times;
  ponderable->ravg() += m_avg * times;

  return true;
}


/*!
  \brief Parses the monomer XML \a element specifically for \a version.

  Parses the monomer XML element passed as argument and for each
  encountered data will set the data to this monomer (this is
  called XML rendering).The parsing is delegated to a function that is specific
for for \a version of the polymer chemistry definition.

  The XML element is found in the polymer chemistry definition and has the
following form:

  \code
      <monomers>
        <mnm>
          <name>Glycine</name>
          <code>G</code>
          <formula>C2H3N1O1</formula>
        </mnm>
        <mnm>
          <name>Alanine</name>
          <code>A</code>
          <formula>C3H5N1O1</formula>
        </mnm>
  \endcode

  After setting all the data, this monomer calculates it masses and
  validates itself. If any of these steps fails, the error is reported
  by returning false.

  \sa validate()
  */
bool
Monomer::renderXmlMnmElement(const QDomElement &element, int version)
{

  if(version == 1)
    {
      // NoOp
      version = 1;
    }

  QDomElement child;

  /* In a polymer chemistry definition, the xml node we are in is
   * structured this way:
   *
   * <mnm>
   *   <name>Glycine</name>
   *   <code>G</code>
   *   <formula>C2H3N1O1</formula>
   * </mnm>
   *
   * And the element parameter points to the
   *
   * <mnm> element tag:
   *  ^
   *  |
   *  +----- here we are right now.
   *
   * Which means that element.tagName() == "mnm" and that we'll have
   * to go one step down to the first child of the current node in
   * order to get to the <name> element.
   *
   */

  if(element.tagName() != "mnm")
    return false;

  child = element.firstChildElement("name");

  if(child.isNull())
    return false;

  m_name = child.text();

  child = child.nextSiblingElement("code");

  if(child.isNull())
    return false;

  m_code = child.text();

  child = child.nextSiblingElement("formula");

  if(child.isNull())
    return false;

  if(!Formula::renderXmlFormulaElement(child))
    return false;

  if(!this->calculateMasses(MONOMER_CHEMENT_NONE))
    {
      qDebug() << "Failed to calculate masses for monomer" << m_name;

      return false;
    }

  if(!validate())
    return false;

  return true;
}


/*!
  \brief Formats this monomer's data as a string suitable to be used as an XML
element in the polymer chemistry definition.

  The typical monomer element that is generated in this function looks like
  this:

  \code
      <monomers>
      <mnm>
        <name>Glycine</name>
        <code>G</code>
        <formula>C2H3N1O1</formula>
      </mnm>
  \endcode

  The formatting of the XML element takes into account \a offset and \a
indent by prepending the string with \a offset * \a indent character substring.

  \a indent defaults to two spaces.

  Returns a dynamically allocated string that needs to be freed after
  use.
  */
QString *
Monomer::formatXmlMnmElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString *string = new QString();

  // Prepare the lead.
  newOffset = offset;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  *string += QString("%1<mnm>\n").arg(lead);

  // Prepare the lead.
  ++newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  // Continue with indented elements.

  *string += QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  *string += QString("%1<code>%2</code>\n").arg(lead).arg(m_code);

  *string += QString("%1<formula>%2</formula>\n").arg(lead).arg(m_formula);

  // Prepare the lead for the closing element.
  --newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  *string += QString("%1</mnm>\n").arg(lead);

  return string;
}


/*!
  \brief Parses into this monomer the XML monomer \a element passed as argument.

  The XML element comes from a polymer sequence file, where the monomer is
singled out (not in a sequence string) because it might be modified.

  \a version indicates the format version of this XML \a element.

  As soon as the monomer code is known, while parsing the \a element, the
corresponding monomer is searched in the list of monomers in the member polymer
chemistry definition (\c mcsp_polChemDef). Then, the found monomer is copied
into \c this monomer so that both monomers are identical, effectively
initializing this monomer to the monomer described by the \a element.

  If the \a element contains one or more \c mdf modifications, these
modifications are allocated as \l{Modif}'s and validated. If these
modifications validate successfully, they are appended to this monomer's list
of modifications.

  Returns true if initializationt of his monomer with the contents of \a
element succeeded, false otherwise.

  \sa formatXmlMonomerElement(int offset, const QString &indent)
  */
bool
Monomer::renderXmlMonomerElement(const QDomElement &element, int version)
{
  if(version == 1)
    {
      // no-op
      version = 1;
    }

  QDomElement child;
  QDomElement indentedChild;

  if(element.tagName() != "monomer")
    return false;

  child = element.firstChildElement("code");

  if(child.isNull())
    return false;

  QString code = child.text();

  Monomer other(mcsp_polChemDef, "NOT_SET");
  const QList<Monomer *> &refList = mcsp_polChemDef->monomerList();

  int index = -1;
  index     = isCodeInList(code, refList, &other);
  if(index == -1)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  *this = other;

  // Sanity check
  if(m_code != code)
    qFatal("Programming error. Both codes should be identical.");

  // And now we have to manage the prop objects.
  child = child.nextSiblingElement();

  while(!child.isNull())
    {
      if(child.tagName() != "mdf")
        return false;

      // Allocate the modification that will be set to the monomer
      // element.

      Modif *modif = new Modif(mcsp_polChemDef, "NOT_SET", "NOT_SET");

      if(!modif->renderXmlMdfElement(child, version))
        {
          delete modif;
          return false;
        }

      if(!modif->calculateMasses())
        {
          qDebug() << __FILE__ << __LINE__
                   << "Failed to calculate masses for modification"
                   << modif->name();

          delete modif;
          return false;
        }

      // The validation will take care of checking that the <targets>
      // element did have correct text inside.

      if(!modif->validate())
        {
          delete modif;
          return false;
        }

      // OK, at this point we can add the new modif to the list of
      // modifs.
      if(!mpa_modifList)
        mpa_modifList = new QList<Modif *>();

      mpa_modifList->append(modif);

      child = child.nextSiblingElement();
    }

  return true;
}


/*!
  \brief Formats a string suitable to be used as an XML element in a
  polymer sequence file.

  The typical monomer element that is generated in this function looks like
this:

  \code
  <monomer>
  <code>S</code>
  <prop>
  <name>MODIF</name>
  <data>Phosphorylation</data>
  </prop>
  <prop>
  <name>COMMENT</name>
  <data>Phosphorylation is only partial</data>
  </prop>
  </monomer>
  \endcode

  The formatting of the XML element takes into account \a offset and \a
indent by prepending the string with \a offset * \a indent character substring.

  \a indent defaults to two spaces.

  Returns a dynamically allocated string that needs to be freed after
  use.
  */
QString *
Monomer::formatXmlMonomerElement(int offset, const QString &indent) const
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString *string = new QString();

  // Prepare the lead.
  newOffset = offset;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  *string += QString("%1<monomer>\n").arg(lead);

  // Prepare the lead.
  ++newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  // Continue with indented elements.

  *string += QString("%1<code>%2</code>\n").arg(lead).arg(m_code);

  // The monomer may have any number of modif objects, which we have
  // to document here.
  if(mpa_modifList && mpa_modifList->size())
    {
      for(iter = 0; iter < mpa_modifList->size(); ++iter)
        {
          Modif *modif = mpa_modifList->at(iter);

          QString *modifString = modif->formatXmlMdfElement(newOffset);
          Q_ASSERT(modifString);

          *string += *modifString;
          delete modifString;
        }
    }

  // Prepare the lead for the closing element.
  --newOffset;
  lead.clear();
  iter = 0;
  while(iter < newOffset)
    {
      lead += indent;
      ++iter;
    }

  *string += QString("%1</monomer>\n").arg(lead);

  return string;
}


/*!
  \brief Outputs a string representing this monomer using qDebug().
*/
void
Monomer::debugPutStdErr() const
{
  qDebug() << m_name << m_code << m_formula;
}

} // namespace libXpertMass

} // namespace MsXpS
