/* 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>
#include <QFileInfo>
#include <QDir>


/////////////////////// Local includes
#include "PolChemDef.hpp"
#include "Monomer.hpp"
#include "Polymer.hpp"
#include "IsotopicDataLibraryHandler.hpp"
#include "IsotopicDataUserConfigHandler.hpp"


int polChemDefSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMass::PolChemDefSPtr>(
    "MsXpS::libXpertMass::PolChemDefSPtr");

int polChemDefCstSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMass::PolChemDefCstSPtr>(
    "MsXpS::libXpertMass::PolChemDefCstSPtr");


namespace MsXpS
{

namespace libXpertMass
{

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

\brief The PolChemDef class provides a complete set of chemical entities
fully qualifying a polymer chemistry definition, like Proteins, Saccharides or
Nucleic acids.

  The PolChemDef class provides a full set of chemical entity definitions
(\l{Isotope}s, \l{Monomer}s, chemical \l{Modif}ications, \l{CrossLink}s),
chemical reaction models (in the liquid or gas phase, like \l Polymer cleavage:
\l CleaveSpec or \l Oligomer fragmentation: \l FragSpec)\dots
  */

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

\brief The name of the polymer chemistry definition, like \e{protein-1-letter}
or \e{nucac}, for example.

This name is typically identical to both the name of the directory where all
the data defining this \c PolChemDef is stored and the name of the XML file
that contains the definition itself.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_xmlDataFilePath

\brief The path to the XML data file that contains the description of this
polymer chemistry definition..
*/


/*!
\variable MsXpS::libXpertMass::PolChemDef::m_isotopicDataFilePath

\brief The path to the file that contains this polymer chemistry definition's
isotopic data.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_leftCap

\brief The \l Formula that defines how of the left end of a polymer
sequence of this \l PolChemDef needs to be capped in order to finalize the
polymerization state of the \l Polymer sequence.

\sa PolChemDef::m_rightCap
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_rightCap

\brief The \l Formula that defines how of the right end of a polymer
sequence of this \l PolChemDef needs to be capped in order to finalize the
polymerization state of the \l Polymer sequence.

\sa PolChemDef::m_leftCap
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_codeLength

\brief The maximum length of a \l Monomer code in this defintion.

The valid syntax of a Monomer code is that the first character of the code is
uppercase and all the remaining ones are lowercase. The total number of
characters cannot exceed m_codeLength.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_delimitedCodes

\brief The set of \l Monomer codes separated by '@' characters, like
"@Ala@Tyr@Phe@".
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_ionizeRule

\brief The \l{IonizeRule}{ionization rule} that governs the manner the \l
Polymer sequences of this polymer chemistry definition are ionized by default.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::msp_isotopicData

\brief The isotopic data defining the fundamentals of this \l PolChemDef
instance.
*/

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

\brief The list of \l{Monomer}s defined to be part of this \l PolChemDef
instance.
*/


/*!
\variable MsXpS::libXpertMass::PolChemDef::m_modifList

\brief The list of \l{Modif}s defined to be part of this \l PolChemDef
instance.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_crossLinkerList

The list of \l{CrossLinker}s defined to be part of this \l PolChemDef
instance.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_cleaveSpecList

\brief The list of \l{CleaveSpec}s defining the various ways to cleave
\l Polymer sequences of this \l PolChemDef instance.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_fragSpecList

\brief The list of \l{FragSpec}s defining the various ways to fragment \l
Oligomer sequences of this \l PolChemDef instance.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_monomerSpecList

\brief The list of \l{MonomerSpec}s defining how \l{Monomer}s are represented
in the graphical user interface.
*/


/*!
\variable MsXpS::libXpertMass::PolChemDef::m_modifSpecList

\brief The list of \l{ModifSpec}s defining how \l{Modif}s are represented
in the graphical user interface.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::m_crossLinkerSpecList

\brief The list of \l{CrossLinkerSpec}s defining how \l{CrossLinker}s are
represented in the graphical user interface.
*/

/*!
\variable MsXpS::libXpertMass::PolChemDef::mp_repositoryList

\brief The list of \l PolChemDef instances available in the repositories.
*/


/*!
\variable MsXpS::libXpertMass::POL_CHEM_DEF_FILE_FORMAT_VERSION

\brief The latest version of the format of the file containing the
polymer chemistry definition.

Brought to 1 20230130 for massXpert2
*/
const int POL_CHEM_DEF_FILE_FORMAT_VERSION = 1;

/*!
\brief Constructs a polymer chemistry definition.
*/
PolChemDef::PolChemDef()
{
  // qDebug() << "Constructing 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;

  return;
}


