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


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


/////////////////////// Local includes
#include "globals.hpp"
#include "Formula.hpp"


namespace MsXpS
{

namespace libXpertMass
{


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

\brief The Formula class provides sophisticated abstractions to work with
formulas.

There are two peculiarities with this Formula implementation:

\list
\li The \e{Actionformula}
\li The \e{Title}
\endlist

\e{\b{The actionformula}}: the main textual element in this Formula
class is the \e{actionformula} (member m_formula). A formula is the description
of the atomic composition of a compound. For example, the string \e{C2H6} is a
formula. While the previous \e{C2H6} example describes a static chemical
object, a Formula can also describe a dynamic chemical event, like a reaction,
by describing what chemical entities are gained by the molecule during the
chemical reaction (the "plus" component of the actionformula) and what chemical
entities are lost by the molecule (the "minus" component). For example, an
acetylation reaction can be described by the loss of \e{H2O} with gain of
\e{CH3COOH}. The net chemical gain on the molecule will be \e{CH3CO}. In this
example, one would thus define an actionformula in the following way:
\e{-H20+CH3COOH}.  The "minus" formula associated with the '-' action accounts
for the leaving group of the reaction, while the "plus" formula associated with
the '+' action accounts for the entering group of the reaction. Note that there
is no limitation on the amount of such actions, as one could have an action
formula like this \e{-H+CO2-H2O+C2H6}. An \e{actionformula} does not need to
have any action sign (+ or -), and if it has no sign, the actionformula is a
plus-signed formula by default, which is what the reader would expect for a
standard formula.

\e{\b{The title}}: the actionformula may be documented with a title: a prefix
text enclosed in double quotes, like the following: \e{"Decomposed adenine"
C5H4N5 +H}. This documentation element is called the \e{title}. Note that the
presence of a title in a formula does not change anything to its workings as
long as the \e{title} is effectively enclosed in double quotes. The title is by
no means a required textual element for an actionformula to work correctly. It
is mainly used in some particular context, like the calculator. An actionformula
behaves exactly the same as a simple formula from an end user perspective. When
created, a Formula has its formula string containing the formula (be it a pure
formula or an actionformula). Behind the scenes, functions are called to
separate all the '+'-associated formulas from all the '-'-associated formulas
so that masses are correctly associated to each "leaving" or "entering"
chemical groups. Formulas that are '-'-associated are stored in the so-called
"minus formula", while '+'-associated ones are stored in the "plus formula".
Note that all the formulas in Formula are QString objects.

  Upon parsing of the formula, the m_minusFormula and the m_plusFormula members
are populated with formulas (in the  \e{-H+CO2-H2O+C2H6} example, the
"minus formula" would contain "H1H2O", while the "plus formula" would contain
"CO2C2H6") and these are next used to account for the net formula.
*/

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

    This enum type specifies the result of an actionformula parsing process:

    \value NOT_SET
           The value was not set
    \value FAILURE
           The splitting work failed
    \value HAS_PLUS_COMPONENT
           The action formula has a plus component
    \value HAS_MINUS_COMPONENT
           The action formula has a minus component
    \value HAS_BOTH_COMPONENTS
           The action formula has both plus and minus components
*/


/*!
  \variable MsXpS::libXpertMass::Formula::m_formula

  \brief String representing the actionformula.
*/

/*!
  \variable MsXpS::libXpertMass::Formula::m_plusFormula

  \brief String representing the "plus" component of the main m_formula.

  This member datum is set upon parsing of m_formula.
*/

/*!
  \variable MsXpS::libXpertMass::Formula::m_minusFormula

  \brief String representing the "minus" component of the main m_minusFormula.

  This member datum is set upon parsing of m_formula.
*/

/*!
  \variable MsXpS::libXpertMass::Formula::m_symbolCountMap

  \brief Map relating the symbols (as keys) found in the formula and their
counts (atoms, in fact, as values).

  Note that the count value type is double, which allows for interesting
things to be done with Formula. Also, the count value might be negative if the
net mass of an actionformula is negative.

  \sa Formula::splitActionParts()
*/

/*!
  \variable MsXpS::libXpertMass::Formula::mcsp_isotopicData

  \brief The isotopic data that the formula is based on.
*/


/*!
  \variable int MsXpS::libXpertMass::Formula::m_forceCountIndex

  \brief The m_forceCountIndex tells if when defining a chemical composition
formula, the index '1' is required when the count of a symbol is not specified
and thus considered to be '1' by default. If true, water should be described as
"H2O1", if false, it might be described as "H2O".
*/


/*!
   \brief Constructs a formula initialized with the \a formula actionformula
string.

  \a formula needs not be an actionformula, but it might be an
actionformula. This formula gets copied into the \c m_formula without any
processing afterwards.
*/
Formula::Formula(const QString &formula) : m_formula{formula}
{
}

/*!
   \brief Constructs a formula as a copy of \a other.

   The copy is deep with \e{all} the data copied from \a other to the new
formula. There is no processing afterwards.
*/
Formula::Formula(const Formula &other)
  : m_formula{other.m_formula},
    m_plusFormula{other.m_plusFormula},
    m_minusFormula{other.m_minusFormula}
{
  m_symbolCountMap = other.m_symbolCountMap;
}


/*!
   \brief Destructs this formula.

   There is nothing to be delete explicitly.
*/
Formula::~Formula()
{
}

/*!
  \brief Initializes all the member data of this formula by copying to it the
data from \a other.

  The copy is deep with \e{all} the data from \a other being copied into this
formula.

  There is no processing afterwards.
*/
Formula &
Formula::operator=(const Formula &other)
{
  if(&other == this)
    return *this;

  m_formula        = other.m_formula;
  m_plusFormula    = other.m_plusFormula;
  m_minusFormula   = other.m_minusFormula;
  m_symbolCountMap = other.m_symbolCountMap;

  return *this;
}


/*! Sets the actionformula \a formula to this Formula.

  The  \a formula is copied to this m_formula.  No other processing is
  performed afterwards.
*/
void
Formula::setFormula(const QString &formula)
{
  m_formula = formula;
}

/*! Sets the actionformula from \a formula to this Formula.

  The actionformula from \a formula is copied to this m_formula. No
 processing is performed afterwards.
*/
void
Formula::setFormula(const Formula &formula)
{
  m_formula = formula.m_formula;
}

/*!
  \brief Appends to this formula the \a formula.

  The \a formula string is appended to the m_formula without check. No
processing is performed afterwards. The \a formula is copied to a
temporary formula that is stripped of its spaces, both in the formula and
before and after it before it is appended to m_formula.
*/
void
Formula::appendFormula(const QString &formula)
{
  QString local_formula = formula.simplified();

  // Remove the spaces before appending.
  local_formula.remove(QRegularExpression("\\s+"));

  m_formula.append(local_formula);
}


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

/*!
  \brief Sets m_forceCountIndex to \a forceCountIndex.

  When a formula contains a chemical element in a single copy, it is standard
practice to omit the count index: H2O is the same as H2O1. If forceCountIndex is
true, then the formula has to be in the form H2O1. This is required for some
specific calculations.
*/
void
Formula::setForceCountIndex(bool forceCountIndex)
{
  m_forceCountIndex = forceCountIndex;
}

/*!
  \brief Clears \e{all} the formula member data.
*/
void
Formula::clear()
{
  m_formula.clear();
  m_plusFormula.clear();
  m_minusFormula.clear();

  m_symbolCountMap.clear();
}

/*!
  \brief Sets the m_plusFormula formula to \a formula.
*/
void
Formula::setPlusFormula(const QString &formula)
{
  m_plusFormula = formula;
}

/*!
  \brief Returns the m_plusFormula formula.
*/
const QString &
Formula::plusFormula() const
{
  return m_plusFormula;
}

/*!
  \brief Sets the m_minusFormula formula to \a formula.
*/
void
Formula::setMinusFormula(const QString &formula)
{
  m_minusFormula = formula;
}

/*!
  \brief Returns the m_minusFormula formula.
*/
const QString &
Formula::minusFormula() const
{
  return m_minusFormula;
}


/*! Returns true if this Formula and \a other are identical, false otherwise.

  The comparison is only performed on the m_formula actionformula, not on any
other member data that derived from processing of m_formula.
*/
bool
Formula::operator==(const Formula &other) const
{
  return (m_formula == other.m_formula);
}

/*! Returns true if this Formula and \a other are different, false otherwise.

  The comparison is only performed on the m_formula actionformula, not on any
other member data that derived from processing of m_formula.
*/
bool
Formula::operator!=(const Formula &other) const
{
  return (m_formula != other.m_formula);
}


/*!
  \brief Calls actions(const QString &formula) on this Formula's
actionformula m_formula. Returns '+' if it only contains "plus"
elements or '-' if at least one "minus" element was found.

  If m_formula contains no sign at all, then it is considered to contain only
  '+' elements and the function returns '+'. If at least one element is found
  associated to a '-', then the "minus" action prevails and the function
returns '-'.

  \sa actions(const QString &formula), splitActionParts()
  */
QChar
Formula::actions() const
{
  return actions(m_formula);
}

/*!
  \brief Returns '+' if \a formula only contains "plus" elements or '-'
if at least one "minus" element was found.

  If \a formula contains no sign at all, then it is considered to contain only
  '+' elements and the function returns '+'. If at least one element is found
  associated to a '-', then the "minus" action prevails and the function
returns '-'.

  \sa actions(), splitActionParts()
  */
QChar
Formula::actions(const QString &formula)
{
  double minusCount = formula.count('-', Qt::CaseInsensitive);

  return (minusCount == 0 ? '+' : '-');
}

/*!
  \brief Removes the title from the member actionformula.

  The \e{title} of a formula is the string, enclosed in
  double quotes, that is located in front of the actual chemical
  actionformula.  This function removes that \e{title} string from the
  member actionformula using a QRegularExpression.

  Returns the count of removed characters.
*/
int
Formula::removeTitle()
{
  int length = m_formula.length();

  m_formula.remove(QRegularExpression("\".*\""));

  return (length - m_formula.length());
}


/*!
  \brief Removes \e{all} the space characters from the member actionformula.

  Spaces can be placed anywhere in formula for more readability. However, it
  might be required that these character spaces be removed. This function does
  just this, using a QRegularExpression.

  Returns the number of removed characters.
*/
int
Formula::removeSpaces()
{
  int length = m_formula.length();

  // We want to remove all the possibly-existing spaces.

  m_formula.remove(QRegularExpression("\\s+"));

  // Return the number of removed characters.
  return (length - m_formula.length());
}

/*!
  \brief Tells the "plus" ('+') and "minus" ('-') parts in the member
actinformula.

  Parses the m_formula actionformula and separates all the minus components
  of that actionformula from all the plus components. The different components
  are set to their corresponding formula (m_minusFormula and m_plusFormula).

  At the end of the split work, each sub-formula (plus- and/or minus-)
  is actually parsed for validity, using the \a isotopic_data_csp IsotopicData
as reference.

  If \a times is not 1, then the accounting of the plus/minus formulas is
  compounded by this factor.

  If \a store is true, the symbol/count data obtained while
  parsing of the plus/minus actionformula components are stored
(m_symbolCountMap).

  If \a reset is true, the symbol/count data are reset before the parsing of
the actionformula. Setting this parameter to false may be useful if the
caller needs to "accumulate" the accounting of the formulas.

The parsing of the actionformula is performed by performing its deconstruction
using m_subFormulaRegExp.

Returns MsXpS::libXpertMass::FormulaSplitResult::FAILURE if the splitting failed,
MsXpS::libXpertMass::FormulaSplitResult::HAS_PLUS_COMPONENT if at least one of the
components of the actionformula was found to be of type plus,
MsXpS::libXpertMass::FormulaSplitResult::HAS_MINUS_COMPONENT  if at least one of the
components of the actionformula was found to be of type minus. The result can be
an OR'ing of both values
(MsXpS::libXpertMass::FormulaSplitResult::HAS_BOTH_COMPONENTS) in the m_formula
actionformula.
*/
int
Formula::splitActionParts(IsotopicDataCstSPtr isotopic_data_csp,
                          double times,
                          bool store,
                          bool reset)
{
  if(!isotopic_data_csp->size())
    qFatal("Programming error. The isotopic data cannot be empty.");

  int formula_split_result = static_cast<int>(FormulaSplitResult::NOT_SET);

  // We are asked to put all the '+' components of the formula
  // into corresponding formula and the same for the '-' components.

  m_plusFormula.clear();
  m_minusFormula.clear();

  if(reset)
    m_symbolCountMap.clear();

  // Because the formula that we are analyzing might contain a title
  // and spaces , we first remove these. But make a local copy of
  // the member datum.

  QString formula = m_formula;

  // qDebug() << "splitActionParts before working:"
  //          << "m_formula:" << m_formula;

  // One formula can be like this:

  // "Decomposed adenine" C5H4N5 +H

  // The "Decomposed adenine" is the title
  // The C5H4N5 +H is the formula.

  formula.remove(QRegularExpression("\".*\""));

  // We want to remove all the possibly-existing spaces.

  formula.remove(QRegularExpression("\\s+"));

#if 0

  // This old version tried to save computing work, but it then anyways
  // calls for parsing of the formula which is the most computing-intensive
  // part. Thus we now rely on the regular expression to simultaneously
// check the syntax, divide the formula into its '+' and '-' parts,
  // and finally check that the symbol is known to the isotopic data.
  ^
  // If the formula does not contain any '-' character, then we
  // can approximate that all the formula is a '+' formula, that is, a
  // plusFormula:

  if(actions() == '+')
    {
      // qDebug() << "Only plus actions.";

      m_plusFormula.append(formula);

      // At this point we want to make sure that we have a correct
      // formula. Remove all the occurrences of the '+' sign.
      m_plusFormula.replace(QString("+"), QString(""));

      if(m_plusFormula.length() > 0)
        {
          // qDebug() << "splitActionParts: with m_plusFormula:" <<
          // m_plusFormula;

          if(!parse(isotopic_data_csp, m_plusFormula, times, store, reset))
            return FormulaSplitResult::FAILURE;
          else
            return FORMULA_SPLIT_PLUS;
        }
    }
  // End of
  // if(actions() == '+')

  // If we did not return at previous block that means there are at least one
  // '-' component in the formula. we truly have to iterate in the formula...
#endif


  // See the explanations in the header file for the member datum
  // m_subFormulaRegExp and its use with globalMatch(). One thing that is
  // important to see, is that the RegExp matches a triad : [ [sign or not]
  // [symbol] [count] ], so, if we have, says, formula "-H2O", it would match:

  // First '-' 'H' '2'
  // Second <no sign> 'O' <no count = 1>

  // The problem is that at second match, the algo thinks that O1 is a +
  // formula, while in fact it is part of a larger minus formula: -H2O. So we
  // need to check if, after a '-' in a formula, there comes or not a '+'. If so
  // we close the minus formula and start a plus formula, if not, we continue
  // adding matches to the minus formula.

  // qDebug() << "Now regex parsing with globalMatch feature of formula:"
  //          << formula;

  bool was_minus_formula = false;

  for(const QRegularExpressionMatch &match :
      subFormulaRegExp.globalMatch(formula))
    {
      QString sub_match = match.captured(0);

      qDebug() << "Entering [+-]?<symbol><count?> sub-match:" << sub_match;

      QString sign            = match.captured(1);
      QString symbol          = match.captured(2);
      QString count_as_string = match.captured(3);
      double count_as_double  = 1.0;

      if(!count_as_string.isEmpty())
        {
          bool ok         = false;
          count_as_double = count_as_string.toDouble(&ok);
          if(!ok)
            return static_cast<int>(FormulaSplitResult::FAILURE);
        }
      else
        {
          count_as_string = "1";
        }

      // Check that the symbol is known to the isotopic data.
      // qDebug() << "The symbol:" << symbol << "with count:" <<
      // count_as_double;

      if(!isotopic_data_csp->containsSymbol(symbol))
        {
          // qDebug() << "The symbol is not known to the Isotopic data table.";
          return static_cast<int>(FormulaSplitResult::FAILURE);
        }

      // Determine if there was a sign
      if(sign == "-")
        {
          // qDebug() << "Appending found minus formula:"
          //          << QString("%1%2").arg(symbol).arg(count_as_string);

          m_minusFormula.append(
            QString("%1%2").arg(symbol).arg(count_as_string));

          formula_split_result |=
            static_cast<int>(FormulaSplitResult::HAS_MINUS_COMPONENT);

          if(store)
            {
              // qDebug() << "Accounting symbol / count pair:" << symbol << "/"
              //          << count_as_double * static_cast<double>(times);

              accountSymbolCountPair(
                symbol, -1 * count_as_double * static_cast<double>(times));

              // qDebug() << " ...done.";
            }

          // Let next round know that we are inside a minus formula group.
          was_minus_formula = true;
        }
      else if(sign.isEmpty() && was_minus_formula)
        {
          // qDebug() << "Appending new unsigned formula to the minus formula:"
          //          << QString("%1%2").arg(symbol).arg(count_as_string);

          m_minusFormula.append(
            QString("%1%2").arg(symbol).arg(count_as_string));

          if(store)
            {
              // qDebug() << "Accounting symbol / count pair:" << symbol << "/"
              //          << count_as_double * static_cast<double>(times);

              accountSymbolCountPair(
                symbol, -1 * count_as_double * static_cast<double>(times));

              // qDebug() << " ...done.";
            }

          // Let next round know that we are still inside a minus formula group.
          was_minus_formula = true;
        }
      else
        // Either there was a '+' sign or there was no sign, but
        // we were not continuing a minus formula, thus we are parsing
        // a true '+' formula.
        {
          // qDebug() << "Appending found plus formula:"
          //          << QString("%1%2").arg(symbol).arg(count_as_string);

          m_plusFormula.append(
            QString("%1%2").arg(symbol).arg(count_as_string));

          formula_split_result |=
            static_cast<int>(FormulaSplitResult::HAS_PLUS_COMPONENT);

          if(store)
            {
              // qDebug() << "Accounting symbol / count pair:" << symbol << "/"
              //          << count_as_double * static_cast<double>(times);

              accountSymbolCountPair(
                symbol, count_as_double * static_cast<double>(times));

              // qDebug() << " ...done.";
            }

          was_minus_formula = false;
        }
    }

  // qDebug() << "Formula" << formula << "splits"
  //<< "(+)" << m_plusFormula << "(-)" << m_minusFormula;

  return formula_split_result;
}

/*!
  \brief Accounts for \a symbol and corresponding \a count in the member map.

  The m_symbolCountMap relates each atom (chemical element) symbol with its
occurrence count as encountered while parsing the member actionformula.

  If the symbol was not encountered yet, a new key/value pair is created.
Otherwise, the count value is updated.

  Returns the new count status for \a symbol.
*/
double
Formula::accountSymbolCountPair(const QString &symbol, double count)
{
  // We receive a symbol and we need to account for it count count in the member
  // symbol count map (count might be < 0).

  // Try to insert the new symbol/count pairinto the map. Check if that was done
  // or not. If the result.second is false, then that means the the insert
  // function did not perform because a pair by that symbol existed already. In
  // that case we just need to increment the count for the the pair.

  // qDebug() << "Accounting symbol:" << symbol << "for count:" << count;

  double new_count = 0;

  std::pair<std::map<QString, double>::iterator, bool> res =
    m_symbolCountMap.insert(std::pair<QString, double>(symbol, count));

  if(!res.second)
    {
      // qDebug() << "The symbol was already in the symbol/count map. with
      // count:"
      //          << res.first->second;

      // One pair by that symbol key existed already, just update the count and
      // store that value for reporting.
      res.first->second += count;
      new_count = res.first->second;
      // new_count might be <= 0.

      // qDebug() << "For symbol" << symbol << "the new count:" << new_count;
    }
  else
    {
      // qDebug() << "Symbol" << symbol
      //          << "was not there already, setting the count to:" << count;

      // We just effectively added during the insert call above a new pair to
      // the map by key symbol with value count.
      new_count = count;
    }

  // We should check if the symbol has now a count of 0. In that case, we remove
  // the symbol altogether because we do not want to list naught symbols in the
  // final formula.

  if(!new_count)
    {
      // qDebug() << "For symbol" << symbol
      //<< "the new count is 0. Thus we erase the map item altogether.";

      m_symbolCountMap.erase(symbol);
    }

  // Update what's the text of the formula to represent what is in
  // atomCount list.

  // qDebug() << "Now computing the new formula as an elemental composition.";
  m_formula = elementalComposition();

  // qDebug() << "... Done with a new elemental composition formula:" <<
  // m_formula;

  return new_count;
}

/*!
  \brief Returns a reference to the atom symbol / count member map.
*/
const std::map<QString, double>
Formula::getSymbolCountMap() const
{
  return m_symbolCountMap;
}

/*!
  \brief Accounts the actionformula in \a text using \a isotopic_data_csp as
reference data using \a times as a compounding factor.

  The \a text formula is converted into a temporary Formula and processed:

  \list
  \li First validate() is called on the temporary formula, with storage of the
symbol/count data;
  \li The symbol/count data thus generated is used to update the member map.
  \endlist

  Returns the member symbol/count m_symbolCountMap size.
*/
std::size_t
Formula::accountFormula(const QString &text,
                        IsotopicDataCstSPtr isotopic_data_csp,
                        double times)
{
  // qDebug() << "Accounting in this formula:" << m_formula
  //<< "the external formula:" << text;

  // qDebug() << "Before having merged the external formula's map into this one,
  // " "this one has size:"
  //<< m_symbolCountMap.size();

  // We get a formula as an elemental composition text string and we want to
  // account for that formula in *this formula.

  // First off, validate the text.

  Formula formula(text);

  // The formula is asked to validate with storage of the found symbol/count
  // pairs and with resetting of the previous contents of the symbol/count map.
  if(!formula.validate(isotopic_data_csp, true, true))
    {
      qDebug() << "Formula:" << text << "failed to validate.";
      return -1;
    }

  // Now, for each item in the formula's symbol/count map, aggregate the found
  // data to *this symbol/count map. We'll have "merged" or "aggreated" the
  // other formula into *this one.

  std::map<QString, double> map_copy = formula.getSymbolCountMap();

  // qDebug()
  //<< "The external formula, after validation has a symbol/count map of size:"
  //<< map_copy.size();

  std::map<QString, double>::const_iterator iter     = map_copy.cbegin();
  std::map<QString, double>::const_iterator iter_end = map_copy.cend();

  while(iter != iter_end)
    {
      accountSymbolCountPair(iter->first, iter->second * times);
      ++iter;
    }

  // qDebug() << "After having merged the external formula's map into this one,
  // " "this one has size:"
  //<< m_symbolCountMap.size();

  // Update what's the text of the formula to represent what is in
  // atomCount list.
  m_formula = elementalComposition();

  // qDebug() << "And now this formula has text: " << m_formula;

  return m_symbolCountMap.size();
}


#if 0

Old version that parsed the actionformula char by char.
bool
Formula::checkSyntax(const QString &formula, bool forceCountIndex)
{
  // Static function.

  // qDebug() << "Checking syntax with formula:" << formula;

  QChar curChar;

  bool gotUpper = false;
  bool wasSign  = false;
  bool wasDigit = false;

  // Because the formula that we are analyzing might contain a title
  // and spaces , we first remove these. But make a local copy of
  // the member datum.

  QString localFormula = formula;

  // One formula can be like this:

  // "Decomposed adenine" C5H4N5 +H

  // The "Decomposed adenine" is the title
  // The C5H4N5 +H is the formula.

  localFormula.remove(QRegularExpression("\".*\""));

  // We want to remove all the possibly-existing spaces.

  localFormula.remove(QRegularExpression("\\s+"));


  for(int iter = 0; iter < localFormula.length(); ++iter)
    {
      curChar = localFormula.at(iter);

      // qDebug() << __FILE__ << "@" << __LINE__ << __FUNCTION__ << "()"
      //<< "Current character:" << curChar;

      // FIXME One improvement that would ease modelling the Averagine would
      // be to silently allow double formula indices (that is, double atom
      // counts). They would not be compulsory

      if(curChar.category() == QChar::Number_DecimalDigit)
        {
          // We are parsing a digit.

          // We may not have a digit after a +/- sign.
          if(wasSign)
            return false;

          wasSign  = false;
          wasDigit = true;

          continue;
        }
      else if(curChar.category() == QChar::Letter_Lowercase)
        {
          // Current character is lowercase, which means we are inside
          // of an atom symbol, such as Ca(the 'a') or Nob(either
          // 'o' or 'b'). Thus, gotUpper should be true !

          if(!gotUpper)
            return false;

          // We may not have a lowercase character after a +/- sign.
          if(wasSign)
            return false;

          // Let the people know that we have parsed a lowercase char
          // and not a digit.
          wasSign = false;

          wasDigit = false;
        }
      else if(curChar.category() == QChar::Letter_Uppercase)
        {
          // Current character is uppercase, which means that we are
          // at the beginning of an atom symbol.

          // There are two cases:
          // 1. We are starting for the very beginning of the formula, and
          // nothing came before this upper case character. That's fine.
          // 2. We had previously parsed a segment of the formula, and in this
          // case, we are closing a segment. If the parameter
          // obligatoryCountIndex is true, then we need to ensure that the
          // previous element had an associated number, even it the count
          // element is 1. This is required for the IsoSpec stuff in the gui
          // programs.

          if(iter > 0)
            {
              if(forceCountIndex)
                {
                  if(!wasDigit)
                    {
                      qDebug()
                        << "Returning false because upper case char was not"
                           "preceded by digit while not at the first char of "
                           "the formula";

                      return false;
                    }
                }
            }

          // Let the people know what we got:

          wasSign  = false;
          gotUpper = true;
          wasDigit = false;
        }
      else
        {
          if(curChar != '+' && curChar != '-')
            return false;
          else
            {
              // We may not have 2 +/- signs in a raw.
              if(wasSign)
                return false;
            }

          wasSign  = true;
          gotUpper = false;
          wasDigit = false;
        }
    }
  // end for (int iter = 0 ; iter < localFormula.length() ; ++iter)

  // Note that if we want an obligatory count index, then, at the end of the
  // formula, *compulsorily* we must have parsed a digit.

  if(forceCountIndex && !wasDigit)
    {
      qDebug()
        << "Returning false because the formula does not end with a digit.";

      return false;
    }

  // At this point we found no error condition.
  return true;
}
#endif

/*!
  \brief Returns true if the member actionformula is syntactically valid, false
otherwise.

  \sa checkSyntax(const QString &formula, bool force_count_index)
*/
bool
Formula::checkSyntax() const
{
  // The default formula is always m_formula.

  return checkSyntax(m_formula, m_forceCountIndex);
}

/*!
  \brief Returns true if the \a formula actionformula is syntactically
valid, false otherwise.

  If \a force_count_index is true, the syntax check accounts for the
requirement that all the symbols in the formula must be indexed, even if that
symbol's count is 1. This means that H2O would not pass the check, while H2O1
would.

  The formula is first stripped of its title (if any), then all the spaces are
removed.

  MsXpS::libXpertMass::Formula::subFormulaRegExp is then used to extract each "plus"
and / or "minus" component while checking its syntactic
validity.

  \note The syntax checking code does not verify that the actionformula is
chemically valid, that is, the "Cz4" symbol / count pair would check even if
the Cz chemical element does not exist.

  \sa validate()
*/
bool
Formula::checkSyntax(const QString &formula, bool force_count_index)
{
  // Because the formula that we are analyzing might contain a title
  // and spaces , we first remove these. But make a local copy of
  // the member datum.

  QString localFormula = formula;

  // One formula can be like this:

  // "Decomposed adenine" C5H4N5 +H

  // The "Decomposed adenine" is the title
  // The C5H4N5 +H is the formula.

  localFormula.remove(QRegularExpression("\".*\""));

  // We want to remove all the possibly-existing spaces.

  localFormula.remove(QRegularExpression("\\s+"));

  // qDebug() << "The formula is:" << localFormula;

  // The raw formula might include:
  // +/- sign before the symbol
  // then the symbol (one uppercase any lowercase)
  // then the count as an integer or a double.

  for(const QRegularExpressionMatch &match :
      subFormulaRegExp.globalMatch(localFormula))
    {
      QString full_match = match.captured(0);

      // qDebug() << "The full sub-match:" << full_match;

      QString sign         = match.captured(1);
      QString symbol       = match.captured(2);
      QString count_string = match.captured(3);

      if(!count_string.isEmpty())
        {
          bool ok = false;
          // Verify that it correctly converts to double.
          count_string.toDouble(&ok);
          if(!ok)
            return false;
        }
      else
        {
          if(force_count_index)
            {
              qDebug() << "Error: symbol" << symbol << "has no index.";

              return false;
            }
          else
            {
              // qDebug() << "Symbol" << symbol
              //          << "has no index but that is tolerated.";
            }
        }

      // qDebug() << "Sign:" << match.captured(1) << "Symbol:" <<
      // match.captured(2)
      //          << "Count:" << match.captured(3);
    }

  return true;
}

/*! Returns true if the formula validates successfully, false otherwise.

The polymorphic function validate(IsotopicDataCstSPtr isotopic_data_csp, bool
store, bool reset) is called with both arguments set to false.

The validation of this Formula instance is performed against the \a
isotopic_data_csp isotopic reference data.
*/
bool
Formula::validate(IsotopicDataCstSPtr isotopic_data_csp)
{
  return validate(isotopic_data_csp, false, false);
}

/*! Returns true if the formula validates successfully, false otherwise.

  The validation of the formula involves:

   \list
   \li Checking that the member actionformula is not empty. Returns false
otherwise;
   \li Splitting the actionformula into "plus" and "minus" components
(\l{splitActionParts}). If that steps fails, returns false;
   \li Verifying that both the m_plusFormula and the m_minusFormula are not
empty. Returns false otherwise;
  \endlist

  If \a store is true, the symbol / count data obtained while splitting the
"plus" and "minus" components of the actionformula are stored in the member
m_symbolCountMap map.

  If \a reset is true, the member symbol / count is first
reset.

  \a isotopic_data_csp are the isotopic data used as reference to ensure
chemical validity of the formula components.
  */
bool
Formula::validate(IsotopicDataCstSPtr isotopic_data_csp, bool store, bool reset)
{
  if(isotopic_data_csp == nullptr || isotopic_data_csp.get() == nullptr)
    qFatal("Programming error. The isotopic data pointer cannot be nullptr.");

  if(!isotopic_data_csp->size())
    qFatal("Programming error. The isotopic data cannot be empty.");

  qDebug() << "isotopic_data_csp.get():" << isotopic_data_csp.get();

  if(!m_formula.size())
    {
      qDebug() << "The formula is empty.";
      return false;
    }

  // qDebug() << "Now splitting formula" << m_formula << "into its action
  // parts.";

  int result = splitActionParts(isotopic_data_csp, 1, store, reset);

  if(result == static_cast<int>(FormulaSplitResult::FAILURE))
    {
      qDebug() << "Failed splitting the formula into its action parts.";
      return false;
    }

  // Both the action formulas cannot be empty.
  if(!m_plusFormula.size() && !m_minusFormula.size())
    {
      qDebug() << "Both the plus and minus formulas are empty.";
      return false;
    }

  // qDebug() << "Success: the formula validated fine.";

  return true;
}

/*!
  \brief Accounts this formula's monoisotopic and average masses into \a mono
and \a avg, using \a times as a compounding factor.

  The masses corresponding to the member actionformula m_formula are
calculated first and then the \a mono and \a avg parameters are updated
by incrementing their value with the calculated values. This incrementation
might be compounded by that \a times factor.

  The masses of m_formula are computed using data from \a isotopic_data_csp.

  Returns true if no error was encountered, false otherwise.

  \sa splitActionParts()
*/
bool
Formula::accountMasses(IsotopicDataCstSPtr isotopic_data_csp,
                       double *mono,
                       double *avg,
                       double times)
{
  // Note the 'times' param below that ensures we create proper symbol/count
  // map items by taking that compounding factor into account.

  qDebug() << qSetRealNumberPrecision(6)
           << "We get two mono and avg variables with values:" << *mono << "-"
           << *avg << "and times:" << times;

  if(isotopic_data_csp == nullptr)
    qFatal("Programming error. The pointer cannot be nullptr.");

  if(splitActionParts(
       isotopic_data_csp, times, true /* store */, true /* reset */) ==
     static_cast<int>(FormulaSplitResult::FAILURE))
    return false;

  // qDebug() << "Formula::accountMasses:"
  //<< "after splitActionParts:"
  //<< "store: true ; reset: true"
  //<< "m_formula:" << m_formula << "text" << Formula::toString();

  // At this point m_symbolCountMap has all the symbol/count pairs needed to
  // account for the masses.

  std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
  std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();

  // for(auto item : m_symbolCountMap)
  // qDebug() << "One symbol count item:" << item.first << "/" << item.second;

  bool ok = false;

  while(iter != iter_end)
    {
      QString symbol = iter->first;

      // qDebug() << "Getting masses for symbol:" << symbol;

      if(mono != nullptr)
        {
          double mono_mass =
            isotopic_data_csp->getMonoMassBySymbol(iter->first, &ok);

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

          *mono += mono_mass * iter->second;
        }

      ok = false;

      if(avg != nullptr)
        {
          double avg_mass =
            isotopic_data_csp->getAvgMassBySymbol(iter->first, &ok);

          if(!ok)
            return false;

          *avg += avg_mass * iter->second;
        }

      ++iter;
    }

  return true;
}


/*!
  \brief Accounts this formula's monoisotopic and average masses into \a
ponderable, using \a times as a compounding factor.

  This function uses \l{accountMasses()}.

  \sa splitActionParts()
*/
bool
Formula::accountMasses(IsotopicDataCstSPtr isotopic_data_csp,
                       Ponderable *ponderable,
                       double times)
{
  if(ponderable == nullptr)
    qFatal("Fatal error: pointer cannot be nullptr. Program aborted.");

  return accountMasses(
    isotopic_data_csp, &ponderable->rmono(), &ponderable->ravg(), times);


  //// Note the 'times' param below.
  // if(splitActionParts(isotopic_data_csp, times, true, true) ==
  // FormulaSplitResult::FAILURE)
  // return false;

  //// At this point m_symbolCountMap has all the symbol/count pairs needed to
  //// account for the masses.

  // std::map<QString, int>::const_iterator iter     =
  // m_symbolCountMap.cbegin(); std::map<QString, int>::const_iterator iter_end
  // = m_symbolCountMap.cend();

  // while(iter != iter_end)
  //{
  // double mono_mass = isotopic_data_csp->getMonoMassBySymbol(iter->first);
  // ponderable->rmono() += mono_mass * iter->second;

  // double avg_mass = isotopic_data_csp->getAvgMassBySymbol(iter->first);
  // ponderable->ravg() += avg_mass * iter->second;

  //++iter;
  //}

  return true;
}


//! Account the atoms in \c this \c m_formula.
/*!
  \brief Accounts this Formula's actionformula m_formula in the symbol / count
member m_symbolCountMap.

  Calls splitActionParts() to actually parse m_formula and account its
components to m_symbolCountMap. The accounting of the symbol / count can be
compounded by the \a times factor.

  While splitting the "plus" and "minus" components of the actionformula, their
validity is checked against the reference isotopic data \a isotopic_data_csp.

  This function is used when processively accounting many different formulas
into the symbol / count map. The formula is set to a new value and this
function is called without resetting the symbol / count map, effectively adding
formulas onto formulas sequentially.

  Returns true if no error was encountered, false otherwise.

  \sa splitActionParts(), Polymer::elementalComposition()
  */
bool
Formula::accountSymbolCounts(IsotopicDataCstSPtr isotopic_data_csp, int times)
{
  // Note the 'times' param below.
  if(splitActionParts(isotopic_data_csp, times, true, false) ==
     static_cast<int>(FormulaSplitResult::FAILURE))
    return false;

  return true;
}

/*!
  \brief Returns a formula matching the contents of the symbol / count member
map.

  The returned formula is formatted according to the IUPAC convention about the
ordering of the chemical elements: CxxHxxNxxOxxSxxPxx.

  The "plus" components are output first and the "minus" components after.

  If \a symbol_count_pairs_p is not nullptr, each symbol / count pair is added
to it.
*/
QString
Formula::elementalComposition(
  std::vector<std::pair<QString, double>> *symbol_count_pairs_p) const
{
  // Iterate in the symbol count member map and for each item output the symbol
  // string accompanied by the corresponding count. Note that the count for any
  // given symbol might be negative. We want to craft an elemental composition
  // that accounts for "actions", that is a +elemental formula and a -elemental
  // formula.

  std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
  std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();

#if 0

  qDebug() << "While computing the elemental composition corresponding to the "
              "symbol/count map:";
  for(auto pair : m_symbolCountMap)
    qDebug().noquote() << "(" << pair.first << "," << pair.second << ")";

#endif

  QStringList negativeStringList;
  QStringList positiveStringList;

  while(iter != iter_end)
    {
      QString symbol = iter->first;
      double count   = iter->second;

      if(count < 0)
        {
          negativeStringList.append(
            QString("%1%2").arg(symbol).arg(-1 * count));
        }
      else
        {
          positiveStringList.append(QString("%1%2").arg(symbol).arg(count));
        }

      ++iter;
    }

  // We want to provide a formula that lists the positive component
  // first and the negative component last.

  // Each positive/negative component will list the atoms in the
  // conventional order : CxxHxxNxxOxx and all the rest in
  // alphabetical order.

  // We want to provide for each positive and negative components of the
  // initial formula object, an elemental formula that complies with the
  // convention : first the C atom, next the H, N, O, S, P atoms and all the
  // subsequent ones in alphabetical order.

  // Sort the lists.
  negativeStringList.sort();
  positiveStringList.sort();

  // Thus we look for the four C, H, N, O, S,P atoms, and we create the
  // initial part of the elemental formula. Each time we find one
  // such atom we remove it from the list, so that we can later just
  // append all the remaining atoms, since we have sorted the lists
  // above.

  // The positive component
  // ======================

  int symbol_index_in_list = 0;
  QString positiveComponentString;

  // Carbon
  symbol_index_in_list =
    positiveStringList.indexOf(QRegularExpression("C\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      positiveComponentString += positiveStringList.at(symbol_index_in_list);
      positiveStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("C", m_symbolCountMap.at("C")));
    }

  // Hydrogen
  symbol_index_in_list =
    positiveStringList.indexOf(QRegularExpression("H\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      positiveComponentString += positiveStringList.at(symbol_index_in_list);
      positiveStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("H", m_symbolCountMap.at("H")));
    }

  // Nitrogen
  symbol_index_in_list =
    positiveStringList.indexOf(QRegularExpression("N\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      positiveComponentString += positiveStringList.at(symbol_index_in_list);
      positiveStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("N", m_symbolCountMap.at("N")));
    }

  // Oxygen
  symbol_index_in_list =
    positiveStringList.indexOf(QRegularExpression("O\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      positiveComponentString += positiveStringList.at(symbol_index_in_list);
      positiveStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("O", m_symbolCountMap.at("O")));
    }

  // Sulfur
  symbol_index_in_list =
    positiveStringList.indexOf(QRegularExpression("S\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      positiveComponentString += positiveStringList.at(symbol_index_in_list);
      positiveStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("S", m_symbolCountMap.at("S")));
    }

  // Phosphorus
  symbol_index_in_list =
    positiveStringList.indexOf(QRegularExpression("P\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      positiveComponentString += positiveStringList.at(symbol_index_in_list);
      positiveStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("P", m_symbolCountMap.at("P")));
    }

  // Go on with all the other ones, if any...

  for(int iter = 0; iter < positiveStringList.size(); ++iter)
    {
      positiveComponentString += positiveStringList.at(iter);

      QRegularExpression regexp("([A-Z][a-z]*)(\\d*[\\.]?\\d*)");
      QRegularExpressionMatch match = regexp.match(positiveStringList.at(iter));

      if(match.hasMatch())
        {
          QString symbol  = match.captured(1);
          QString howMany = match.captured(2);

          bool ok      = false;
          double count = howMany.toDouble(&ok);

          if(!count && !ok)
            qFatal(
              "Fatal error at %s@%d -- %s(). "
              "Failed to parse an atom count."
              "Program aborted.",
              __FILE__,
              __LINE__,
              __FUNCTION__);

          if(symbol_count_pairs_p)
            symbol_count_pairs_p->push_back(
              std::pair<QString, double>(symbol, count));
        }
    }

  // qDebug() << __FILE__ << __LINE__
  //<< "positiveComponentString:" << positiveComponentString;


  // The negative component
  // ======================

  symbol_index_in_list = 0;
  QString negativeComponentString;

  // Carbon
  symbol_index_in_list =
    negativeStringList.indexOf(QRegularExpression("C\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      negativeComponentString += negativeStringList.at(symbol_index_in_list);
      negativeStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("C", m_symbolCountMap.at("C")));
    }

  // Hydrogen
  symbol_index_in_list =
    negativeStringList.indexOf(QRegularExpression("H\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      negativeComponentString += negativeStringList.at(symbol_index_in_list);
      negativeStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("H", m_symbolCountMap.at("H")));
    }

  // Nitrogen
  symbol_index_in_list =
    negativeStringList.indexOf(QRegularExpression("N\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      negativeComponentString += negativeStringList.at(symbol_index_in_list);
      negativeStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("N", m_symbolCountMap.at("N")));
    }

  // Oxygen
  symbol_index_in_list =
    negativeStringList.indexOf(QRegularExpression("O\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      negativeComponentString += negativeStringList.at(symbol_index_in_list);
      negativeStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("O", m_symbolCountMap.at("O")));
    }

  // Sulfur
  symbol_index_in_list =
    negativeStringList.indexOf(QRegularExpression("S\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      negativeComponentString += negativeStringList.at(symbol_index_in_list);
      negativeStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("S", m_symbolCountMap.at("S")));
    }

  // Phosphorus
  symbol_index_in_list =
    negativeStringList.indexOf(QRegularExpression("P\\d*[\\.]?\\d*"));
  if(symbol_index_in_list != -1)
    {
      negativeComponentString += negativeStringList.at(symbol_index_in_list);
      negativeStringList.removeAt(symbol_index_in_list);

      if(symbol_count_pairs_p)
        symbol_count_pairs_p->push_back(
          std::pair<QString, double>("P", m_symbolCountMap.at("P")));
    }

  // Go on with all the other ones, if any...

  for(int iter = 0; iter < negativeStringList.size(); ++iter)
    {
      negativeComponentString += negativeStringList.at(iter);

      QRegularExpression regexp("([A-Z][a-z]*)(\\d*[\\.]?\\d*)");
      QRegularExpressionMatch match = regexp.match(negativeStringList.at(iter));

      if(match.hasMatch())
        {
          QString symbol  = match.captured(1);
          QString howMany = match.captured(2);

          bool ok      = false;
          double count = howMany.toInt(&ok, 10);

          if(!count && !ok)
            qFatal(
              "Fatal error at %s@%d -- %s(). "
              "Failed to parse an atom count."
              "Program aborted.",
              __FILE__,
              __LINE__,
              __FUNCTION__);

          if(symbol_count_pairs_p)
            symbol_count_pairs_p->push_back(
              std::pair<QString, double>(symbol, count));
        }
    }


  // qDebug() << __FILE__ << __LINE__
  //<< "negativeComponentString:" << negativeComponentString;

  // Create the final elemental formula that comprises both the
  // positive and negative element. First the positive element and
  // then the negative one. Only append the negative one, prepended
  // with '-' if the string is non-empty.

  QString elementalComposition = positiveComponentString;

  if(!negativeComponentString.isEmpty())
    elementalComposition += QString("-%1").arg(negativeComponentString);

  // qDebug() << __FILE__ << __LINE__
  // <<"elementalComposition:" << elementalComposition;

  return elementalComposition;
}


/*!
  \brief Returns the total count of symbols (atoms) in this formula.

  The determination is performed by summing up all the count values for all the
symbols in the member symbol / count pairs in the member map m_symbolCountMap.
*/
double
Formula::totalAtoms() const
{

  double total_atom_count = 0;

  std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
  std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();

  while(iter != iter_end)
    {
      total_atom_count += iter->second;
      ++iter;
    }

  return total_atom_count;
}


/*!
  \brief Returns the total count of isotopes in this formula using \a
isotopic_data_csp as the reference isotopic data.

  The determination is performed by summing up all the isotope counts for
all the symbols keys in the member symbol / count map m_symbolCountMap.
*/
double
Formula::totalIsotopes(IsotopicDataCstSPtr isotopic_data_csp) const
{
  double total_isotope_count = 0;

  std::map<QString, double>::const_iterator iter = m_symbolCountMap.cbegin();
  std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();

  while(iter != iter_end)
    {
      total_isotope_count +=
        iter->second * isotopic_data_csp->getIsotopeCountBySymbol(iter->first);

      ++iter;
    }

  return total_isotope_count;
}

/*!
  \brief Returns the count value associated with key \a symbol in the symbol /
count member map m_symbolCountMap.
*/
double
Formula::symbolCount(const QString &symbol) const
{
  // Return the symbol index.

  std::map<QString, double>::const_iterator iter_end = m_symbolCountMap.cend();

  std::map<QString, double>::const_iterator iter =
    m_symbolCountMap.find(symbol);

  if(iter == iter_end)
    return 0;

  return iter->second;
}

/*!
  \brief Returns true if the member "minus" formula component is not empty,
false otherwise.
*/
bool
Formula::hasNetMinusPart()
{
  return m_minusFormula.size();
}


/*!
  \brief Parses a formula XML \a element, sets the data to the
member actionformula m_formula and checks it syntax.

  Returns true if parsing and syntax checking were successful, false
  otherwise.

  \sa checkSyntax()
  */
bool
Formula::renderXmlFormulaElement(const QDomElement &element)
{
  if(element.tagName() != "formula")
    return false;

  m_formula = element.text();

  // qDebug() << "Rendering formula element with text:" << m_formula;

  // Do not forget that we might have a title associated with the
  // formula and spaces. checkSyntax() should care of removing these
  // title and spaces before checking for chemical syntax
  // correctness.

  return checkSyntax();
}

} // namespace libXpertMass

} // namespace MsXpS
