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


/////////////////////// Std lib includes
#include <set>


/////////////////////// Qt includes
#include <QDebug>
#include <QFile>
#include <QIODevice>


/////////////////////// IsoSpec
#include <IsoSpec++/isoSpec++.h>
#include <IsoSpec++/element_tables.h>


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataManualConfigHandler.hpp"

namespace MsXpS
{

namespace libXpertMassCore
{


/*!
\class MsXpS::libXpertMassCore::IsotopicDataManualConfigHandler
\inmodule libXpertMassCore
\ingroup PolChemDefBuildingdBlocks
\inheaderfile IsotopicDataManualConfigHandler.hpp

\brief The IsotopicDataManualConfigHandler class handles a peculiar kind of
\l{IsotopicData} that cannot be handled with the other handlers.

This kind of IsotopicData handler is typically used when definition of brand
new chemical element isotopes are to be performed. For example, if one works
with radioactive carbon C14, then that isotope is not available in the
IsoSpec's library and cannot be inserted in these data using the
\l{IsotopicDataUserConfigHandler}.

One interesting feature of this kind of IsotopicData formalism is that these
data collectively describe a chemical elemental composition formula,
like, for glucose: C6H12O6. Each chemical element is decribed using isotopes
with mass/abundance pairs. There is thus virtually no limitation on the
complexity of the isotopic distribution to be defined. The format of these data
stored on file is nothing like the format used to store Library- or
User-Config- isotopic data (\l{IsotopicDataLibraryHandler},
\l{IsotopicDataUserConfigHandler}).

  In the example below we are defining the isotopic composition of
radiolabelled glucose (C6H12O6) where only two of the the six C atoms are 14[C]
atoms with a radiolabelling efficiency of 95%:

  \code
  [Element]
  symbol C count 4
  # The four atoms that are not labelled appear with natural
  # abundances for both stable isotopes
    [Isotopes] 2
      mass 12.0 prob 0.989
    mass 13.003354 prob 0.010788
  [Element]
  # The two atoms that are 95% labelled with 14[C] need to use
  # a new artificial symbol
  symbol Cx count 2
    [Isotopes] 3
      # 5% of non labelled atoms are of natural 12[C] abundance
      mass 12.000 prob 0.05*0.989211941850466
      # 5% of non labelled atoms are of natural 13[C] abundance
      mass 13.0033548352 prob 0.05*0.010788058149533084
      # The remaining 95% of the atoms are 14[C] atoms with abundance 100%
      mass 14.003241989 prob 0.95*1.00
  [Element]
  symbol H count 12
    [Isotopes] 2
      mass 1.0078250 prob 0.99988
      mass 2.01410177 prob 0.00011570
  [Element]
  symbol O count 6
    [Isotopes] 3
      mass 15.9949 prob 0.99756
      mass 16.999 prob 0.000380
      mass 17.999 prob 0.002051
  \endcode

  Comments are allowed and are on lines that have as their first non-space
character the '#' character. These lines are ignored when loading data.

  The data can be loaded from and written to file.

  \sa IsotopicDataLibraryHandler, IsotopicDataUserConfigHandler
*/

/*!
\typealias MsXpS::libXpertMassCore::SymbolCountMap

Alias for to std::map<QString, int>.
*/

/*!
  \variable
MsXpS::libXpertMassCore::IsotopicDataManualConfigHandler::m_symbolCountMap

  Holds symbol/count pairs to document the count of any symbol in the isotopic
data. This symbol count is stored in the file using the following format:

  \code
  [Element]
  symbol C count 6
  \endcode

  The number of carbon atoms in the formula being defined is 6, as in glucose:
C6H1206.
*/

/*!
    \typedef MsXpS::libXpertMassCore::IsotopicDataManualConfigHandlerSPtr
    \relates IsotopicDataManualConfigHandler

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

/*!
    \typedef MsXpS::libXpertMassCore::IsotopicDataManualConfigHandlerCstSPtr
    \relates IsotopicDataManualConfigHandler

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

/*!
  \brief Constructs the \l{IsotopicDataManualConfigHandler} with \a file_name.
*/
IsotopicDataManualConfigHandler::IsotopicDataManualConfigHandler(
  const QString &file_name)
  : IsotopicDataBaseHandler(file_name)
{
}

/*!
  \brief Constructs the \l{IsotopicDataManualConfigHandler}.

  \a isotopic_data_sp Isotopic data

  \a file_name File name
*/
IsotopicDataManualConfigHandler::IsotopicDataManualConfigHandler(
  IsotopicDataSPtr isotopic_data_sp, const QString &file_name)
  : IsotopicDataBaseHandler(isotopic_data_sp, file_name)
{
}

/*!
  \brief Destructs the \l{IsotopicDataManualConfigHandler}.
*/
IsotopicDataManualConfigHandler::~IsotopicDataManualConfigHandler()
{
  // qDebug();
}

/*!
  \brief Assigns \a map to m_symbolCountMap.
*/
void
IsotopicDataManualConfigHandler::setSymbolCountMap(const SymbolCountMap &map)
{
  m_symbolCountMap = map;
}

/*!
  \brief Returns a reference to m_symbolCountMap.
*/
const SymbolCountMap &
IsotopicDataManualConfigHandler::getSymbolCountMap() const
{
  return m_symbolCountMap;
}

/*!
  \brief Loads isotopic data from \a file_name.

  Returns the count of \l{Isotope}s that were allocated and stored in the
msp_isotopicData member.
  */
qsizetype
IsotopicDataManualConfigHandler::loadData(const QString &file_name)
{
  if(file_name.isEmpty())
    {
      qCritical() << "File name is emtpy. Failed to open file for reading.";
      return 0;
    }

  QFile file(file_name);

  if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
      qCritical() << "Failed to open file" << file_name << "for reading.";
      return 0;
    }

