/* 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
 */


/////////////////////// Qt includes
#include <QDebug>


/////////////////////// Local includes
#include "PolChemDef.hpp"
#include "Monomer.hpp"
#include "Polymer.hpp"
#include "Modification_default_vignette.hpp"
#include "Cross_link_default_vignette.hpp"


namespace msxps
{

	namespace massxpert
	{



const int POL_CHEM_DEF_FILE_FORMAT_VERSION = 5;

//! Constructs a polymer chemistry definition.
PolChemDef::PolChemDef()
{
  //   qDebug() << __FILE__ << __LINE__
  // 	    << "PolChemDef():" << this;

  // We have to set the m_codeLength member to 1. It would make no
  // sense to have monomers described with codes of 0-character
  // length.
  m_codeLength = 1;
  m_refCount   = 0;

  return;
}


//! Constructs a polymer chemistry definition.
/*! The \p spec argument provides data for the initialization of the
  polymer chemistry definition.

  \param spec polymer chemistry definition specification.
  */
PolChemDef::PolChemDef(const PolChemDefSpec &spec)
{
  m_name = spec.name();

  //   qDebug() << __FILE__ << __LINE__
  // 	    << "PolChemDef():" << m_name << this;

  m_filePath = spec.filePath();

  m_codeLength = 1;
  m_refCount   = 0;
  return;
}

//! Destroys the polymer chemistry definition
PolChemDef::~PolChemDef()
{
  //   qDebug() << __FILE__ << __LINE__
  // 	    << "Entering ~PolChemDef():" << m_name << this;

  // We have to free all the allocated stuff in the QList members !
  while(!m_atomList.isEmpty())
    delete m_atomList.takeFirst();

  while(!m_monomerList.isEmpty())
    delete m_monomerList.takeFirst();

  while(!m_modifList.isEmpty())
    delete m_modifList.takeFirst();

  while(!m_crossLinkerList.isEmpty())
    delete m_crossLinkerList.takeFirst();

  while(!m_cleaveSpecList.isEmpty())
    delete m_cleaveSpecList.takeFirst();

  while(!m_fragSpecList.isEmpty())
    delete m_fragSpecList.takeFirst();

  while(!m_monomerSpecList.isEmpty())
    delete m_monomerSpecList.takeFirst();

  while(!m_modifSpecList.isEmpty())
    delete m_modifSpecList.takeFirst();

  //   qDebug() << __FILE__ << __LINE__
  // 	    << "Leaving ~PolChemDef():" << this;
}


//! Sets the name.
/*!
  \param str New name.
  */
void
PolChemDef::setName(const QString &str)
{
  m_name = str;
}


//! Returns the name.
/*!
  \return The name.
  */
QString
PolChemDef::name() const
{
  return m_name;
}


//! Sets the file path.
/*!
  \param str New file path.
  */
void
PolChemDef::setFilePath(const QString &str)
{
  m_filePath = str;
}


//! Returns the file path.
/*!
  \return The file path.
  */
QString
PolChemDef::filePath() const
{
  return m_filePath;
}


//! Computes the directory path.
/*!
  \return The directory path.
  */
QString
PolChemDef::dirPath() const
{
  QFileInfo fileInfo(m_filePath);
  QDir dir(fileInfo.dir());
  return dir.absolutePath();
}


//! Sets the left cap formula.
/*!
  \param formula New formula.
  */
void
PolChemDef::setLeftCap(const Formula &formula)
{
  m_leftCap = formula;
}


//! Returns the left cap formula.
/*!
  \return The formula.
  */
const Formula &
PolChemDef::leftCap() const
{
  return m_leftCap;
}


//! Sets the right cap formula.
/*!
  \param formula New formula.
  */
void
PolChemDef::setRightCap(const Formula &formula)
{
  m_rightCap = formula;
}


//! Returns the right cap formula.
/*!
  \return The formula.
  */
const Formula &
PolChemDef::rightCap() const
{
  return m_rightCap;
}


//! Sets the code length.
/*!
  \param value New code length.
  */
void
PolChemDef::setCodeLength(int value)
{
  m_codeLength = value;
}


//! Returns the code length.
/*!
  \return The code length.
  */
int
PolChemDef::codeLength() const
{
  return m_codeLength;
}


void
PolChemDef::setRepositoryList(QList<PolChemDef *> *repositoryList)
{
  Q_ASSERT(repositoryList);

  mp_repositoryList = repositoryList;
}


//! Constructs a string with all the known monomer codes.
/*! The string starts with '@' and goes on with all the monomer codes
  separated by the same '@' character. The string is ended with a '@'.

  \return always true.
  */
bool
PolChemDef::calculateDelimitedCodes()
{
  // We have to produce a QString containing all the codes from the
  // monomers known to this polymer chemistry definition.

  m_delimitedCodes.clear();
  m_delimitedCodes.append('@');

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

      m_delimitedCodes.append(monomer->code());
      m_delimitedCodes.append('@');
    }

