/* 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 "IonizeRule.hpp"


namespace MsXpS
{

namespace libXpertMass
{


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

\brief The IonizeRule class provides an ionization rule.

Ionizations are chemical reactions that bring a charge (or more
charges) to an analyte. The chemical reaction is described by the
inherited \l Formula class. The electric charge that is brought by
this reaction is described by a member of the class, m_charge. It
might happen that for polymers, ionization reactions might occur
more than once. This is described by one member of the class,
m_level.

An IonizeRule like the following, if used to ionize a molecule of Mr 1000

\list
\li Formula +H
\li Charge 1
\li Level 1
\endlist

would lead to an ion of mass 1001 and of m/z 1001.

In protein chemistry, the ionization reaction is mainly a
protonation reaction, which brings a single charge. Thus, the Formula would be
"+H" and the charge 1. In MALDI, we would have a single protonation, thus
level would be set to 1 by default. In electrospray ionization, more than one
ionization reaction occurs, and we could have a protein that is 25+, thus
having an ionization level of 25, for example.

An IonizeRule like the following, if used to ionize a molecule of Mr 1000

\list
\li Formula +H
\li Charge 1
\li Level 4
\endlist

would lead to an ion of mass 1004 and of m/z 251 (1004 / 4).

An IonizeRule like
the following, if used to ionize a molecule of Mr 1000

\list
\li Formula +Mg (in fact Mg2+)
\li Charge 2
\li Level 4
\endlist

would lead to an ion of mass 1000 + (24 * 4) and of m/z 137 = (1096 / 8).

\note Both the charge and level should have positive values, since
the real nature of the ionization is beared by the Formula (with, for example
 in protein chemistry, "+H" for protonation and, in nucleic acids chemistry,
"-H" for deprotonation).

Thus, an IonizeRule is valid if it generates a m/z ratio after
ionization of the analyte that is different than the previous (M)
and if its Formula validates.  This means that the following
should be true:

\list
\li The Formula should be valid (that is, should contain at least
one symbol (which might have a very small mass, like when the ionization is
done by electron gain or loss);
\li The charge is > 0 (the ionization event should bring one
charge, otherwise there is no ionization). To reset the ionization
to 0 (that is to deionize the analyte, set the level to 0);
\li The level is >= 0(if the level is 0, then the analyte is
considered not ionized);
\endlist

\note We do not consider compulsory that the Formula brings
a mass difference whatsoever, because some ionizations might not involve heavy
mass transfers, like electron gain or electron loss. However, the Formula must
validate successfully and that means thit it cannot be empty. In that case, use
the Nul chemical element that has not weight from the polymer chemistry
definitions shipped with the software package.
*/

/*!
\variable int MsXpS::libXpertMass::IonizeRule::m_charge

\brief The charge that is brought to the molecule when it is ionized by the
IonizeRule with an ionization level (m_level) of 1.
*/

/*!
\variable int MsXpS::libXpertMass::IonizeRule::m_level

\brief The number of times this IonizRule is used to ionize a molecule.

For example, applying this IonizeRule to a molecule

\list
\li Formula +H
\li Charge 1
\li Level 4
\endlist

would protonate it 4 times, with a charge of 4 and an increment in mass of the
mass of 4 protons.
*/

/*!
\variable int MsXpS::libXpertMass::IonizeRule::m_isValid

\brief Tells if this IonizeRule is valid,

\sa isValid
*/

/*!
\brief  Constructs an IonizeRule initialized as an empty object.
*/
IonizeRule::IonizeRule() : Formula(), m_charge(0), m_level(0)
{
  m_isValid = false;
}


/*!
\brief  Constructs an IonizeRule as a copy of \a other.
*/
IonizeRule::IonizeRule(const IonizeRule &other)
  : Formula(other),
    m_charge(other.m_charge),
    m_level(other.m_level),
    m_isValid(other.m_isValid)
{
}


/*!
\brief  Assigns \a other to this IonizeRule.
Returns a reference to this ionization rule.
*/
IonizeRule &
IonizeRule::operator=(const IonizeRule &other)
{
  if(&other == this)
    return *this;

  Formula::operator=(other);

  m_charge = other.m_charge;
  m_level  = other.m_level;

  m_isValid = other.m_isValid;

  return *this;
}


/*!
\brief  Sets the charge to \a value.

An IonizeRule with a(charge <= 0) is invalid by definition. If so, the
m_isValid member is set to false.
*/
void
IonizeRule::setCharge(int value)
{
  m_charge = value;

  if(m_charge <= 0)
    m_isValid = false;
}


/*!
\brief  Returns the charge.
*/
int
IonizeRule::charge() const
{
  return m_charge;
}


/*!
\brief  Sets the ionzation level to \a value.

An IonizeRule might have an ionization level == 0 but not level
  < 0, as this means that the analyte is not ionized at all. If
  (level < 0), then the m_isValid member is set to false.
*/
void
IonizeRule::setLevel(int value)
{
  m_level = value;

  if(m_level < 0)
    m_isValid = false;
}


/*!
\brief  Returns the ionization level.
*/
int
IonizeRule::level() const
{
  return m_level;
}

/*!
\brief Returns the formula.
*/
QString
IonizeRule::formula() const
{
  return Formula::toString();
}