/*!
\brief Constructs a polymer chemistry definition on the basis of \a
pol_chem_def_spec.

The  \a pol_chem_def_spec polymer chemistry definition specification provides
the name of the polymer chemistry definition and of the file that contains
it.
*/
PolChemDef::PolChemDef(const PolChemDefSpec &pol_chem_def_spec)
{
  m_name = pol_chem_def_spec.name();

  // qDebug() << "Constructing PolChemDef *:" << this << "with name:" <<
  // m_name;

  m_xmlDataFilePath = pol_chem_def_spec.getFilePath();

  m_codeLength = 1;

  return;
}

/*!
\brief Destroys the polymer chemistry definition
*/
PolChemDef::~PolChemDef()
{
  // qDebug() << "Entering ~PolChemDef *:" << this << "with name:" << m_name;

  // We have to free all the allocated stuff in the QList members !
  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()
  // 	    << "Leaving ~PolChemDef():" << this;
}


/*!
\brief Sets the \a name of this PolChemDef instance.
*/
void
PolChemDef::setName(const QString &name)
{
  m_name = name;
}


/*!
\brief Returns the name of this PolChemDef instance.
*/
QString
PolChemDef::name() const
{
  return m_name;
}


/*!
\brief Sets the \a file_path of this PolChemDef instance.
*/
void
PolChemDef::setXmlDataFilePath(const QString &file_path)
{
  m_xmlDataFilePath = file_path;
}


/*!
\brief Returns the file path of this PolChemDef instance.
*/
QString
PolChemDef::getXmlDataFilePath() const
{
  return m_xmlDataFilePath;
}


/*!
\brief Returns the absolute directory path of this PolChemDef instance.
*/
QString
PolChemDef::getXmlDataDirPath() const
{
  // qDebug() << "The xml data file path:" << m_xmlDataFilePath;

  QFileInfo fileInfo(m_xmlDataFilePath);
  QDir dir(fileInfo.dir());

  // qDebug() << "Returning the pol chem def data dir path:" <<
  dir.absolutePath();

  return dir.absolutePath();
}

/*!
\brief Sets the path of the isotopic data file to \a file_path.
*/
void
PolChemDef::setIsotopicDataFilePath(const QString &file_path)
{
  m_isotopicDataFilePath = file_path;
}

/*!
\brief Returns the path of the isotopic data file.
*/
QString
PolChemDef::getIsotopicDataFilePath() const
{
  return m_isotopicDataFilePath;
}

/*!
\brief Returns the path of the isotopic data file.

The deduction is based on the fact that the file should have
"isotopic-data.dat" as its name.
*/
QString
PolChemDef::deduceIsotopicDataFilePath() const
{
  // From the xml data file path, deduce the file name of the isotopic data.
  QFileInfo file_info(m_xmlDataFilePath);

  if(!file_info.exists())
    qFatal(
      "Programming error. At this stage the name of the polymer chemistry "
      "definition file should be known.");

  QDir dir(file_info.dir());

  QString isotopic_data_file_path =
    dir.absolutePath() + QDir::separator() + "isotopic-data.dat";

  return isotopic_data_file_path;
}


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

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


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


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


/*!
\brief Sets the \a code_length.
*/
void
PolChemDef::setCodeLength(int code_length)
{
  m_codeLength = code_length;
}


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

/*!
\brief Constructs a string with the codes of all the \l{Monomer}s in this
PolChemDef instance.

The codes are delimited by the '@' character.

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


/*!
\brief Returns a string with the codes of all the \l{Monomer}s in this
PolChemDef instance.

The codes are delimited by the '@' character.
*/
const QString &
PolChemDef::delimitedCodes()
{
  if(m_delimitedCodes.isEmpty())
    calculateDelimitedCodes();

  return m_delimitedCodes;
}


/*!
\brief Sets the ionization rule to \a ionize_rule.
*/
void
PolChemDef::setIonizeRule(const IonizeRule &ionize_rule)
{
  m_ionizeRule = ionize_rule;
}


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

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

/*!
\brief Sets the isotopic data to \a isotopic_data_sp.
*/
void
PolChemDef::setIsotopicDataSPtr(IsotopicDataSPtr isotopic_data_sp)
{
  msp_isotopicData = isotopic_data_sp;
}

/*!
\brief Returns the isotopic data as const shared pointer..
*/
IsotopicDataCstSPtr
PolChemDef::getIsotopicDataCstSPtr() const
{
  return msp_isotopicData;
}

/*!
\brief Returns the isotopic data as non-const shared pointer..
*/
IsotopicDataSPtr
PolChemDef::getIsotopicDataSPtr()
{
  return msp_isotopicData;
}


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

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

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

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

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

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

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

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

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

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