  //  Close the string with a delim char:
  m_delimitedCodes.append('@');

  return true;
}


//! Returns the delimited codes string.
/*! If the string is found to be empty(that is the string has never
  been created), calculateDelimitedCodes() is called first.

  \return The delimited codes string.
  */
const QString &
PolChemDef::delimitedCodes()
{
  if(m_delimitedCodes.isEmpty())
    calculateDelimitedCodes();

  return m_delimitedCodes;
}


//! Sets the ionization rule.
/*!
  \param ionizeRule New ionization rule.
  */
void
PolChemDef::setIonizeRule(const IonizeRule &ionizeRule)
{
  m_ionizeRule = ionizeRule;
}


//! Returns the ionization rule.
/*! \return The ionization rule.
 */
const IonizeRule &
PolChemDef::ionizeRule() const
{
  return m_ionizeRule;
}


//! Returns the ionization rule.
/*! \return The ionization rule.
 */
IonizeRule *
PolChemDef::ionizeRulePtr()
{
  return &m_ionizeRule;
}


//! Returns the list of atoms.
/*!
  \return The list of atoms.
  */
const QList<Atom *> &
PolChemDef::atomList() const
{
  return m_atomList;
}


//! Returns the list of atoms.
/*!
  \return The list of atoms.
  */
QList<Atom *> *
PolChemDef::atomListPtr()
{
  return &m_atomList;
}


//! Returns the list of monomers.
/*!
  \return The list of monomers.
  */
const QList<Monomer *> &
PolChemDef::monomerList() const
{
  return m_monomerList;
}


//! Returns the list of monomers.
/*!
  \return The list of monomers.
  */
QList<Monomer *> *
PolChemDef::monomerListPtr()
{
  return &m_monomerList;
}


//! Returns the list of modifications.
/*!
  \return The list of modifications.
  */
const QList<Modif *> &
PolChemDef::modifList() const
{
  return m_modifList;
}


//! Returns the list of modifications.
/*!
  \return The list of modifications.
  */
QList<Modif *> *
PolChemDef::modifListPtr()
{
  return &m_modifList;
}


//! Returns the list of cross-linkers.
/*!
  \return The list of cross-linkers.
  */
const QList<CrossLinker *> &
PolChemDef::crossLinkerList() const
{
  return m_crossLinkerList;
}


//! Returns the list of cross-linkers.
/*!
  \return The list of cross-linkers.
  */
QList<CrossLinker *> *
PolChemDef::crossLinkerListPtr()
{
  return &m_crossLinkerList;
}


//! Returns the list of cleavage specifications.
/*!
  \return The list of cleavage specifications.
  */
const QList<CleaveSpec *> &
PolChemDef::cleaveSpecList() const
{
  return m_cleaveSpecList;
}


//! Returns the list of cleavage specifications.
/*!
  \return The list of cleavage specifications.
  */
QList<CleaveSpec *> *
PolChemDef::cleaveSpecListPtr()
{
  return &m_cleaveSpecList;
}


//! Returns the list of fragmentation specifications.
/*!
  \return The list of fragmentation specifications.
  */
const QList<FragSpec *> &
PolChemDef::fragSpecList() const
{
  return m_fragSpecList;
}


//! Returns the list of fragmentation specifications.
/*!
  \return The list of fragmentation specifications.
  */
QList<FragSpec *> *
PolChemDef::fragSpecListPtr()
{
  return &m_fragSpecList;
}


//! Returns the modification object corresponding to name.
/*!  \param name name of the modification to be searched for. Can be
  empty, in which case the function returns false.

  \param modification if non 0, pointer in which to store the address
  of the found modification. Set to point to the found modification,
  otherwise left unchanged. Can be 0 in which case nothing happens.

  \return true if the modif was found, false otherwise.
  */
bool
PolChemDef::modif(const QString &name, Modif *modification) const
{
  if(name.isEmpty())
    return false;

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

      if(modif->name() == name)
        {
          if(modification)
            {
              modif->clone(modification);

              return true;
            }

          return true;
        }
    }

  return false;
}