  // File format:
  //
  // [Element]
  // 	symbol C count 6
  // 	[Isotopes] 2
  // 		mass 12.0 prob 0.989
  // 		mass 13.003354 prob 0.010788
  // [Element]
  // 	symbol H count 13
  // 	[Isotopes] 2
  // 		mass 1.0078250 prob 0.99988
  // 		mass 2.01410177 prob 0.00011570
  // [Element]
  // 	symbol O count 6
  // 	[Isotopes] 3
  // 		mass 15.9949 prob 0.99756
  // 		mass 16.999 prob 0.000380
  // 		mass 17.999 prob 0.002051
  //

  // qDebug() << "Loading isotopic data from file:" << local_file_name;

  // File-parsing helper variables.
  bool was_started_one_element = false;
  bool was_symbol_count_line   = false;
  bool was_isotopes_count_line = false;
  bool was_mass_prob_line      = false;

  // Instantiate a symbol that we'll use as a place holder for the element name.
  QString symbol = "";

  // Instantiate a count to document the symbol count.
  qsizetype element_count = 0;

  // Instantiate a count to document the number of isotopes that were defined
  // for a given element.
  qsizetype element_isotope_count = 0;

  // Increment each time an isotope is parsed and added to the vector *for a
  // given element stanza*. This helps sanity checking.
  qsizetype added_isotopes_for_element = 0;

  // Maintain a counter of the whole count of isotopes for sanity checks.
  qsizetype all_parsed_isotopes_count = 0;

  double mass = 0;
  double prob = 0;

  bool ok = false;

  QRegularExpression comment_regexp("^\\s*#");

  QRegularExpression symbol_count_regexp(
    "^\\s*symbol\\s+([A-Z][a-z]?)\\s+count\\s+(\\d+)");

  QRegularExpression isotopes_regexp("^\\s*\\[Isotopes\\]\\s(\\d+)");

  // QRegularExpression massProbRegexp = QRegularExpression(
  //"^\\s+mass\\s+(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\s+prob\\s+([^\\d^\\.^-]+)(-?"
  //"\\d*\\.?\\d*[e]?[-]?[+]?\\d*)");