/*!
\brief Searches in the member list of modifications a modification by \a name.

If the modification object is found, and \a modification_p is non-nullptr, it
is copied in \a modification_p.

Returns true if the modification was found, false otherwise.
*/
bool
PolChemDef::referenceModifByName(const QString &name,
                                 Modif *modification_p) 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_p)
            {
              *modification_p = *modif;

              return true;
            }

          return true;
        }
    }

  return false;
}

/*!
\brief Searches in the member list of cross-linkers a cross-linker by \a name.

If the cross-linker object is found, and \a cross_linker_p is non-nullptr, it
is copied in \a cross_linker_p.

Returns true if the cross-linker was found, false otherwise.
*/
bool
PolChemDef::referenceCrossLinkerByName(const QString &name,
                                       CrossLinker *cross_linker_p) 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(cross_linker_p)
            {
              *cross_linker_p = *localCrossLinker;

              return true;
            }

          return true;
        }
    }

  return false;
}


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

/*!
\brief Returns a pointer to the list of monomer specifications.
*/
QList<MonomerSpec *> *
PolChemDef::monomerSpecListPtr()
{
  return &m_monomerSpecList;
}

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

/*!
\brief Returns a pointer to the list of modification specifications.
*/
QList<ModifSpec *> *
PolChemDef::modifSpecListPtr()
{
  return &m_modifSpecList;
}

/*!
\brief Returns a const reference to the list of cross-linker specifications.
*/
QList<CrossLinkerSpec *> &
PolChemDef::crossLinkerSpecList() const
{
  return m_crossLinkerSpecList;
}

/*!
\brief Returns a pointer to the list of cross-linker specifications.
*/
QList<CrossLinkerSpec *> *
PolChemDef::crossLinkerSpecListPtr()
{
  return &m_crossLinkerSpecList;
}

/*!
\brief Parses the polymer chemistry definition file and updates \a
pol_chem_def_sp accordingly.

Upon parsing of the file and validation of the data, the \a pol_chem_def_sp is
updated, essentially initializing it with the data from the file.

Note that the \a pol_chem_def_sp should have a working set of isotopic data.

Returns true if the parsing was successful and false otherwise.
*/
bool
PolChemDef::renderXmlPolChemDefFile(PolChemDefSPtr pol_chem_def_sp)
{
  Q_ASSERT(pol_chem_def_sp != nullptr && pol_chem_def_sp.get() != nullptr);

  // qDebug() << "The PolChemDef *:" << pol_chem_def_sp.get()
  //<< "and usage:" << pol_chem_def_sp.use_count();

  ///////////////////// ATTENTION ///////////////////
  // Before reading the polymer chemistry data file, we need to make sure we
  // actually have read the isotopic data!

  // qDebug() << "First check if we have isotopic data ready.";

  if(pol_chem_def_sp->getIsotopicDataSPtr() == nullptr)
    {
      // qDebug() << "No isotopic data found in the polymer chemistry "
      //             "definition, need to load the data.";

      // First read the data!
      std::size_t count = pol_chem_def_sp->loadIsotopicData(pol_chem_def_sp);

      if(!count)
        qFatal("Programming error. The isotopic data could not be loaded.");

      // qDebug() << "At this point the isotopic data were loaded fine with"
      //<< count << "isotopes loaded.";
    }

  QDomDocument doc("polChemDefData");
  QDomElement element;
  QDomElement child;
  QDomElement indentedChild;

  QFile file(pol_chem_def_sp->m_xmlDataFilePath);

  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 version="1">
  // <polchemdefdata">
  //   <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))
    {
      qDebug() << "Failed to set doc content.";

      file.close();
      return false;
    }

  file.close();

  // qDebug() << "Closed the polymer chemistry definition file.";

  element = doc.documentElement();

  if(element.tagName() != "polchemdefinition")
    {
      qDebug() << "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");

  // qDebug() << "The format of the definition:" << text;

  bool ok = false;

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

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

      return false;
    }

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

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

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

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

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

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

  // <ionizerule>
  child = child.nextSiblingElement();
  if(child.tagName() != "ionizerule")
    return false;
  if(!pol_chem_def_sp->m_ionizeRule.renderXmlIonizeRuleElement(child))
    return false;

  // We have to ascertain that the IonizeRule is valid.
  IsotopicDataCstSPtr isotopic_data_csp =
    pol_chem_def_sp->getIsotopicDataCstSPtr();
  if(!pol_chem_def_sp->m_ionizeRule.IonizeRule::validate(isotopic_data_csp))
    return false;

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

  indentedChild = child.firstChildElement();
  while(!indentedChild.isNull())
    {
      // qDebug() << "Now rendering one of all the mnm elements.";

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

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

      if(!monomer->renderXmlMnmElement(indentedChild, version))
        {
          qDebug() << "Failed to render mnm element.";

          delete monomer;
          return false;
        }

      pol_chem_def_sp->m_monomerList.append(monomer);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of MonomerList:" <<
  // pol_chem_def_sp->m_monomerList.size()
  //<< "pol chem def usage:" << pol_chem_def_sp.use_count();

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

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

      pol_chem_def_sp->m_modifList.append(modif);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of ModifList:" << pol_chem_def_sp->m_modifList.size()
  //<< "pol chem def usage:" << pol_chem_def_sp.use_count();

  // <crosslinkers>

  // Note that crosslinkers have appeared since version 3.

  child = child.nextSiblingElement();
  if(child.tagName() != "crosslinkers")
    return false;

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

      // We set the formula to nothing below because that formula might be empty
      // in the polymer chemistry definition.
      crossLinker = new CrossLinker(pol_chem_def_sp, "NOT_SET", "");

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

      // qDebug() << "Rendered CrossLinker: " << crossLinker->name()
      //<< "with masses:" << crossLinker->mono() << "/"
      //<< crossLinker->avg();

      pol_chem_def_sp->m_crossLinkerList.append(crossLinker);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of CrossLinkerList:"
  //<< pol_chem_def_sp->m_crossLinkerList.size()
  //<< "pol chem def usage:" << pol_chem_def_sp.use_count();

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

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

      pol_chem_def_sp->m_cleaveSpecList.append(cleaveSpec);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of CleaveSpecList:"
  //<< pol_chem_def_sp->m_cleaveSpecList.size()
  //<< "pol chem def usage:" << pol_chem_def_sp.use_count();

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

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

      pol_chem_def_sp->m_fragSpecList.append(fragSpec);

      indentedChild = indentedChild.nextSiblingElement();
    }

  // qDebug() << "Size of FragSpecList:" <<
  // pol_chem_def_sp->m_fragSpecList.size()
  //<< "pol chem def usage:" << pol_chem_def_sp.use_count();

  // qDebug() << "Returning true" << "pol chem def usage:" <<
  // pol_chem_def_sp.use_count();

  return true;
}