bool
PolChemDef::crossLinker(const QString &name, CrossLinker *crossLinker) const
{
  if(name.isEmpty())
    return false;

  for(int iter = 0; iter < m_crossLinkerList.size(); ++iter)
    {
      CrossLinker *localCrossLinker = m_crossLinkerList.at(iter);

      if(localCrossLinker->name() == name)
        {
          if(crossLinker)
            {
              localCrossLinker->clone(crossLinker);

              return true;
            }

          return true;
        }
    }

  return false;
}


//! Returns the hash of vignette renderers.
/*!
  \return The hash of vignette renderers.
  */
QHash<QString, ChemEntVignetteRenderer *> *
PolChemDef::chemEntVignetteRendererHash()
{
  return &m_chemEntVignetteRendererHash;
}


//! Searches a chemical entity vignette renderer associated to \p key.
/*! Searches in the chemical entity vignette renderers' hash for a
  vignette renderer having a key \p key.

  \param key Key with which to look for a vignette renderer.

  \return The vignette renderer associated to \p key.
  */
ChemEntVignetteRenderer *
PolChemDef::chemEntVignetteRenderer(const QString &key)
{
  return m_chemEntVignetteRendererHash.value(key);
}


//! Creates a chemical entity vignette renderer.
/*!  The returned renderer must either be set as a shared renderer by
  a ChemEntVignette instance(which takes care of adding a
  reference count to it) or must be dealt with properly so as to avoid
  leaks.

  \param filePath File path to the graphics file.

  \param parent Parent object.

  \return The newly allocated chemical entity vignette renderer.

  \sa newMonomerVignetteRenderer(const QString &code, QObject *parent).
  \sa newModifVignetteRenderer(const QString &name, QObject *parent).
  */
ChemEntVignetteRenderer *
PolChemDef::newChemEntVignetteRenderer(const QString &filePath, QObject *parent)
{
  return new ChemEntVignetteRenderer(filePath, parent);
}


//! Creates a monomer vignette renderer.
/*!

  \param str Code of the monomer.

  \param parent Parent object.

  \return The newly allocated monomer vignette renderer or 0 upon
  failure.

  \sa newModifVignetteRenderer(const QString &str, QObject *parent).
  */
ChemEntVignetteRenderer *
PolChemDef::newMonomerVignetteRenderer(const QString &str, QObject *parent)
{
  MonomerSpec *monomerSpec = 0;
  bool found               = false;

  for(int iter = 0; iter < m_monomerSpecList.size(); ++iter)
    {
      monomerSpec = m_monomerSpecList.at(iter);

      if(monomerSpec->code() == str)
        {
          found = true;
          break;
        }
    }

  if(!found)
    return (0);

  QString filePath = dirPath() + "/" + monomerSpec->vector();

  ChemEntVignetteRenderer *chemEntVignetteRenderer =
    new ChemEntVignetteRenderer(filePath, parent);

  if(!chemEntVignetteRenderer->isValid())
    {
      delete chemEntVignetteRenderer;
      return 0;
    }

  m_chemEntVignetteRendererHash.insert(monomerSpec->code(),
                                       chemEntVignetteRenderer);

  chemEntVignetteRenderer->setHash(&m_chemEntVignetteRendererHash);

  return chemEntVignetteRenderer;
}


//! Creates a modification vignette renderer.
/*!

  \param str Name.

  \param parent Parent object.

  \return The newly allocated modification vignette renderer or 0 upon
  failure.

  \sa newMonomerVignetteRenderer(const QString &str, QObject *parent).
  */
ChemEntVignetteRenderer *
PolChemDef::newModifVignetteRenderer(const QString &modifName, QObject *parent)
{
  ModifSpec *modifSpec = 0;
  bool found           = false;

  for(int iter = 0; iter < m_modifSpecList.size(); ++iter)
    {
      modifSpec = m_modifSpecList.at(iter);

      if(modifSpec->name() == modifName)
        {
          found = true;
          break;
        }
    }

  // If the modification name modifName is for a modification that has no
  // vignette associated to it in modification_dictionary, then use
  // the default one(if there is one in the directory corresponding
  // to current polymer chemistry definition). If there is not any,
  // then use the one that is defined in the
  // Modification_default_vignette.hpp file included in this source
  // file.

  QString filePath;

  if(!found)
    filePath = dirPath() + "/default-modif-vignette.svg";
  else
    filePath = dirPath() + "/" + modifSpec->vector();

  ChemEntVignetteRenderer *chemEntVignetteRenderer = 0;

  if(!QFile(filePath).exists())
    {
      // None of the two files exists. Try to construct the renderer
      // using the svg vignette declared in
      // Modification_default_vignette.hpp in the form of QByteArray
      // defaultModifVignette.
      chemEntVignetteRenderer =
        new ChemEntVignetteRenderer(defaultModifVignette, parent);
    }
  else
    {
      chemEntVignetteRenderer = new ChemEntVignetteRenderer(filePath, parent);
    }

  if(!chemEntVignetteRenderer->isValid())
    {
      delete chemEntVignetteRenderer;

      return 0;
    }

  m_chemEntVignetteRendererHash.insert(modifName, chemEntVignetteRenderer);

  chemEntVignetteRenderer->setHash(&m_chemEntVignetteRendererHash);

  return chemEntVignetteRenderer;
}