  QRegularExpression mass_prob_regexp = QRegularExpression(
    "^\\s*mass\\s(\\d*\\.?\\d*[e]?[-]?[+]?\\d*)\\sprob\\s(\\d*\\.?\\d*[e]?["
    "-]"
    "?["
    "+]?\\d*)");

  // qDebug() << "The mass prob regexp is valid?" <<
  // massProbRegexp.isValid();

  // mass 13.003354835200
  // prob 0.010788058149533083507343178553128382191061973571777343750000

  // Make sure we clear the room.

  msp_isotopicData->clear();

  QList<IsotopeQSPtr> element_isotopes;

  // This set is to ensure that we do not have twice the same element frame
  // (that is, with the same symbol).

  std::set<QString> symbol_set;

  // File format:

  // [Element]
  // 	symbol C count 6
  // 	[Isotopes] 2
  // 		mass 12.0 prob 0.989
  // 		mass 13.003354 prob 0.010788

  QTextStream in_stream(&file);

  while(!in_stream.atEnd())
    {
      QString line = in_stream.readLine();

      // Ignore empty lines
      if(line.length() < 1)
        continue;


      line = line.simplified();

      // qDebug() << "Current  line:" << line;

      // Ignore comment lines
      QRegularExpressionMatch match = comment_regexp.match(line);
      if(match.hasMatch())
        continue;

      if(line == "[Element]")
        {
          // qDebug() << "That's the [Element] stanza opening line.";

          // We are starting a new Element stanza. It cannot be that we both
          // have already started one Element stanza and that not a single
          // mass and probability line had been encountered. Either this is the
          // very first Element stanza that we read and was_started_one_element
          // is false or we were reading one Element stanza that has finished
          // and then was_mass_prob_line has to be true.
          if(was_started_one_element && !was_mass_prob_line)
            {
              qDebug() << "Error: one element is complete but has no isotopes.";
              return 0;
            }

          if(was_started_one_element)
            {

              // qDebug()
              //<< "We had already seen the [Element] stanza opening line.";

              // We are starting a new Element configuration stanza, but in
              // fact another was already cooking. We need to terminate it.

              // Sanity check: the number of purportedly listed isotopes needs
              // to be identical to the number of isotopes actually added to the
              // vector of isotopes.

              if(element_isotope_count != element_isotopes.size() ||
                 added_isotopes_for_element != element_isotopes.size())
                {
                  qDebug() << "Error. We did not parse the expected number of "
                              "isotopes.";
                  return 0;
                }

              // qDebug() << "And there are the right number of isotopes
              // cooked.";

              // At this point we have everything we need to add this new
              // chemical set.

              // qDebug() << "Creating new chemical set.";

              if(!newChemicalSet(
                   symbol, element_count, element_isotopes, false))
                {
                  qDebug() << "Failed to add new chemical set.";
                  return 0;
                }

              // qDebug() << "The completed chemical set: "
              //"symbol/element_count/isotope_count:"
              //<< symbol << "/" << element_count << "/"
              //<< element_isotopes.size();

              // Sanity check: the total count of added isotopes for this symbol
              // needs to correct.

              if(added_isotopes_for_element !=
                 msp_isotopicData->getIsotopeCountBySymbol(symbol))
                qFatal("Programming error.");

              // Now clear for next run.
              symbol                     = "";
              element_isotope_count      = 0;
              added_isotopes_for_element = 0;
              element_isotopes.clear();
            }

          // Tell that we actually have entered the first line of an Element
          // stanza.
          was_started_one_element = true;

          // Reset all the other values so that we know we are just at the
          // beginning of the parsing work.
          was_symbol_count_line   = false;
          was_isotopes_count_line = false;
          was_mass_prob_line      = false;

          // Go the next line.
          continue;
        }

      // At this point we are already inside of the [Element] stanza. Parse the
      // various lines inside it.

      // File format:

      // [Element]
      // 	symbol C count 6
      // 	[Isotopes] 2
      // 		mass 12.0 prob 0.989
      // 		mass 13.003354 prob 0.010788

      match = symbol_count_regexp.match(line);
      if(match.hasMatch())
        {

          // qDebug() << "Matched the symbol count line.";

          // If we are parsing "symbol C count 100", then it is not possible
          // that we did not encounter before [Element] or that we already have
          // parsed one same line as "symbol C count 100".

          if(!was_started_one_element || was_symbol_count_line)
            {
              qDebug() << "Error encountered in the symbol/count line.";
              return 0;
            }

          symbol = match.captured(1);

          element_count = match.captured(2).toInt(&ok);
          if(!ok || !element_count)
            {
              qDebug() << "Error encountered in the symbol/count line.";
              return 0;
            }

          // Now check if that symbol was encountered already, which would be an
          // error.
          auto res = symbol_set.insert(symbol);
          if(!res.second)
            {
              // We did not insert the symbol because one already existed. That
              // is an error.

              qDebug() << "An element by symbol" << symbol
                       << "has already been processed: "
                          "this is not permitted.";
              return 0;
            }

          // qDebug() << "Processed element symbol:" << symbol
          //<< "with count:" << element_count;

          // Do not store the element symbol/count pair yet, we'll wait to
          // encounter a new Element stanza which will close this one.

          was_symbol_count_line   = true;
          was_isotopes_count_line = false;
          was_mass_prob_line      = false;

          continue;
        }
      // End of
      // line matched the symbol/count regexp

      // File format:

      // [Element]
      // 	symbol C count 6
      // 	[Isotopes] 2
      // 		mass 12.0 prob 0.989
      // 		mass 13.003354 prob 0.010788

      match = isotopes_regexp.match(line);
      if(match.hasMatch())
        {

          // qDebug() << "Matched the [Isotopes] count stanza opening line.";

          // We cannot be parsing the [Isotopes] stanza opening header if we
          // have not previously parsed the symbol/count line.
          if(!was_symbol_count_line)
            {
              qDebug() << "Error encounteredd in the isotopes lines.";
              return 0;
            }

          // Store the number of isotopes for the current Element.
          element_isotope_count = match.captured(1).toInt(&ok);
          if(!ok || !element_isotope_count)
            {
              qDebug() << "Error encounteredd in the isotopes lines.";
              return 0;
            }

          // qDebug() << "The isotope count is:" << element_isotope_count;

          was_isotopes_count_line = true;
          was_symbol_count_line   = false;
          was_mass_prob_line      = false;

          continue;
        }
      // End of
      // line matched the [Isotopes] count regexp

      // File format:

      // [Element]
      // 	symbol C count 6
      // 	[Isotopes] 2
      // 		mass 12.0 prob 0.989
      // 		mass 13.003354 prob 0.010788

      match = mass_prob_regexp.match(line);
      if(match.hasMatch())
        {

          // qDebug() << "Matched the mass prob line.";

          // If we match an isotope's mass/prob line, either --- at previous
          // line --- we had seen the [Isotopes] stanza opening line or we had
          // seen another mass prob line.


          if(!was_isotopes_count_line && !was_mass_prob_line)
            {
              qDebug() << "Error encountered in the mass/prob line.";
              return 0;
            }

          mass = match.captured(1).toDouble(&ok);
          if(!ok)
            {
              qDebug() << "Error encountered in the mass/prob line.";
              return 0;
            }

          prob = match.captured(2).toDouble(&ok);
          if(!ok)
            {
              qDebug() << "Error encountered in the mass/prob line.";
              return 0;
            }

          // At this point we have everything we need to actually create the
          // isotope by symbol and mass and prob. There are a number of fields
          // that are left to value 0 but this is of no worries.

          // qDebug() << "Iterated in isotope:" << symbol << ":" << mass << "/"
          //<< prob;

          // At this point create a brand new Isotope with the relevant data.

          // Isotope::Isotope(
          // QString element,
          // QString symbol,
          // double mass,
          // double probability)

          // There are a number of fields that are left to value 0 but this is
          // of no worries. Store the isotope in the vector of isotopes that
          // will be added to the isotopic data later when finishing the parsing
          // of the element frame widget.

          element_isotopes.push_back(
            QSharedPointer<Isotope>::create(symbol, symbol, mass, prob));

          ++added_isotopes_for_element;
          ++all_parsed_isotopes_count;

          // qDebug() << "The atom now is:" << atom.asText();

          was_mass_prob_line      = true;
          was_isotopes_count_line = false;
          was_symbol_count_line   = false;

          continue;
        }
      // End of
      // line matched the isotope mass/prob regexp
    }