/*!
\brief Loads the isotopic data stored in the file \a file_path.

The data loaded from file are parsed and validated. Upon parsing of the file,
the data are stored in the \a pol_chem_def_sp instance's isotopic data member.

If \a file_path is empty, it is reconstructed using the directory of the
polymer chemistry definition and the fact that the isotopic data file name is
"isotopic-data.dat".

Returns the count of parsed isotopes.
*/
std::size_t
PolChemDef::loadIsotopicData(PolChemDefSPtr pol_chem_def_sp,
                             const QString &file_path)
{
  // There are three situations:
  //
  // 1. The file_path exists and the data are loaded from there.
  //
  // 2. A file named isotopic-data.dat is found in the polymer chemistry
  // definition directory and it is loaded.
  //
  // 3. No file named isotopic-data.dat is found in the polymer chemistry
  // defintion directory and then data are loaded from the IsoSpec tables.

  // If the provided argument is not empty and actually describes an existing
  // file, then save the data there.
  QString local_file_path(file_path);
  QFileInfo local_file_info(local_file_path);

  if(local_file_path.isEmpty() || !local_file_info.exists())
    {
      // qDebug() << "The provided file name" << file_path << "does not exist.";

      // Then try the member datum that provides the name of the file from which
      // to load the isotopic data.
      local_file_path = pol_chem_def_sp->getIsotopicDataFilePath();
      local_file_info.setFile(local_file_path);

      if(local_file_path.isEmpty() || !local_file_info.exists())
        {
          // qDebug() << "The member datum file name " << local_file_path
          //<< "does not exist.";

          // Last resort: deduce the isotopic data file name from the directory
          // of the polymer chemistry definition.

          local_file_path = pol_chem_def_sp->getXmlDataDirPath();

          // qDebug() << "The XML data dir path:" << local_file_path;

          local_file_info.setFile(local_file_path);

          if(!local_file_info.exists() || !local_file_info.isDir())
            qFatal(
              "Programming error. At this stage the name of the polymer "
              "chemistry definition directory should be known and should be a "
              "directory.");

          local_file_path = local_file_info.absoluteFilePath() +
                            QDir::separator() + "isotopic-data.dat";

          // qDebug() << "Crafted the isotopic-data.dat file path:"
          //<< local_file_path;

          // Finally for a later last check:
          local_file_info.setFile(local_file_path);
        }
    }

  // qDebug() << "Allocating brand new isotopic data instance.";

  // At this point we can delete the pre-exsting isotopic data.
  pol_chem_def_sp->msp_isotopicData = std::make_shared<IsotopicData>();

  // Allocate a specific handler to the kind of isotopic data (library tables or
  // user file).

  std::size_t count = 0;

  if(local_file_path.isEmpty() || !local_file_info.exists())
    {
      // qDebug() << "Loading the isotopic data from the library.";
      IsotopicDataLibraryHandler isotopic_data_handler(
        pol_chem_def_sp->msp_isotopicData);

      count = isotopic_data_handler.loadData();
    }
  else
    {
      // qDebug() << "Loading the isotopic data from file:" << local_file_path;

      IsotopicDataUserConfigHandler isotopic_data_handler(
        pol_chem_def_sp->msp_isotopicData);

      count = isotopic_data_handler.loadData(local_file_path);

      if(!count)
        {
          qDebug() << "Failed to load any isotopic data.";
          return false;
        }

      // Now set the file path to the pol chem def.

      pol_chem_def_sp->setIsotopicDataFilePath(local_file_path);
    }

  if(!count)
    {
      qDebug() << "Failed to load any isotopic data.";
      return false;
    }

  return count;
}


