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


/////////////////////// Qt includes
#include <QString>

/////////////////////// Local includes
#include "Isotope.hpp"


namespace msxps
{
namespace minexpert
{


//! Construct an isotope initialized.
/*!

  The \c m_mass and \c m_abundance values are set to 0.0.

*/
Isotope::Isotope()
{
}


//! Destructor.
/*!

*/
Isotope::~Isotope()
{
  // qDebug() << "Nothing to delete.";
}


//! Construct an isotope initialized with \p mass and \p abundance.
/*!

  \c m_mass and \c m_abundances are set to the values \p mass and \p
  abundance. These values cannot be less than 0.

  \param mass mass initializer. Cannot be negative.

  \param abundance abundance initializer. Cannot be negative.

*/
Isotope::Isotope(double mass, double abundance)
{
  if(mass < 0 || abundance < 0)
    qFatal("Programming error.");

  m_mass      = mass;
  m_abundance = abundance;
}


//! Construct an Isotope instance as a copy of \p other.
/*!

  No verification is performed on the values in \p other.

  \param other Isotope to be used as a mold.

*/
Isotope::Isotope(const Isotope &other)
{
  m_mass      = other.m_mass;
  m_abundance = other.m_abundance;
}


//! Create a new isotope as a copy of \c this.
/*!

  The newly allocated Isotope instance is initialized using \c this.

  \return The new isotope, which must be deleted when no longer in use.

*/
Isotope *
Isotope::clone() const
{
  Isotope *other = new Isotope(*this);

  return other;
}


//! Modifie \p other to be identical to \p this.
/*!

  \param other isotope. Cannot be Q_NULLPTR.

*/
void
Isotope::clone(Isotope *other) const
{
  if(other == Q_NULLPTR)
    qFatal("Programming error.");

  if(other == this)
    return;

  other->m_mass      = m_mass;
  other->m_abundance = m_abundance;
}


//! Modifie \p this to be identical to \p other.
/*!

  \param other Isotope instance to be used as a mold.

*/
void
Isotope::mold(const Isotope &other)
{
  if(&other == this)
    return;

  m_mass      = other.m_mass;
  m_abundance = other.m_abundance;
}


//! Assign \p other to \c this isotope.
/*!

  \param other Isotope instance used as the mold to set values to \c this
  instance.

  \return a reference to \p this.

*/
Isotope &
Isotope::operator=(const Isotope &other)
{
  if(&other != this)
    mold(other);

  return *this;
}


//! Set the mass.
/*!

  \param mass mass. Cannot be negative.

*/
void
Isotope::setMass(double mass)
{
  if(mass < 0)
    qFatal("Programming error.");

  m_mass = mass;
}


//! Return the mass.
/*!

  \return the mass.

*/
double
Isotope::mass() const
{
  return m_mass;
}


//! Return the mass as a string.
/*!

  The string is constructed with \p decimalPlaces decimal places after the
  decimal separator.

  \param decimalPlaces. Defaults to ATOM_DEC_PLACES.

  \sa ATOM_DEC_PLACES.

  \return The mass formatted as a string having ATOM_DEC_PLACES
  decimals after the decimal separator.

*/
QString
Isotope::massString(int decimalPlaces) const
{
  if(decimalPlaces < 0)
    decimalPlaces = 0;

  QString mass;
  mass.setNum(m_mass, 'f', decimalPlaces);

  return mass;
}


//! Set the abundance.
/*!

  \param abundance abundance. Cannot be negative.

*/
void
Isotope::setAbundance(double abundance)
{
  if(abundance < 0)
    qFatal("Programming error.");

  m_abundance = abundance;
}


//! Increment abundance by \p abundance.
/*!

  \param abundance value by which to increment the isotope
  abundance. Cannot be negative.

*/
void
Isotope::incrementAbundance(double abundance)
{
  if(abundance < 0)
    qFatal("Programming error.");

  m_abundance += abundance;
}


//! Decrement abundance by \p abundance.
/*!

  \param abundance value by which to decrement the isotope
  abundance. Cannot be negative.

*/
void
Isotope::decrementAbundance(double abundance)
{
  if(abundance < 0)
    qFatal("Programming error.");

  m_abundance -= abundance;
}


//! Return the abundance.
/*!

  \return the abundance.

*/
double
Isotope::abundance() const
{
  return m_abundance;
}


//! Return the abundance as a string.
/*!

  The string is constructed with \p decimalPlaces decimal places after the
  decimal separator.

  \param decimalPlaces. Defaults to ATOM_DEC_PLACES.

  \sa ATOM_DEC_PLACES.

  \return The mass formatted as a string having ATOM_DEC_PLACES
  decimals after the decimal separator.

*/
QString
Isotope::abundanceString(int decimalPlaces) const
{
  if(decimalPlaces < 0)
    decimalPlaces = 0;

  QString abundance;

  abundance.setNum(m_abundance, 'f', decimalPlaces);

  return abundance;
}


//! Test equality.
/*!

  \param other Isotope to be compared with \p this.

  \return true if the Isotope instances are identical, false otherwise.

*/
bool
Isotope::operator==(const Isotope &other) const
{
  return (m_mass == other.m_mass && m_abundance == other.m_abundance);
}


//! Test inequality.
/*!

  \param other isotope to be compared with \p this.

  \return true if the isotopes are different, false otherwise.

*/
bool
Isotope::operator!=(const Isotope &other) const
{
  return (m_mass != other.m_mass || m_abundance != other.m_abundance);
}


//! Test if less than.
/*!

  The comparison only involves the the mass of the Isotope instances.

  \param other isotope to be compared with \c this.

  \return true if the this isotope has a lesser mass than \p other.

*/
bool
Isotope::operator<(const Isotope &other) const
{
  return (m_mass < other.m_mass);
}


//! Test if a first Isotope instance is less than a second.
/*!

  The comparison only involves the mass of the Isotope instances.

  \param first Isotope to be compared.
  \param second Isotope to be compared.

  \return true if \p first has a mass strictly smaller than the mass of \p
  second.

*/
bool
Isotope::lessThan(const Isotope *first, const Isotope *second)
{
  if(first == Q_NULLPTR || second == Q_NULLPTR)
    qFatal("Programming error.");

  return (first->m_mass < second->m_mass);
}


//! Parse an Isotope XML element and sets the data to the Isotope.
/*!

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

  The data are checked for validity: the mass and the abundance cannot
  be negative.

  The DTD says this: <!ELEMENT isotope(mass , abund)>

  A typical isotope element is as follows:

  \verbatim
  <isotope>
  <mass>1.0078250370</mass>
  <abund>99.9885000000</abund>
  </isotope>
  \endverbatim

  \param element a reference to a const QDomElement instance that
  points at an isotope XML element.

  \param version version number of the XML file format.

  \return true if the parsing is successful, false otherwise.

  \sa formatXmlIsotopeElement()

*/
bool
Isotope::renderXmlIsotopeElement(const QDomElement &element, int version)
{
  // For the time being the version is not necessary here. As of
  // version up to 2, the current function works ok.

  Q_UNUSED(version);

  QDomElement child;

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

  child = element.firstChildElement("mass");

  if(child.isNull())
    return false;

  m_mass = (child.text().toDouble());

  if(m_mass < 0)
    return false;

  child = child.nextSiblingElement("abund");

  if(child.isNull())
    return false;

  m_abundance = (child.text().toDouble());

  if(m_abundance < 0)
    return false;

  return true;
}


//! Format a string suitable to use as an XML element.
/*!

  Format a string suitable to be used as an XML element in a
  polymer chemistry definition file as part of an atom element.

  The DTD says this: <!ELEMENT isotope(mass , abund)>

  A typical isotope element is as follows(~ represent space
  characters, actually):

  \verbatim
  <isotope>
  <mass>1.0078250370</mass>
  <abund>99.9885000000</abund>
  </isotope>
  \endverbatim

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

  \param indent string to be used for the indenting of the XML elements inside
  the XML element. Defaults to two spaces(QString(" ")).

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

  \sa renderXmlIsotopeElement()

*/
QString *
Isotope::formatXmlIsotopeElement(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<isotope>\n").arg(lead);

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

  // Continue with indented elements.

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

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

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

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

  return string;
}


QString
Isotope::asText() const
{
  return QString("%1 / %2\n").arg(massString(), abundanceString());
}

} // namespace minexpert

} // namespace msxps