  // We have finished iterating in the file's lines but we were parsing an atom,
  // append it.

  // Sanity check
  if(!was_started_one_element)
    {
      qDebug() << "Error: not a single element could be parsed.";
      return 0;
    }

  // Sanity check: the number of purportedly listed isotopes needs
  // to be identical to the number of isotopes actually added to the
  // vector of isotopes.

  if(element_isotope_count != element_isotopes.size() ||
     added_isotopes_for_element != element_isotopes.size())
    {
      qDebug() << "Error. We did not parse the expected number of "
                  "isotopes.";
      return 0;
    }

  // At this point we have everything we need to add this new
  // chemical set. Do not yet update the mass maps, we'll do at the end of the
  // process.

  if(!newChemicalSet(symbol, element_count, element_isotopes, false))
    {
      qDebug() << "Failed to add new chemical set.";
      return 0;
    }

  // qDebug() << "The completed chemical set: "
  //"symbol/element_count/isotope_count:"
  //<< symbol << "/" << element_count << "/" << element_isotopes.size();

  // Sanity check: the total count of added isotopes for this symbol
  // needs to correct.

  if(added_isotopes_for_element !=
     msp_isotopicData->getIsotopeCountBySymbol(symbol))
    qFatal("Programming error.");

  // Sanity check: the total count of isotopes added to the isotopic data member
  // datum needs to match the total count of parsed isotopes.
  if(all_parsed_isotopes_count != msp_isotopicData->size())
    qFatal("Programming error.");