ChemEntVignetteRenderer *
PolChemDef::newCrossLinkerVignetteRenderer(const QString &name, QObject *parent)
{
  CrossLinkerSpec *crossLinkerSpec = 0;
  bool found                       = false;

  for(int iter = 0; iter < m_crossLinkerSpecList.size(); ++iter)
    {
      crossLinkerSpec = m_crossLinkerSpecList.at(iter);

      if(crossLinkerSpec->name() == name)
        {
          found = true;
          break;
        }
    }

  // If the crossLinker name is for a crossLink that has no
  // vignette associated to it in cross_linker_dictionary, then use
  // the default one(if there is one in the directory corresponding
  // to current polymer chemistry definition). If there is not any,
  // then use the one that is defined in the
  // Modification_default_vignette.hpp file included in this source
  // file.

  QString filePath;

  if(!found)
    filePath = dirPath() + "/default-cross-link-vignette.svg";
  else
    filePath = dirPath() + "/" + crossLinkerSpec->vector();

  ChemEntVignetteRenderer *chemEntVignetteRenderer = 0;

  if(!QFile(filePath).exists())
    {
      // None of the two files exists. Try to construct the renderer
      // using the svg vignette declared in
      // Cross_link_default_vignette.hpp in the form of QByteArray
      // defaultModifVignette.
      chemEntVignetteRenderer =
        new ChemEntVignetteRenderer(defaultCrossLinkerVignette, parent);
    }
  else
    {
      chemEntVignetteRenderer = new ChemEntVignetteRenderer(filePath, parent);
    }

  if(!chemEntVignetteRenderer->isValid())
    {
      delete chemEntVignetteRenderer;

      return 0;
    }

  m_chemEntVignetteRendererHash.insert(name, chemEntVignetteRenderer);

  chemEntVignetteRenderer->setHash(&m_chemEntVignetteRendererHash);

  return chemEntVignetteRenderer;
}


//! Sets the reference count.
/*! Sets the number of times this polymer chemistry definition is in
  use.

  \param refCount New reference count.
  */
void
PolChemDef::setRefCount(int refCount)
{
  m_refCount = refCount;
}


//! Returns the reference count.
/*! Returns the number of times that this polymer chemistry definition
  is in use.

  \return The reference count.
  */
int
PolChemDef::refCount()
{
  return m_refCount;
}


//! Increments the reference count by one unit.
void
PolChemDef::incrementRefCount()
{
  ++m_refCount;

  //   qDebug() << __FILE__ << __LINE__
  // 	    << "incrementRefCount() to:" << m_refCount << m_name << this;
}


