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

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


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


// 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];

/////////////////////// pappsomspp includes
#include <pappsomspp/core/trace/trace.h>
#include <pappsomspp/core/types.h>


/////////////////////// Local includes
#include "MsXpS/libXpertMassCore/globals.hpp"
#include "MsXpS/libXpertMassCore/Formula.hpp"
#include "MsXpS/libXpertMassCore/IsotopicClusterGenerator.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataLibraryHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp"
#include "MsXpS/libXpertMassCore/IsotopicDataManualConfigHandler.hpp"

namespace MsXpS
{
namespace libXpertMassCore
{


/*!
\class MsXpS::libXpertMassCore::IsotopicClusterGenerator
\inmodule libXpertMassCore
\ingroup XpertMassCoreMassCalculations
\inheaderfile IsotopicClusterGenerator.hpp

\brief The IsotopicClusterGenerator class provides the features needed to model
isotopic clusters starting from (elemental-composition, charge) pairs.
Neutral analytes can have their isotopic cluster modelle by setting the charge
to 0.

The modelling uses the member isotopic data. The generated isotopic clusters
only contain cluster centroid peaks. If peaks should have a profile, then they
need to be shaped.

\sa IsotopicClusterShaper, MassPeakShaper
*/

/*!
\enum MsXpS::libXpertMassCore::IsotopicDataType

This enum specifies the type of isotopic data.

\value NOT_SET: Not configured.
.
\value LIBRARY_CONFIG: The isotopic data are loaded intact from the IsoSpec
library data and are considered pristine natural abundance data.
.
\value USER_CONFIG: The isotopic data are in the same format as for
LIBRARY_CONFIG but might have been modified by the user to configure new
abundances.
.
\value MANUAL_CONFIG: The isotopic data are in a specific format, different
than the two above, that actually crafts the isotopes starting from scratch.
*/

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

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

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