  // We have touched the isotopic data, ensure the maps are current.
  if(!msp_isotopicData->updateMassMaps())
    qFatal("Programming error. Failed to update the mass maps.");

  m_fileName = file_name;

  return msp_isotopicData->size();
}

/*!
  \brief Loads isotopic data from \a file_name.

Returns the count of \l{Isotope}s that were allocated and stored in the
msp_isotopicData member.
*/
qsizetype
IsotopicDataManualConfigHandler::loadData()
{
  return loadData(m_fileName);
}

/*!
  \brief Write all the IsotopicData to \a file_name.

  If \a file_name is empty, \l{m_fileName} is tried. If both are empty, the
function returns 0. If any one of the file names are correct (\a file_name takes
precedence over \l{m_fileName}), then \l{m_fileName} is set to that file name.

The format of the file consists in a single line of data per \l{Isotope} as
created using Isotope::toString(). Each isotope is output to
its own line.

Returns the count of \l{Isotope}s written to file or 0 if the file does not
exist or is not readable.
*/
qsizetype
IsotopicDataManualConfigHandler::writeData(const QString &file_name)
{
  if(file_name.isEmpty() && m_fileName.isEmpty())
    return 0;

  QString temp_file_name;

  // The passed filename takes precedence over the member datum. So copy
  // that file name to the member datum.

  if(!file_name.isEmpty())
    temp_file_name = file_name;
  else
    temp_file_name = m_fileName;

  QFile file(temp_file_name);

  if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
      qDebug("Failed to open file for writing.");
      return false;
    }

  QTextStream out(&file);

  out
    << "# This file contains isotopic data in a format that can accommodate\n";
  out
    << "# comments in the form of lines beginning with the '#' character.\n\n";

  qsizetype isotope_count = 0;

  // We want to write the isotopic data exactly in the same order as we might
  // have loaded them. This is why we need to iterated in the vector of isotopes
  // and not the in the map that is ordered according to the symbols (the keys).

  QString last_symbol;

