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


#include <QDateTime>
#include <QFile>
#include <QIODevice>

/////////////////////// Local includes
#include "Polymer.hpp"
#include "Modif.hpp"
#include "FragSpec.hpp"
#include "CleaveSpec.hpp"
#include "PolChemDef.hpp"
#include "CrossLink.hpp"


int polymerMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMass::Polymer>("MsXpS::libXpertMass::Polymer");

int polymerSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMass::PolymerSPtr>("MsXpS::libXpertMass::PolymerSPtr");

int polymerCstSPtrMetaTypeId =
  qRegisterMetaType<MsXpS::libXpertMass::PolymerCstSPtr>(
    "MsXpS::libXpertMass::PolymerCstSPtr");


namespace MsXpS
{

namespace libXpertMass
{


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

\brief The Polymer class provides abstractions to work with
a polymer molecule (protein or saccharide , for example).

The Polymer class provides a polymer sequence. In the
protein world, a polymer sequence is a protein. From a computing standpoint,
that sequence is first created by chaining amino acid residues (the residue
chain). In a second step, the entity is set to a finished polymer state by
adding the N-terminal proton and the C-terminal hydroxyl residue. It might
happen also that the ends of a polymer be chemically modified (acetylation of
the N-terminal residue, for example).

The Polymer class allows modelling a polymer sequence in its finest details.
*/

/*!
   \variable int MsXpS::libXpertMass::POL_SEQ_FILE_FORMAT_VERSION

   \brief Version number of the polymer sequence file format.
 */

/*!
   \variable int MsXpS::libXpertMass::Polymer::m_name

   \brief The name of the polymer, for example, "Apomyoglobin".
 */

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

   \brief The code of the polymer, for example, the accession number in
SwissProt.
 */

/*!
  \variable int MsXpS::libXpertMass::Polymer::m_author

  \brief The author or creator of the file.
 */

/*!
  \variable int MsXpS::libXpertMass::Polymer::m_dateTime

  \brief The date and time of last modification.
 */

/*!
  \variable int MsXpS::libXpertMass::Polymer::m_leftEndModif

  \brief The left end modification.
 */

/*!
  \variable int MsXpS::libXpertMass::Polymer::m_rightEndModif

  \brief The right end modification.
 */

/*!
  \variable int MsXpS::libXpertMass::Polymer::mpa_crossLinkList

  \brief The list of CrossLink events in the polymer sequence.
 */

/*!
  \brief Constructs a polymer with \a name, \a code, \a author and the \a
pol_chem_def_csp polymer chemistry definition.
  */
Polymer::Polymer(PolChemDefCstSPtr pol_chem_def_csp,
                 const QString &name,
                 const QString &code,
                 const QString &author)
  : Ionizable(pol_chem_def_csp, name),
    m_code(code),
    m_author(author),
    m_leftEndModif(pol_chem_def_csp, "NOT_SET"),
    m_rightEndModif(pol_chem_def_csp, "NOT_SET")
{
  mpa_crossLinkList = new CrossLinkList();
}


/*!
  \brief Destructs the polymer.
  */
Polymer::~Polymer()
{
  delete mpa_crossLinkList;
}


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


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


/*!
  \brief Sets the \a code.
  */
void
Polymer::setCode(const QString &code)
{
  m_code = code;
}

/*!
  \brief Returns the code.
  */
QString
Polymer::code() const
{
  return m_code;
}
/*!
  \brief Sets the \a author.
  */
void
Polymer::setAuthor(const QString &author)
{
  m_author = author;
}
/*!
  \brief Returns the author.
  */
QString
Polymer::author() const
{
  return m_author;
}

/*!
  \brief Sets the file path to \a file_path.
  */
void
Polymer::setFilePath(const QString &file_path)
{
  m_filePath = file_path;
}

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

/*!
    \brief Set the date and time to \a date_time.
 */
void
Polymer::setDateTime(const QString &date_time)
{
  m_dateTime = QDateTime::fromString((QString)date_time, "yyyy-MM-dd:mm:ss");
}

/*!
  \brief Returns the date and time.
  */
QString
Polymer::dateTime() const
{
  return m_dateTime.toString("yyyy-MM-dd:mm:ss");
}

/*!
  \brief Returns true if a least one monomer in the sequence between residues
\a left_index and \a right_index is modified.
*/
bool
Polymer::hasModifiedMonomer(int left_index, int right_index) const
{
  if(left_index >= size() || right_index >= size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(left_index > right_index)
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  int startIndex = left_index;
  int endIndex   = right_index;

  if(left_index == -1)
    startIndex = 0;
  if(right_index == -1)
    endIndex = size() - 1;

  for(int iter = startIndex; iter <= endIndex; ++iter)
    {
      if(at(iter)->isModified())
        return true;
    }

  return false;
}

/*!
  \brief Sets this polymer's left end modification to \a name.

    Returns true if a modification by \a name was found in the member polymer
chemistry definition's list of modifications and if that modification could be
set and its masses could be calculated successfully, otherwise returns false.
*/
bool
Polymer::setLeftEndModif(const QString &name)
{
  const QList<Modif *> &refList = getPolChemDefCstSPtr()->modifList();

  if(name.isNull() || name.isEmpty())
    {
      // Reset the modif to nothing.
      m_leftEndModif.reset();
    }

  if(Modif::isNameInList(name, refList, &m_leftEndModif) != -1)
    {
      if(!m_leftEndModif.calculateMasses())
        return false;
      else
        return true;
    }
  else
    return false;
}

/*!
  \brief Sets this polymer's left end modification to \a modif.

    Returns true if the modification could be set and its masses could be
calculated successfully, otherwise returns false.
*/
bool
Polymer::setLeftEndModif(const Modif &modif)
{
  m_leftEndModif = modif;

  if(!m_leftEndModif.calculateMasses())
    return false;
  else
    return true;
}

/*!
  \brief Returns this polymer's left end modif.
*/
const Modif &
Polymer::leftEndModif() const
{
  return m_leftEndModif;
}

/*!
  \brief Returns true if this polymer is modified at its left end.
*/
bool
Polymer::isLeftEndModified() const
{
  return m_leftEndModif.checkSyntax();
}

/*!
  \brief Sets this polymer's right end modification to \a name.

    Returns true if a modification by \a name was found in the member polymer
chemistry definition's list of modifications and if that modification could be
set and its masses could be calculated successfully, otherwise returns false.
*/
bool
Polymer::setRightEndModif(const QString &name)
{
  const QList<Modif *> &refList = getPolChemDefCstSPtr()->modifList();

  if(name.isNull() || name.isEmpty())
    {
      // Reset the modif to nothing.
      m_rightEndModif.reset();
    }

  if(Modif::isNameInList(name, refList, &m_rightEndModif) != -1)
    {
      if(!m_rightEndModif.calculateMasses())
        return false;
      else
        return true;
    }
  else
    return false;
}

/*!
  \brief Sets this polymer's right end modification to \a modif.

    Returns true if the modification could be set and its masses could be
calculated successfully, otherwise returns false.
*/
bool
Polymer::setRightEndModif(const Modif &modif)
{
  m_rightEndModif = modif;

  if(!m_rightEndModif.calculateMasses())
    return false;
  else
    return true;
}

/*!
  \brief Returns this polymer's right end modif.
*/
const Modif &
Polymer::rightEndModif() const
{
  return m_rightEndModif;
}

/*!
  \brief Returns true if this polymer is modified at its right end.
*/
bool
Polymer::isRightEndModified() const
{
  return m_rightEndModif.checkSyntax();
}

/*!
  \brief Returns a reference to the list of CrossLink entities associated to
this polymer.
*/
const CrossLinkList &
Polymer::crossLinkList() const
{
  return *mpa_crossLinkList;
}

/*!
  \brief Returns the list of CrossLink entities associated to this polymer.
*/
CrossLinkList *
Polymer::crossLinkListPtr()
{
  return mpa_crossLinkList;
}

/*!
  \brief Fills-in the \a index_list_p with indices of monomers involved in
cross-links.

  This function iterates in this polymer's list of cross-links and
checks for each cross-link the monomers that are involved in that cross-link
have indices:
  \list
    \li Fully contained in the indices range [\a start_index, \a end_index]
    \li Only partially contained in the range
    \li Not contained at all in the range.
  \endlist

  The \a index_list_p is filled only with indices of monomers involved in
cross-links of which the monomers are fully contained in range [\a start_index,
\a end_index].

  If \a partials is not nullptr, then, it is set to the count of cross-links
involving monomers only partially contained in range [\a start_index,
\a end_index].

  Returns true if at least one cross-link was found to be fully encompassed by
the range, false otherwise.
  */
bool
Polymer::crossLinkedMonomerIndexList(int start_index,
                                     int end_index,
                                     QList<int> *index_list_p,
                                     int *partials)
{
  if(index_list_p == nullptr)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // We are asked to return a list of all the indices of the
  // monomers involved in cross-links that link any monomer in the
  // region delimited by startIndex and endIndex.

  bool oneFound = false;

  // Iterate in the list of cross-links set to *this polymer.  For
  // each iterated cross-link, check if it is fully encompasse by
  // the region delimited by [startIndex--endIndex]. If so, get the
  // indices of the monomers involved in that cross-link and append
  // these (no duplicates) to the indexList passed as param to the
  // function.
  for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter)
    {
      CrossLink *crossLink = mpa_crossLinkList->at(jter);

      // qDebug() << __FILE__ << __LINE__
      //          << "Cross-link:" << crossLink;

      int ret = crossLink->encompassedBy(start_index, end_index);

      if(ret == CROSS_LINK_ENCOMPASSED_FULL)
        {
          // qDebug() << __FILE__ << __LINE__
          //          << "Cross-link:" << crossLink
          //          << "is encompassed by region:"
          //          << startIndex << "-" << endIndex;

          QList<int> localIndexList;

          crossLink->monomerIndexList(&localIndexList);

          // qDebug() << __FILE__ << __LINE__
          //          << "Index list:" << localIndexList;

          // Avoid duplicating indices in the target index
          // list. This will have the excellent side effect of
          // condensating into one single region a number of
          // contained cross-linked regions. For example, from the
          // Kunitz inhibitor, there are the following cross-links:

          // 90 -- 187
          // 230 -- 280
          // 239 -- 263
          // 255 -- 276
          // 286 -- 336
          // 295 -- 319
          // 311 -- 332

          // Thanks to our strategy below, the cross-links
          // 230 -- 280
          // 239 -- 263
          // 255 -- 276
          // become contained in one single region:
          // 230--276.

          // Same for the cross-links
          // 286 -- 336
          // 295 -- 319
          // 311 -- 332
          // Which become contained in 286--336

          for(int iter = 0, size = localIndexList.size(); iter < size; ++iter)
            {
              int index = localIndexList.at(iter);

              if(!index_list_p->contains(index))
                index_list_p->append(index);
            }

          oneFound = true;
        }
      else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
        {
          if(partials)
            ++(*partials);
        }
    }

  return oneFound;
}


/*!
  \brief Fills-in the \a crosslink_list_p with cross-links.

  This function iterates in this polymer's list of cross-links and checks for
each cross-link the monomers that are involved in that cross-link have indices:
  \list
    \li Fully contained in the indices range [\a start_index, \a end_index]
    \li Only partially contained in the range
    \li Not contained at all in the range.
  \endlist

  The \a crosslink_list_p is filled only with cross-links found to be
fully encompassed by the range.

  If \a partials is not nullptr, then, it is set to the count of cross-links
involving monomers only partially contained in range [\a start_index,
\a end_index].

  Returns true if at least one cross-link was found to be fully encompassed by
the range, false otherwise.
*/
bool
Polymer::crossLinkList(int start_index,
                       int end_index,
                       QList<CrossLink *> *crosslink_list_p,
                       int *partials)
{
  if(crosslink_list_p == nullptr)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // We are asked to return a list of all the cross-links that are
  // fully encompassed by the [startIndex--endIndex] region.

  bool oneFound = false;

  // Iterate in the list of cross-links set to *this polymer.  For
  // each iterated cross-link, check if it is fully encompassed by
  // the [startIndex--endIndex] region. If so simply add it to the
  // list of cross-links.
  for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter)
    {
      CrossLink *crossLink = mpa_crossLinkList->at(jter);

      // qDebug() << __FILE__ << __LINE__
      //          << "Cross-link:" << crossLink;

      int ret = crossLink->encompassedBy(start_index, end_index);

      if(ret == CROSS_LINK_ENCOMPASSED_FULL)
        {
          // qDebug() << __FILE__ << __LINE__
          //          << "Cross-link:" << crossLink
          //          << "is encompassed by region:"
          //          << startIndex << "-" << endIndex;
          crosslink_list_p->append(crossLink);

          oneFound = true;
        }
      else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
        {
          if(partials)
            ++(*partials);
        }
    }