/*!
\brief Returns true if this IonizeRule is identical to \a other, false
otherwise.
*/
bool
IonizeRule::operator==(const IonizeRule &other) const
{
  int tests = 0;

  tests += Formula::operator==(other);
  tests += (m_charge == other.m_charge);
  tests += (m_level == other.m_level);
  tests += (m_isValid == other.m_isValid);

  if(tests < 4)
    return false;

  return true;
}


/*!
\brief Returns true if this IonizeRule is different than \a other, false
otherwise.
*/
bool
IonizeRule::operator!=(const IonizeRule &other) const
{
  int tests = 0;

  tests += Formula::operator!=(other);
  tests += (m_charge != other.m_charge);
  tests += (m_level != other.m_level);
  tests += (m_isValid != other.m_isValid);

  if(tests > 0)
    return true;

  return false;
}

/*!
\brief Validates this IonizeRule against the \a isotopic_data_csp.

An IonizeRule is valid if it generates a new m/z ratio after
ionization (or deionization if (\c m_level == 0) of the analyte
that is different than the previous one and if its \l Formula
validates successfully. This means that the following should be true:

\list
\li The Formula should be valid (that is, should contain at least
one atom (use a 0-weighing atom if necessary, like Nul from the
polymer chemistry definitions shipped with the software);

\li The charge should > 0;

\li The level should >= 0;
\endlist

If these three tests do not fail, the IonizeRule is considered
valid and the m_isValid boolean value is set to true; false
otherwise.

Returns true if validation succeeds, false otherwise.

\sa Formula::validate()
*/
bool
IonizeRule::validate(IsotopicDataCstSPtr isotopic_data_csp)
{
  if(isotopic_data_csp == nullptr)
    qFatal("Programming error. The pointer cannot be nullptr.");

  int tests = 0;

  tests += Formula::validate(isotopic_data_csp);
  tests += (m_charge > 0);
  tests += (m_level >= 0);

  if(tests < 3)
    {
      m_isValid = false;

      return false;
    }

  m_isValid = true;

  return true;
}


/*!
\brief  Returns true if this IonizeRule is valid, false otherwise.
\sa validate()
*/
bool
IonizeRule::isValid() const
{
  return m_isValid;
}


/*!
\brief  Renders the ionization rule XML \a element.

The XML element is parsed and the data extracted from the XML data
  are set to this IonizeRule instance.

  The DTD says this: <!ELEMENT ionizerule(formula,charge,level)>

  A typical ionization rule element looks like this:

  \code
  <ionizerule>
  <formula>+H</formula>
  <charge>1</charge>
  <level>1</level>
  </ionizerule>
  \endcode

  Note that this IonizeRule is not valid, as it has not been
  validated by calling validate(). The caller is reponsible for
  checking the validity of the IonizeRule prior use.

Returns true if the parsing is successful, false otherwise.

\sa formatXmlIonizeRuleElement(int offset, const QString &indent)
*/
bool
IonizeRule::renderXmlIonizeRuleElement(const QDomElement &element)
{
  QDomElement child;

  //   <ionizerule>
  //     <formula>+H</formula>
  //     <charge>1</charge>
  //     <level>1</level>
  //   </ionizerule>

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

  // <formula>
  child = element.firstChildElement();
  if(child.tagName() != "formula")
    return false;
  if(!renderXmlFormulaElement(child))
    return false;

  // <charge>
  child = child.nextSiblingElement();
  if(child.tagName() != "charge")
    return false;
  bool ok  = false;
  m_charge = child.text().toInt(&ok);
  if(!m_charge && !ok)
    return false;

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

  // We have not validated this IonizeRule, as we should have the
  // reference list of atoms to do that. The caller is responsible
  // for the validate() call.
  m_isValid = false;

  return true;
}


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

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

  The DTD says this: <!ELEMENT ionizerule(formula,charge,level)>

  A typical ionization rule element looks like this:

  \code

  <ionizerule>
  ~~<formula>+H</formula>
  ~~<charge>1</charge>
  ~~<level>1</level>
  </ionizerule>

  \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 renderXmlIonizeRuleElement(const QDomElement &element)
*/
QString *
IonizeRule::formatXmlIonizeRuleElement(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;
    }

  /* We are willing to create an <ionizerule> node that should look like this:
   *
   *<ionizerule>
   *  <formula>+H</formula>
   *  <charge>1</charge>
   *  <level>1</level>
   *</ionizerule>
   *
   */

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

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

  // Continue with indented elements.

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

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

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

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

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

  return string;
}

/*!
\brief Returns a string holding a textual representation of the member data.
*/
QString
IonizeRule::toString() const
{
  QString text;

  text += QString("Formula: %1").arg(Formula::toString());
  text += QString(" - charge: %1 -- level: %2").arg(m_charge).arg(m_level);

  return text;
}

/*
\brief Outputs a string holding a textual representation of the member data
using
qDebug().
*/
void
IonizeRule::debugPutStdErr()
{
  qDebug() << __FILE__ << __LINE__
           << QString("Ionizerule: charge=%1; level=%2; formula=%3")
                .arg(m_charge)
                .arg(m_level)
                .arg(m_formula);
}

} // namespace libXpertMass

} // namespace MsXpS