  for(auto item : msp_isotopicData->m_isotopes)
    {
      QString symbol = item->getSymbol();

      // We only write the  [Element] and [Isotopes] stanza header once for each
      // new symbol found in the iterated items.

      if(symbol != last_symbol)
        {

          // This is the first time we encounter an isotope by symbol, we open a
          // new Element stanza.
          out << "[Element]\n";

          // We now need to write the symbol count line. If the symbol key is
          // not found, throws an exception.
          int symbol_count = m_symbolCountMap.at(symbol);
          out
            << QString("\tsymbol %1 count %2\n").arg(symbol).arg(symbol_count);

          // We also need to write the Isotopes stanza:
          int symbol_isotope_count =
            msp_isotopicData->getIsotopeCountBySymbol(symbol);
          out << QString("\t[Isotopes] %1\n").arg(symbol_isotope_count);
        }

      // At this point we can write the currently iterated isotope's mass:prob
      // pair.

      out << QString("\t\tmass %1 prob %2\n")
               .arg(item->getMass(), 0, 'f', 60)
               .arg(item->getProbability(), 0, 'f', 60);

      last_symbol = symbol;

      ++isotope_count;
    }

  out.flush();

  file.close();

  if(isotope_count != msp_isotopicData->size())
    qFatal("Programming error. Failed to write all the isotopes to file.");

  // Now we know that temp_file_name is fine. Store into m_fileName.
  m_fileName = temp_file_name;

  return isotope_count;
}

/*!
  \brief Add a set of \l{Isotope}s belonging to chemical element \a symbol.

  The count of atoms of \a symbol is defined with \a element_count. The
isotopes to be added are provided in \a isotopes.

  If the new chemical data pertain to a symbol that was already added to the
IsotopicData, then that is an error.

  If \a update_maps is true, the \l{IsotopicData} maps need to be updated.

  Returns false if the new chemical data pertain to a \a symbol that was
already added to the IsotopicData.

  \sa IsotopicData::updateMonoMassMap
  \sa IsotopicData::updateAvgMassMap
*/
bool
IsotopicDataManualConfigHandler::newChemicalSet(
  const QString &symbol,
  int element_count,
  const QList<IsotopeQSPtr> &isotopes,
  bool update_maps)
{
  // It is of uttermost importance that we update the symbol/count pair because
  // that is going to be used when performing the IsoSpec arrays configuration
  // and later isotopic cluster calculations.

  std::pair<SymbolCountMapIter, bool> res =
    m_symbolCountMap.insert(std::pair<QString, int>(symbol, element_count));

  if(!res.second)
    {
      // Sanity check: it is not possible to add new chemical sets by a given
      // symbol multiple times.

      qDebug() << "Error: isotopes by that symbol: " << symbol
               << "were already found in the isotopic data set.";

      return false;
    }

  qsizetype count_before = msp_isotopicData->size();

  // Update the mass maps according to second param below.
  msp_isotopicData->appendNewIsotopes(isotopes, update_maps);

  qsizetype count_after = msp_isotopicData->size();

  if(count_after - count_before != isotopes.size())
    qFatal("Programming error.");

  return true;
}

/*!
  \brief Returns a string containing a formula corresponding to the
IsotopicData.

  The formula is actually computed using the symbol/count pairs stored in
m_symbolCountMap.
*/
QString
IsotopicDataManualConfigHandler::craftFormula() const
{
  QString formula;

  for(auto item : m_symbolCountMap)
    formula.append(QString("%1%2").arg(item.first).arg(item.second));

  return formula;
}

/*!
  \brief Returns the count of \l{Isotope}s in this collection.
*/
qsizetype
IsotopicDataManualConfigHandler::checkConsistency()
{
  return msp_isotopicData->size();
}


} // namespace libXpertMassCore

} // namespace MsXpS


#if 0

Example from IsoSpec.

const int elementNumber = 2;
const int isotopeNumbers[2] = {2,3};

const int atomCounts[2] = {2,1};


const double hydrogen_masses[2] = {1.00782503207, 2.0141017778};
const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610};

const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses};

const double hydrogen_probs[2] = {0.5, 0.5};
const double oxygen_probs[3] = {0.5, 0.3, 0.2};

const double* probs[2] = {hydrogen_probs, oxygen_probs};

IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts, isotope_masses, probs), 0.99);

#endif
