/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.


   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


/////////////////////// Local includes
#include "monomer.hpp"
#include "polChemDef.hpp"
#include "crossLink.hpp"


namespace massXpert
{

  //! Constructs a monomer.
  /*!

    \param polChemDef Polymer chemistry definition. Cannot be 0.

    \param name Name. Cannot be empty.

    \param code Code.

    \param formula Formula.
  */
  Monomer::Monomer(const PolChemDef *polChemDef, QString name,
                   QString code, QString formula)
    : PolChemDefEntity(polChemDef, name),
      Formula(formula), Ponderable(0, 0),
      m_code(code)
  {
    mpa_modifList = 0;
  }


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


  //! Creates a new monomer.
  /*! The newly created monomer is initialized using \c this. The
    initialization is shallow, as the property list is not used in the
    initialization.

    \return The newly created monomer, which should be deleted when no
    longer in use.
  */
  Monomer *
  Monomer::clone() const
  {
    Monomer *other = new Monomer(mp_polChemDef,
                                 m_name,
                                 m_code,
                                 m_formula);
    Ponderable::clone(other);

    if (mpa_modifList)
      {
        other->mpa_modifList = new QList<Modif *>();

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

            other->mpa_modifList->append(modif);
          }
      }

    return other;
  }


  //! Modifies \p other to be similar(shallow cloning) to \p this.
  /*! The copying is shallow, as the data in the property list are not
    copied.

    \param other monomer.
  */
  void
  Monomer::clone(Monomer *other) const
  {
    Q_ASSERT(other);

    if (other == this)
      return;

    PolChemDefEntity::clone(other);
    Formula::clone(other);
    Ponderable::clone(other);

    other->m_code = m_code;

    if (other->mpa_modifList)
      qDeleteAll(*other->mpa_modifList);

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

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

            other->mpa_modifList->append(modif);
          }
      }
    else
      {
        delete(other->mpa_modifList);

        other->mpa_modifList = 0;
      }
  }


  //! Modifies \p this to be similar(shallow molding) to \p other.
  /*! The copying is shallow, as the data in the property list are not
    copied.

    \param other monomer to be used as a mold.
  */
  void
  Monomer::mold(const Monomer &other)
  {
    if (&other == this)
      return;

    PolChemDefEntity::mold(other);
    Formula::mold(other);
    Ponderable::mold(other);

    m_code = other.m_code;

    if (mpa_modifList)
      qDeleteAll(*mpa_modifList);

    if (other.mpa_modifList)
      {
        if(!mpa_modifList)
          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 = 0;
      }
  }


  //! Sets the code.
  /*! \param str new code.
   */
  void
  Monomer::setCode(const QString &str)
  {
    m_code = str;
  }


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



  //! Tests equality.
  /*! This is a shallow comparison, as properties are not involved in
    the comparison.

    \param other monomer to compare with \p this.

    \return true if monomers are similar, false otherwise.
  */
  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;
  }


  //! Tests inequality.
  /*! This is a shallow comparison, as properties are not involved in
    the comparison.

    \param other monomer to compare with \p this.

    \return true if monomers differ, false otherwise.
  */
  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;
  }


  //! Checks the code syntax.
  /*!
    \attention This is not a validation, as this syntax checking only
    makes sure that the code is made of one uppercase character at first
    position and of lowercase characters following it.

    \return true if successful, 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 = mp_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;
  }


  //! Searches \p this monomer in a list according to code.
  /*! The list of reference monomers belongs to the monomer.

    \return the index of the found monomer or -1 if none is found or if
    the code is empty.
  */
  int
  Monomer::isCodeKnown() const
  {
    const QList<Monomer*> &refList = mp_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;
  }


  //! Searches a monomer in a list according to the \p str code.
  /*! Searches for an monomer instance having a code identical to
    argument \p str in the monomer list \p refList. If such monomer is
    found, and if \p other is non-0, \p this monomer's data are copied
    into \p other.

    \param str code.

    \param refList list of reference monomers.

    \param other monomer in which to copy the data from the found
    monomer. Defaults to 0, in which case no copying occurs.

    \return the int index of the found monomer or -1 if no monomer instance is
    found or if \p str is empty.
  */
  int
  Monomer::isCodeInList(const QString &str,
                        const QList<Monomer*> &refList,
                        Monomer *other)
  {
    Monomer *monomer = 0;

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

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

        if(monomer->m_code == str)
          {
            if (other)
              monomer->clone(other);

            return iter;
          }
      }

    return -1;
  }


  //! Searches \p this monomer in a list according to name.
  /*! The list of reference monomers belongs to the monomer.

    \return the index of the found monomer or -1 if none is found or if
    the name is empty.
  */
  int
  Monomer::isNameKnown() const
  {
    const QList<Monomer*> &refList = mp_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;
  }


  //! Searches a monomer in a list according to the \p str name.
  /*! Searches for an monomer instance having a name identical to
    argument \p str in the monomer list \p refList. If such monomer is
    found, and if \p other is non-0, \p this monomer's data are copied
    into \p other.

    \param str name.

    \param refList list of reference monomers.

    \param other monomer in which to copy the data from the found
    monomer. Defaults to 0, in which case no copying occurs.

    \return the int index of the found monomer or -1 if no monomer instance is
    found or if \p str is empty.
  */
  int
  Monomer::isNameInList(const QString &str,
                        const QList<Monomer*> &refList,
                        Monomer *other)
  {
    Monomer *monomer = 0;

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

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

        if(monomer->m_name == str)
          {
            if (other != 0)
              monomer->clone(other);

            return iter;
          }
      }

    return -1;
  }


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


  QString
  Monomer::formula() const
  {
    return Formula::text();
  }


  bool
  Monomer::isModifTarget(const Modif &modif) const
  {
    // Pure convenience function.

    return modif.hasMonomerTarget(m_code);
  }



  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 accomodate 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;
  }


  //! Unmodifies the monomer for the specific modif.
  /*! \param modif modification to be removed.

    \return always 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("%s@%d", __FILE__, __LINE__);

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

        mpa_modifList = 0;
      }

    return true;
  }


  //! Unmodifies the monomer totally(all its modifications).
  /*!

    \return always true.
  */
  bool
  Monomer::unmodify()
  {
    if (mpa_modifList)
      {
        qDeleteAll(*mpa_modifList);

        delete mpa_modifList;
      }

    return true;
  }


  bool
  Monomer::isModified() const
  {
    if (mpa_modifList && mpa_modifList->size())
      return true;

    return false;
  }


  int
  Monomer::modifCount(const QString& 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(name == modif->name())
          ++count;
      }

    return count;
  }



  //! Validates \p this monomer.
  /*! Validation is performed by ensuring that all member data have sane
    values. Note that the masses(monoisotopic and average) are not
    concerned by the validation process because the presence of at least
    one isotope in the isotope list essentially makes sure that masses
    are available(see below).

    \li the name cannot be empty;

    \li the code cannot be empty and must pass test of
    checkCodeSyntax(int max_length);

    \li the formula must validate.

    \return true if the monomer was successfully validated, false otherwise.

    \sa checkCodeSyntax(int max_length).
  */
  bool
  Monomer::validate()
  {
    const QList<Atom *> &refList = mp_polChemDef->atomList();

    if (m_name.isEmpty())
      return false;

    if (!checkCodeSyntax())
      return false;

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

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

    return true;
  }


  //! Calculates the masses(mono and avg).
  /*! The calculation is performed by computing the mono and avg masses
    of the formula.

    \return true if calculations were successful, false otherwise.

    \sa Formula::splitParts()
  */
  bool
  Monomer::calculateMasses(int how)
  {
    const QList<Atom *> &refList = mp_polChemDef->atomList();

    m_mono = 0;
    m_avg = 0;

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

    if (how & MXT_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;
  }


  //! Increments the masses in the arguments.
  /*! The values pointed to by the first two arguments are updated by
    incrementation using the masses(monoisotopic and average)
    compounded by the \p times argument.

    For example, if \p times is 2 ; \p *mono is 100 and \p *avg is 101 ;
    \p this monomer's monoisotopic mass is 200 and average mass is 202,
    then the computation leads to \p *mono = 100 + 2 * 200 and \p *avg =
    101 + 2 * 202.

    \param mono monoisotopic mass to update. Defaults to 0, in which
    case this mass is not updated.

    \param avg average mass to update. Defaults to 0, in which case this
    mass is not updated.

    \param times times that the increment should be performed. Defaults
    to 1.

    \return always 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;
  }



  //! Increments the masses in the argument.
  /*! The values in the argument are updated by incrementation using the
    masses(monoisotopic and average) compounded by the \p times
    argument.

    For example, if \p times is 2 ; \p *mono is 100 and \p *avg is 101 ;
    \p this monomer's monoisotopic mass is 200 and average mass is 202,
    then the computation leads to \p *mono = 100 + 2 * 200 and \p *avg =
    101 + 2 * 202.

    \param ponderable The ponderable of which the masses need to be updated.

    \param times times that the increment should be performed. Defaults
    to 1.

    \return always true.
  */
  bool
  Monomer::accountMasses(Ponderable *ponderable, int times) const
  {
    Q_ASSERT(ponderable);

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

    return true;
  }



  //! Parses a monomer XML element from a polymer chemistry definition.
  /*! Parses the monomer XML element passed as argument and for each
    encountered data will set the data to \p this monomer(this is
    called XML rendering).

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

    \param element XML element to be parsed and rendered.

    \return true if parsing, calculation of the masses and validation
    were successful, false otherwise.

    \sa validate().
    \sa formatXmlMnmElement(int offset, const QString &indent).
  */
  bool
  Monomer::renderXmlMnmElement(const QDomElement &element, int version)
  {
    // For the time being the version is not necessary here. As of
    // version up to 2, the current function works ok.

    if (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 (!calculateMasses())
      {
        qDebug() << __FILE__ << __LINE__
                 << "Failed to calculate masses for monomer"
                 << m_name;

        return false;
      }

    if (!validate())
      return false;


    return true;
  }


  //! Formats a string suitable to use as an XML element.
  /*! Formats a string suitable to be used as an XML element in a
    polymer chemistry definition file. The typical monomer element that
    is generated in this function looks like this:

    \verbatim
    <mnm>
    <name>Valine</name>
    <code>V</code>
    <formula>C5H9NO</formula>
    </mnm>
    \endverbatim

    \param offset times the \p indent string must be used as a lead in the
    formatting of elements.

    \param indent string used to create the leading space that is placed
    at the beginning of indented XML elements inside the XML
    element. Defaults to two spaces(QString(" ")).

    \return a dynamically allocated string that needs to be freed after
    use.

    \sa renderXmlMnmElement(const QDomElement &element)
  */
  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;
  }


  //! Parses a monomer XML element from a polymer sequence.
  /*! Parses the monomer XML element passed as argument. As soon as the
    monomer code is known, the corresponding monomer is searched in the
    list of reference monomers passed as argument. \p this monomer is
    then molded according to the found monomer, so that \p this is
    identical to the reference monomer.

    If properties are available for the monomer element, these are
    parsed and the resulting property objects are set to \p this
    monomer.

    \param element XML element to be parsed and rendered.

    \return true if parsing was successful, false otherwise.

    \sa formatXmlMonomerElement(int offset, const QString &indent).
  */
  bool
  Monomer::renderXmlMonomerElement(const QDomElement &element, int version)
  {
    // We have to check for the version since
    // POL_SEQ_FILE_FORMAT_VERSION 5

    if (version >= 5)
      return renderXmlMonomerElementV2(element, version);

    QDomElement child;
    QDomElement indentedChild;


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

    child = element.firstChildElement("code");

    if (child.isNull())
      return false;

    m_code = child.text();

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

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

    // Mold this monomer.
    mold(other);

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

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

        indentedChild = child.firstChildElement();
        if(indentedChild.tagName() != "name")
          return false;

        Prop *prop =
          static_cast<Prop *>(propAllocator(indentedChild.text(),
                                            mp_polChemDef));

        if(prop)
          {
            if (!prop->renderXmlElement(child, version))
              {
                delete prop;
                return false;
              }

            // At this point, the modification lies inside the prop
            // object.
            Modif *modif =
              new Modif(*static_cast<Modif *>(prop->data()));

            // 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);

            delete prop;
          }
        else
          return false;

        child = child.nextSiblingElement();
      }

    return true;
  }


  bool
  Monomer::renderXmlMonomerElementV2(const QDomElement &element, int version)
  {
    QDomElement child;
    QDomElement indentedChild;


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

    child = element.firstChildElement("code");

    if (child.isNull())
      return false;

    m_code = child.text();

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

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

    // Mold this monomer.
    mold(other);

    // 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(mp_polChemDef, "NOT_SET", "NOT_SET");

        if(!modif->renderXmlMdfElementV2(child))
          {
            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;
  }


  //! Formats a string suitable to use as an XML element.
  /*! 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:

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

    \param offset times the \p indent string must be used as a lead in the
    formatting of elements.

    \param indent string used to create the leading space that is placed
    at the beginning of indented XML elements inside the XML
    element. Defaults to two spaces(QString(" ")).

    \return a dynamically allocated string that needs to be freed after
    use.

    \sa renderXmlMonomerElement(const QDomElement &element)
  */
  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;
  }


  //! Creates a string
  /*!

   */
  void
  Monomer::debugPutStdErr() const
  {
    qDebug() << __FILE__ << __LINE__
             << m_name << m_code
             << m_formula;
  }

} // namespace massXpert
