/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the MsXpertSuite project.
 *
 * The MsXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Local includes
#include "FragRule.hpp"
#include "PolChemDef.hpp"


namespace MsXpS
{

namespace libXpertMass
{


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

\brief The FragRule class provides a model for specifying gas phase
fragmentation rules for refining fragmentation specifications (\l FragSpec)
of \l{Oligomer} \l{Sequence}s.

Fragmentation rules characterize in more detail the chemical reaction that
governs the fragmentation of the polymer in the gas-phase. The rule is a
conditional rule. Its logic is based on the presence of specified monomers
right at the place of the fragmentation and before or after that precise
location.

In saccharide chemistry, fragmentations are a very complex topic. This is
because a given monomer will fragment according to a given chemistry if it is
preceded in the sequence by a monomer of a given identity and according to
another chemistry if its direct environment is different.

This paradigm is implemented using a sequence environment logic based on
conditions that can be formulated thanks to three monomer codes:

\list
\li The monomer at which the fragmentation takes place(current code);
\li The monomer preceeding the current code (previous code);
\li The monomer following the current code (following code);
\endlist

The use of these codes is typically according to this logic:

\e{If current monomer is Glu and that previous monomer is Gly and
following monomer is Arg, then fragmentation should occur according
to this formula : "-H2O"}.

\sa FragSpec
*/


/*!
\variable MsXpS::libXpertMass::FragRule::m_prevCode

\brief The \l Monomer code located before the actual fragmentation site.
*/


/*!
\variable MsXpS::libXpertMass::FragRule::m_currCode

\brief The \l Monomer code at the actual fragmentation site.
*/


/*!
\variable MsXpS::libXpertMass::FragRule::m_nextCode

\brief The \l Monomer code located after the actual fragmentation site.
*/


/*!
\variable MsXpS::libXpertMass::FragRule::m_comment

\brief A comment associated to the FragRule.
*/


/*!
\brief  Constructs a fragmentation rule.


  \a pol_chem_def_csp Polymer chemistry definition. Cannot be nullptr.

  \a name Name. Cannot be empty.

  \a prevCode Previous monomer code. Defaults to the null string.

  \a currCode  Current monomer code. Defaults to the null string.

  \a nextCode Next monomer code. Defaults to the null string.

  \a formula Formula. Defaults to the null string.

  \a comment Comment. Defaults to the null string.
*/
FragRule::FragRule(PolChemDefCstSPtr pol_chem_def_csp,
                   QString name,
                   QString prevCode,
                   QString currCode,
                   QString nextCode,
                   QString formula,
                   const QString &comment)
  : PolChemDefEntity(pol_chem_def_csp, name),
    Formula(formula),
    m_prevCode(prevCode),
    m_currCode(currCode),
    m_nextCode(nextCode),
    m_comment(comment)
{
}


/*!
\brief  Constructs a FragRule instance as a copy of \a other.
 */
FragRule::FragRule(const FragRule &other)
  : PolChemDefEntity(other),
    Formula(other),
    m_prevCode(other.m_prevCode),
    m_currCode(other.m_currCode),
    m_nextCode(other.m_nextCode),
    m_comment(other.m_comment)
{
}


/*!
\brief  Destructs the fragmentation rule.
*/
FragRule::~FragRule()
{
}


/*!
\brief  Assigns \a other to this FragRule instance.

Returns a reference to this fragmentation rule.
*/
FragRule &
FragRule::operator=(const FragRule &other)
{
  if(&other == this)
    return *this;

  PolChemDefEntity::operator=(other);
  Formula::operator         =(other);

  m_prevCode = other.m_prevCode;
  m_currCode = other.m_currCode;
  m_nextCode = other.m_nextCode;
  m_comment  = other.m_comment;

  return *this;
}


/*!
\brief Sets the previous monomer \a code.
 */
void
FragRule::setPrevCode(const QString &code)
{
  m_prevCode = code;
}


/*!
\brief Returns the previous monomer code.
*/
QString
FragRule::prevCode() const
{
  return m_prevCode;
}


/*!
\brief Sets the current monomer \a code.
*/
void
FragRule::setCurrCode(const QString &code)
{
  m_currCode = code;
}


/*!
\brief Returns the current monomer code.
*/
QString
FragRule::currCode() const
{
  return m_currCode;
}


/*!
\brief Sets the next monomer \a code.
*/
void
FragRule::setNextCode(const QString &code)
{
  m_nextCode = code;
}


/*!
\brief Returns the next monomer code.
*/
QString
FragRule::nextCode() const
{
  return m_nextCode;
}


/*!
\brief Sets the \a comment.
*/
void
FragRule::setComment(const QString &comment)
{
  m_comment = comment;
}


/*!
\brief  Returns the comment.
*/
QString
FragRule::comment() const
{
  return m_comment;
}

/*!
\brief Returns the \l Formula as a string.
*/
QString
FragRule::formula() const
{
  return Formula::toString();
}


/*!
\brief  Searches by \a name a FragRule in \a frag_rule_list.

If a FragRule instance is found and \a other is non-nullptr, the found
fragmentation rule's data are copied into \a other.

Returns the index of the found FragRule or -1 if none is found or if \a name
is empty.
*/
int
FragRule::isNameInList(const QString &name,
                       const QList<FragRule *> &frag_rule_list,
                       FragRule *other)
{
  FragRule *fragRule = 0;

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

  for(int iter = 0; iter < frag_rule_list.size(); ++iter)
    {
      fragRule = frag_rule_list.at(iter);
      Q_ASSERT(fragRule);

      if(fragRule->m_name == name)
        {
          if(other)
            *other = *fragRule;

          return iter;
        }
    }

  return -1;
}


/*!
\brief  Validates the FragRule.

The validation involves checking that:

\list
\li The name is not empty.
\li The previous code is valid if non empty.
\li The current code is valid if non empty.
\li The next code is valid if non empty.
\endlist

Returns true upon success, false otherwise.
*/
bool
FragRule::validate()
{
  const QList<Monomer *> &monomerRefList = mcsp_polChemDef->monomerList();

  if(m_name.isEmpty())
    return false;

  if(!m_prevCode.isEmpty())
    if(Monomer::isCodeInList(m_prevCode, monomerRefList) == -1)
      return false;

  if(!m_currCode.isEmpty())
    if(Monomer::isCodeInList(m_currCode, monomerRefList) == -1)
      return false;

  if(!m_nextCode.isEmpty())
    if(Monomer::isCodeInList(m_nextCode, monomerRefList) == -1)
      return false;

  IsotopicDataCstSPtr isotopic_data_csp =
    mcsp_polChemDef->getIsotopicDataCstSPtr();

  return Formula::validate(isotopic_data_csp);

  return true;
}


/*!
\brief  Parses the FragRule XML \a element.

Upon parsing and validation of the parsed data, the member data are updated,
thus essentially initializing this FragRule instance.

Returns true if parsing and formula validation were successful, false otherwise.
*/
bool
FragRule::renderXmlFgrElement(const QDomElement &element)
{
  QDomElement child;

  bool prevSet    = false;
  bool currSet    = false;
  bool nextSet    = false;
  bool commentSet = false;

  /* The xml node we are in is structured this way:
   *
   * <fgr>
   *   <name>one_rule</name>
   *   <formula>+H2O</formula>
   *   <prev-mnm-code>M</prev-mnm-code>
   *   <this-mnm-code>Y</this-mnm-code>
   *   <next-mnm-code>T</next-mnm-code>
   *   <comment>opt_comment</comment>
   * </fgr>
   *
   * And the element parameter points to the
   *
   * <fgr> element tag:
   *  ^
   *  |
   *  +----- here we are right now.
   *
   * Which means that element.tagName() == "fgr" and that
   * we'll have to go one step down to the first child of the
   * current node in order to get to the <name> element.
   *
   * The DTD:
   *    <!ELEMENT fgr(name,formula,prev-mnm-code?,
   *                   this-mnm-code?,next-mnm-code?,comment?)>
   */

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

  child = element.firstChildElement();

  if(child.isNull() || child.tagName() != "name")
    return false;

  m_name = child.text();

  child = child.nextSiblingElement();

  if(child.isNull() || child.tagName() != "formula")
    return false;

  if(!Formula::renderXmlFormulaElement(child))
    return false;

  // Since the following items are not obligatory, we have to while()
  // until we have no more items...

  child = child.nextSiblingElement();

  while(!child.isNull())
    {
      if(child.tagName() == "prev-mnm-code")
        {
          if(prevSet)
            return false;
          else
            {
              m_prevCode = child.text();
              prevSet    = true;
            }
        }
      else if(child.tagName() == "curr-mnm-code")
        {
          if(currSet)
            return false;
          else
            {
              m_currCode = child.text();
              currSet    = true;
            }
        }
      else if(child.tagName() == "next-mnm-code")
        {
          if(nextSet)
            return false;
          else
            {
              m_nextCode = child.text();
              nextSet    = true;
            }
        }
      else if(child.tagName() == "comment")
        {
          if(commentSet)
            return false;
          else
            {
              m_comment  = child.text();
              commentSet = true;
            }
        }

      child = child.nextSiblingElement();
    }

  if(!validate())
    return false;

  return true;
}


/*!
\brief Formats a string suitable to use as an XML element.


The string is suitable to be used as an XML element in a polymer chemistry
definition file. The typical fragmentation rule element that is generated in
this function looks like this:

  \code
  <fgr>
  <name>a-fgr-2</name>
  <formula>+H100</formula>
  <prev-mnm-code>F</prev-mnm-code>
  <curr-mnm-code>D</curr-mnm-code>
  <next-mnm-code>E</next-mnm-code>
  <comment>comment here!</comment>
  </fgr>
  \endcode

The formatting of the XML element takes into account \a offset and \a
indent by prepending the string with \a offset * \a indent character substring.

\a indent defaults to two spaces.

Returns a dynamically allocated string that needs to be freed after use.
*/
QString *
FragRule::formatXmlFgrElement(int offset, const QString &indent)
{

  int newOffset;
  int iter = 0;

  QString lead("");
  QString *string = new QString();


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

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

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

  // Continue with indented elements.

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

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

  if(!m_prevCode.isEmpty())
    *string += QString("%1<prev-mnm-code>%2</prev-mnm-code>\n")
                 .arg(lead)
                 .arg(m_prevCode);

  if(!m_currCode.isEmpty())
    *string += QString("%1<curr-mnm-code>%2</curr-mnm-code>\n")
                 .arg(lead)
                 .arg(m_currCode);

  if(!m_nextCode.isEmpty())
    *string += QString("%1<next-mnm-code>%2</next-mnm-code>\n")
                 .arg(lead)
                 .arg(m_nextCode);

  if(!m_comment.isEmpty())
    *string += QString("%1<comment>%2</comment>\n").arg(lead).arg(m_comment);

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

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


  return string;
}

} // namespace libXpertMass

} // namespace MsXpS