  return oneFound;
}

/*!
  \brief Prepares for the removal of \a monomer from this polymer's sequence.

  The challenge is that monomers might be involved in cross-links. In that
case, removing a monomer that was involved in a cross-link need preparation:
it needs to first be uncross-linked.

  Returns true;

  \sa uncrossLink()
*/
bool
Polymer::prepareMonomerRemoval(Monomer *monomer)
{
  Q_ASSERT(monomer);

  // We are asked to destroy **all** the crossLinks that involve the
  // 'monomer'.

  // Iterate in the list of crossLinks, and for each crossLink check
  // if it involves 'monomer'.

  if(!mpa_crossLinkList->size())
    return true;

  int iter = mpa_crossLinkList->size();

  while(iter)
    {
      CrossLink *crossLink = mpa_crossLinkList->at(iter - 1);

      if(crossLink->involvesMonomer(monomer) != -1)
        {
          // The current crossLink involves the monomer for which all
          // the crossLinks should be destroyed.

          uncrossLink(crossLink);
          // The uncrossLinking takes care of removing the crossLink
          // from the list.
        }
      --iter;
    }

  return true;
}

/*!
  \brief Removes the monomer at \a index.

  The monomer is first uncross-linked (if it was).

  Returns false if the uncross-linking of the monomer failed, true otherwise.
*/
bool
Polymer::removeMonomerAt(int index)
{
  Q_ASSERT(index > -1);
  Q_ASSERT(index < size());

  Monomer *monomer = const_cast<Monomer *>(at(index));

  if(!prepareMonomerRemoval(monomer))
    return false;

  m_monomerList.removeAt(index);

  delete monomer;

  return true;
}

/*!
  \brief Computes a MD5SUM has with the data described in \a hash_data_specifs.

  If hash_data_specifs & HASH_ACCOUNT_SEQUENCE, the sequence is included in the
hash computation.
  If hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF, the monomer modifications
are included in the hash computation.
  If hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF, the polymer modifications
are include in the hash computation.

  Returns the hash.
*/
QByteArray
Polymer::md5Sum(int hash_data_specifs) const
{
  // We first need to craft a complete string that encapsulates the
  // maximum number of information from the polymer sequence (sequence,
  // modifications in the monomers...) depending on the parameter passed
  // to the function.

  QString *dataString = new QString;

  if(hash_data_specifs & HASH_ACCOUNT_SEQUENCE)
    {
      for(int iter = 0; iter < size(); ++iter)
        {
          const Monomer *monomer = at(iter);

          dataString->append(monomer->code());

          if(hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF)
            {
              if(monomer->isModified())
                {
                  QList<Modif *> *modifList = monomer->modifList();

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

                      dataString->append(modif->name());
                    }
                }
            }
        }
    }