//! Decrements the reference count by one unit.
void
PolChemDef::decrementRefCount()
{
  --m_refCount;

  //       qDebug() << __FILE__ << __LINE__
  //     	    << "decrementRefCount() to:" << m_refCount << m_name << this;

  if(!m_refCount)
    {
      // The count has come down to zero, remove this definition from
      // its repository list.

      if(mp_repositoryList)
        {
          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << "Going to remove  this from repository:"
          // 		      << m_name << this;

          int res = mp_repositoryList->removeAll(this);

          if(!res)
            qFatal(
              "Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
        }

      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "Going to 'delete  this':" << m_name << this;

      delete this;
    }
}


//! Returns the list of monomer specifications.
/*!  \return The list.
 */
QList<MonomerSpec *> *
PolChemDef::monomerSpecList()
{
  return &m_monomerSpecList;
}


//! Returns the list of modification specifications.
/*!  \return The list.
 */
QList<ModifSpec *> *
PolChemDef::modifSpecList()
{
  return &m_modifSpecList;
}


//! Returns the list of cross-link specifications.
/*!  \return The list.
 */
QList<CrossLinkerSpec *> *
PolChemDef::crossLinkerSpecList()
{
  return &m_crossLinkerSpecList;
}


//! Parses a polymer chemistry definition file.
/*! Parses the polymer chemistry definition data XML file. Updates the
  member data according to all the child elements parsed.

  \return true if parsing was successful, false otherwise.
  */
bool
PolChemDef::renderXmlPolChemDefFile()
{
  QDomDocument doc("polChemDefData");
  QDomElement element;
  QDomElement child;
  QDomElement indentedChild;

  QFile file(m_filePath);

  Atom *atom = 0;
  Monomer *monomer         = 0;
  Modif *modif             = 0;
  CrossLinker *crossLinker = 0;
  CleaveSpec *cleaveSpec   = 0;
  FragSpec *fragSpec       = 0;


  // The general structure of the file we are reading is this:
  //
  // <polchemdefinition>
  // <atomdefdata>
  //   <atom>
  //     <name>Hydrogen</name>
  //     <symbol>H</symbol>
  //     <isotope>
  //       <mass>1.0078250370</mass>
  //       <abund>99.9885000000</abund>
  //     </isotope>
  //     <isotope>
  //       <mass>2.0141017870</mass>
  //       <abund>0.0115000000</abund>
  //     </isotope>
  //   </atom>
  // ...
  // </atomdefdata>
  // <polchemdefdata version="0.1">
  //   <name>protein</name>
  //   <leftcap>+H</leftcap>
  //   <rightcap>+OH</rightcap>
  //   <codelen>1</codelen>
  //   <ionizerule>
  //     <formula>+H</formula>
  //     <charge>1</charge>
  //     <level>1</level>
  //   </ionizerule>
  //   <monomers>
  //     <mnm>
  //       <name>Glycine</name>
  //       <code>G</code>
  //       <formula>C2H3NO</formula>
  //     </mnm>
  //   </monomers>
  //   <modifs>
  //     <mdf>
  //       <name>Phosphorylation</name>
  //       <formula>-H+H2PO3</formula>
  //     </mdf>
  //   </modifs>
  //   <cleavespecs>
  //     <cls>
  //       <name>CyanogenBromide</name>
  //       <pattern>M/</pattern>
  //       <clr>
  //         <re-mnm-code>M</re-mnm-code>
  //         <re-formula>-CH2S+O</re-formula>
  //       </clr>
  //     </cls>
  //  </cleavespecs>
  //   <fragspecs>
  //     <fgs>
  //       <name>a</name>
  //       <end>LE</end>
  //       <formula>-C1O1</formula>
  //       <fgr>
  //         <name>a-fgr-1</name>
  //         <formula>+H200</formula>
  //         <prev-mnm-code>E</prev-mnm-code>
  //         <this-mnm-code>D</this-mnm-code>
  //         <next-mnm-code>F</next-mnm-code>
  //         <comment>comment here!</comment>
  //       </fgr>
  //     </fgs>
  //  </fragspecs>
  // </polchemdefdata>
  // </polchemdefinition>

  if(!file.open(QIODevice::ReadOnly))
    return false;

  if(!doc.setContent(&file))
    {
      file.close();
      return false;
    }

  file.close();

  element = doc.documentElement();

  if(element.tagName() != "polchemdefinition")
    {
      qDebug() << __FILE__ << __LINE__
               << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  ///////////////////////////////////////////////
  // Check the version of the document.

  QString text;

  if(!element.hasAttribute("version"))
    text = "1";
  else
    text = element.attribute("version");

  bool ok = false;

  int version = text.toInt(&ok, 10);

  if(version < 1 || !ok)
    {
      qDebug() << __FILE__ << __LINE__
               << "Polymer chemistry definition file has bad "
                  "version number:"
               << version;

      return false;
    }


  //////////////////////////////////////////////
  // <atom data>

  child = element.firstChildElement();
  if(child.tagName() != "atomdefdata")
    {
      qDebug() << __FILE__ << __LINE__
               << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "atom")
        return false;

      atom = new Atom();

      if(!atom->renderXmlAtomElement(indentedChild, version))
        {
          delete atom;
          return false;
        }

      m_atomList.append(atom);

      indentedChild = indentedChild.nextSiblingElement();
    }


  //////////////////////////////////////////////
  // <polymer chemistry data>

  child = child.nextSiblingElement();
  if(child.tagName() != "polchemdefdata")
    {
      qDebug() << __FILE__ << __LINE__
               << "Polymer chemistry definition file is erroneous\n";
      return false;
    }

  // <name>
  child = child.firstChildElement();
  if(child.tagName() != "name")
    return false;
  m_name = child.text();

  // <leftcap>
  child = child.nextSiblingElement();
  if(child.tagName() != "leftcap")
    return false;
  m_leftCap.setFormula(child.text());

  // <rightcap>
  child = child.nextSiblingElement();
  if(child.tagName() != "rightcap")
    return false;
  m_rightCap.setFormula(child.text());

  // <codelen>
  child = child.nextSiblingElement();
  if(child.tagName() != "codelen")
    return false;
  ok           = false;
  m_codeLength = child.text().toInt(&ok);
  if(m_codeLength == 0 && !ok)
    return false;

  // <ionizerule>
  child = child.nextSiblingElement();
  if(child.tagName() != "ionizerule")
    return false;
  if(!m_ionizeRule.renderXmlIonizeRuleElement(child))
    return false;
  // We have to ascertain that the IonizeRule is valid.
  if(!m_ionizeRule.validate(m_atomList))
    return false;

  // <monomers>
  child = child.nextSiblingElement();
  if(child.tagName() != "monomers")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "mnm")
        return false;

      monomer = new Monomer(this, "NOT_SET");

      if(!monomer->renderXmlMnmElement(indentedChild, version))
        {
          delete monomer;
          return false;
        }

      m_monomerList.append(monomer);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // <modifs>
  child = child.nextSiblingElement();
  if(child.tagName() != "modifs")
    return false;

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

      modif = new Modif(this, "NOT_SET");

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

      m_modifList.append(modif);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // <crosslinkers>

  // Note that crosslinkers have appeared since version 3.

  if(version > 2)
    {
      child = child.nextSiblingElement();
      if(child.tagName() != "crosslinkers")
        return false;

      indentedChild = child.firstChildElement();
      while(!indentedChild.isNull())
        {
          if(indentedChild.tagName() != "clk")
            return false;

          crossLinker = new CrossLinker(this, "NOT_SET", "NOT_SET");

          if(!crossLinker->renderXmlClkElement(indentedChild, version))
            {
              delete crossLinker;
              return false;
            }

          m_crossLinkerList.append(crossLinker);

          indentedChild = indentedChild.nextSiblingElement();
        }
    }

  // <cleavespecs>
  child = child.nextSiblingElement();
  if(child.tagName() != "cleavespecs")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "cls")
        return false;

      cleaveSpec = new CleaveSpec(this, "NOT_SET");

      if(!cleaveSpec->renderXmlClsElement(indentedChild, version))
        {
          delete cleaveSpec;
          return false;
        }

      m_cleaveSpecList.append(cleaveSpec);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // <fragspecs>
  child = child.nextSiblingElement();
  if(child.tagName() != "fragspecs")
    return false;

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() != "fgs")
        return false;

      fragSpec = new FragSpec(this, "NOT_SET");

      if(!fragSpec->renderXmlFgsElement(indentedChild, version))
        {
          delete fragSpec;
          return false;
        }

      m_fragSpecList.append(fragSpec);

      indentedChild = indentedChild.nextSiblingElement();
    }

  return true;
}