/*!
\brief Returns a string with the XML DTD for a polymer chemistry
definition file.
*/
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 (polchemdefdata)>\n"
    "<!ATTLIST polchemdefinition version NMTOKEN #REQUIRED>\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;
}


/*!
\brief Writes the polymer chemistry definition to file.

The file's name is from m_xmlDataFilePath.

Returns 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_xmlDataFilePath);

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

      return false;
    }

  QTextStream stream(&file);
  stream.setEncoding(QStringConverter::Utf8);

  // 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 <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.toString());

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

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

/*!
\brief Writes the isotopic data in the \a pol_chem_def_sp PolChemDef instance
to file \a file_path.

Returns the count of isotopes written to file.
*/
std::size_t
PolChemDef::writeIsotopicData(PolChemDefSPtr pol_chem_def_sp,
                              const QString &file_path)
{
  // There are three situations:
  //
  // 1. The file_path is not empty and the data are saved there.
  //
  // 2. The file_path is empty and the data are stored in the member file path
  // name (if not empty).
  //
  // 3. The file path is crafted from the directory of the polymer chemistry
  // definition.


  // Whatever the situation, the isotopic data handler we need here is this one:
  IsotopicDataUserConfigHandler isotopic_data_handler(
    pol_chem_def_sp->msp_isotopicData);

  // We'll instantiate the proper isotopic data handler depending on the
  // situation.

  if(!file_path.isEmpty())
    {
      // We have a file name, store in there.
      pol_chem_def_sp->setIsotopicDataFilePath(file_path);
      return isotopic_data_handler.writeData(file_path);
    }

  // Check the member datum.

  if(!pol_chem_def_sp->m_isotopicDataFilePath.isEmpty())
    {
      // We have a file name, store in there.
      return isotopic_data_handler.writeData(
        pol_chem_def_sp->m_isotopicDataFilePath);
    }

  // Last resort: deduce the isotopic data file name from the directory
  // of the polymer chemistry definition.

  QString local_file_path = pol_chem_def_sp->getXmlDataDirPath();
  QFileInfo local_file_info(local_file_path);

  if(!local_file_info.exists())
    qFatal(
      "Programming error. At this stage the name of the polymer "
      "chemistry definition file should be known.");

  QDir dir(local_file_info.dir());

  local_file_path =
    dir.absolutePath() + QDir::separator() + "isotopic-data.dat";

  pol_chem_def_sp->setIsotopicDataFilePath(local_file_path);

  return isotopic_data_handler.writeData(local_file_path);
}

/*!
\brief Returns a string list with the mass difference between all the
\l{Monomer}s in this PolChemDef instace.

If the difference is below the \a threshold, the difference is added to the
string list, otherwise it is skipped. The masses that are compared between every
two monomers is of \a mass_type.
*/
QStringList *
PolChemDef::differenceBetweenMonomers(double threshold, MassType mass_type)
{
  //     qDebug()
  // 	     << "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(mass_type != MassType::MASS_MONO &&
             mass_type != MassType::MASS_AVG)
            {
              qDebug() << "Please set the mass type "
                          "to either MassType::MASS_MONO or "
                          "MassType::MASS_AVG";

              return list;
            }

          double diff = 0;

          if(mass_type == 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 libXpertMass

} // namespace MsXpS
