/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2024 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>
#include <QDebug>

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

namespace MsXpS
{

namespace libXpertMass
{


// #include <libisospec++/isoSpec++.h>
//
// extern const int elem_table_ID[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const int elem_table_atomicNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double
// elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const int elem_table_massNo[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const int
// elem_table_extraNeutrons[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const bool elem_table_Radioactive[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
// extern const double
// elem_table_log_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];

// This is the order of the columns in the gui TableView
// ID
// ELEMENT,
// SYMBOL,
// ATOMIC_NO,
// MASS,
// MASS_NO,
// EXTRA_NEUTRONS,
// PROBABILITY,
// LN_PROBABILITY,
// RADIOACTIVE,

/*!
    \enum MsXpS::libXpertMass::IsotopeFields

    \brief This enum type documents the various member data in \l{Isotope}.

    The values assigned to the various enum members are used to specify the
    columsn in the GUI table view. They are also used to access substrings in
the proper order in the \l{Isotope::initialize()}.

    \value ID       Indicates Isotope::m_id.
    \value ELEMENT       Indicates Isotope::m_element.
    \value SYMBOL       Indicates the Isotope::m_symbol.
    \value ATOMIC_NUMBER       Indicates the Isotope::m_atomicNo.
    \value MASS       Indicates the Isotope::m_mass.
    \value MASS_NUMBER       Indicates the Isotope::m_massNo.
    \value EXTRA_NEUTRONS       Indicates the Isotope::m_extraNeutrons.
    \value PROBABILITY       Indicates the Isotope::m_probability.
    \value LN_PROBABILITY       Indicates the Isotope::m_lnProbability.
    \value RADIOACTIVE       Indicates the Isotope::m_radioactive.
    \omitvalue LAST
*/

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

\brief The Isotope class models an isotope.

The Isotope class models an Isotope by featuring all the methods and
member data required to fully characterize an isotope. The member data in this
class have been inspired by the element tables from the IsoSpec library.
Please, see \l{https://github.com/MatteoLacki/IsoSpec/}.
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_id

  \brief The unambiguous id for the chemical element (Hydrogen: 1, Helium, 2,
Carbon 6, for example). The \a id is repeated for each isotope of each chemical
element.
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_element

  \brief The element, like "carbon" or "nitrogen" (lowercase).
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_symbol
  \brief The symbol, like "C" or "N".
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_atomicNo
  \brief The atomic number (Z), like 6 for "C", number of protons.
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_mass
  \brief The mass.
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_massNo
  \brief The mass number, (A) like 12 for "C", number of (protons + neutrons).
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_extraNeutrons
  \brief The extra neutrons in the isotope's nucleus.
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_probability
  \brief The probability of this isotope, that is, its abundance.
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_lnProbability
  \brief The ln (natural logarithm) of the probability
          \c p (e\sup{ln(p)}=p).
*/

/*!
  \variable int MsXpS::libXpertMass::Isotope::m_radioactive
  \brief The true if the isotope is unstable (disintegrates), false
otherwise.
*/

/*!
    \typedef IsotopeSPtr
    \relates Isotope

    Synonym for std::shared_ptr<Isotope>.
*/

/*!
    \typedef IsotopeCstSPtr
    \relates Isotope

    Synonym for std::shared_ptr<const Isotope>.
*/

/*!
  \brief Constructs the \l{Isotope} with all the required arguments.

  The isotope is created as a fully documented instance if all the following
parameters a correctly set:

  \a id \l{MsXpS::libXpertMass::Isotope::m_id}

  \a element \l{MsXpS::libXpertMass::Isotope::m_element}

  \a symbol \l{MsXpS::libXpertMass::Isotope::m_symbol}

  \a atomicNo \l{MsXpS::libXpertMass::Isotope::m_atomicNo}

  \a mass \l{MsXpS::libXpertMass::Isotope::m_mass}

  \a massNo \l{MsXpS::libXpertMass::Isotope::m_massNo}

  \a extraNeutrons \l{MsXpS::libXpertMass::Isotope::m_extraNeutrons}

  \a probability \l{MsXpS::libXpertMass::Isotope::m_probability}

  \a lnProbability \l{MsXpS::libXpertMass::Isotope::m_lnProbability}

  \a radioactive \l{MsXpS::libXpertMass::Isotope::m_radioactive}
*/
Isotope::Isotope(int id,
                 QString element,
                 QString symbol,
                 int atomicNo,
                 double mass,
                 int massNo,
                 int extraNeutrons,
                 double probability,
                 double lnProbability,
                 bool radioactive)
  : m_id(id),
    m_element(element),
    m_symbol(symbol),
    m_atomicNo(atomicNo),
    m_mass(mass),
    m_massNo(massNo),
    m_extraNeutrons(extraNeutrons),
    m_probability(probability),
    m_lnProbability(lnProbability),
    m_radioactive(radioactive)
{
}


/*!
  \brief Constructs the \l{Isotope} as a copy of \a other.
*/
Isotope::Isotope(const Isotope &other)
{
  m_id            = other.m_id;
  m_element       = other.m_element;
  m_symbol        = other.m_symbol;
  m_atomicNo      = other.m_atomicNo;
  m_mass          = other.m_mass;
  m_massNo        = other.m_massNo;
  m_extraNeutrons = other.m_extraNeutrons;
  m_probability   = other.m_probability;
  m_lnProbability = other.m_lnProbability;
  m_radioactive   = other.m_radioactive;
}

/*!
  \brief Constructs the \l{Isotope} using all the data in the \a text string.

  The strings contains all the Isotope data, separated by a comma ',' exactly
with the same format as that implemented by Isotope::toString().

  \sa Isotope::initialize(), Isotope::toString()
*/
Isotope::Isotope(const QString &text)
{
  // This is somehow the reverse of toString().

  if(!initialize(text))
    qFatal("Failed to initialize an isotope with text.");
}

/*!
  \brief Destructs the \l{Isotope}.
*/
Isotope::~Isotope()
{
}

/*!
  Initializes the \l{Isotope} using all the data in the \a text string.

  The string passed as argument is QString::simplified() and
QString::split() with ',' as the delimiter.

  The obtained strings are converted to the corresponding numerical or textual
values to initalize all the member data. The code is similar to:

  \code
  m_radioactive =
    string_list[static_cast<int>(IsotopeFields::RADIOACTIVE)].toInt(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope radioactive.";
      return false;
    }
    \endcode

  Returns true if the string contained valid substrings that successfully
initialized the \l{Isotope}, false otherwise.

  \sa Isotope::Isotope(const QString &text)
*/
bool
Isotope::initialize(const QString &text)
{
  if(text.isEmpty())
    return false;

  // At this point deconstruct the line. Make sure we remove all spaces from
  // beginning and end.

  QString local_text = text.simplified();

  // At this point, use a regular expression to match the text.

  QStringList string_list = local_text.split(',');

  // qDebug() << "Expecting " << static_cast<int>(IsotopeFields::LAST)
  //<< "comma-separated fields for isotope-describing text line."
  //<< "QStringList is:" << string_list;

  if(string_list.size() != static_cast<int>(IsotopeFields::LAST))
    {
      qDebug() << "The text does not match an Isotope definition.";
      return false;
    }

  bool ok = false;

  m_id = string_list[static_cast<int>(IsotopeFields::ID)].toInt(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope ID.";
      return false;
    }

  m_element = string_list[static_cast<int>(IsotopeFields::ELEMENT)];

  if(m_element.isEmpty())
    {
      qDebug() << "Failed to extract the element name.";
      return false;
    }

  m_symbol = string_list[static_cast<int>(IsotopeFields::SYMBOL)];

  if(m_symbol.isEmpty())
    {
      qDebug() << "Failed to extract the element symbol.";
      return false;
    }

  m_atomicNo =
    string_list[static_cast<int>(IsotopeFields::ATOMIC_NUMBER)].toInt(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope atomic number.";
      return false;
    }

  m_mass = string_list[static_cast<int>(IsotopeFields::MASS)].toDouble(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope mass.";
      return false;
    }

  m_massNo =
    string_list[static_cast<int>(IsotopeFields::MASS_NUMBER)].toDouble(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope mass number.";
      return false;
    }

  m_extraNeutrons =
    string_list[static_cast<int>(IsotopeFields::EXTRA_NEUTRONS)].toInt(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope extra neutrons.";
      return false;
    }

  m_probability =
    string_list[static_cast<int>(IsotopeFields::PROBABILITY)].toDouble(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope probability.";
      return false;
    }

  m_lnProbability =
    string_list[static_cast<int>(IsotopeFields::LN_PROBABILITY)].toDouble(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope log probability.";
      return false;
    }

  m_radioactive =
    string_list[static_cast<int>(IsotopeFields::RADIOACTIVE)].toInt(&ok);

  if(!ok)
    {
      qDebug() << "Failed to extract the isotope radioactive.";
      return false;
    }

  return true;
}

/*!
  \brief Sets the id of the isotope to \a id.

  \sa getId()
*/
void
Isotope::setId(int id)
{
  m_id = id;
}


/*!
  \brief Returns the id of the isotope.

  \sa setId()
*/
int
Isotope::getId() const
{
  return m_id;
}

/*!
  \brief Sets the element of the isotope to \a element.

  \sa getElement()
*/
void
Isotope::setElement(const QString &element)
{
  m_element = element;
}


/*!
  \brief Returns the element of the isotope.

  \sa setElement()
*/
QString
Isotope::getElement() const
{
  return m_element;
}


/*!
  \brief Sets the symbol of the isotope to \a symbol.

  \sa getSymbol()
*/
void
Isotope::setSymbol(const QString &symbol)
{
  m_symbol = symbol;
}


/*!
  \brief Returns the symbol of the isotope.

  \sa setSymbol()
*/
QString
Isotope::getSymbol() const
{
  return m_symbol;
}


/*!
  \brief Sets the the atomic number of the isotope to \a atomic_number.

  \sa getAtomicNo()
*/
void
Isotope::setAtomicNo(int atomic_number)
{
  m_atomicNo = atomic_number;
}


/*!
  \brief Returns the atomic number of the isotope.

  \sa setAtomicNo()
*/
int
Isotope::getAtomicNo() const
{
  return m_atomicNo;
}


/*!
  \brief Sets the the mass of the isotope to \a mass.

  \sa getMass()
*/
void
Isotope::setMass(double mass)
{
  m_mass = mass;
}

/*!
  \brief Returns the mass of the isotope.

  \sa setMass()
*/
double
Isotope::getMass() const
{
  return m_mass;
}


/*!
  \brief Sets the the mass number of the isotope to \a mass_number.

  \sa getMassNo()
*/
void
Isotope::setMassNo(int mass_number)
{
  m_massNo = mass_number;
}

/*!
  \brief Returns the mass number of the isotope.

  \sa setMassNo()
*/
int
Isotope::getMassNo() const
{
  return m_massNo;
}


/*!
  \brief Sets the extra neutrons of the isotope to \a extra_neutrons.

  \sa getExtraNeutrons()
*/
void
Isotope::setExtraNeutrons(int extra_neutrons)
{
  m_extraNeutrons = extra_neutrons;
}

/*!
  \brief Returns the extra neutrons of the isotope.

  \sa setExtraNeutrons()
*/
int
Isotope::getExtraNeutrons() const
{
  return m_extraNeutrons;
}

/*!
  \brief Sets the probability (the abundance) of the isotope to \a probability.

  \sa getProbability()
*/
void
Isotope::setProbability(double probability)
{
  m_probability = probability;
}

/*!
  \brief Returns the probability (the abundance) of the isotope.

  \sa setProbability()
*/
double
Isotope::getProbability() const
{
  return m_probability;
}

/*!
  \brief Sets the ln(probability) of the isotope to \a ln_probability.

  \sa getLnProbability()
*/
void
Isotope::setLnProbability(double ln_probability)
{
  m_lnProbability = ln_probability;
}

/*!
  \brief Returns the ln(probability) of the isotope.

  \sa setLnProbability()
*/
double
Isotope::getLnProbability() const
{
  return m_lnProbability;
}

/*!
  \brief Sets if the isotope is radioactive to \a is_radioactive.

  \sa getRadioactive()
*/
void
Isotope::setRadioactive(bool is_radioactive)
{
  m_radioactive = is_radioactive;
}

/*!
  \brief Returns true if the isotope is radioactive, false otherwise.

  \sa setRadioactive()
*/
bool
Isotope::getRadioactive() const
{
  return m_radioactive;
}

/*!
  \brief Validates the isotope.

  The symbol, mass and probability member data are scrutinized and
if errors are detected an error counter is incremented.

  \a errors_p Pointer to a string where the detected errors (if any) are stored
as meaningful strings. If \a errors_p is nullptr, the errors are not stored.

  \code

    if(m_symbol.isEmpty())
      {
        ++error_count;
        errors += "The symbol is not set.";
      }
  \endcode

  Returns the error count. If no error occurred, the returned value is 0.
*/
int
Isotope::validate(QString *errors_p) const
{
  int error_count = 0;
  QString errors;

  // The minimal set of data that an isotope needs to have is the symbol, the
  // mass and the probability.

  if(m_symbol.isEmpty())
    {
      ++error_count;
      errors += "The symbol is not set.";
    }

  if(m_mass <= 0)
    {
      ++error_count;
      errors += "The mass is not set.";
    }

  if(m_probability > 1 || m_probability <= 0)
    {
      ++error_count;
      errors += "The probability is not set.";
    }

  // Now, if there were errors, we would like to document them with the symbol
  // name if it was set.

  if(error_count)
    {
      if(errors_p)
        {
          if(m_symbol.isEmpty())
            *errors_p = errors + "\n";
          else
            // Document the name of the symbol to make the errors more
            // meaningful.
            *errors_p += "For symbol " + m_symbol + ": " + errors + "\n";
        }
    }

  return error_count;
}

/*!
  \brief Assigns to this isotope the \a other isotope's member data.

  Each member datum in \a other is copied to this isotope.

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

  m_id            = other.m_id;
  m_element       = other.m_element;
  m_symbol        = other.m_symbol;
  m_atomicNo      = other.m_atomicNo;
  m_mass          = other.m_mass;
  m_massNo        = other.m_massNo;
  m_extraNeutrons = other.m_extraNeutrons;
  m_probability   = other.m_probability;
  m_lnProbability = other.m_lnProbability;
  m_radioactive   = other.m_radioactive;

  return *this;
}

/*!
  \brief Tests the equality between this isotope and \a other.

  Each member datum in \a other is compared to this isotope's member datum. If a
difference is detected, a counter is incremented.

  Returns true if no difference has been encountered (the counter is 0) and
false otherwise.

  \sa operator!=()
*/
bool
Isotope::operator==(const Isotope &other) const
{
  int errors = 0;

  errors += (m_id == other.m_id ? 0 : 1);
  errors += (m_element == other.m_element ? 0 : 1);
  errors += (m_symbol == other.m_symbol ? 0 : 1);
  errors += (m_atomicNo == other.m_atomicNo ? 0 : 1);
  errors += (m_mass == other.m_mass ? 0 : 1);
  errors += (m_massNo == other.m_massNo ? 0 : 1);
  errors += (m_extraNeutrons == other.m_extraNeutrons ? 0 : 1);
  errors += (m_probability == other.m_probability ? 0 : 1);
  errors += (m_lnProbability == other.m_lnProbability ? 0 : 1);
  errors += (m_radioactive == other.m_radioactive ? 0 : 1);

  return (errors > 0 ? false : true);
}


/*!
  \brief Tests the inequality between this isotope and \a other.

  Computes the negation of the result obtained by calling \l{operator==()}.

  Returns true if at lease one difference has been encountered and false
otherwise.

  \sa operator==()
*/
bool
Isotope::operator!=(const Isotope &other) const
{
  return !(*this == other);
}

/*!
  \brief Returns a string containing a comma-separated textual representation
of all the member data values.

  All the member data values are separated using commas ','. Only the values
are stored in the string, without naming the variables:

  \code
    return QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10")
    .arg(m_id)
    .arg(m_element)
    .arg(m_symbol)
    .arg(m_atomicNo)
    .arg(m_mass, 0, 'f', 60)
    .arg(m_massNo)
    .arg(m_extraNeutrons)
    .arg(m_probability, 0, 'f', 60)
    .arg(m_lnProbability, 0, 'f', 60)
    .arg(m_radioactive ? 1 : 0);
  \endcode

  Returns a string.
*/
QString
Isotope::toString() const
{
  // We need to use CSV because there might be spaces in the
  // text in the IsoSpec tables.
  return QString("%1,%2,%3,%4,%5,%6,%7,%8,%9,%10")
    .arg(m_id)
    .arg(m_element)
    .arg(m_symbol)
    .arg(m_atomicNo)
    .arg(m_mass, 0, 'f', 60)
    .arg(m_massNo)
    .arg(m_extraNeutrons)
    .arg(m_probability, 0, 'f', 60)
    .arg(m_lnProbability, 0, 'f', 60)
    .arg(m_radioactive ? 1 : 0);
}

} // namespace libXpertMass

} // namespace MsXpS