//! Creates the XML DTD for a polymer chemistry definition file.
/*! \return The DTD in string format.
 */
QString *
PolChemDef::formatXmlDtd()
{
  QString *string = new QString(
    "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
    "<!-- DTD for polymer definitions, used by the\n"
    "'massXpert' mass spectrometry application.\n"
    "Copyright 2006,2007,2008 Filippo Rusconi\n"
    "Licensed under the GNU GPL -->\n"
    "<!DOCTYPE polchemdefinition [\n"
    "<!ELEMENT polchemdefinition (atomdefdata,polchemdefdata)>\n"
    "<!ATTLIST polchemdefinition version NMTOKEN #REQUIRED>\n"
    "<!ELEMENT atomdefdata (atom+)>\n"
    "<!ELEMENT atom (name,symbol,isotope+)>\n"
    "<!ELEMENT symbol (#PCDATA)>\n"
    "<!ELEMENT isotope (mass , abund)>\n"
    "<!ELEMENT mass (#PCDATA)>\n"
    "<!ELEMENT abund (#PCDATA)>\n"
    "<!ELEMENT polchemdefdata "
    "(name,leftcap,rightcap,codelen,ionizerule,monomers,modifs,crosslinkers,"
    "cleavespecs,fragspecs)>\n"
    "<!ELEMENT ionizerule (formula,charge,level)>\n"
    "<!ELEMENT monomers (mnm*)>\n"
    "<!ELEMENT modifs (mdf*)>\n"
    "<!ELEMENT crosslinkers (clk*)>\n"
    "<!ELEMENT cleavespecs (cls*)>\n"
    "<!ELEMENT fragspecs (fgs*)>\n"
    "<!ELEMENT mnm (name,code,formula)>\n"
    "<!ELEMENT mdf (name,formula,targets,maxcount)>\n"
    "<!ELEMENT clk (name,formula,modifname*)>\n"
    "<!ELEMENT cls (name,pattern,clr*)>\n"
    "<!ELEMENT fgs (name,end,formula,sidechaincontrib,comment?,fgr*)>\n"
    "<!ELEMENT clr "
    "(name,(le-mnm-code,le-formula)?,(re-mnm-code,re-formula)?)>\n"
    "<!ELEMENT fgr "
    "(name,formula,prev-mnm-code?,curr-mnm-code?,next-mnm-code?,comment?)>\n"
    "<!ELEMENT leftcap (#PCDATA)>\n"
    "<!ELEMENT rightcap (#PCDATA)>\n"
    "<!ELEMENT codelen (#PCDATA)>\n"
    "<!ELEMENT charge (#PCDATA)>\n"
    "<!ELEMENT maxcount (#PCDATA)>\n"
    "<!ELEMENT level (#PCDATA)>\n"
    "<!ELEMENT name (#PCDATA)>\n"
    "<!ELEMENT modifname (#PCDATA)>\n"
    "<!ELEMENT code (#PCDATA)>\n"
    "<!ELEMENT formula (#PCDATA)>\n"
    "<!ELEMENT sidechaincontrib (#PCDATA)>\n"
    "<!ELEMENT targets (#PCDATA)>\n"
    "<!ELEMENT pattern (#PCDATA)>\n"
    "<!ELEMENT end (#PCDATA)>\n"
    "<!ELEMENT le-mnm-code (#PCDATA)>\n"
    "<!ELEMENT re-mnm-code (#PCDATA)>\n"
    "<!ELEMENT le-formula (#PCDATA)>\n"
    "<!ELEMENT re-formula (#PCDATA)>\n"
    "<!ELEMENT comment (#PCDATA)>\n"
    "<!ELEMENT prev-mnm-code (#PCDATA)>\n"
    "<!ELEMENT curr-mnm-code (#PCDATA)>\n"
    "<!ELEMENT next-mnm-code (#PCDATA)>\n"
    "]>\n");

  return string;
}