  if(hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF)
    {
      if(isLeftEndModified())
        {
          dataString->append(leftEndModif().formula());
        }
      if(isRightEndModified())
        {
          dataString->append(rightEndModif().formula());
        }
    }

  // Now that we have the data string, we can craft the hash itself:

  QByteArray hash =
    QCryptographicHash::hash(dataString->toUtf8(), QCryptographicHash::Md5);

  delete dataString;

  return hash;
}

// MASS CALCULATION FUNCTIONS

/*!
  \brief Accounts the masses of this polymer.

  Accounting masses means calculating masses and adding the results to
objects. Here the masses of this polymer are calculated and added to those of
this polymer.

  The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

  Returns true upon success, false otherwise.

  \sa CalcOptions
*/
bool
Polymer::accountMasses(const CalcOptions &calc_options)
{
  // We do not want to reset masses prior to calculating the masses
  // because we are accounting them in the polymer itself.
  return calculateMasses(calc_options, false);
}

/*!
  \brief Accounts the masses of this polymer.

  Accounting masses means calculating masses and adding the results to
objects. Here the masses of this polymer are calculated and set to \a mono and
\a avg.

  The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

  \sa CalcOptions
*/
bool
Polymer::accountMasses(Polymer *polymer,
                       const CalcOptions &calc_options,
                       double *mono,
                       double *avg)
{
  // We do not want to reset masses prior to calculating the masses
  // because we are accounting them in the polymer itself.
  return calculateMasses(polymer, calc_options, mono, avg, false);
}

/*!
  \brief Calculates the masses of this polymer.

  The calculated masses are set to this polymer. If \a reset is true, the
masses of this polymer are first reset, otherwise this polymer's masses are
incremented with those obtained from the calculation.

  The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

  Returns true.

  \sa CalcOptions
*/
bool
Polymer::calculateMasses(const CalcOptions &calc_options, bool reset)
{
  return calculateMasses(this, calc_options, &m_mono, &m_avg, reset);
}

/*!
  \brief Calculates the masses of \a polymer_p.

  The calculated masses are set to \a mono and \a avg (that cannot be nullptr).

   If \a reset is true, \a mono and \a avg are reset, otherwise they are
incremented with the masses obtained from the calculation.

  The calculation of the masses (monoisotopic and average) is configured in \a
calc_options.

  Returns true.

  \sa CalcOptions
*/
bool
Polymer::calculateMasses(Polymer *polymer_p,
                         const CalcOptions &calc_options,
                         double *mono,
                         double *avg,
                         bool reset)
{

  Q_ASSERT(polymer_p != nullptr);
  Q_ASSERT(mono != nullptr && avg != nullptr);

  int ret = 0;

  if(reset)
    {
      // Reset the masses to 0.
      *mono = 0;
      *avg  = 0;
    }

  // The calc_options parameter holds a CoordinateList instance
  // listing all the coordinates of the different(if any) region
  // selections of the polymer sequence. This CoordinateList is
  // never empty, as it should at least contain the pseudo-selection
  // of the sequence, that is [start of sequence, cursor index] or
  // the [-1, -1] values for whole sequence mass
  // calculation. Iterate in this CoordinateList and for each item
  // call this function.

  Q_ASSERT(calc_options.coordinateList().size());

  // For each Coordinates item in the calc_options.coordinateList()
  // list of such items, perform the mass calculation.


  for(int iter = 0; iter < calc_options.coordinateList().size(); ++iter)
    {
      // New coordinates instance we are iterating into.
      Coordinates coordinates(*(calc_options.coordinateList().at(iter)));

      qDebug() << "Iterating in Coordinates:" << coordinates.indicesAsText();

      // If the start value is less than 0(typically it is set to
      // -1) then set it to 0(the first monomer of the sequence.

      if(coordinates.start() < 0)
        coordinates.setStart(0);

      // If the end value is less than 0(typically it is set to -1)
      // then set it to be the size of the polymer so that all the
      // protein is taken into account in the calculation.

      if(coordinates.end() < 0)
        coordinates.setEnd(polymer_p->size() - 1);

      // If the end value is greater than the polymer size, set it
      // to the polymer size.
      if(coordinates.end() >= polymer_p->size())
        coordinates.setEnd(polymer_p->size() - 1);

      // First account for the residual chain masses.

      qDebug() << "calculateMasses: accounting for residual chain indices"
               << "[" << coordinates.start() << "--" << coordinates.end()
               << "]";

      for(int jter = coordinates.start(); jter <= coordinates.end(); ++jter)
        {
          qDebug() << "Going to call at() with value" << jter;

          Monomer *monomer = const_cast<Monomer *>(polymer_p->at(jter));

          if(calc_options.isDeepCalculation())
            monomer->calculateMasses(calc_options.monomerEntities());

          monomer->accountMasses(mono, avg);
        }
    }

  // Even if we are not in the residual chain loop, we have to account
  // for the crossLinks, if so requires it. The crossLinks are a
  // monomer chemical entity, but because it is unpractical to
  // calculate their ponderable contribution in the loop above, we
  // deal with them here. This is difficult stuff. In fact, the
  // crossLinks, which in reality belong to at least two monomers
  //(monomers can be engaged in more than a crossLink), are not
  // stored as properties in the monomers(contrary to monomer
  // modifications, for example). The crossLinks are stored in a list
  // of such instances in the polymer(m_crossLinkList of CrossLink
  // pointers). Now, the point is: if one of the monomers of a
  // crossLink is selected but not the other partners, then what
  // should be do about that crossLink accounting ?

  if(calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
    {
      // qDebug() << "Cross-links are to be accounted for.";

      // We have to take into account the crossLinks. Hmmm... hard
      // task. The calculation is to be performed for the sequence
      // stretch from localStart to localEnd. We can iterate in the
      // crossLink list and for each crossLink check if it involves
      // monomers that *all* are contained in the sequence stretch
      //(or sequence stretches, that is a number of Coordinates
      // items in the calc_options.coordinateList()) we're
      // calculating the mass of. If at least one monomer of any
      // crossLink is not contained in the [localStart--localEnd]
      // sequence stretch, than increment a count variable and do
      // not account the mass.

      const CrossLinkList &crossLinkList = polymer_p->crossLinkList();

      int crossLinkPartial = 0;

      for(int jter = 0; jter < crossLinkList.size(); ++jter)
        {
          CrossLink *crossLink = crossLinkList.at(jter);

          ret = crossLink->encompassedBy(calc_options.coordinateList());

          if(ret == CROSS_LINK_ENCOMPASSED_FULL)
            {
              // qDebug() << "CrossLink at iter:" << jter
              //<< "is fully encompassed: accounting its masses.";

              // The crossLink is fully encompassed by our monomer
              // stretch, so we should take it into account.

              ret = crossLink->accountMasses(mono, avg);

              Q_ASSERT(ret);
            }
          else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
            {
              // qDebug()
              //<< "CrossLink at iter:" << jter
              //<< "is only partially encompassed: not accounting its"
              //  "masses.";

              ++crossLinkPartial;
            }
          else
            {
              // qDebug()
              //<< "CrossLink at iter:" << jter
              //<< "is not encompassed at all: not accounting its masses.";
            }
        }

      emit(polymer_p->crossLinksPartiallyEncompassedSignal(crossLinkPartial));
    }

  // We now have to account for the left/right cappings. However,
  // when there are multiple region selections(that is multiple
  // Coordinate elements in the calc_options.coordinateList()) it is
  // necessary to know if the user wants each of these Coordinates
  // to be considered real oligomers(each one with its left/right
  // caps) or as residual chains. Thus there are two cases:

  // 1. Each Coordinates item should be considered an oligomer
  //(SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
  // the left and right caps should be accounted for. This is
  // typically the case when the user selects multiple regions to
  // compute the mass of cross-linked oligomers.

  // 2. Each Coordinates item should be considered a residual chain
  //(SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
  // one item should see its left and right caps accounted for. This
  // is typically the case when the user selects multiple regions
  // like it would select repeated sequence elements in a polymer
  // sequence: all the regions selected are treated as a single
  // oligomer.

  // Holds the number of times the chemical entities are to be
  // accounted for.
  int times = 0;

  if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS)
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "SELECTION_TYPE_RESIDUAL_CHAINS";

      times = 1;
    }
  else
    {
      // 	qDebug() << __FILE__ << __LINE__
      // 		  << "SELECTION_TYPE_OLIGOMERS";

      times = calc_options.coordinateList().size();
    }

  // Account for the left and right cap masses, if so required.
  if(calc_options.capping() & CAP_LEFT)
    {
      ret =
        Polymer::accountCappingMasses(polymer_p, CAP_LEFT, mono, avg, times);
      Q_ASSERT(ret);
    }

  if(calc_options.capping() & CAP_RIGHT)
    {
      ret =
        Polymer::accountCappingMasses(polymer_p, CAP_RIGHT, mono, avg, times);

      Q_ASSERT(ret);
    }

  // Account for the left and right modification masses, if so
  // required and the region(s) require(s) it: we have to make it
  // clear if the selection encompasses indices 0(left end) and/or
  // polymerSize-1(right end).

  // Note that if we are force to take into account either or both
  // the left/right end modif, then even if the selected region does
  // not encompass the end(s), their modif(s) must be taken into
  // account.

  if(calc_options.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF)
    {
      if(calc_options.polymerEntities() & POLYMER_CHEMENT_FORCE_LEFT_END_MODIF)
        {
          ret = Polymer::accountEndModifMasses(
            polymer_p, POLYMER_CHEMENT_LEFT_END_MODIF, mono, avg);

          Q_ASSERT(ret);
        }
      else
        {
          if(calc_options.coordinateList().encompassIndex(0))
            {
              ret = Polymer::accountEndModifMasses(
                polymer_p, POLYMER_CHEMENT_LEFT_END_MODIF, mono, avg);

              Q_ASSERT(ret);
            }
        }
    }

  if(calc_options.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF)
    {
      if(calc_options.polymerEntities() & POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF)
        {
          ret = Polymer::accountEndModifMasses(
            polymer_p, POLYMER_CHEMENT_RIGHT_END_MODIF, mono, avg);

          Q_ASSERT(ret);
        }
      else
        {
          if(calc_options.coordinateList().encompassIndex(polymer_p->size() -
                                                          1))
            {
              ret = Polymer::accountEndModifMasses(
                polymer_p, POLYMER_CHEMENT_RIGHT_END_MODIF, mono, avg);

              Q_ASSERT(ret);
            }
        }
    }

  //   qDebug() <<__FILE__ << __LINE__
  // 	    << "CalculateMasses Mono:"
  // 	    << polymer->mono()
  // 	    << "Avg:"
  // 	    << polymer->avg();

  return true;
}

