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


#pragma once


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


/////////////////////// Local includes
#include <libmass/AtomCount.hpp>


namespace msXpSlibmass
{

//! Enum that specifies the result of a formula splitting process
/*!

  A formula can represent a static chemical object (like water, with H20), but
  can also represent some dynamic chemical event, like loss of a chemical
  entity and gain of another chemical entity, like -H20+CH3COOH. In this
  latter case, the formula is said to be an actionformula. When working with
  actionformulas, it is first necessary to split the action formula into its
  minus component and its plus component. Depending on the composition of the
  formula, the splitting work might lead to only a plus component or only a
  minus component, or both components. This enum serves to characterize the
  type of components found in an actionformula (or a formula with no action,
  that is equivalent to an action formula with only a plus component).

*/
enum FormulaSplitResult
{
  FORMULA_SPLIT_FAIL  = 1 << 0, //!< The splitting work failed.
  FORMULA_SPLIT_PLUS  = 1 << 1, //!< The action formula a plus component
  FORMULA_SPLIT_MINUS = 1 << 2, //!< The action formula a minus component
  FORMULA_SPLIT_BOTH =
    (FORMULA_SPLIT_PLUS |
     FORMULA_SPLIT_MINUS) //!< The action formula had both component types
};


//! The Formula class provides a formula (or, better, an actionformula).
/*!

  A formula is the description of the atomic composition of a compound. For
  example, the string <em>C2H6</em> is a formula. While, the previous
  <em>C2H6</em> example describes a static chemical object, a Formula can also
  describe a dynamic chemical event, like a reaction, by describing what
  chemical elements are gained by the molecule during the chemical reaction
  (the plus component of the actionformula) and what chemical elements are
  lost by the molecule (the minus component). For example, an acetylation
  reaction can be described by the loss of <em>H2O</em> with gain of
  <em>CH3COOH</em>. The net chemical gain on the molecule will be
  <em>CH3CO</em>. In this example, one would thus define an actionformula in
  the following form: <em>-H20+CH3COOH</em>.  The formula associated with the
  '-' action accounts for the leaving group of the reaction, while the 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 <em>-H+CO2-H20+C2H6</em>.

  An 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.

  Another interesting feature of the Formula is its documentation system. A
  Formula can come with a documentation prefix enclosed in double quotes, like
  the following <em>"Decomposed adenine" C5H4N5 +H</em>. This documentation
  element is called the <em>title</em>. Note that the presence of a title in a
  formula does not change anything to its workings as long as the
  <em>title</em> 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 \c m_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" (m_minusFormula),
  while '+'-associated ones are stored in the "plus formula" (m_plusFormula).
  Note that all the formulas in Formula are QString objects).

  Upon parsing of the formula, the minus formula and the plus formula
  are populated with formulas (in the example above, the minus formula
  would contain "HH20", while the plus formula would contain "CO2C2H6") and
  these are next used to create AtomCount objects that are stored
  in the list of AtomCount objects with which the accounting of the
  masses of the formula is performed.

*/
class Formula
{
  protected:
  //! Actionformula.
  /*!

    This actionformula is not modified upon processing, this is always the
    original formula with which a Formula instance is created or later
    modified.

*/
  QString m_formula;

  //! Plus-formula (or '+'-associated formula).
  /*!

    After parsing the \c m_formula, all of its '+' components are stored in
    this member.

    */
  QString m_plusFormula;

  //! Minus-formula (or '-'-associated formula).
  /*!

    After parsing the \c m_formula, all of its '-' components are stored in
    this member.

    */
  QString m_minusFormula;

  //! List of dynamically allocated AtomCount objects.
  /*!

    This list stores all the AtomCount objects that are created upon parsing
    of the formula.

    */
  QList<AtomCount *> m_atomCountList;

  //! Tell if elements present a single time need to have an index
  /*!

    When a formula is like H2O, if true, then the formula does not validate
    if it is not defined like so: H2O1.
    */
  bool m_forceCountIndex = false;

  void setPlusFormula(const QString &formula);
  const QString &plusFormula() const;

  void setMinusFormula(const QString &formula);
  const QString &minusFormula() const;

  int removeTitle();
  int removeSpaces();

  int splitParts(const QList<Atom *> &refList,
                 int times  = 1,
                 bool store = false,
                 bool reset = false);

  bool parse(const QList<Atom *> &refList,
             const QString &formula,
             int times  = 1,
             bool store = false,
             bool reset = false);

  int accountInList(const AtomCount &, int = 1);

  public:
  Formula(const QString &formula = QString());
  Formula(const Formula &formula);

  virtual ~Formula();

  virtual Formula *clone() const;
  virtual void clone(Formula *other) const;
  virtual void mold(const Formula &other);
  virtual Formula &operator=(const Formula &other);

  void setFormula(const QString &formula);
  void setFormula(const Formula &formula);
  QString text() const;

  void setForceCountIndex(bool forceCountIndex);
  void clear();

  int accountInList(const QString &text,
                    const QList<Atom *> &refList,
                    int times = 1);

  const QList<AtomCount *> &atomCountList() const;

  int atomSymbolCount(const QString &symbol) const;

  virtual bool operator==(const Formula &other) const;
  virtual bool operator!=(const Formula &other) const;

  static QChar actions(const QString &formula);
  QChar actions() const;

  static bool checkSyntax(const QString &formula, bool forceCountIndex = false);
  bool checkSyntax() const;

  virtual bool validate(const QList<Atom *> &, bool = false, bool = false);

  virtual bool accountMasses(const QList<Atom *> &refList,
                             double *mono = Q_NULLPTR,
                             double *avg  = Q_NULLPTR,
                             int          = 1);

  virtual bool accountMasses(const QList<Atom *> &, Ponderable *, int = 1);

  bool accountAtoms(const QList<Atom *> &, int);
  QString elementalComposition(
    std::vector<std::pair<QString, int>> *dataVector = nullptr) const;

  int totalAtoms() const;
  int totalIsotopes(const QList<Atom *> &refList) const;
  bool totalEntities(const QList<Atom *> &refList,
                     int *totalAtoms    = Q_NULLPTR,
                     int *totalIsotopes = Q_NULLPTR) const;

  bool deepAtomCopy(const QList<Atom *> &refList);

  bool renderXmlFormulaElement(const QDomElement &);
};

} // namespace msXpSlibmass