 Alias for std::pair<pappso::TraceCstSPtr, int>.
 */

/*!
 \typedef MsXpS::libXpertMassCore::IsotopicClusterGeneratorSPtr
 \relates IsotopicClusterGenerator.

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

/*!
 \typedef MsXpS::libXpertMassCore::IsotopicClusterGeneratorCstSPtr
 \relates IsotopicClusterGenerator.

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

/*!
\variable MsXpS::libXpertMassCore::IsotopicClusterGenerator::msp_isotopicData

\brief The isotopic data needed for the computations.
*/

/*!
\variable MsXpS::libXpertMassCore::IsotopicClusterGenerator::m_isotopicDataType

\brief The \l IsotopicDataType type of data.
*/

/*!
\variable
MsXpS::libXpertMassCore::IsotopicClusterGenerator::m_maxSummedProbability

\brief The summed probability of all the isotopic cluster peaks. The
computation stops when this probability is reached.
*/

/*!
\variable
MsXpS::libXpertMassCore::IsotopicClusterGenerator::m_normalizeIntensity

\brief The most intense cluster peak's intensity that is used to normalize all
the other cluster peaks.
*/

/*!
\variable MsXpS::libXpertMassCore::IsotopicClusterGenerator::m_sortType

\brief The type of sorting required for the generated cluster peak centroids.
*/

/*!
\variable MsXpS::libXpertMassCore::IsotopicClusterGenerator::m_sortOrder

\brief The order of the sorting for the generated cluster peak centroids.
*/

/*!
\variable
MsXpS::libXpertMassCore::IsotopicClusterGenerator::m_formulaChargePairs

\brief The set of (elemental composition, charge) pairs.
*/

/*!
\variable
MsXpS::libXpertMassCore::IsotopicClusterGenerator::m_isotopicClusterChargePairs

\brief The set of (isotopic cluster, charge) pairs.

Neutral analytes will have the charge set to 0.
*/


/*!
\brief Constructs a IsotopicClusterGenerator instance.
*/
IsotopicClusterGenerator::IsotopicClusterGenerator()
{
}

/*!
\brief Constructs a IsotopicClusterGenerator instance.

\list
\li \a isotopic_data_sp: The isotopic data used for the calculations.
\endlist
*/
IsotopicClusterGenerator::IsotopicClusterGenerator(
  libXpertMassCore::IsotopicDataSPtr isotopic_data_sp)
  : msp_isotopicData(isotopic_data_sp)
{
}

/*!
\brief Destructs this IsotopicClusterGenerator instance.
*/
IsotopicClusterGenerator::~IsotopicClusterGenerator()
{
  // qDebug();
}

/*!
\brief Sets the isotopic data type  to \a isotopic_data_type.
*/
void
IsotopicClusterGenerator::setIsotopicDataType(
  IsotopicDataType isotopic_data_type)
{
  m_isotopicDataType = isotopic_data_type;
}

/*!
\brief Sets the isotopic data to \a isotopic_data_sp.
*/
void
IsotopicClusterGenerator::setIsotopicData(
  libXpertMassCore::IsotopicDataSPtr isotopic_data_sp)
{
  msp_isotopicData = isotopic_data_sp;
}

/*!
\brief Returns the isotopic data.
*/
libXpertMassCore::IsotopicDataSPtr
IsotopicClusterGenerator::getIsotopicData() const
{
  return msp_isotopicData;
}

/*!
\brief Adds the (elemental composition, charge) pair \a
formula_charge_pair to the member list of FormulaChargePair instances.

The member list of FormulaChargePair instances is first cleared.
*/
void
IsotopicClusterGenerator::setFormulaChargePair(
  FormulaChargePair &formula_charge_pair)
{
  m_formulaChargePairs.clear();
  m_formulaChargePairs.push_back(formula_charge_pair);

  // qDebug() << formula_charge_pair.first << formula_charge_pair.second;
}

/*!
\brief Adds the (elemental composition, charge) pair \a
formula_charge_pair to the member list of FormulaChargePair instances.
*/
void
IsotopicClusterGenerator::appendFormulaChargePair(
  FormulaChargePair &formula_charge_pair)
{
  m_formulaChargePairs.push_back(formula_charge_pair);

  // qDebug() << formula_charge_pair.first << formula_charge_pair.second;
}

/*!
\brief Adds the (elemental composition, charge) pairs \a
formula_charge_pairs to the member list of FormulaChargePair instances.

The member list of FormulaChargePair instances is first cleared.
*/
void
IsotopicClusterGenerator::setFormulaChargePairs(
  const std::vector<FormulaChargePair> &formula_charge_pairs)
{
  m_formulaChargePairs.clear();

  m_formulaChargePairs.assign(formula_charge_pairs.begin(),
                              formula_charge_pairs.end());

  // qDebug() << "Set" << m_formulaChargePairs.size() << "formula/charge pairs";
  // for(auto pair : m_formulaChargePairs)
  // qDebug() << pair.first << "/" << pair.second;
}

/*!
\brief Sets the summed probability maximum value to \a max_probability.
*/
void
IsotopicClusterGenerator::setMaxSummedProbability(double max_probability)
{
  m_maxSummedProbability = max_probability;
}

/*!
\brief Sets the normalization intensity to \a normalize_intensity.
*/
void
IsotopicClusterGenerator::setNormalizationIntensity(int normalize_intensity)
{
  m_normalizeIntensity = normalize_intensity;
}

/*!
\brief Sets the \a sort_type.
*/
void
IsotopicClusterGenerator::setSortType(pappso::Enums::SortType sort_type)
{
  m_sortType = sort_type;
}

/*!
\brief Sets the \a sort_order.
*/
void
IsotopicClusterGenerator::setSortOrder(pappso::Enums::SortOrder sort_order)
{
  m_sortOrder = sort_order;
}

/*!
\brief Validates the elemental composition \a formula.

The \a formula needs to be fully indexed, that is, even an atom present only
once needs to be indexed with '1', like this \c H2O1.

Returns true if validation was successful, false otherwise.

\sa Formula::validate()
*/
bool
IsotopicClusterGenerator::validateFormula(Formula &formula)
{
  // qDebug() << "Checking formula:" << formula.toString()
  //<< "against an isotopic data set of " << msp_isotopicData->size()
  //<< "isotopes";

  // As of 1.4.3, the formula syntax check will automatically set to 1
  // any missing index for any element symbol in the formula.
  //
  // Check the syntax of the formula. Note that we need an obligatory count
  // index even for elements that are present a single time (H2O1 note the
  // 1).
  //
  // IsoSpec requires that even single-count element be qualified with an
  // index (H2O1)
  //   formula.setForceCountIndex(true);

  // We have to validate because the formula might be "C5H6N3-O1", in which case
  // if would fail the simple checkSyntax() call (that call needs to be used on
  // already split parts of a formula).

  ErrorList error_list;

  if(!formula.validate(msp_isotopicData,
                       true /*store atom count*/,
                       true /*reset counts*/,
                       &error_list))
    return false;

  return true;
}

/*!
\brief Validates all the elemental compositions in this
IsotopicClusterGenerator instance.

Each \l FormulaChargePair in m_formulaChargePairs is validated for its
elemental composition by first creating a \l Formula out of it.

Returns true if validation was successful, false otherwise.

\sa validateFormula, Formula::validate()
*/
bool
IsotopicClusterGenerator::validateAllFormulas()
{
  for(FormulaChargePair &pair : m_formulaChargePairs)
    {
      Formula formula(pair.first);

      if(!validateFormula(formula))
        return false;
    }

  return true;
}

/*!
\brief Runs the IsoSpec-based isotopic calculations.

\list

\li \a element_count: the number of elements in the chemical composition.
\li \a charge: the charge of the analyte. Might be 0 for neutral analytes.

\li \a per_element_isotopes_count_array_p: pointer to int array. This array
lists the number of isotopes that each element has. Typically, C has 2, O has 3,
P has 1...

\li \a per_element_symbol_count_array_p: pointer to int array. This array lists
the count of atoms of each element. Typically H20 will have 2 for H and 1 for O.

\li \a per_element_isotope_masses_arrays_p_p: pointer to pointer to double array
(array of arrays of double). Each array contains a new sub-array for each
symbol. The sub-array contains the isotopic masses for one of the element
symbols.

\li \a per_element_isotope_probs_arrays_p_p: pointer to pointer to double array
(array of arrays of double).  Each array contains a new sub-array for each
symbol. The sub-array contains the isotopic probs for one of the element
symbols.

\endlist

Returns a pappso::Trace with the calculated isotopic cluster.
*/
pappso::TraceSPtr
IsotopicClusterGenerator::runIsotopicDataCalculations(
  std::size_t element_count,
  int charge,
  int *per_element_isotopes_count_array_p,
  int *per_element_symbol_count_array_p,
  double **per_element_isotope_masses_arrays_p_p,
  double **per_element_isotope_probs_arrays_p_p)
{
  // We get all the isotopic data relevant to the isotopic cluster modelling
  // calculation as performed by the IsoSpec library.

  if(per_element_isotopes_count_array_p == nullptr ||
     per_element_symbol_count_array_p == nullptr ||
     per_element_isotope_masses_arrays_p_p == nullptr ||
     per_element_isotope_probs_arrays_p_p == nullptr)
    qFatal("Programming error. The pointers cannot be nullptr.");


  if(m_maxSummedProbability <= 0 || m_maxSummedProbability > 1)
    {
      qDebug() << "The maximum summed probability has an incorrect value:"
               << m_maxSummedProbability;
      return nullptr;
    }

  IsoSpec::IsoLayeredGenerator iso(
    IsoSpec::Iso(element_count,
                 per_element_isotopes_count_array_p,
                 per_element_symbol_count_array_p,
                 per_element_isotope_masses_arrays_p_p,
                 per_element_isotope_probs_arrays_p_p),
    // The three values below are from the documentation (default values in the
    // constructor). We have added them 20230329 because we discovered that they
    // were needed on minGW64, otherwise we would experience crashes.
    1000,
    1000,
    true,
    m_maxSummedProbability);

  // qDebug() << "iso's mono peak mass:" << iso.getMonoisotopicPeakMass();

  // Each time we run a calculation, we do store the results in a new
  // IsotopicCluster.

  pappso::TraceSPtr isotopic_cluster_sp = std::make_shared<pappso::Trace>();

  // We store the results as std::vector<std::shared_ptr<libmass:PeakCentroid>>
  // because we'll want to sort the values according to the user's requirements.

  double effective_summed_probs = 0;

  // Iterate in all the cluster configurations and output all the ones that
  // summatively make a total probability <= to the probability set by the
  // user.

  /////////////// ATTENTION ////////////////
  // The loop below is tricky,

  while(iso.advanceToNextConfiguration())
    {
      double iso_prob = iso.prob();
      double iso_mz   = 0;
      if(charge)
        {
          iso_mz = iso.mass() / charge;
        }
      else
        {
          iso_mz = iso.mass();
        }

      // qDebug() << "For current configuration (charge accounted for):" <<
      // iso_mz
      //<< iso_prob
      //<< "and effective_probs_sum : " << effective_summed_probs;

      // Create a peak centroid and store it (remark that we change the mass
      // of the ion into m/z because the user had set the charge corresponding
      // to the formula for which the isotopic cluster is being computed.

      isotopic_cluster_sp->push_back(pappso::DataPoint(iso_mz, iso_prob));

      // qDebug() << "Pushed back new peak centroid:" << iso_mz << "/" <<
      // iso_prob;

      // We do this increment at the end of the block. Indeed, if we had set up
      // at the top of the block, then, if the very first centroid had already a
      // prob > m_maxSummedProbability, then we would end up with an empty
      // cluster!

      effective_summed_probs += iso_prob;

      if(effective_summed_probs > m_maxSummedProbability)
        {
          // qDebug() << "Reached the max value: effective_summed_probs:"
          //<< effective_summed_probs
          //<< "and m_maxSummedProbability:" << m_maxSummedProbability
          //<< "BREAKING.";
          break;
        }
      else
        {
          // qDebug() << "Not yet reached the max value: effective_summed_probs
          // : "
          //<< effective_summed_probs
          //<< "and m_maxSummedProbability:" << m_maxSummedProbability;
        }
    }

  // Now perform the normalization to the Gaussian apex intensity value if
  // so is requested by the user For this we first need to find
  // what is the most intensity peak centroid. Then, we'll normalize against
  // it by dividing all intensity that that most intense value and
  // multiplying by the requested gaussian apex intensity value.

  // qDebug() << "Now asking for normalization.";

  // Just a debug check.
  // qDebug() << "Before normalizing, first data point:"
  //<< isotopic_cluster_sp->front().toString();

  normalizeIntensities(isotopic_cluster_sp);

  // Just a debug check.
  // qDebug() << "After normalizing, first data point:"
  //<< isotopic_cluster_sp->front().toString();

  // Now check if the user requests to kind of sorting of the PeakCentroid
  // instances.

  sortPeakCentroids(isotopic_cluster_sp);

  // qDebug() << "Now returning a cluster of size:" <<
  // isotopic_cluster_sp->size();

  return isotopic_cluster_sp;
}

/*!
\brief Calculates the isotopic cluster's peak centroids for \a
formula_charge_pair.

\list 1
\li The elemental composition formula string is converted to a \l Formula and
validated.
\li The proper isotopic data handler is allocated (\l IsotopicDataType).
\li The number of symbols in the elemental composition is determined.
\li The int arrays and arrays of double arrays are allocated.
\li The arrays are filled-in with \l configureIsotopicData().
\li The calculations are performed on these arrays with \l
runIsotopicDataCalculations().
\endlist

Returns the results of the computation in the form of a
IsotopicClusterChargePair instance.
*/
IsotopicClusterChargePair
IsotopicClusterGenerator::generateIsotopicClusterCentroids(
  FormulaChargePair formula_charge_pair)
{
  // qDebug() << "Starting generation of isotopic cluster for formula:"
  //<< formula_charge_pair.first;

  Formula formula(formula_charge_pair.first);

  // The check will generate useful data inside the Formula!
  if(!validateFormula(formula))
    return std::pair(std::make_shared<const pappso::Trace>(), 0);

  // Use the correct handler!

  std::unique_ptr<IsotopicDataBaseHandler> isotopic_data_handler_up = nullptr;

  if(m_isotopicDataType == IsotopicDataType::LIBRARY_CONFIG)
    {
      isotopic_data_handler_up =
        std::make_unique<IsotopicDataLibraryHandler>(msp_isotopicData);
    }
  else if(m_isotopicDataType == IsotopicDataType::USER_CONFIG)
    {
      isotopic_data_handler_up =
        std::make_unique<IsotopicDataUserConfigHandler>(msp_isotopicData);
    }
  else if(m_isotopicDataType == IsotopicDataType::MANUAL_CONFIG)
    {
      isotopic_data_handler_up =
        std::make_unique<IsotopicDataManualConfigHandler>(msp_isotopicData);
    }
  else
    qFatal("Programming error. The isotopic data type is not correct.");


  // At this point we need to create the arrays exactly as we do in the user
  // manual config. So we need to know how many different chemical element
  // symbols we have in the formula.

  std::map<QString, double> symbol_double_count_map =
    formula.getSymbolCountMapCstRef();

  std::size_t element_count = symbol_double_count_map.size();

  // qDebug() << "Number of different symbols in the formula:" << element_count;

  if(!element_count)
    {
      qDebug() << "There is not a single element in the Formula.";
      return std::pair(std::make_shared<const pappso::Trace>(), 0);
    }

  // qDebug() << "The validated formula has a symbol/count map size:"
  //<< element_count;

  // We have to copy the symbol/count map obtained by validating
  // the formula into the isotopic data handler. That map is essential for the
  // crafting by the handler of the different IsoSpec arrays.

  // At this point, all the elements defined by the user have been completed and
  // we'll have to create the static arrays that are needed by IsoSpec.

  // We now need to construct the C arrays for IsoSpec. The arrays need to
  // be filled-in very accurately.

  // This array lists the number of isotopes that each element has.
  // Typically, C has 2, O has 3, P has 1...
  int *per_element_isotopes_count_array_p = nullptr;

  // This array lists the count of atoms of each element.
  // Typically H20 will have 2 for H and 1 for O.
  int *per_element_symbol_count_array_p = nullptr;

  // These are arrays of arrays! Each array contains a new sub-array for each
  // symbol. The sub-array contains the isotopic masses (or probs) for one of
  // the element symbols. The sub-arrays are allocated by the handler below.
  double **per_element_isotope_masses_arrays_p_p = nullptr;
  double **per_element_isotope_probs_arrays_p_p  = nullptr;

  // The isotopic cluster calculations do not understand
  // formulas with double counts for the symbols!!
  // We thus need to convert the symbol_count_map that is
  // <QString,double>-based to a map that has its second
  // member, not of double type but of int type.

  std::map<QString, int> symbol_int_count_map;
  std::map<QString, double>::const_iterator iter =
    symbol_double_count_map.begin();

  while(iter != symbol_double_count_map.end())
    {
      symbol_int_count_map[iter->first] = static_cast<int>(iter->second);

      // qDebug() << "Old " << iter->first << "double version:" << iter->second
      //          << "new int version:" << symbol_int_count_map[iter->first];

      ++iter;
    }

  // We pass the array pointers by reference.
  if(!configureIsotopicData(symbol_int_count_map,
                            per_element_isotopes_count_array_p,
                            per_element_symbol_count_array_p,
                            per_element_isotope_masses_arrays_p_p,
                            per_element_isotope_probs_arrays_p_p))
    {
      qDebug() << "Failed to actually prepare the isotopic data tables for the "
                  "computation.";

      return std::pair(std::make_shared<const pappso::Trace>(), 0);
    }

  // At this point we have all the arrays needed to work.

  pappso::TraceSPtr isotopic_cluster_sp =
    runIsotopicDataCalculations(element_count,
                                formula_charge_pair.second,
                                per_element_isotopes_count_array_p,
                                per_element_symbol_count_array_p,
                                per_element_isotope_masses_arrays_p_p,
                                per_element_isotope_probs_arrays_p_p);

  // FIXME
  // To avoid a memory leak, we need to delete the mass and prob heap-allocated
  // arrays.

  // delete[] per_element_isotopes_count_array_p;
  // delete[] per_element_symbol_count_array_p;

  // for(std::size_t iter = 0; iter < element_count; ++iter)
  //{
  // delete[] per_element_isotope_masses_arrays_p_p[iter];
  // delete[] per_element_isotope_probs_arrays_p_p[iter];
  //}

  if(isotopic_cluster_sp == nullptr)
    qDebug() << "Failed to compute an isotopic cluster for formula:"
             << formula_charge_pair.first;

  // Just a debug check.
  // qDebug() << "After normalizing, first data point:"
  //<< isotopic_cluster_sp->front().toString();

  // qDebug() << "Done generating cluster for formula:"
  //<< formula_charge_pair.first;

  return std::pair(isotopic_cluster_sp, formula_charge_pair.second);
}

/*!
\brief Configures the isotopic data in a set of arrays for the (symbol,count)
pairs in \a symbol_count_map.

\list

\li \a per_element_isotopes_count_array_p: pointer to int array. This array
lists
the number of isotopes that each element has. Typically, C has 2, O has 3, P has
1...

\li \a per_element_symbol_count_array_p: pointer to int array. This array lists
the count of atoms of each element. Typically H20 will have 2 for H and 1 for O.

\li \a per_element_isotope_masses_arrays_p_p: pointer to pointer to double array
(array of arrays of double). Each array contains a new sub-array for each
symbol. The sub-array contains the isotopic masses for one of the element
symbols.

\li \a per_element_isotope_probs_arrays_p_p: pointer to pointer to double array
(array of arrays of double).  Each array contains a new sub-array for each
symbol. The sub-array contains the isotopic probs for one of the element
symbols.

\endlist

Returns a pappso::Trace with the calculated isotopic cluster.
*/
bool
IsotopicClusterGenerator::configureIsotopicData(
  std::map<QString, int> &symbol_count_map,
  int *&per_element_isotopes_count_array_p,
  int *&per_element_symbol_count_array_p,
  double **&per_element_isotope_masses_arrays_p_p,
  double **&per_element_isotope_probs_arrays_p_p)
{
  // Start by allocating the arrays we'll have to feed in this configuration
  // work.

  // However, we need to ensure that we actually have some stuff to work on.
  // That stuff is kind of a formula in the form of the std::map<QString, int>
  // m_symbolCountMap that pairs the symbols of the atoms in the formula and
  // the count for each symbol.

  if(!symbol_count_map.size())
    return false;

  // qDebug() << "The isotopic data have" << msp_isotopicData->size()
  //<< "isotopes";

  // Example used in the comments below: glucose, C6H12O6.

  // How many isotopes of each element symbols are there?
  // C:2, H:2, O:3
  per_element_isotopes_count_array_p = new int[symbol_count_map.size()];

  // How many atoms of each chemical element symbol are there?
  // C:6, H:12, O:6
  per_element_symbol_count_array_p = new int[symbol_count_map.size()];

  // Each subarray contains the mass of one of the isotopes for the element
  // symbol.
  // First array for C (two masses), second array for H (two masses), third
  // array for O (three masses).
  per_element_isotope_masses_arrays_p_p = new double *[symbol_count_map.size()];

  // Each subarray contains the prob of one of the isotopes for the element
  // symbol.
  // First array for C (two probs), second array for H (two probs), third
  // array for O (three probs).
  per_element_isotope_probs_arrays_p_p = new double *[symbol_count_map.size()];

  // Index that will allow to address the right slot in the arrays being
  // filled with isotopic data.
  int current_symbol_index = 0;

  for(auto item : symbol_count_map)
    {
      QString symbol   = item.first;
      int symbol_count = item.second;

      // qDebug() << "Iterating in symbol/count:" << symbol << "/" <<
      // symbol_count;

      // Immediately fill-in the symbol count value.
      per_element_symbol_count_array_p[current_symbol_index] = symbol_count;

      // Now get iterator bounding the isotopes for this symbol.

      std::pair<QList<IsotopeQSPtr>::const_iterator,
                QList<IsotopeQSPtr>::const_iterator>
        iter_pair = msp_isotopicData->getIsotopesBySymbol(symbol);

      // Handy shortcuts
      QList<IsotopeQSPtr>::const_iterator iter     = iter_pair.first;
      QList<IsotopeQSPtr>::const_iterator iter_end = iter_pair.second;

      qsizetype isotope_count = std::distance(iter, iter_end);

      // qDebug() << "For symbol:" << symbol << "there are:" << isotope_count
      //<< "isotopes";

      // Sanity check
      if(isotope_count != msp_isotopicData->getIsotopeCountBySymbol(symbol))
        qFatal(
          "Programming error. The is something wrong with the isotopic "
          "data.");

      // Fill-in the isotope count for the current symbol.
      per_element_isotopes_count_array_p[current_symbol_index] = isotope_count;

      // Fill-in the isotopes (mass/prob). For each element symbol in the
      // symbol/count map, we allocate a double array the size of the number
      // of isotopes so as to store the mass of each isotope (same for the
      // probs later). Once we have done that fill-in, we can set the address
      // of the array of the array of array below.

      // Allocate a double array for the masses and another one for the probs.

      double *masses_p = new double[isotope_count];
      double *probs_p  = new double[isotope_count];

      int current_isotope_index = 0;

      while(iter != iter_end)
        {
          IsotopeQSPtr isotope_qsp = *iter;

          // qDebug() << "For symbol" << symbol << "iterating with index"
          //<< current_isotope_index << "with Isotope *"
          //<< isotope_qsp.get();

          masses_p[current_isotope_index] = isotope_qsp->getMass();
          probs_p[current_isotope_index]  = isotope_qsp->getProbability();

          // Increment the iterator
          ++iter;
          // Increment the isotope index so that we fill next array cell.
          ++current_isotope_index;
        }

      // At this time the masses and probs for the isotopes of current symbol
      // have been filled-in.

      per_element_isotope_masses_arrays_p_p[current_symbol_index] = masses_p;
      per_element_isotope_probs_arrays_p_p[current_symbol_index]  = probs_p;

      // We need to increment this index so as to address the right slot in
      // the arrays!
      ++current_symbol_index;
    }

  // At this point we have documented all the isotopic data required to
  // perform a calculation with IsoSpec.

  return true;
}

/*!
\brief Normalizes the intensities of the isotopic cluster's peak centroids in
\a isotopic_cluster_sp.

If normalization is asked for, the most intense peak centroid in \a
isotopic_cluster_sp is determined. That intensity becomes the
m_normalizeIntensity value and all the other peak centroids' intensities are
normalized.

\note The normalization occurs \e{in place}.
*/
void
IsotopicClusterGenerator::normalizeIntensities(
  pappso::TraceSPtr &isotopic_cluster_sp)
{

  // qDebug() << "The normalize_intensity is:" << m_normalizeIntensity;

  // The most intense peak centroid's intensity value needs to be set to the
  // requested value and the same change ratio needs to be applied to all the
  // other peak centroids.

  // No normalization is asked for.
  if(m_normalizeIntensity == std::numeric_limits<int>::min())
    {
      // qDebug() << "No normalization was asked for. Skipping.";
      return;
    }

  if(!isotopic_cluster_sp->size())
    {
      qDebug() << "The isotopic cluster has not a single data point.";
      return;
    }
  // First get the most intense centroid.

  double max_found_intensity = 0;

  // qDebug() << "isotopic_cluster_sp->size():" << isotopic_cluster_sp->size();

  pappso::Trace::iterator vector_iterator = std::max_element(
    isotopic_cluster_sp->begin(),
    isotopic_cluster_sp->end(),
    [](const pappso::DataPoint first, const pappso::DataPoint second) {
      return first.y < second.y;
    });

  if(vector_iterator == isotopic_cluster_sp->end())
    qFatal("Programming error");

  max_found_intensity = (*vector_iterator).y;

  if(!max_found_intensity)
    qFatal("The maximum intensity of the whole isotopic cluster is 0.");

  // qDebug().noquote() << "Peak centroid with maximum intensity: "
  //<< vector_iterator->toString();

  // Calculate the ratio between the final requested intensity and the greatest
  // intensity. That ratio will be multiplied to intensity of the each peak
  // centroid, which is why we call it a factor.

  double intensity_factor = m_normalizeIntensity / max_found_intensity;

  // qDebug() << "Will multiply this intensity factor to each data point's "
  //"intensity value:"
  //<< intensity_factor;

  // Just a debug check.
  // qDebug() << "Before normalizing, first data point:"
  //<< isotopic_cluster_sp->front().toString();

  std::for_each(
    isotopic_cluster_sp->begin(),
    isotopic_cluster_sp->end(),
    [intensity_factor](pappso::DataPoint &data_point) // modify in-place
    {
      // qDebug() << "Before normalization:" <<
      // data_point.toString();

      double normalized_intensity = data_point.y * intensity_factor;

      data_point.y = normalized_intensity;

      // qDebug() << "After normalization:" <<
      // data_point.toString();
    });

  // At this point the centroids' intensity have been normalized.

  // Just a debug check.
  // qDebug() << "After normalizing, first data point:"
  //<< isotopic_cluster_sp->front().toString();
}

/*!
\brief Sorts the peak centroids of the isotopic cluster \a isotopic_cluster_sp.

The sort is performed according to \l m_sortType.
*/
void
IsotopicClusterGenerator::sortPeakCentroids(
  pappso::TraceSPtr &isotopic_cluster_sp)
{
  if(m_sortType == pappso::Enums::SortType::none)
    return;

  return isotopic_cluster_sp->sort(m_sortType, m_sortOrder);
}

/*!
\brief Returns a string containing a space-separated set of m/z, intensity
pairs, representing the isotopic cluster in \a isotopic_cluster_sp.
*/
QString
IsotopicClusterGenerator::clusterToString(
  const pappso::TraceCstSPtr &isotopic_cluster_sp) const
{

  // Export the results as a string.  Note how we do export the relative
  // intensity. If there was normalization, that value was updated, otherwise it
  // had been initialized identical to the intensity upon creation of the
  // PeakCentroid instances in the vector.

  QString text;

  for(const pappso::DataPoint &dp : *isotopic_cluster_sp)
    {
      text += QString("%1 %2\n").arg(dp.x, 0, 'f', 30).arg(dp.y, 0, 'f', 30);
    }

  // qDebug().noquote() << "Cluster to string: " << text;

  return text;
}

/*!
\brief Returns a string containing a space-separated set of m/z, intensity
pairs, representing the isotopic clusters in the member isotopic clusters \l
m_isotopicClusterChargePairs.
*/
QString
IsotopicClusterGenerator::clustersToString() const
{

  // Export the results as a string.  Note how we do export the relative
  // intensity. If there was normalization, that value was updated, otherwise it
  // had been initialized identical to the intensity upon creation of the
  // PeakCentroid instances in the vector.

  QString text;

  for(auto isotopic_cluster_charge_pair : m_isotopicClusterChargePairs)
    {
      text += clusterToString(isotopic_cluster_charge_pair.first);
    }

  // qDebug().noquote() << text;

  return text;
}

/*!
\brief Starts the computations.

The member m_isotopicClusterChargePairs are first cleared.

Returns the count of IsotopicClusterChargePair instances generated upon the
calculations.
*/
std::size_t
IsotopicClusterGenerator::run()
{
  // Iterate in all the formula/charge pairs and for each compute an isotopic
  // cluster.

  m_isotopicClusterChargePairs.clear();

  for(auto formula_charge_pair : m_formulaChargePairs)
    {
      IsotopicClusterChargePair pair =
        generateIsotopicClusterCentroids(formula_charge_pair);

      if(!pair.first->size())
        {
          qFatal() << "The isotopic cluster is empty for formula:"
                   << formula_charge_pair.first;
        }

      m_isotopicClusterChargePairs.push_back(pair);
    }

  return m_isotopicClusterChargePairs.size();
}

/*!
\brief Returns the member list ofIsotopicClusterChargePair instances.
*/
const std::vector<IsotopicClusterChargePair> &
IsotopicClusterGenerator::getIsotopicClusterChargePairs() const
{
  return m_isotopicClusterChargePairs;
}


} // namespace libXpertMassCore
} // namespace MsXpS