/*!
  \brief Accounts for the mass of the end caps.

  The polymer sequence is actually a chain of monomers (that is, residues). In
order to compute the mass of the polymer in its finished state, it is necessary
to add the masses of its end caps (typically, a proton and a hydroxyl group in
protein chemistry, respectively capping the N-terminus and the C-terminus).

  The mass of the the left end is added to the monoisotopic and average masses
of this polymer if (\a how & CAP_LEFT). The mass of the the right end is added
to the monoisotopic and average masses of this polymer if (\a how & CAP_RIGHT).

  The masses of the caps are multiplied by \a times before accounting them to
this polymer's masses.

  Returns true.
*/
bool
Polymer::accountCappingMasses(int how, int times)
{
  return accountCappingMasses(this, how, &m_mono, &m_avg, times);
}


/*!
  \brief Accounts for the \a{polymer}'s masses of its end caps to \a mono and
\a avg.

  The polymer sequence is actually a chain of monomers (that is, residues). In
order to compute the mass of the polymer in its finished state, it is necessary
to add the masses of its end caps (typically, a proton and a hydroxyl group in
protein chemistry, respectively capping the N-terminus and the C-terminus).

  The mass of the the left end is added to the \a mono and \a avg
masses if (\a how & CAP_LEFT). The mass of the the right end is added
to the \a mono and \a avg masses if (\a how & CAP_RIGHT).

  The masses of the caps are multiplied by \a times before accounting them to
\a mono and \a avg

  \a mono and \a avg cannot be nullptr. \a polymer cannot be nullptr.
  Returns true.
*/
bool
Polymer::accountCappingMasses(
  Polymer *polymer, int how, double *mono, double *avg, int times)
{
  Q_ASSERT(polymer);
  Q_ASSERT(mono && avg);

  PolChemDefCstSPtr polChemDef = polymer->getPolChemDefCstSPtr();

  IsotopicDataCstSPtr isotopic_data_csp = polChemDef->getIsotopicDataCstSPtr();

  Formula formula;

  if(how & CAP_LEFT)
    {
      formula = polChemDef->leftCap();
    }
  else if(how & CAP_RIGHT)
    {
      formula = polChemDef->rightCap();
    }
  else if(how & CAP_NONE)
    return true;
  else
    Q_ASSERT(0);

  if(!formula.accountMasses(isotopic_data_csp, mono, avg, times))
    return false;

  return true;
}

/*!
  \brief Accounts for this polymer's end modifications masses as defined by \a
how.

  The left end's modification masses are accounted for in this polymer if (how &
POLYMER_CHEMENT_LEFT_END_MODIF), and the right end's are if (how &
POLYMER_CHEMENT_RIGHT_END_MODIF).

  Returns true upon success, false otherwise.
*/
bool
Polymer::accountEndModifMasses(int how)
{
  return accountEndModifMasses(this, how, &m_mono, &m_avg);
}

/*!
  \brief Accounts for the \a{polymer}'s end modifications masses as defined by
\a how.

  The masses are accounted for into \a ponderable without resetting its masses.
If (how & POLYMER_CHEMENT_LEFT_END_MODIF), the left end modification masses
are accounted for and the right end's are if (how
& POLYMER_CHEMENT_RIGHT_END_MODIF).
*/
bool
Polymer::accountEndModifMasses(Polymer *polymer,
                               int how,
                               Ponderable *ponderable)
{
  Q_ASSERT(polymer);
  Q_ASSERT(ponderable);

  return accountEndModifMasses(
    polymer, how, &ponderable->rmono(), &ponderable->ravg());
}

