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


namespace msXpSmassXpert
{

//! 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<msXpSlibmass::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<msXpSlibmass::Atom *> &refList = mp_polChemDef->atomList();

  m_mono = 0;
  m_avg  = 0;

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

  if(how & 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 msXpSmassXpert