//! Write the polymer chemistry definition to file.
/*!  \return true if successful, false otherwise.
 */
bool
PolChemDef::writeXmlFile()
{
  QString *string = 0;
  QString indent("  ");
  QString lead;

  int offset = 0;
  int iter   = 0;

  // We are asked to send an xml description of the polymer chemistry
  // definition.

  QFile file(m_filePath);

  if(!file.open(QIODevice::WriteOnly))
    {
      qDebug() << __FILE__ << __LINE__ << "Failed to open file" << m_filePath
               << "for writing.";

      return false;
    }

  QTextStream stream(&file);
  stream.setCodec("UTF-8");

  // The DTD
  string = formatXmlDtd();
  stream << *string;
  delete string;


  // Open the <polchemdefinition> element.
  stream << QString("<polchemdefinition version=\"%1\">\n")
              .arg(POL_CHEM_DEF_FILE_FORMAT_VERSION);

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

  // Open the <atomdefdata> element.
  stream << QString("%1<atomdefdata>\n").arg(lead);

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

  // All the atoms in the List.
  for(iter = 0; iter < m_atomList.size(); ++iter)
    {
      string = m_atomList.at(iter)->formatXmlAtomElement(2);
      stream << *string;
      delete string;
    }

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

  // Close the <atomdefdata> element.
  stream << QString("%1</atomdefdata>\n").arg(lead);

  // Open the <polchemdefdata> element.
  stream << QString("%1<polchemdefdata>\n").arg(lead);

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

  stream << QString("%1<name>%2</name>\n").arg(lead).arg(m_name);

  stream
    << QString("%1<leftcap>%2</leftcap>\n").arg(lead).arg(m_leftCap.text());

  stream
    << QString("%1<rightcap>%2</rightcap>\n").arg(lead).arg(m_rightCap.text());

  stream << QString("%1<codelen>%2</codelen>\n").arg(lead).arg(m_codeLength);

  // Before writing the ionization rule, set the level to 1. This
  // member datum is set to 0 in the constructor.
  m_ionizeRule.setLevel(1);
  string = m_ionizeRule.formatXmlIonizeRuleElement(offset);
  stream << *string;
  delete string;

  stream << QString("%1<monomers>\n").arg(lead);

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

  for(iter = 0; iter < m_monomerList.size(); ++iter)
    {
      QString *newString = m_monomerList.at(iter)->formatXmlMnmElement(offset);

      stream << *newString;

      delete newString;
    }

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

  stream << QString("%1</monomers>\n").arg(lead);


  stream << QString("%1<modifs>\n").arg(lead);

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

  for(iter = 0; iter < m_modifList.size(); ++iter)
    {
      QString *newString = m_modifList.at(iter)->formatXmlMdfElement(offset);

      stream << *newString;

      delete newString;
    }

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

  stream << QString("%1</modifs>\n").arg(lead);


  stream << QString("%1<crosslinkers>\n").arg(lead);

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

  for(iter = 0; iter < m_crossLinkerList.size(); ++iter)
    {
      QString *newString =
        m_crossLinkerList.at(iter)->formatXmlClkElement(offset);

      stream << *newString;

      delete newString;
    }

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

  stream << QString("%1</crosslinkers>\n").arg(lead);


  stream << QString("%1<cleavespecs>\n").arg(lead);

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

  for(int iter = 0; iter < m_cleaveSpecList.size(); ++iter)
    {
      QString *newString =
        m_cleaveSpecList.at(iter)->formatXmlClsElement(offset);

      stream << *newString;

      delete newString;
    }

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

  stream << QString("%1</cleavespecs>\n").arg(lead);


  stream << QString("%1<fragspecs>\n").arg(lead);


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

  for(int iter = 0; iter < m_fragSpecList.size(); ++iter)
    {
      QString *newString = m_fragSpecList.at(iter)->formatXmlFgsElement(offset);

      stream << *newString;

      delete newString;
    }

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

  stream << QString("%1</fragspecs>\n").arg(lead);

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

  stream << QString("%1</polchemdefdata>\n").arg(lead);

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

  stream << QString("%1</polchemdefinition>\n").arg(lead);

  file.close();

  return true;
}