/*!
  \brief Accounts for the \a{polymer}'s end modifications masses as defined by
\a how.

  The masses are accounted for into \a mono and \a avg without
resetting these masses. If \c{(how & POLYMER_CHEMENT_LEFT_END_MODIF)}, the left
end
modification masses are accounted for and the right end's are if \c{(how&
POLYMER_CHEMENT_RIGHT_END_MODIF)}.

  Returns true upon success and false otherwise.
*/
bool
Polymer::accountEndModifMasses(Polymer *polymer,
                               int how,
                               double *mono,
                               double *avg)
{
  Q_ASSERT(polymer);
  Q_ASSERT(mono && avg);

  // Make a safe copy of the polymer's left/right end modif and use it
  // for doing the calculation INTO the 'mono' and 'avg' variables.

  if(how & POLYMER_CHEMENT_LEFT_END_MODIF)
    {
      Modif modif(polymer->leftEndModif());

      if(!modif.accountMasses(mono, avg))
        return false;
    }
  if(how & POLYMER_CHEMENT_RIGHT_END_MODIF)
    {
      Modif modif(polymer->rightEndModif());

      if(!modif.accountMasses(mono, avg))
        return false;
    }

  return true;
}

/*!
  \brief Performs the actual cross-linking as described in \a cross_link_p.

  The chemical representation of the cross-link must have been performed in \a
cross_link_p.

  Returns true upon success or false if the CrossLink does not validate
successfully.
*/
bool
Polymer::crossLink(CrossLink *cross_link_p)
{
  Q_ASSERT(cross_link_p);

  // This function must be called once all the members taking part
  // into the crossLink have been set.

  if(!cross_link_p->validate())
    return false;

  // OK, from the perspective of the chemical modification of the
  // monomers involved in the crosslink, everything is fine.

  // Now is the moment that we actually perform the crossLink : this
  // is done simply by adding *this crossLink to the list of
  // crossLinks that belongs to the polymer.

  mpa_crossLinkList->append(cross_link_p);

  // If the crossLink dialog is open, inform it that it can refresh
  // the data.
  emit(crossLinkChangedSignal(this));

  return true;
}


/*!
  \brief Undoes the cross-link  \a cross_link_p.

  Returns true upon success or false if the CrossLink does not validate
successfully.
*/
bool
Polymer::uncrossLink(CrossLink *cross_link_p)
{
  Q_ASSERT(cross_link_p);

  if(!cross_link_p->validate())
    return false;

  mpa_crossLinkList->removeAt(mpa_crossLinkList->indexOf(cross_link_p));

  delete cross_link_p;
  cross_link_p = 0;

  // If the crossLink dialog is open, inform it that it can refresh
  // the data.
  emit(crossLinkChangedSignal(this));

  return true;
}

/*!
  \brief Determines the element composition of this polymer.

  The elemental composition is performed by looking into the core chemical
entities of the polymer, like the monomers, the modifications, but also by
accounting for the IonizeRule \a ionize_rule, and the CalcOptions \a
calc_options.

  The polymer sequence is accounted for by looking at the \a coordinate_list
list of Coordinates.

  Returns the elemental composition.

  \sa Coordinates, CoordinateList, IonizeRule
*/
QString
Polymer::elementalComposition(const IonizeRule &ionize_rule,
                              const CoordinateList &coordinate_list,
                              const CalcOptions &calc_options)
{
  Formula formula;

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  // Iterate in all the oligomers that are encompassed in the
  // selection.

  for(int iter = 0; iter < coordinate_list.size(); ++iter)
    {
      // New coordinates instance we are iterating into.
      Coordinates *coordinates = coordinate_list.at(iter);

      for(int jter = coordinates->start(); jter < coordinates->end() + 1;
          ++jter)
        {
          const Monomer *iterMonomer = at(jter);
          Q_ASSERT(iterMonomer);

          // Set the formula of the new monomer in the same formula
          // instance.
          formula.setFormula(iterMonomer->formula());

          // Incrementally account for the new formula in the same
          // atomcount list in the formula.
          if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
            qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

          if(calc_options.monomerEntities() & MONOMER_CHEMENT_MODIF &&
             iterMonomer->isModified())
            {
              for(int jter = 0; jter < iterMonomer->modifList()->size(); ++jter)
                {
                  Modif *modif = iterMonomer->modifList()->at(jter);

                  formula.setFormula(modif->formula());

                  // Incrementally account for the new formula in the same
                  // atomcount list in the formula.
                  if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
                    qFatal(
                      "Fatal error at %s@%d. "
                      "Aborting.",
                      __FILE__,
                      __LINE__);
                }
            }
        }
      // End of for (int jter = m_startIndex ; jter < m_endIndex + 1;
      // ++jter)
    }

  //     qDebug() << __FILE__ << __LINE__
  // 	      << "Formula after accounting for all the residual chains:"
  // 	      << formula.elementalComposition();

  // We now have to account for the left/right cappings. However,
  // when there are multiple region selections(that is multiple
  // Coordinate elements in the calc_options.coordinateList()) it is
  // necessary to know if the user wants each of these Coordinates
  // to be considered real oligomers(each one with its left/right
  // caps) or as residual chains. Thus there are two cases:

  // 1. Each Coordinates item should be considered an oligomer
  //(SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
  // the left and right caps should be accounted for. This is
  // typically the case when the user selects multiple regions to
  // compute the mass of cross-linked oligomers.

  // 2. Each Coordinates item should be considered a residual chain
  //(SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
  // one item should see its left and right caps accounted for. This
  // is typically the case when the user selects multiple regions
  // like it would select repeated sequence elements in a polymer
  // sequence: all the regions selected are treated as a single
  // oligomer.

  // Holds the number of times the chemical entities are to be
  // accounted for.
  int times = 0;

  if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS)
    {
      times = 1;
      // qDebug() << __FILE__ << __LINE__
      //          << "SELECTION_TYPE_RESIDUAL_CHAINS ; times:" << times;
    }
  else
    {
      times = calc_options.coordinateList().size();

      // qDebug() << __FILE__ << __LINE__
      //          << "SELECTION_TYPE_OLIGOMERS ; times:" << times;
    }

  // Account for the left and right cap masses, if so required.

  if(calc_options.capping() & CAP_LEFT)
    {
      formula.setFormula(mcsp_polChemDef->leftCap());

      // Incrementally account for the new formula in the same
      // atomcount list in the formula.
      if(formula.accountSymbolCounts(isotopic_data_csp, times) == false)
        qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

      // qDebug() << __FILE__ << __LINE__
      //          << "Formula after accounting left cap:"
      //          << formula.elementalComposition();
    }

  if(calc_options.capping() & CAP_RIGHT)
    {
      formula.setFormula(mcsp_polChemDef->rightCap());

      // Incrementally account for the new formula in the same
      // atomcount list in the formula.
      if(formula.accountSymbolCounts(isotopic_data_csp, times) == false)
        qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

      // qDebug() << __FILE__ << __LINE__
      //          << "Formula after accounting right cap:"
      //          << formula.elementalComposition();
    }

  // Account for the left and right modification masses, if so
  // required and the region(s) require(s) it: we have to make it
  // clear if the selection encompasses indices 0(left end) and/or
  // polymerSize-1(right end).

  if(calc_options.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF)
    {
      if(coordinate_list.encompassIndex(0))
        {
          Modif modif = leftEndModif();

          formula.setFormula(modif.formula());

          // qDebug() << __FILE__ << __LINE__
          //          << "Accounting for left end modif:"
          //          << modif.name();

          // Incrementally account for the new formula in the same
          // atomcount list in the formula.
          if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
            qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

          // qDebug() << __FILE__ << __LINE__
          //          << "Formula after accounting left end modif:"
          //          << formula.elementalComposition();
        }
    }

  if(calc_options.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF)
    {
      if(coordinate_list.encompassIndex(size() - 1))
        {
          Modif modif = rightEndModif();

          formula.setFormula(modif.formula());

          // 	    qDebug() << __FILE__ << __LINE__
          // 		      << "Accounting for right end modif:"
          // 		      << modif.name();

          // Incrementally account for the new formula in the same
          // atomcount list in the formula.
          if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
            qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

          // qDebug() << __FILE__ << __LINE__
          //          << "Formula after accounting right end modif:"
          //          << formula.elementalComposition();
        }
    }

  // At this point we should not forget if the user asks to take into
  // account the cross-links... However, BE CAREFUL that cross-links
  // can only be taken into account if all the partners of a given
  // cross-link are actually encompassed into the selection.

  if(calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
    {

      for(int iter = 0; iter < crossLinkList().size(); ++iter)
        {
          CrossLink *crossLink = crossLinkList().at(iter);

          if(crossLink->encompassedBy(coordinate_list) ==
             CROSS_LINK_ENCOMPASSED_FULL)
            {
              // The crossLink is fully encompassed by our monomer
              // stretch, so we should take it into account.

              // qDebug() << __FILE__ << __LINE__
              //          << "Accounting for fully encompassed cross-link:"
              //          << crossLink->name();

              if(!crossLink->formula().isEmpty())
                {
                  formula.setFormula(crossLink->formula());

                  // qDebug() << __FILE__ << __LINE__
                  //          << "Cross-link formula:" <<
                  //          crossLink->formula();

                  // Incrementally account for the new formula in the same
                  // atomcount list in the formula.
                  if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
                    qFatal(
                      "Fatal error at %s@%d. "
                      "Aborting.",
                      __FILE__,
                      __LINE__);
                }

              // And now each modification that belongs to the
              // crosslinker.

              for(int jter = 0; jter < crossLink->modifList().size(); ++jter)
                {
                  QString iterFormulaString =
                    crossLink->modifList().at(jter)->formula();

                  // qDebug() << __FILE__ << __LINE__
                  //          << "Cross-link's modif formula:"
                  //          << iterFormulaString;

                  formula.setFormula(iterFormulaString);

                  // Incrementally account for the new formula in the same
                  // atomcount list in the formula.
                  if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
                    qFatal(
                      "Fatal error at %s@%d. "
                      "Aborting.",
                      __FILE__,
                      __LINE__);
                }
            }
          // End of
          // if (ret == CROSS_LINK_ENCOMPASSED_FULL)
        }
      // End of
      // for (int iter = 0; iter < crossLinkList->size(); ++iter)
    }
  // End of
  // if (calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)


  // The ionization rule. Do not forget to take into account the
  // level!
  formula.setFormula(ionize_rule.formula());

  // Incrementally account for the new formula in the same
  // atomcount list in the formula.
  if(formula.accountSymbolCounts(isotopic_data_csp, ionize_rule.level()) ==
     false)
    qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);

  // qDebug() << __FILE__ << __LINE__
  //          << "Formula after accounting ionization: "
  //          << formula.elementalComposition();

  return formula.elementalComposition();
}

/*!
  \brief Parses the XML \a element representing a sequence of monomer codes.

  We are getting this: \c{<codes>MEFEEGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>}
about which we need to create \l{Monomer}s and add them to this polymer's
\l{Sequence}.

  Returns true if parsing and conversion of the text to a monomer list
  were successful, false otherwise.

  \sa Sequence::makeMonomerList()
  */
bool
Polymer::renderXmlCodesElement(const QDomElement &element)
{
  QString sequence;

  // We are getting this:
  //  <codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
  // We have to make monomers and add them to the list of monomers.

  if(element.tagName() != "codes")
    {
      qDebug() << "Expected codes element not found.";
      return false;
    }

  m_monomerText = element.text();

  // qDebug() << "Now rendering the <codes> element, that is the sequence:"
  //<< m_monomerText;

  if(makeMonomerList(mcsp_polChemDef, false) == -1)
    {
      qDebug() << "Failed to make the monomer list.";
      return false;
    }
  else
    return true;
}

/*!
  \brief Extracts the name of the polymer chemistry definition from the \a
file_path polymer sequence file.

  Returns the polymer chemistry definition name.
  */
QString
Polymer::xmlPolymerFileGetPolChemDefName(const QString &file_path)
{
  QDomDocument doc("polSeqData");
  QDomElement element;
  QDomElement child;
  QDomElement indentedChild;

  QFile file(file_path);

  /*
     <polseqdata>
     <polchemdef_name>protein</polchemdef_name>
     ...
     */

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

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

  file.close();

  element = doc.documentElement();

  if(element.tagName() != "polseqdata")
    {
      qDebug() << "Polymer sequence file is erroneous\n";
      return QString("");
    }

  // <polchemdef_name>
  child = element.firstChildElement();
  if(child.tagName() != "polchemdef_name")
    return QString("");

  return child.text();
}


/*!
  \brief Extracts from \a element, using the proper function (\a version), the
polymer end modification.

  The \a element tag is found in the polymer sequence XML file.

  If the \a element tag name is \c{le_modif}, the modification name is set to
the left end modification of this polymer sequence; if the tag name is
\c{re_modif}, the right end of this polymer is modifified. The modifications
are then rendered in place.

  Returns true if no error was encountered, false otherwise.

  \sa Modif::renderXmlMdfElement()
*/
bool
Polymer::renderXmlPolymerModifElement(const QDomElement &element, int version)
{
  if(element.tagName() != "le_modif" && element.tagName() != "re_modif")
    return false;

  if(version == 1)
    {
      // no-op
      version = 1;
    }

  QDomElement child;

  if(element.tagName() == "le_modif")
    {
      // Go down to the <mdf> element.
      child = element.firstChildElement();

      if(child.isNull())
        return true;

      if(!m_leftEndModif.renderXmlMdfElement(child, version))
        return false;
      else
        return true;
    }
  else if(element.tagName() == "re_modif")
    {
      // Go down to the <mdf> element.
      child = element.firstChildElement();

      if(child.isNull())
        return true;

      if(!m_rightEndModif.renderXmlMdfElement(child, version))
        return false;
      else
        return true;
    }

  return false;
}