QStringList *
PolChemDef::differenceBetweenMonomers(double threshold, int monoOrAvg)
{
  //     qDebug() << __FILE__ << __LINE__
  // 	     << "threshold" << threshold;

  QStringList *list = new QStringList();
  ;

  // We iterate in the list of monomers and compute a difference of
  // mass for each monomer with respect to all the other ones. If
  // the mass difference is less or equal to the threshold, then the
  // pair is returned.

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

      monomer1->calculateMasses();

      for(int jter = 0; jter < m_monomerList.size(); ++jter)
        {
          // We are not going to explain that the same monomer has the
          // same mass !
          if(iter == jter)
            continue;

          Monomer *monomer2 = m_monomerList.at(jter);

          monomer2->calculateMasses();

          // At this point we have the masses for both monomers.

          if(monoOrAvg != MassType::MASS_MONO &&
             monoOrAvg != MassType::MASS_AVG)
            {
              qDebug() << __FILE__ << __LINE__
                       << "Please set the mass type "
                          "to value MassType::MASS_MONO or "
                          "MassType::MASS_AVG";

              return list;
            }

          double diff = 0;

          if(monoOrAvg == MassType::MASS_MONO)
            {
              diff = monomer2->mass(MassType::MASS_MONO) -
                     monomer1->mass(MassType::MASS_MONO);
            }
          else
            //(monoOrAvg == MassType::MASS_AVG)
            {
              diff = monomer2->mass(MassType::MASS_AVG) -
                     monomer1->mass(MassType::MASS_AVG);
            }

          // At this point, make sure that diff is within the
          // threshold. Note that to avoid duplicates, we remove all
          // values that are negative.

          if(diff >= 0 && diff <= threshold)
            {
              QString line = QString("%1 - %2 = %3")
                               .arg(monomer2->name())
                               .arg(monomer1->name())
                               .arg(diff);

              list->append(line);
            }
        }
      // End of
      // for (int jter = 0; jter < m_monomerList.size(); ++jter)
    }
  // End of
  // for (int iter = 0; iter < m_monomerList.size(); ++iter)

  // Only return a list if it contains at least one item.

  if(!list->size())
    {
      delete list;
      list = 0;
    }

  return list;
}

} // namespace massxpert

} // namespace msxps