/*!
  \brief Extracts from \a element, using the proper function (\a version),
all the \l{CrossLink}s contained in it.

  Each cross-link is rendered apart and applied to this polymer.

  Returns true if no error was encountered, false otherwise.

  \sa crossLink()
*/
bool
Polymer::renderXmlCrossLinksElement(const QDomElement &element, int version)
{
  if(version == 1)
    {
      // no-op
      version = 1;
    }

  QDomElement child;
  QDomElement indentedChild;

  // element is <crosslinks>

  //   <crosslinks>
  //     <crosslink>
  //       <name>DisulfideBond</name>
  //       <targets>;2;6;</targets>
  //     </crosslink>
  //   </crosslinks>

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

  child = element.firstChildElement();

  // There can be any number of <crosslink> elements.
  while(!child.isNull())
    {
      if(child.tagName() != "crosslink")
        return false;

      indentedChild = child.firstChildElement();

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

      // We actually do have a <crosslink> element, so we can allocate
      // one now.

      // qDebug() << "Rendering a polymer sequence CrossLink by name:"
      //<< indentedChild.text();

      CrossLink *aCrossLink = new CrossLink(
        mcsp_polChemDef, this, indentedChild.text(), "NOT_SET", "NOT_SET");

      // And now find in the polymer chemistry definition the right
      // crossLinker and copy it into our newly allocated one.

      if(!mcsp_polChemDef->referenceCrossLinkerByName(
           indentedChild.text(), static_cast<CrossLinker *>(aCrossLink)))
        {
          delete aCrossLink;
          return false;
        }

      // At this point the crossLinker superclass of crossLink is
      // updated with the ref one.

      indentedChild = indentedChild.nextSiblingElement();

      if(indentedChild.tagName() != "targets")
        {
          delete aCrossLink;
          return false;
        }

      if(aCrossLink->populateMonomerList(indentedChild.text()) == -1)
        {
          delete aCrossLink;
          return false;
        }

      indentedChild = indentedChild.nextSiblingElement();

      if(!indentedChild.isNull())
        {
          if(indentedChild.tagName() != "comment")
            {
              delete aCrossLink;
              return false;
            }
        }

      // At this point the crossLink element is finished rendering,
      // all we have to do is perform the crossLink proper.

      if(!crossLink(aCrossLink))
        {
          delete aCrossLink;
          return false;
        }

      child = child.nextSiblingElement();
    }

  return true;
}

/*!
  \brief Parses the \a file_path polymer sequence file.

  During parsing, the encountered data are set to this polymer. This parsing is
called "rendering".

  Returns true if parsing succeeded, false otherwise.
  */
bool
Polymer::renderXmlPolymerFile(QString file_path)
{
  QString localFilePath;

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

  Monomer *monomer = nullptr;

  /*
     <polseqdata>
     <polchemdef_name>protein</polchemdef_name>
     <name>Sample</name>
     <code>SP2003</code>
     <author>rusconi</author>
     <datetime>1967-09-224:09:23</datetime>
     */

  if(file_path.isEmpty())
    localFilePath = m_filePath;
  else
    localFilePath = file_path;

  QFile file(localFilePath);

  if(!file.open(QIODevice::ReadOnly))
    {
      qDebug() << "Could not open file.";
      return false;
    }

  if(!doc.setContent(&file))
    {
      qDebug() << "Failed to set file contents to doc object.";
      file.close();
      return false;
    }

  file.close();

  element = doc.documentElement();

  if(element.tagName() != "polseqdata")
    {
      qDebug() << "Polymer sequence 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() << "Polymer sequence file has bad version number: " << version;

      return false;
    }

  // <polchemdef_name>
  child = element.firstChildElement();
  if(child.tagName() != "polchemdef_name")
    return false;
  //    mcsp_polChemDef->setName(child.text());

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

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

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

  // <datetime>
  child = child.nextSiblingElement();
  if(child.tagName() != "datetime")
    return false;
  m_dateTime = QDateTime::fromString(child.text(), "yyyy-MM-dd:mm:ss");

  // <polseq>
  child = child.nextSiblingElement();

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

  /*
     <polseq>
     <codes>MEFEEDF</codes>
     <monomer>
     <code>S</code>
     <prop>
     <name>MODIF</name>
     <data>Phosphorylation</data>
     </prop>
     </monomer>
     <codes>GRKDKNFLKMGRK</codes>
     </polseq>
     <le_modif>
     <mdf>
     <name>Acetylation</name>
     <formula>-H+C2H3O</formula>
     <targets>*</targets>
    <maxcount>1</maxcount>
     </mdf>
     </le_modif>
     <re_modif>
     <mdf>
     <name>Phosphorylation</name>
     <formula>-H+H2PO3</formula>
     <targets>*</targets>
     <maxcount>1</maxcount>
     </mdf>
     </re_modif>
     */

  // There can be any number of <codes> and <monomer> elements, in
  // whatever order.

  indentedChild = child.firstChildElement();

  while(!indentedChild.isNull())
    {
      if(indentedChild.tagName() == "codes")
        {
          if(!renderXmlCodesElement(indentedChild))
            {
              qDebug() << "Failed to render the XML codes element.";
              return false;
            }
        }
      else if(indentedChild.tagName() == "monomer")
        {
          monomer = new Monomer(mcsp_polChemDef, "NOT_SET");

          if(!monomer->renderXmlMonomerElement(indentedChild, version))
            {
              qDebug() << "Failed to render the XML monomer element.";
              delete monomer;

              return false;
            }
          m_monomerList.append(monomer);
        }
      else
        return false;

      indentedChild = indentedChild.nextSiblingElement();
    }

  //  Go on to the next element(has to be <le_modif>.

  QString error;

  child = child.nextSiblingElement();

  if(child.tagName() != "le_modif")
    error = "Expected le_modif element not found.";

  if(!renderXmlPolymerModifElement(child, version))
    error = "Failed to render the left end modif element.";

  // Go on to the next element(has to be <re_modif>.

  child = child.nextSiblingElement();

  if(child.tagName() != "re_modif")
    error = "Expected re_modif element not found.";

  if(!renderXmlPolymerModifElement(child, version))
    error = "Failed to render the right end modif element.";

  // Go on to the next element(has to be <crosslinks>.

  child = child.nextSiblingElement();

  if(child.tagName() != "crosslinks")
    error = "Expected crosslinks element not found.";

  if(!renderXmlCrossLinksElement(child, version))
    error = "Failed to render the crosslinks element.";

  if(!error.isEmpty())
    {
      qDebug() << "Rendering of XML file failed with error:" << error;
      return false;
    }

  setFilePath(localFilePath);

  // qDebug() << "Finished rendering the XML file, returning true.";

  return true;
}


/*!
  \brief Creates the XML DTD for a polymer sequence file.

  Returns The DTD in a dynamically allocated string.
 */
QString *
Polymer::formatXmlDtd()
{
  QString *string = new QString(
    "<?xml version=\"1.0\"?>\n"
    "<!-- DTD for polymer sequences, used by the\n"
    "'massXpert' mass spectrometry application.\n"
    "Copyright 2006,2007,2008 Filippo Rusconi - Licensed under "
    "the GNU GPL -->\n"
    "<!DOCTYPE polseqdata [\n"
    "<!ELEMENT polseqdata "
    "(polchemdef_name,name,code,author,datetime,polseq,le_modif,"
    "re_modif,crosslinks,prop*)>\n"
    "<!ATTLIST polseqdata version NMTOKEN #REQUIRED>\n"
    "<!ELEMENT polchemdef_name (#PCDATA)>\n"
    "<!ELEMENT mdf (name,formula,targets)>\n"
    "<!ELEMENT name (#PCDATA)>\n"
    "<!ELEMENT formula (#PCDATA)>\n"
    "<!ELEMENT targets (#PCDATA)>\n"
    "<!ELEMENT code (#PCDATA)>\n"
    "<!ELEMENT author (#PCDATA)>\n"
    "<!ELEMENT datetime (#PCDATA)>\n"
    "<!ELEMENT polseq (codes|monomer)*>\n"
    "<!ELEMENT le_modif (mdf?)>\n"
    "<!ELEMENT re_modif (mdf?)>\n"
    "<!ELEMENT codes (#PCDATA)>\n"
    "<!ELEMENT crosslink (name,targets)>\n"
    "<!ELEMENT crosslinks (crosslink*)>\n"
    "<!ELEMENT monomer (code, mdf*)>\n"
    "<!ELEMENT prop (name, data+)>\n"
    "<!ATTLIST data type (str | int | dbl) \"str\">\n"
    "<!ELEMENT data (#PCDATA)>\n"
    "]>\n");

  return string;
}


/*!
  \brief Writes this polymer to file.

  Returns true if successful, false otherwise.
 */
bool
Polymer::writeXmlFile()
{
  QString *string = 0;
  QString indent("  ");


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

  QFile file(m_filePath);

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

      return false;
    }

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

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

  // Open the <polseqdata> element.
  //"<!ELEMENT polseqdata(polchemdef_name,name,code,
  // author,datetime,polseq,prop*)>\n"

  stream << QString("<polseqdata version=\"%1\">\n")
              .arg(POL_SEQ_FILE_FORMAT_VERSION);

  Q_ASSERT(!mcsp_polChemDef->name().isEmpty());
  stream << QString("%1<polchemdef_name>%2</polchemdef_name>\n")
              .arg(indent)
              .arg(mcsp_polChemDef->name());

  stream << QString("%1<name>%2</name>\n")
              .arg(indent)
              .arg(m_name.isEmpty() ? "Not Set" : m_name);

  stream << QString("%1<code>%2</code>\n")
              .arg(indent)
              .arg(m_code.isEmpty() ? "Not Set" : m_code);

  Q_ASSERT(!m_author.isEmpty());
  stream << QString("%1<author>%2</author>\n").arg(indent).arg(m_author);

  m_dateTime = QDateTime::currentDateTime();
  stream << QString("%1<datetime>%2</datetime>\n").arg(indent).arg(dateTime());

  string = formatXmlPolSeqElement(POL_SEQ_FILE_FORMAT_VERSION);

  if(string == 0)
    {
      qDebug() << "Failed to produce the <polseq> element string.";

      return false;
    }

  stream << *string;
  delete string;


  // Now deal with the polymer modifications. These are represented as
  // <mdf> elements.

  // Left end modif
  Q_ASSERT(!m_leftEndModif.name().isEmpty());

  stream << QString("%1<le_modif>\n").arg(indent);

  if(m_leftEndModif.name() != "NOT_SET")
    {
      string = m_leftEndModif.formatXmlMdfElement(POL_SEQ_FILE_FORMAT_VERSION);

      stream << *string;
      delete string;
    }

  stream << QString("%1</le_modif>\n").arg(indent);


  // Right end modif
  Q_ASSERT(!m_rightEndModif.name().isEmpty());

  stream << QString("%1<re_modif>\n").arg(indent);

  if(m_rightEndModif.name() != "NOT_SET")
    {
      string = m_rightEndModif.formatXmlMdfElement(POL_SEQ_FILE_FORMAT_VERSION);

      stream << *string;
      delete string;
    }

  stream << QString("%1</re_modif>\n").arg(indent);

  string = 0;
  string = formatXmlCrossLinksElement(POL_SEQ_FILE_FORMAT_VERSION);

  if(!string)
    {
      qDebug() << "Failed to produce the <crosslinks> element string.";

      return false;
    }

  stream << *string;
  delete string;

  // Note that at some point, there might be any number of polymer
  // <prop> elements at this place...


  // Finally close the polseqdata.

  stream << QString("</polseqdata>\n");

  return true;
}


/*!
  \brief Formats this polymer's sequence as a string suitable to use as an XML
element.

  This function generates a string holding all the elements pertaining to this
polymer' \e sequence (the list of
  monomers, potentially modified, \e not all the other data). The typical
element that is generated in this function looks like this:

  \code
  <polseq>
  <codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
  <monomer>
  <code>S</code>
  <mdf>
  <name>Phosphorylation</name>
  <formula></formula>
  <targets>*</targets>
  </mdf>
  </monomer>
  </polseq>
  \endcode

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

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

  \sa writeXmlFile()
  */
QString *
Polymer::formatXmlPolSeqElement(int offset, const QString &indent)
{
  int newOffset;
  int iter = 0;

  QString lead("");
  QString codesString("");
  QString *monomerString = 0;
  QString *string        = new QString();

  const Monomer *monomer = 0;


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


  // At this point, we have to iterate in the sequence. If the
  // monomers are not modified, then put their codes in a raw, like
  // "ETGSH", in a <codes> element. As soon as a monomer is modified,
  // whatever the modification --that is, it has a prop object in its
  // --m_propList, it and its contents should be listed in a detailed
  // <monomer> element.

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

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

  // Iterate in the polymer sequence.

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

      // Check if the monomer is modified. If not, we just append its
      // code to the elongating codesString, else we use a more
      // thorough monomer element-parsing function.

      if(!monomer->isModified())
        {
          codesString += monomer->code();
          continue;
        }
      else
        {
          // If something was baking in codesString, then we have to
          // create the element right now, fill the data in it and
          // close it before opening one <monomer> element below.

          if(!codesString.isEmpty())
            {
              *string += QString("%1<codes>%2%3")
                           .arg(lead)
                           .arg(codesString)
                           .arg("</codes>\n");

              codesString.clear();
            }

          monomerString = monomer->formatXmlMonomerElement(newOffset);

          if(!monomerString)
            {
              delete string;
              return 0;
            }
          else
            {
              *string += *monomerString;
            }
        }
    }

  // If something was baking in codesString, then we have to
  // create the element right now, fill the data in it and
  // close it before opening one <monomer> element below.

  if(!codesString.isEmpty())
    {
      *string +=
        QString("%1<codes>%2%3").arg(lead).arg(codesString).arg("</codes>\n");

      codesString.clear();
    }


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

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

  return string;
}

/*!
  \brief Formats an XML element suitable to describe the \c <crosslinks>
element.

  Iterates in the cross-link list of this polymer and crafts XML elements
describing them.

  The XML element looks like this:

  \code
  <crosslinks>
    <crosslink>
      <name>DisulfideBond</name>
      <targets>;2;6;</targets>
    </crosslink>
  </crosslinks>
  \endcode

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

  Returns the XML element as a dynamically allocated string.
*/
QString *
Polymer::formatXmlCrossLinksElement(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;
    }

  // This is the kind of string we have to generate.

  //   <crosslinks>
  //     <crosslink>
  //       <name>DisulfideBond</name>
  //       <targets>;2;6;</targets>
  //     </crosslink>
  //   </crosslinks>


  // At this point, we have to iterate in the list of crosslinks and
  // for each crosslink determine what's the crosslinker and which
  // monomer are actually crosslinked together.

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

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

  for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter)
    {
      CrossLink *crossLink = mpa_crossLinkList->at(jter);
      Q_ASSERT(crossLink);

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

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

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

      // Create the string with all the monomer indices(which are the
      // targets of the crossLink).

      *string += QString("%1<targets>%2</targets>\n")
                   .arg(lead)
                   .arg(crossLink->monomerIndexText());

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

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

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

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

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

  return string;
}

/*!
  \brief Validates the Sequence of this polymer.

  Returns true if validation was successful, false, otherwise.

  \sa Sequence::validate()
*/
bool
Polymer::validate()
{
  return Sequence::validate(mcsp_polChemDef);
}


/*! Outputs a string describing the polymer using qDebug().

 Used for debugging purposes.
  */
void
Polymer::debugPutStdErr()
{
  qDebug() << m_name << m_code << mcsp_polChemDef->name() << m_author
           << m_filePath << m_leftEndModif.name() << m_rightEndModif.name();


  for(int iter = 0; iter < m_monomerList.size(); ++iter)
    {
      qDebug() << m_monomerList.at(iter)->code();
    }
}

} // namespace libXpertMass

} // namespace MsXpS
