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


#include <omp.h>
#include <limits>


/////////////////////// Qt includes
#include <qmath.h>
#include <QDebug>
#include <QFile>
#include <QMetaType>
#include <QPair>


/////////////////////// Local includes
#include <libmass/MassSpectrum.hpp>
#include <globals/globals.hpp>
#include <minexpert/nongui/MassSpecDataStats.hpp>
#include <pwiz/data/msdata/MSDataFile.hpp>
#include <pwiz/data/msdata/DefaultReaderList.hpp>


using namespace pwiz::msdata;


int massSpectrumMetaTypeId =
  qRegisterMetaType<msXpSlibmass::MassSpectrum>("msXpSlibmass::MasSpectrum");


namespace msXpSlibmass
{

#define RESOLUTION_PPM 10


class MassSpecDataStats;

//! Construct a totally empty MassSpectrum
MassSpectrum::MassSpectrum()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "default constructor"
  //<< this;
}


//! Copy constructor (with deep copy of the Trace's DataPoint objects)
MassSpectrum::MassSpectrum(const MassSpectrum &other) : Trace(other)
{
  m_rt = other.m_rt;
  m_dt = other.m_dt;

  m_minMz = other.m_minMz;
  m_maxMz = other.m_maxMz;

  m_binningType = other.m_binningType;
  m_binCount    = other.m_binCount;
  m_lastBinIdx  = other.m_lastBinIdx;
  m_binSize     = other.m_binSize;
  m_binSizeType = other.m_binSizeType;

  m_applyMzShift = other.m_applyMzShift;
  m_mzShift      = other.m_mzShift;

  m_removeZeroValDataPoints = other.m_removeZeroValDataPoints;
}


//! Copy constructor (with deep copy of the Trace's DataPoint objects)
MassSpectrum::MassSpectrum(const Trace &other) : Trace(other)
{
}


//! Construct a MassSpectrum with \p title
/*!
 */
MassSpectrum::MassSpectrum(const QString &title) : Trace{title}
{
}


//! Destruct the MassSpectrum instance.
MassSpectrum::~MassSpectrum()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
}


//! Assignment operator (with deep copy of \p other::Trace's DataPoint objects
//! into \c this.
/*!

  This function starts by deleting all of \c this instance DataPoint objects.
  Then all of the DataPoint instances in \p other are duplicated into \e new
  DataPoint instances appended to \c this MassSpectrum object.

  The first use of this assignment operator was for JavaScripting because
  translation between JS object and C++ object required deep copy of all the
  data members of MassSpectrum.

*/
MassSpectrum &
MassSpectrum::operator=(const MassSpectrum &other)
{
  if(this == &other)
    return *this;

  Trace::operator=(other);

  m_rt = other.m_rt;
  m_dt = other.m_dt;

  m_minMz = other.m_minMz;
  m_maxMz = other.m_maxMz;

  m_binningType = other.m_binningType;
  m_binCount    = other.m_binCount;
  m_lastBinIdx  = other.m_lastBinIdx;
  m_binSize     = other.m_binSize;
  m_binSizeType = other.m_binSizeType;

  m_applyMzShift = other.m_applyMzShift;
  m_mzShift      = other.m_mzShift;

  m_removeZeroValDataPoints = other.m_removeZeroValDataPoints;

  return *this;
}


//! Reset the MassSpectrum
void
MassSpectrum::reset()
{
  Trace::reset();

  m_rt = 0;
  m_dt = 0;

  m_minMz = std::numeric_limits<double>::max();
  m_maxMz = std::numeric_limits<double>::min();

  m_binningType = msXpS::BinningType::BINNING_TYPE_NONE;
  m_binCount    = 0;
  m_lastBinIdx  = 0;
  m_binSize     = 0;
  m_binSizeType = msXpS::MassToleranceType::MASS_TOLERANCE_NONE;

  m_applyMzShift = false;
  m_mzShift      = 0;

  m_removeZeroValDataPoints = false;
}


//! Get the retention time member datum value
/*!
  \return the retention time value at which \c this spectrum was acquired.
  */
double
MassSpectrum::rt() const
{
  return m_rt;
}


//! Get a reference to the retention time member datum value
/*!
  \return a reference to the retention time value at which \c this spectrum
  was acquired. The reference handle allows for modification of the retention
  time value.
  */
double &
MassSpectrum::rrt()
{
  return m_rt;
}


//! Get the drift time member datum value
double
MassSpectrum::dt() const
{
  return m_dt;
}


//! Get a reference to the drift time member datum value
double &
MassSpectrum::rdt()
{
  return m_dt;
}

//! Set the minimum m/z value encountered in a list of mass spectra
void
MassSpectrum::setMinMz(double value)
{
  m_minMz = value;
}


//! Set the maximum m/z value encountered in a list of mass spectra
void
MassSpectrum::setMaxMz(double value)
{
  m_maxMz = value;
}


//! Set the type of the binning that is to be performed during combination
void
MassSpectrum::setBinningType(msXpS::BinningType binningType)
{
  m_binningType = binningType;
}

//! Get the binning type that is to be performed during combination
msXpS::BinningType
MassSpectrum::binningType()
{
  return m_binningType;
}


//! Set the nominal value of the bin size (to be processed with the size type)
void
MassSpectrum::setBinSize(double value)
{
  m_binSize = value;
}


//! Get the nominal value of the bin size (to be processed with the size type)
double
MassSpectrum::binSize() const
{
  return m_binSize;
}


//! Set the bin size type to be used to process the bin size nominal value
void
MassSpectrum::setBinSizeType(int type)
{
  if(type <= msXpS::MassToleranceType::MASS_TOLERANCE_NONE ||
     type >= msXpS::MassToleranceType::MASS_TOLERANCE_LAST)
    {
      m_binSize     = 0.005;
      m_binSizeType = msXpS::MassToleranceType::MASS_TOLERANCE_MZ;
    }
  else
    m_binSizeType = type;
}


//! Get the bin size type to be used to process the bin size nominal value
int
MassSpectrum::binSizeType() const
{
  return m_binSizeType;
}


//! Set if the m/z shift calculation should be applied to the combined spectra
void
MassSpectrum::applyMzShift(bool applyMzShift)
{
  m_applyMzShift = applyMzShift;
}


//! Tell if the m/z shift calculation should be applied to the combined spectra
bool
MassSpectrum::applyMzShift() const
{
  return m_applyMzShift;
}


//! Set if the data points having m/z value of 0 should be removed from the
//! spectrum after combination.
void
MassSpectrum::setRemoveZeroValDataPoints(bool remove)
{
  m_removeZeroValDataPoints = remove;
}


//! Tell if the data points having m/z value of 0 should be removed from the
//! spectrum after combination.
bool
MassSpectrum::isRemoveZeroValDataPoints()
{
  return m_removeZeroValDataPoints;
}


/************************** INITIALIZATION METHODS **************************/
/************************** INITIALIZATION METHODS **************************/
/************************** INITIALIZATION METHODS **************************/

//! Initialize the MassSpectrum with data
/*!

  Calls Trace::initialize() and sets m_rt.

  Note that mzList and iList cannot have different sizes otherwise the program
  crashes.

  \param mzList list of m/z values
  \param iList list of intensity values
  \param rt the retention time at which the mass spectrum was acquired

*/
int
MassSpectrum::initializeRt(const QList<double> &mzList,
                           const QList<double> &iList,
                           double rt)
{
  Trace::initialize(mzList, iList);
  m_rt = rt;

  return size();
}


//! Initialize the MassSpectrum with data
/*!

  Calls Trace::initialize() and sets m_dt.

  Note that mzList and iList cannot have different sizes otherwise the program
  crashes.

  \param mzList list of m/z values
  \param iList list of intensity values
  \param dt the drift time at which the mass spectrum was acquired
  */
int
MassSpectrum::initializeDt(const QList<double> &mzList,
                           const QList<double> &iList,
                           double dt)
{
  Trace::initialize(mzList, iList);

  m_dt = dt;

  return size();
}


//! Initialize the MassSpectrum with data
/*!

  Overload of MassSpectrum::initialize().

*/
int
MassSpectrum::initializeRt(const QList<double> &mzList,
                           const QList<double> &iList,
                           double rt,
                           double mzStart,
                           double mzEnd)
{
  Trace::initialize(mzList, iList, mzStart, mzEnd);

  m_rt = rt;

  return size();
}

//! Initialize the MassSpectrum with data
/*!

  Overload of MassSpectrum::initialize().
  */
int
MassSpectrum::initializeDt(const QList<double> &mzList,
                           const QList<double> &iList,
                           double dt,
                           double mzStart,
                           double mzEnd)
{
  Trace::initialize(mzList, iList, mzStart, mzEnd);

  m_dt = dt;

  return size();
}


//! Initialize the MassSpectrum with data
/*!

  Overload of MassSpectrum::initialize().
  */
int
MassSpectrum::initialize(const QByteArray *mzByteArray,
                         const QByteArray *iByteArray,
                         int compressionType,
                         double mzStart,
                         double mzEnd)
{
  Trace::initialize(mzByteArray, iByteArray, compressionType, mzStart, mzEnd);

  return size();
}


//! Initialize the MassSpectrum with data
/*!

  Overload of MassSpectrum::initialize().
  */
int
MassSpectrum::initialize(const Trace &trace)
{
  return Trace::initialize(trace);
}


/************************** MZ SHIFT METHODS **************************/
/************************** MZ SHIFT METHODS **************************/
/************************** MZ SHIFT METHODS **************************/

//! Computes the mz shift between \p massSpectrum and \c this MassSpectrum
/*!

  The shift is computed by making the difference between the keys of the
  first DataPoint instance in \p massSpectrum and \this MassSpectrum instance.

  As a side effect, \c m_mzShift is set to this difference value.

  \param massSpectrum the MassSpectrum to use for the m/z shift calculation
  against \c this MassSpectrum.

  \return a double containing the m/z shift.

*/
double
MassSpectrum::determineMzShift(const MassSpectrum &other)
{
  return determineMzShift(other.first()->m_key);
}


//! Computes the mz shift between \p otherMzValue and \c this MassSpectrum
/*!

    The shift is computed by making the difference between the keys \p
    otherMzValue and \this MassSpectrum instance' first DataPoint instance.

    As a side effect, \c m_mzShift is set to this difference value.

    \param otherMzValue mz value to use for the m/z shift calculation against \c
    this MassSpectrum's first DataPoint instance.

    \return a double containing the m/z shift.

*/
double
MassSpectrum::determineMzShift(double otherMzValue)
{
  if(!size())
    return qSNaN();

  m_mzShift = otherMzValue - first()->m_key;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "m_mzShift:" << m_mzShift;

  return m_mzShift;
}


/************************** TIC VALUE METHODS **************************/
/************************** TIC VALUE METHODS **************************/
/************************** TIC VALUE METHODS **************************/

//! Calculate the total ion current of \c this MassSpectrum.
/*!

  This function calls Trace::valSum().

*/
double
MassSpectrum::tic()
{
  return Trace::valSum();
}


//! Calculate the total ion current of \c this MassSpectrum.
/*!

  This function calls Trace::valSum().

*/
double
MassSpectrum::tic(double mzStart, double mzEnd)
{
  return Trace::valSum(mzStart, mzEnd);
}


//! Compute the total ion current of a MassSpectrumList instance
/*!

  The \p massSpectra list of MassSpectrum instances is iterated in and
  for each MassSpectrum instance the TIC is computed and added to a local
  variable that is returned.

  \param massSpectra list of MassSpectrum instances for which the total
  TIC value is to be computed.

  \return a double value containing the sum of all the total ion current
  values of the whole set of MassSpectrum instances in \p massSpectra.

  \sa tic()
  \sa tic(double mzStart, double mzEnd)

*/
double
MassSpectrum::tic(const QList<MassSpectrum *> &massSpectra)
{
  double totalTic = 0;

#pragma omp parallel for reduction(+ : totalTic)
  for(int jter = 0; jter < massSpectra.size(); ++jter)
    {
      totalTic += massSpectra.at(jter)->tic();
    }

  return totalTic;
}


/************************** COMBINATION METHODS **************************/
/************************** COMBINATION METHODS **************************/
/************************** COMBINATION METHODS **************************/

//! Combine MassSpectrum instance \p other into \c this MassSpectrum.
/*!

  Combine the \p other MassSpectrum into \p this MassSpectrum. The \p mzStart
  and \p mzEnd parameters can be used to only combine the DataPoint instances
  that have a \c m_key value verifying the condition.

  \param other MassSpectrum instance to be combined into \c this MassSpectrum

  \param mzStart left value of the m/z range (-1 if no range is to be checked
  for)

  \param mzEnd right value of the m/z range (-1 if no range is to be checked
  for)

  \return the number of combined DataPoint instances

*/
int
MassSpectrum::combine(const MassSpectrum &other, double mzStart, double mzEnd)
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  // We receive a spectrum that we need to combine into this mass spectrum.
  // However, we need to check each individual DataPoint of the MassSpectrum
  // instance for their m/z value, because it can only be combined if its
  // value is in between both mzStart and mzEnd arguments to this function.

  if(m_applyMzShift)
    determineMzShift(other);

  // Reset the index anchors
  m_lastBinIdx   = 0;
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < other.size(); ++iter)
    {
      DataPoint *iterDataPoint = other.at(iter);

      if(iterDataPoint->m_val == 0)
        continue;

      // Either we do not care of m/z range or the data point satisifies the
      // condition.
      if((mzStart == -1 || mzEnd == -1) ||
         (iterDataPoint->m_key >= mzStart && iterDataPoint->m_key <= mzEnd))
        {
          if(m_binningType != msXpS::BinningType::BINNING_TYPE_NONE)
            combineBinned(*iterDataPoint);
          else
            Trace::combine(*iterDataPoint);

          ++count;
        }
      else if(iterDataPoint->m_key > mzEnd)
        // Because the spectra are ordered, if the currently iterated data point
        // has a mass greater than the accepted mzEnd, then break the loop as
        // this cannot get any better later.

        break;
    }

  return count;
}


//! Subtract MassSpectrum instance \p other from \c this MassSpectrum.
/*!

  This is the reverse operation of combine(const MassSpectrum &other,
  double mzStart, double mzEnd).

*/
int
MassSpectrum::subtract(const MassSpectrum &other, double mzStart, double mzEnd)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__

  if(m_applyMzShift)
    determineMzShift(other);

  // Reset the index anchors
  m_lastBinIdx   = 0;
  m_lastModifIdx = 0;

  int count = 0;

  for(int iter = 0; iter < other.size(); ++iter)
    {
      DataPoint *iterDataPoint = other.at(iter);

      if(iterDataPoint->m_val == 0)
        continue;

      // Either we do not care of m/z range or the data point satisifies the
      // condition.
      if((mzStart == -1 || mzEnd == -1) ||
         (iterDataPoint->m_key >= mzStart && iterDataPoint->m_key <= mzEnd))
        {
          if(m_binningType != msXpS::BinningType::BINNING_TYPE_NONE)
            subtractBinned(*iterDataPoint);
          else
            Trace::subtract(*iterDataPoint);

          ++count;
        }
      else if(iterDataPoint->m_key > mzEnd)
        // Because the spectra are ordered, if the currently iterated data point
        // has a mass greater than the accepted mzEnd, then break the loop as
        // this cannot be any better later.

        break;
    }

  return count;
}


int
MassSpectrum::combine(const Trace &trace, double mzStart, double mzEnd)
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  MassSpectrum mass_spectrum(trace);

  return combine(mass_spectrum, mzStart, mzEnd);
}


int
MassSpectrum::subtract(const Trace &trace, double mzStart, double mzEnd)
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  MassSpectrum mass_spectrum(trace);

  return subtract(mass_spectrum, mzStart, mzEnd);
}


//! Combine the MassSpectrum instances of \p massSpectra into \c this
//! MassSpectrum (serial version)
/*!

  The workings of this function are the following:

  - if the \p massSpectra list is empty, return 0

  - if \c this MassSpectrum is empty, then setup the binning framework (call
  to setupBinnability()). That function will check for various member data to
  establish if binning is requested and of what type it should be.

  - iterate in the \p massSpectra list of MassSpectrum instances and for each
  instance call combine(const MassSpectrum &). Note that the combine() call
  will check if the combine operation should take advantage of binning or not.

  \param massSpectra list of MassSpectrum instances to be combined to \c this
  MassSpectrum

  \return the number of combined MassSpectrum instances.

  \sa combine(const MassSpectrum &)
  */
int
MassSpectrum::combineMonoT(const QList<MassSpectrum *> &massSpectra)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  if(massSpectra.size() == 0)
    return 0;

  // This function might be called recursively with various mass spectrum
  // lists. We only need to seed the bin combination data if that has not
  // been done already, that is this mass spectrum is empty (binning
  // actually fills-in the very first set of (m/z,i) DataPoint instances.

  if(!size())
    {
      if(!setupBinnability(massSpectra))
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  int specCount = massSpectra.size();

  int count = 0;

  for(int iter = 0; iter < specCount; ++iter)
    {
      combine(*massSpectra.at(iter));
#if 0
				exportToFile(QString("/tmp/spectrum-%1-for-combinations.txt")
						.arg(iter + 1));
#endif
      ++count;

#if 0
				exportToFile(QString("/tmp/unranged-combined-spectrum-after-%1-combinations.txt")
						.arg(iter + 1);
#endif
    }

  if(m_removeZeroValDataPoints)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Now removing all 0-m/z data points.";

      removeZeroValDataPoints();
    }

#if 0
						exportToFile(QString("/tmp/combined-spectrum.txt"));
#endif

  return count;
}


//! Combine the MassSpectrum instances of \p massSpectra into \c this
//! MassSpectrum (parallel version)
/*!

  The workings of this function are the following:

  - if the \p massSpectra list is empty, return 0

  - if \c this MassSpectrum is empty, then setup the binning framework (call
  to setupBinnability()). That function will check for various member data to
  establish if binning is requested and of what type it should be.

  - iterate in the \p massSpectra list of MassSpectrum instances and for each
  instance call combine(const MassSpectrum &). Note that the combine() call
  will check if the combine operation should take advantage of binning or not.

  \param massSpectra list of MassSpectrum instances to be combined to \c this
  MassSpectrum

  \param mzStart left value of the m/z range (-1 if no range is to be checked
  for)

  \param mzEnd right value of the m/z range (-1 if no range is to be checked
  for)

  \return the number of combined MassSpectrum instances.
  */
int
MassSpectrum::combine(const QList<MassSpectrum *> &massSpectra,
                      double mzStart,
                      double mzEnd)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  int specCount = massSpectra.size();

  if(specCount == 0)
    return 0;

  // This function might be called recursively with various mass spectrum
  // lists. We only need to seed the bin combination data if that has not
  // been done already, that is this mass spectrum is empty (binning
  // actually fills-in the very first set of (m/z,i) DataPoint instances.

  if(!size())
    {
      if(!setupBinnability(massSpectra))
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  // count is the number of spectra that were effectively combined.
  int count = 0;

  int maxThreads = omp_get_max_threads();
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Max threads: " << maxThreads;

  if(specCount < 10 * maxThreads)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Number of spectra to combine:" << specCount
      //<< "Too small a number of spectra, not using the parallel code.";

      for(int iter = 0; iter < specCount; ++iter)
        {
          combine(*massSpectra.at(iter), mzStart, mzEnd);
#if 0
					exportToFile(QString("/tmp/spectrum-%1-for-combinations.txt")
							.arg(iter + 1));
#endif
          ++count;
        }
    }
  else
    /* of if(specCount < 10 * numbThreads) */
    {
      omp_set_num_threads(maxThreads);

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Number of spectra sufficient to use parallel code."
      //<< "Set number of threads: " << maxThreads;

      // Allocate as many combined mass spectra as we are using threads.
      QList<MassSpectrum *> combinedMassSpectrumList;

      // Set the various slices of massSpectra so that each thread knows what
      // slice to handle.
      QList<QPair<int, int> *> indexRangeList;

      double specPerThread = specCount / maxThreads;
      // Integer part of the fractional result.
      double intPart;
      // We won't use fracPart, but anyway.
      double fracPart = std::modf(specPerThread, &intPart);
      Q_UNUSED(fracPart);

      for(int iter = 0; iter < maxThreads; ++iter)
        {
          // Create a new mass spectrum that is identical to *this one, that it
          // to benefit from the binning configuration data as determined by the
          // call to setupBinnability above.

          combinedMassSpectrumList.append(new MassSpectrum(*this));

          if(iter != maxThreads - 1)
            {
              if(!iter)
                // We actually start from 0 = (iter * intPat), with iter = 0.
                indexRangeList.append(
                  new QPair<int, int>(iter * intPart, (iter + 1) * intPart));
              else
                // We need to increment by one index value the startIdx,
                // otherwise
                // we would combine twice the spectrum at index (iter * intPart)
                indexRangeList.append(new QPair<int, int>(
                  (iter * intPart) + 1, (iter + 1) * intPart));
            }
          else
            {

              // Now is the time to complete to the fractional part, without
              // bothering: simply set the lastIdx of QPair to
              // specCount - 1.

              indexRangeList.append(
                new QPair<int, int>(iter * intPart, specCount - 1));
            }
        }

        // At this point we have setup all the slices to combine them in their
        // own combined mass spectrum.

#pragma omp parallel for shared(count)
      // This code was tested to provide identical results as when run in
      // non-parallel mode (20180226) and provided an increase in execution
      // speed of integration of ion mobility MS data of a factor of 8 when
      // the number of threads was 8.
      for(int iter = 0; iter < maxThreads; ++iter)
        {
          MassSpectrum *p_combinedMassSpectrum =
            combinedMassSpectrumList.at(iter);
          QPair<int, int> *p_indexRange = indexRangeList.at(iter);

          int combineCount =
            p_combinedMassSpectrum->combineSlice(massSpectra,
                                                 p_indexRange->first,
                                                 p_indexRange->second,
                                                 mzStart,
                                                 mzEnd);

          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< QString::asprintf("Combined slice %d-%d\n", p_indexRange->first,
          // p_indexRange->second);

          count += combineCount;
        }
      // End of
      // #pragma omp parallel

      // At this point we need to conclude and combine into *this mass
      // spectrum all the spectra in the list of combine mass spectra. Take
      // advantage of the loop to also free each combine mass spectrum in
      // turn.

      while(combinedMassSpectrumList.size())
        {
          MassSpectrum *massSpectrum = combinedMassSpectrumList.takeFirst();

          // No need to check mz range because we did that already above.
          combine(*massSpectrum, -1, -1);

          delete massSpectrum;
        }

      // At this point, we need to free all the QPair instances
      while(indexRangeList.size())
        delete indexRangeList.takeFirst();
    }
  // End of
  // else /* of if(specCount < 10 * maxThreads) */

  // The combination work is finished, however it has been performed.

  if(m_removeZeroValDataPoints)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Now removing all 0-m/z data points.";

      removeZeroValDataPoints();
    }
#if 0
			exportToFile(QString("/tmp/combined-spectrum.txt"));
#endif

  return count;
}


//! Combine the [\p startIdx--\p endIdx] slice of \p massSpectra into \c this
//! Mass Spectrum
/*!
  \return the number of combined Mass Spectrum instances.
  */
int
MassSpectrum::combineSlice(const QList<MassSpectrum *> &massSpectra,
                           int startIdx,
                           int endIdx,
                           double mzStart,
                           double mzEnd)
{
  if(massSpectra.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "List of spectra to combine is empty."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Even if we only want to combine a part of the massSpectra list, we need
  // to abide by the convention that maybe binning is required, and thus we
  // need to setup binning. But binning setup only occurs on an empty
  // MassSpectrum (*this spectrum) as it needs to setup the basis of all
  // future integrations. Once it is done, no more binning setup can occur.

  if(!size())
    {
      if(!setupBinnability(massSpectra))
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  int firstIdx = std::min(startIdx, endIdx);
  int lastIdx  = std::max(startIdx, endIdx);

  if(firstIdx < 0)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Index cannot be less than 0."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(lastIdx > massSpectra.size())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Out of bounds error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int count = 0;

  for(int iter = firstIdx; iter < lastIdx; ++iter)
    {
      if(!m_isOperationCancelled)
        {
          combine(*(massSpectra.at(iter)), mzStart, mzEnd);
          ++count;
          emit counterSignal(1);
        }
      else
        emit counterSignal(-1);
    }

  return count;
}


//! Subtract \p massSpectra from \c this MassSpectrum (serial version)
/*!
  This is the reverse operation of combine(const QList<MassSpectrum *>
  &massSpectra).
  */
int
MassSpectrum::subtractMonoT(const QList<MassSpectrum *> &massSpectra)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ ;

  if(massSpectra.size() == 0)
    return 0;

  // This function might be called recursively with various mass spectrum
  // lists. We only need to seed the bin combination data if that has not
  // been done already, that is this mass spectrum is empty (binning
  // actually fills-in the very first set of (m/z,i) pairs.

  if(!size())
    if(!setupBinnability(massSpectra))
      qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  int specCount = massSpectra.size();

  int count = 0;

  for(int iter = 0; iter < specCount; ++iter)
    {
      subtract(*massSpectra.at(iter));

      ++count;
    }

  if(m_removeZeroValDataPoints)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Now removing all 0-m/z data points.";

      removeZeroValDataPoints();
    }

  return count;
}


//! Subtract \p massSpectra from \c this MassSpectrum (parallel version)
/*!
  This is the reverse operation of combine(const QList<MassSpectrum *>
  &massSpectra, double mzStart, double mzEnd).
  */
int
MassSpectrum::subtract(const QList<MassSpectrum *> &massSpectra,
                       double mzStart,
                       double mzEnd)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  int specCount = massSpectra.size();

  if(specCount == 0)
    return 0;

  // This function might be called recursively with various mass spectrum
  // lists. We only need to seed the bin combination data if that has not
  // been done already, that is this mass spectrum is empty (binning
  // actually fills-in the very first set of (m/z,i) DataPoint instances.

  if(!size())
    {
      if(!setupBinnability(massSpectra))
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }

  // count is the number of spectra that were effectively subtractd.
  int count = 0;

  int maxThreads = omp_get_max_threads();

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "Max threads: " << maxThreads;

  if(specCount < 10 * maxThreads)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Number of spectra to subtract:" << specCount
               << "Too small a number of spectra, not using the parallel code.";

      for(int iter = 0; iter < specCount; ++iter)
        {
          subtract(*massSpectra.at(iter), mzStart, mzEnd);

#if 0
					exportToFile(QString("/tmp/spectrum-%1-for-subtractions.txt")
							.arg(iter + 1));
#endif
          ++count;
        }
    }
  else
    /* of if(specCount < 10 * numbThreads) */
    {
      omp_set_num_threads(maxThreads);

      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "Number of spectra sufficient to use parallel code."
               << "Set number of threads: " << maxThreads;

      // Allocate as many subtractd mass spectra as we are using threads.
      QList<MassSpectrum *> subtractedMassSpectrumList;

      // Set the various slices of massSpectra so that each thread knows what
      // slice to handle.
      QList<QPair<int, int> *> indexRangeList;

      double specPerThread = specCount / maxThreads;
      // Integer part of the fractional result.
      double intPart;
      // We won't use fracPart, but anyway.
      double fracPart = std::modf(specPerThread, &intPart);
      Q_UNUSED(fracPart);

      for(int iter = 0; iter < maxThreads; ++iter)
        {
          // Create a new mass spectrum that is identical to *this one, that it
          // to benefit from the binning configuration data as determined by the
          // call to setupBinnability above.

          subtractedMassSpectrumList.append(new MassSpectrum(*this));

          if(iter != maxThreads - 1)
            {
              if(!iter)
                // We actually start from 0 = (iter * intPat), with iter = 0.
                indexRangeList.append(
                  new QPair<int, int>(iter * intPart, (iter + 1) * intPart));
              else
                // We need to increment by one index value the startIdx,
                // otherwise
                // we would subtract twice the spectrum at index (iter *
                // intPart)
                indexRangeList.append(new QPair<int, int>(
                  (iter * intPart) + 1, (iter + 1) * intPart));
            }
          else
            {

              // Now is the time to complete to the fractional part, without
              // bothering: simply set the lastIdx of QPair to
              // specCount - 1.

              indexRangeList.append(
                new QPair<int, int>(iter * intPart, specCount - 1));
            }
        }

        // At this point we have setup all the slices to subtract them in their
        // own subtractd mass spectrum.

#pragma omp parallel for shared(count)
      // This code was tested to provide identical results as when run in
      // non-parallel mode (20180226) and provided an increase in execution
      // speed of integration of ion mobility MS data of a factor of 8 when
      // the number of threads was 8.
      for(int iter = 0; iter < maxThreads; ++iter)
        {
          MassSpectrum *p_subtractedMassSpectrum =
            subtractedMassSpectrumList.at(iter);
          QPair<int, int> *p_indexRange = indexRangeList.at(iter);

          int subtractCount =
            p_subtractedMassSpectrum->subtractSlice(massSpectra,
                                                    p_indexRange->first,
                                                    p_indexRange->second,
                                                    mzStart,
                                                    mzEnd);

          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << QString::asprintf("subtractd slice %d-%d\n",
                                        p_indexRange->first,
                                        p_indexRange->second);

          count += subtractCount;
        }
      // End of
      // #pragma omp parallel

      // At this point we need to conclude and subtract from *this mass
      // spectrum all the spectra in the list of subtracted mass spectra. Take
      // advantage of the loop to also free each subtracted mass spectrum in
      // turn.

      while(subtractedMassSpectrumList.size())
        {
          MassSpectrum *massSpectrum = subtractedMassSpectrumList.takeFirst();

          // No need to check mz range because we did that already above.
          subtract(*massSpectrum, mzStart, mzEnd);

          delete massSpectrum;
        }

      // At this point, we need to free all the QPair instances
      while(indexRangeList.size())
        delete indexRangeList.takeFirst();
    }
  // End of
  // else /* of if(specCount < 10 * maxThreads) */

  // The combination work is finished, however it has been performed.

  if(m_removeZeroValDataPoints)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Now removing all 0-m/z data points.";

      removeZeroValDataPoints();
    }
#if 0
			exportToFile(QString("/tmp/subtractd-spectrum.txt"));
#endif

  return count;
}


//! Subtract the [\p startIdx--\p endIdx] slice of \p massSpectra from \c this
//! Mass Spectrum
/*!

  This is the reverse operation of combine(const QList<MassSpectrum *>
  &massSpectra).

*/
int
MassSpectrum::subtractSlice(const QList<MassSpectrum *> &massSpectra,
                            int startIdx,
                            int endIdx,
                            double mzStart,
                            double mzEnd)
{
  if(massSpectra.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "List of spectra to subtract is empty."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int firstIdx = std::min(startIdx, endIdx);
  int lastIdx  = std::max(startIdx, endIdx);

  if(firstIdx < 0)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Index cannot be less than 0."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(lastIdx > massSpectra.size())
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Out of bounds error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int count = 0;

  for(int iter = firstIdx; iter < lastIdx; ++iter)
    {
      subtract(*(massSpectra.at(iter)), mzStart, mzEnd);
      ++count;
    }

  return count;
}


//! Combine the mass spectrum represented by \p mzList and \iList in \c this
//! MassSpectrum.
/*!

  For each paired double value in each list, reconstitute a DataPoint object and
  combine it to \p this MassSpectrum.

  If \p mzStart and \p mzEnd are not -1, then the (m/z,i) pairs out of the
  lists are accounted for only if the m/z value is contained in the [\p
  mzStart -- \p mzEnd] range.

  \param mzList list of double values representing the m/z values of the mass
  spectrum

  \param iList list of double values representing the intensity values of the
  mass spectrum

  \param mzStart left value of the m/z range (-1 if no range is to be checked
  for)

  \param mzEnd right value of the m/z range (-1 if no range is to be checked
  for)

  \return -1 if \p mzList is empty, otherwise the number of combined (m/z,i)
  pairs.

  \sa combine(const QList<double> &mzList, const QList<double> &iList).

*/
int
MassSpectrum::combine(const QList<double> &mzList,
                      const QList<double> &iList,
                      double mzStart,
                      double mzEnd)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // Sanity check

  int listSize = mzList.size();
  if(listSize < 1)
    return -1;

  if(listSize != iList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Reset the index anchors
  m_lastBinIdx   = 0;
  m_lastModifIdx = 0;

  if(m_applyMzShift)
    determineMzShift(mzList.first());

  int count = 0;

  for(int iter = 0; iter < listSize; ++iter)
    {
      double mz = mzList.at(iter);

      // Either we do not care of m/z range or the data point satisifies the
      // condition.
      if((mzStart == -1 || mzEnd == -1) || (mz >= mzStart && mz <= mzEnd))
        {
          DataPoint dataPoint(mz, iList.at(iter));

          if(m_binningType != msXpS::BinningType::BINNING_TYPE_NONE)
            count += combineBinned(dataPoint);
          else
            count += Trace::combine(dataPoint);
        }
    }

  return count;
}


//! Subtract mass data materialized in \p mzList and \p iList from \c this
//! MassSpectrum.
/*!

  This is the reverse operation of combine(const QList<double> &mzList, const
  QList<double> &iList, double mzStart, double mzEnd).

*/
int
MassSpectrum::subtract(const QList<double> &mzList,
                       const QList<double> &iList,
                       double mzStart,
                       double mzEnd)
{

  // Sanity check

  int listSize = mzList.size();
  if(listSize < 1)
    return -1;

  if(listSize != iList.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  // Reset the index anchors
  m_lastBinIdx   = 0;
  m_lastModifIdx = 0;

  if(m_applyMzShift)
    determineMzShift(mzList.first());

  int count = 0;

  for(int iter = 0; iter < listSize; ++iter)
    {
      double mz = mzList.at(iter);

      // Either we do not care of m/z range or the data point satisifies the
      // condition.
      if((mzStart == -1 || mzEnd == -1) || (mz >= mzStart && mz <= mzEnd))
        {
          DataPoint dataPoint(mz, iList.at(iter));

          if(m_binningType != msXpS::BinningType::BINNING_TYPE_NONE)
            count += subtractBinned(dataPoint);
          else
            count += Trace::subtract(dataPoint);
        }
    }

  return count;
}

int
MassSpectrum::combine(const DataPoint &dataPoint)
{
  // We want to combine a DataPoint, so we need to first establish if we are
  // working in binned mode or not. Because the combination method is not the
  // same.

  if(m_binningType != msXpS::BinningType::BINNING_TYPE_NONE)
    return combineBinned(dataPoint);
  else
    return Trace::combine(dataPoint);
}

int
MassSpectrum::subtract(const DataPoint &dataPoint)
{
  // We want to subtract a DataPoint, so we need to first establish if we are
  // working in binned mode or not. Because the combination method is not the
  // same.

  if(m_binningType != msXpS::BinningType::BINNING_TYPE_NONE)
    return subtractBinned(dataPoint);
  else
    return Trace::subtract(dataPoint);
}

//! Combine \p dataPoint into \c this MassSpectrum using bins.
/*!

  The combine operation is performed at the bin that corresponds to the key of
  \p dataPoint. The intensity value of the found bin is incremented by the
  intensity of \p dataPoint. If no bin is found, the program crashes.

  Note that if \c this spectrum is empty, the program crashes, because, by
  definition a binned combine operation requires that the bins be already
  available in \c this MassSpectrum.

  The \p dataPoint key is first copied and the copy is modified according
  to this:

  - if \c m_applyMzShift is true, then m_mzShift is added to the \m dataPoint
  key. These two data bits must have been set by the combining operation
  caller previously to the combining itself.

  - if m_decimalPlaces is not -1, then the decimal places are taken into
  account for the computation.

  Once the steps above have been performed, the bin corresponding to the key
  (that is, m/z value) of \p dataPoint is determined and its value (that is,
  intensity) is incremented by the value of \p dataPoint.

  \param dataPoint DataPoint instance to be combined to \c this MassSpectrum.
  If the value of \p dataPoint is 0, then the combination does not occur and
  -1 is returned.

  \sa combine(const DataPoint &dataPoint)

  \return the index of the updated bin or -1 if no combination occurred.

*/
int
MassSpectrum::combineBinned(const DataPoint &dataPoint)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  if(dataPoint.m_val == 0)
    return -1;

  // Since this combine process is binned, it is absolutely necessary that
  // this spectrum contains data, that is, at least the bins !

  if(!size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "In a binned combine operation, this spectrum cannot be empty\n. "
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  double mz = dataPoint.m_key;

  if(m_applyMzShift)
    mz += m_mzShift;

  if(m_decimalPlaces != -1)
    {
      mz =
        ceil((mz * pow(10, m_decimalPlaces)) - 0.49) / pow(10, m_decimalPlaces);

      // qDebug("%s %d Rounding from %.6f to %.6f\n",
      //__FILE__, __LINE__,
      // dataPoint.m_key, mz);
    }

  // qDebug() << __FILE__ << __LINE__
  //<< QString::asprintf("Calling binIndex with mz = %.6f and mzShift =
  //%.6f\n",
  // mz, m_mzShift);

  int binIdx = binIndex(mz);

  if(binIdx == -1)
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Failed to find the bin for the m/z value of DataPoint. Program "
      "aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // QString msg = QString("must combine (%1,%2) with bin[%3] (%4, %5)")
  //.arg(mz, 0, 'f', 10)
  //.arg(dataPoint.m_val)
  //.arg(binIdx)
  //.arg(at(binIdx)->m_key, 0, 'f', 10)
  //.arg(at(binIdx)->m_val, 0, 'f', 10);
  // qDebug() << __FILE__ << __LINE__ << msg;

  at(binIdx)->m_val += dataPoint.m_val;

  // msg = QString("after combination: (mz,i): (%1,%2)")
  //.arg(at(binIdx)->m_key, 0, 'f', 10)
  //.arg(at(binIdx)->m_val, 0, 'f', 10);
  // qDebug() << __FILE__ << __LINE__ << msg;

  return binIdx;
}


//! Subtract \p dataPoint from \c this MassSpectrum using bins.
/*!

  This is the reverse operation of the combineBinned(const DataPoint
  &dataPoint)
  operation. Here, the value of \p dataPoint has its sign inverted by
  multiplying it by -1. Then combineBinned(DataPoint) is called as for a
  normal
  combine operation.

  \param dataPoint DataPoint instance to be subtracted from \c this
  MassSpectrum

  \sa combineBinned(const DataPoint &dataPoint)

  \return an integer containing the index of the updated bin

*/
int
MassSpectrum::subtractBinned(const DataPoint &dataPoint)
{
  if(dataPoint.m_val == 0)
    return -1;

  DataPoint localDataPoint(dataPoint);

  // Invert the sign of the intensity so that we can reuse the same
  // combineBinned(DataPoint) function as for the positive combine.

  localDataPoint.m_val *= -1;

  return combineBinned(localDataPoint);
}


/************************** BINNING METHODS **************************/
/************************** BINNING METHODS **************************/
/************************** BINNING METHODS **************************/


//! Setup the binning infrastructure
/*!

  Binning is the fact that combination of multiple mass spectra to produce a
  single mass spectrum happens in a fixed set of m/z values that behave like
  bins. The way the set of m/z values (actually the key of a set of DataPoint
  instances) must be defined depends on the settings that the combination user
  has configured in \c this MassSpectrum instance. Typically, this happens by
  the caller analyzing statistically the mass spectra to be combined together
  and retaining some useful parameters.

  For example, the smallest step between a m/z value and the following one in
  the whole set of mass spectra could be considered to be the smallest bin
  size. In that case, the binning type should be the type of the bin size
  should be msXpS::MassDataTolerance::MASS_TOLERANCE_MZ or
  msXpS::MassDataTolerance::MASS_TOLERANCE_AMU. However, the user might want
  to set the bin size value herself, and is provided a means to do so in the
  graphical user interface. In that case a number of bin size types are
  available to accomodate the specifics of the user cases, they are listed in
  msXpS::MassDataTolerance. In the previous case and in this latter case, the
  binning type is msXpS::BinningType::BINNING_TYPE_ARBITRARY.

  Another example is when the binning must be performed according to the
  structure of the first spectrum in the mass spectrum list. In that case, the
  binning type is msXpS::BinningType::BINNING_TYPE_DATA_BASED, and the bins
  are calculated on the fly. In this specific case, the various member data
  are setup according to that calculation and need not be set by the user of
  the combination spectrum.

  \param massSpectra list of MassSpectrum instances

  \return true in all cases unless \p massSpectra is empty, in which case it
  returns false.

*/
bool
MassSpectrum::setupBinnability(const QList<MassSpectrum *> &massSpectra)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;

  // When a mass spectrum is used to perform a combination, it is configured
  // by the caller (the user of the combination spectrum) according to a
  // previous statistical survey of all the mass spectra to be combined into
  // thls one. Furthre, the user has any possibility to declare that she
  // does not want binnability. Thus, *this mass spectrum that is being used
  // to perform the combination, already knows if binnability is requested.

  if(m_binningType == msXpS::BinningType::BINNING_TYPE_NONE)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Spectrum was configured NOT to be binnable.";

      return true;
    }

  // If there are no spectra in the mass spectrum list, then we have nothing
  // to do.

  if(massSpectra.size() == 0)
    return false;

  // If the combination involves a single spectrum, then, do no binning
  // since there is not going to be any real integration.

  if(massSpectra.size() == 1)
    {
      m_binningType = msXpS::BinningType::BINNING_TYPE_NONE;

      // qDebug() << __FILE__ << __LINE__
      //<< "Single spectrum list: no binning.";

      return true;
    }

  // Since setting up the bins must be performed only once, and that
  // operation needs to be performed prior to the first combination, we only
  // perform binning if this spectrum is empty, that is, it is the pristine
  // recipient of a combination operation yet to be started.

  if(size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Setting up bins means that *this spectrum is empty. Which it is not."
      " Programming error. Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Check if the m_minMz value was set by the caller already or not. If
  // not, quickly get it. Note that this can only work because the DataPoint
  // instances in a MassSpectrum are sorted in increasing DataPoint.m_key:
  // we use MassSpectrum::first() to get the first DataPoint instance as the
  // one that has the smallest m/z key value of all.

  if(m_minMz == std::numeric_limits<double>::max())
    {
      for(int iter = 0; iter < massSpectra.size(); ++iter)
        {
          if(massSpectra.at(iter)->first()->key() < m_minMz)
            m_minMz = massSpectra.at(iter)->first()->key();
        }
    }

  // Same for m_maxMz value, but using MassSpectrum::last() to get the last
  // m/z value as the one that has the greatest m/z key value of all.
  if(m_maxMz == std::numeric_limits<double>::min())
    {
      for(int iter = 0; iter < massSpectra.size(); ++iter)
        {
          if(massSpectra.at(iter)->last()->key() > m_maxMz)
            m_maxMz = massSpectra.at(iter)->last()->key();
        }
    }

  // Depending on the binning type, we need to perform different actions:
  //
  // 1. If the binning type is arbitrary, then the m_binSize and
  // m_binSizeType have been preset by the caller function.  Same thing for
  // the minimum mz and the maximum mz values of the whole mass spectrum
  // list.
  //
  // 2. If the binning type is data-based, then the binning is performed by
  // looking at the first spectrum of the mass spectrum list.

  if(m_binningType == msXpS::BinningType::BINNING_TYPE_ARBITRARY)
    m_binCount = populateSpectrumWithArbitraryBins();
  else if(m_binningType == msXpS::BinningType::BINNING_TYPE_DATA_BASED)
    m_binCount = populateSpectrumWithDataBasedBins(*(massSpectra.first()));

#if 0
			qDebug() << __FILE__ << __LINE__
				<< "Writing the spectrum right after setting-up the bins in file /tmp/spectrum-with-bins.txt";

			QFile file("/tmp/spectrum-with-bins.txt");
			file.open(QIODevice::WriteOnly);
			QTextStream fileStream(&file);
			QString *text = asText(-1, -1);
			fileStream << *text;
			delete text;
			fileStream.flush();
			file.close();
#endif

  // qDebug() << __FILE__ << __LINE__
  //<< "Binning type:" << msXpS::binningTypeMap.value(m_binningType)
  //<< "Number of bins:" << size();

  return true;
}


//! Setup the binning infrastructure
/*!

  Binning is the fact that combination of multiple mass spectra to produce a
  single mass spectrum happens in a fixed set of m/z values that behave like
  bins. The way the set of m/z values (actually the key of a set of DataPoint
  instances) must be defined depends on the settings that the combination user
  has configured in \c this MassSpectrum instance. Typically, this happens by
  the caller analyzing statistically the mass spectra to be combined together
  and retaining some useful parameters.

  For example, the smallest step between a m/z value and the following one in
  the whole set of mass spectra could be considered to be the smallest bin
  size. In that case, the binning type should be the type of the bin size
  should be msXpS::MassDataTolerance::MASS_TOLERANCE_MZ or
  msXpS::MassDataTolerance::MASS_TOLERANCE_AMU. However, the user might want
  to set the bin size value herself, and is provided a means to do so in the
  graphical user interface. In that case a number of bin size types are
  available to accomodate the specifics of the user cases, they are listes in
  msXpS::MassDataTolerance. In the previous case and in this latter case, the
  binning type is msXpS::BinningType::BINNING_TYPE_ARBITRARY.

  Another example is when the binning must be performed according to the
  structure of the first spectrum in the mass spectrum list. In that case, the
  binning type is msXpS::BinningType::BINNING_TYPE_DATA_BASED, and the bins
  are calculated on the fly. In this specific case, the various member data
  are setup according to that calculation and need not be set by the user of
  the combination spectrum.

  While, most frequently setupBinnability() is called with a list of mass
  spectra as parameter, this overload using only a single mass spectrum is
  useful when performing combinations in streamed mode, when mass spectra are
  loaded one by one from the data file and then discarded. Typically, this
  function would be called with the first spectrum of the file, and then the
  bins would be used for all the remaining spectra.

  \param massSpectrum MassSpectrum instance to use to craft the bins.

  \return true in all cases unless \p massSpectrum is empty, in which case it
  returns false.

*/
bool
MassSpectrum::setupBinnability(const MassSpectrum &massSpectrum)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;

  // This overload ensures that it is possible to setup binnability starting
  // not from a list of mass spectra, but from a single mass spectrum. This
  // is required in the case of loading data from mass data file in streamed
  // mode, when a single spectrum is handled at any given time and the mass
  // spectra of a mass data file are combined into a static mass spectrum
  // (*this mass spectrum) in turn as they get loaded from disk.


  // When a mass spectrum is used to perform a combination, it is configured
  // by the caller (the user of the combination spectrum) according to a
  // previous statistical survey of all the mass spectra to be combined into
  // thls one. Furthre, the user has any possibility to declare that she
  // does not want binnability. Thus, *this mass spectrum that is being used
  // to perform the combination, already knows if binnability is requested.

  if(m_binningType == msXpS::BinningType::BINNING_TYPE_NONE)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Spectrum was configured NOT to be binnable.";

      return true;
    }

  // If there are no DataPoint instances in the mass spectrum list, then we
  // have nothing to do.

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

  // Since setting up the bins must be performed only once, and that
  // operation needs to be performed prior to the first combination, we only
  // perform binning if this spectrum is empty, that is, it is the pristine
  // recipient of a combination operation yet to be started.

  if(size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Setting up bins means that *this spectrum is empty. Which it is not."
      " Programming error. Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Check if the m_minMz value was set by the caller already or not. If
  // not, that is a major error because the first spectrum of a series of
  // spectra certainly does not need to be of the full m/z range (see data
  // from Orbitrap). These value NEED to be set by the caller ahead of use
  // of *this mass spectrum as a combination spectrum.

  if(m_minMz == std::numeric_limits<double>::max() ||
     m_maxMz == std::numeric_limits<double>::min())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Not possible that m_minMz or m_maxMz is/are not set."
      " Programming error. Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Depending on the binning type, we need to perform different actions:
  //
  // 1. If the binning type is arbitrary, then the m_binSize and
  // m_binSizeType have been preset by the caller function.  Same thing for
  // the minimum mz and the maximum mz values of the whole mass spectrum
  // list.
  //
  // 2. If the binning type is data-based, then the binning is performed by
  // looking at the first spectrum of the mass spectrum list.

  if(m_binningType == msXpS::BinningType::BINNING_TYPE_ARBITRARY)
    m_binCount = populateSpectrumWithArbitraryBins();
  else if(m_binningType == msXpS::BinningType::BINNING_TYPE_DATA_BASED)
    m_binCount = populateSpectrumWithDataBasedBins(massSpectrum);

#if 0
			qDebug() << __FILE__ << __LINE__
				<< "Writing the spectrum right after setting-up the bins in file /tmp/spectrum-with-bins.txt";

			QFile file("/tmp/spectrum-with-bins.txt");
			file.open(QIODevice::WriteOnly);
			QTextStream fileStream(&file);
			QString *text = asText(-1, -1);
			fileStream << *text;
			delete text;
			fileStream.flush();
			file.close();
#endif

  // qDebug() << __FILE__ << __LINE__
  //<< "Binning type:" << msXpS::binningTypeMap.value(m_binningType)
  //<< "Number of bins:" << size();

  return true;
}


//! Populate this MassSpectrum instance with m/z value representing bins.
/*

   The m/z values are computed using member data that have been set in earlier
   processing events (typically by statistically analyzing all the mass
   spectra that are to be combined/subtracted).

   \c m_binSize: the delta value between two consecutive m/z values in the
   spectrum (an m/z value is the key of a DataPoint instance). This delta
   value
   is to be processed using \c m_binSizeType;

   \c m_binSizeType : the type of bin to be applied for the computation of the
   effective delta value between two consecutive DataPoint instances in this
   spectrum;

   Depending on both member data above, the number of decimals required for
   all
   the
   subsequent computations is determined and stored in \c m_decimaPlaces. That
   \c
   m_decimalPlaces value is then used for all the integrations to be performed
   later.

*/
int
MassSpectrum::populateSpectrumWithArbitraryBins()
{
  // qDebug() << __FILE__ << "@" << __LINE__ << "ENTER" << __FUNCTION__ << "()";

  if(size() != 0)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The spectrum must be empty for bins to be setup in it."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);


  // Now starts the tricky stuff. Depending on how the binning has been
  // configured, we need to take diverse actions.

  if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_AMU)
    m_decimalPlaces = msXpS::zeroDecimals(m_binSize) + 1;
  else if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_MZ)
    m_decimalPlaces = msXpS::zeroDecimals(m_binSize) + 1;
  else if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_PPM)
    m_decimalPlaces = msXpS::zeroDecimals(msXpS::ppm(m_minMz, m_binSize)) + 1;
  else if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_RES)
    m_decimalPlaces = msXpS::zeroDecimals(msXpS::res(m_minMz, m_binSize)) + 1;
  else
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "This line should not have been reached. Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< QString::asprintf("bin size: %.6f and decimal places: %d\n",
  // m_binSize,
  // m_decimalPlaces);

  // Now that we have defined the value of m_decimalPlaces, let's use that
  // value.

  double firstMz = ceil((m_minMz * pow(10, m_decimalPlaces)) - 0.49) /
                   pow(10, m_decimalPlaces);
  double lastMz = ceil((m_maxMz * pow(10, m_decimalPlaces)) - 0.49) /
                  pow(10, m_decimalPlaces);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< QString::asprintf("Very first data point: %.6f\n", firstMz)
  //<< QString::asprintf("Very last data point to reach: %.6f\n",
  // lastMz);

  // That is the very first data point in *this spectrum.

  append(new DataPoint(firstMz, 0));

  // Store that very first value for later use in the loop.

  double previousMzBin = firstMz;

  // Now continue adding mz values until we have reached the end of the
  // spectrum, that is the maxMz value, as converted using the decimals to
  // lastMz.

  // debugCount value used below for debugging purposes.
  // int debugCount = 0;

  while(previousMzBin < lastMz)
    {
      double currentMz = 0;

      if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_AMU)
        currentMz = previousMzBin + m_binSize;
      else if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_MZ)
        currentMz = previousMzBin + m_binSize;
      else if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_PPM)
        currentMz = msXpS::addPpm(previousMzBin, m_binSize /* ppm */);
      else if(m_binSizeType == msXpS::MassToleranceType::MASS_TOLERANCE_RES)
        currentMz = msXpS::addRes(previousMzBin, m_binSize /* res */);
      else
        qFatal(
          "Fatal error at %s@%d -- %s(). "
          "This line should not have been reached. Programming error."
          "Program aborted.",
          __FILE__,
          __LINE__,
          __FUNCTION__);

      currentMz = ceil((currentMz * pow(10, m_decimalPlaces)) - 0.49) /
                  pow(10, m_decimalPlaces);

      // if(debugCount == 0)
      //{
      // double mzDiff = currentMz - previousMzBin;
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< QString(
      //"previousMzBin: %1 - currentMz: %2 - mzDiff: %3 - "
      //"m_decimalPlaces: %4")
      //.arg(previousMzBin, 0, 'f', 10)
      //.arg(currentMz, 0, 'f', 10)
      //.arg(mzDiff, 0, 'f', 10)
      //.arg(m_decimalPlaces);
      //}

      double roundedMz = ceil((currentMz * pow(10, m_decimalPlaces)) - 0.49) /
                         pow(10, m_decimalPlaces);

      // If rounding makes the new value identical to the previous one, then
      // that means that we need to increase roughness.

      if(roundedMz == previousMzBin)
        {
          double prevRoundedMz = roundedMz;

          roundedMz = ceil((currentMz * pow(10, m_decimalPlaces + 1)) - 0.49) /
                      pow(10, m_decimalPlaces + 1);

          qDebug()
            << __FILE__ << __LINE__ << __FUNCTION__ << "()"
            << "Had to increment decimal places by one type."
            << QString::asprintf(
                 "previous rounded value: %.6f vs new reounded value: %.6f\n",
                 prevRoundedMz,
                 roundedMz);
        }

      // if(debugCount == 0)
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< QString("After decimals accounting: roundedMz: %1")
      //.arg(roundedMz, 0, 'f', 10);

      // if(debugCount == 0)
      // qDebug() << __FILE__ << __LINE__
      //<< QString("Append bin mz: %1 from computation %2\n")
      //.arg(roundedMz, 0, 'f', 10)
      //.arg(previousMzBin, 0, 'f', 10);

      append(new DataPoint(roundedMz, 0));

      // Use the localMz value for the storage of the previous mz bin.
      previousMzBin = roundedMz;

      // Do the debugging only once each 1000 mz values.
      // if(++debugCount == 100)
      // debugCount = 0;
    }

#if 0

			qDebug() << __FILE__ << __LINE__
				<< "Writing the list of bins setup in the mass spectrum in file /tmp/massSpecBins.txt";

			QFile file("/tmp/massSpecBins.txt");
			file.open(QIODevice::WriteOnly);

			QTextStream fileStream(&file);

			// We use the size of *this spectrum as the right border of the bin range.
			// We cannot use m_binCount because its size is one less then the actual
			// *this.size() because we added one extra smallest mz value before
			// looping in binList above. So *this mass spectrum ends having the same
			// number of data points a the intial spectra in the mass spectrum list
			// that was to be combined in this initially empty mass spectrum.
			for(int iter = 0; iter < size(); ++iter)
			{

				fileStream << QString("%1 %2\n")
					.arg(at(iter)->m_key, 0, 'f', 10)
					.arg(0);
			}

			fileStream.flush();
			file.close();

#endif

  // qDebug() << __FILE__ << __LINE__
  //<< "Prepared bins with " << size() << "elements."
  //<< "starting with mz" << first()->m_key
  //<< "ending with mz" << last()->m_key;

  return size();
}


//! Populate this MassSpectrum instance with m/z value representing bins.
/*

   The \p massSpectrum passed as parameter is analysed and used for crafting
   new DataPoint instances to populate this spectrum with.

   The first DataPoint instance in this MassSpectrum is derived from the \c
   m_minMz. The subsequent data points are derived by incrementing that
   m_minMz value by the m/z steps (bins) as calculated by looking into \p
   massSpectrum. And so on.  The size of \c this MassSpectrum will thus have
   the same size as massSpectrum. Note that \c m_maxMz is not used in this
   computation.

   The value (the intensity) is set to zero.
   */
int
MassSpectrum::populateSpectrumWithDataBasedBins(
  const MassSpectrum &massSpectrum)
{

  // The bins in *this mass spectrum must be calculated starting from the
  // data in the massSpectrum parameter.

  int specSize = massSpectrum.size();

  if(specSize < 2)
    return 0;

  QList<double> binList;

  double localMz = m_minMz;
  if(m_decimalPlaces != -1)
    localMz = ceil((localMz * pow(10, m_decimalPlaces)) - 0.49) /
              pow(10, m_decimalPlaces);

  // This is the very first data point where no actual step computation is
  // needed.
  append(new DataPoint(localMz, 0));

  double previousMzBin = localMz;

  double prevMz = massSpectrum.first()->m_key;
  if(m_decimalPlaces != -1)
    prevMz = ceil((prevMz * pow(10, m_decimalPlaces)) - 0.49) /
             pow(10, m_decimalPlaces);

  for(int iter = 1; iter < specSize; ++iter)
    {
      double curMz = massSpectrum.at(iter)->m_key;
      if(m_decimalPlaces != -1)
        curMz = ceil((curMz * pow(10, m_decimalPlaces)) - 0.49) /
                pow(10, m_decimalPlaces);

      double step = curMz - prevMz;

      // Sanity check:
      // Spectra are ascending-ordered for the mz value. If not, that's fatal.
      if(step < 0)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

      // Update prevMz to be the current one for next iteration.
      prevMz = curMz;

      // Now actually make use of that step value to craft the next DataPoint
      // instance.

      double tempMzBin = previousMzBin + step;
      if(m_decimalPlaces != -1)
        localMz = ceil((tempMzBin * pow(10, m_decimalPlaces)) - 0.49) /
                  pow(10, m_decimalPlaces);
      else
        localMz = tempMzBin;

      append(new DataPoint(localMz, 0));

      // Update previousMzBin to be the last m/z value for the next iteration.
      previousMzBin = localMz;
    }

#if 0

			qDebug() << __FILE__ << __LINE__
				<< "Writing the list of bins setup in the mass spectrum in file /tmp/massSpecBins.txt";

			QFile file("/tmp/massSpecBins.txt");
			file.open(QIODevice::WriteOnly);

			QTextStream fileStream(&file);

			// We use the size of *this spectrum as the right border of the bin range.
			// We cannot use m_binCount because its size is one less then the actual
			// *this.size() because we added one extra smallest mz value before
			// looping in binList above. So *this mass spectrum ends having the same
			// number of data points a the intial spectra in the mass spectrum list
			// that was to be combined in this initially empty mass spectrum.
			for(int iter = 0; iter < size(); ++iter)
			{

				fileStream << QString("%1 %2\n")
					.arg(at(iter)->m_key, 0, 'f', 10)
					.arg(0);
			}

			fileStream.flush();
			file.close();

#endif

  // qDebug() << __FILE__ << __LINE__
  //<< "Prepared bins with " << size() << "elements."
  //<< "starting with mz" << first()->m_key
  //<< "ending with mz" << last()->m_key;

  return size();
}

//! Find the index of the bin containing \p mz
/*!

  Iterates in \c this MassSpectrum and searches the DataPoint instance having
  the \c mz key.

  \param mz m/z value whose corresponding bin is searched.

  \return the index of the DataPoint instance having the \c mz key.

*/
int
MassSpectrum::binIndex(double mz)
{
  // QString msg = QString("Entering binIndex with mz: %1 while m_lastBinIdx =
  // %2 (%3,%4) and m_mzShift = %5")
  //.arg(mz, 0, 'f', 10)
  //.arg(m_lastBinIdx)
  //.arg(at(m_lastBinIdx)->m_key, 0, 'f', 10)
  //.arg(at(m_lastBinIdx)->m_val, 0, 'f', 10)
  //.arg(m_mzShift, 0, 'f', 10);
  // qDebug() << __FILE__ << __LINE__
  //<< msg;

  if(!size())
    return 0;

  if(mz > at(size() - 1)->m_key)
    {
      m_lastBinIdx = size() - 1;
      return m_lastBinIdx;
    }

  // FIXME: it is a problem that we get an mz value that is less than the
  // first bin's mz value. Because, during binnability, we set the very
  // first bin's mz value to the smallest of all the mz values of all the
  // mass spectra in the mass spectrum list (see setupBinnability()). So
  // this situation should never occur.
  if(mz <= at(0)->m_key)
    {
      m_lastBinIdx = 0;

      // qFatal("Fatal error at %s@%d -- %s. "
      //"The binning framework was not setup properly. Exiting."
      //"Program aborted.",
      //__FILE__, __LINE__, __FUNCTION__);
    }

  if(mz < at(m_lastBinIdx)->m_key)
    m_lastBinIdx = 0;

  int lastTestIndex = 0;
  int returnIndex   = -1;

  for(int iter = m_lastBinIdx; iter < size(); ++iter)
    {
      double iterBinMz = at(iter)->m_key;

      // QString msg = QString("Current bin index: %1 (%2,%3")
      //.arg(iter)
      //.arg(iterBinMz, 0, 'f', 10)
      //.arg(at(iter)->m_val, 0, 'f', 10);
      // qDebug() << __FILE__ << __LINE__
      //<< msg;

      if(mz >= iterBinMz)
        {
          if(mz == iterBinMz)
            {
              returnIndex = iter;
              break;
            }
          else
            {
              // If this is the last bin, then we need to return iter.

              if(iter == size() - 1)
                {
                  returnIndex = iter;
                  break;
                }
              else
                lastTestIndex = iter;
            }
        }
      else if(mz < iterBinMz)
        {
          returnIndex = lastTestIndex;
          break;
        }
      else
        {
          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "This line should have never been reached"
                   << QString::asprintf(
                        "mz=%.5f \n iterBinMz=%.5f \n", mz, iterBinMz);

          exit(1);
        }
    }

  // Due to binning, we might be here if mz was greater than the last mz of
  // the spectrum. In this case we just return the last spectrum index.

  if(returnIndex == -1)
    {
      returnIndex = size() - 1;

      // qDebug() << __FILE__ << __LINE__
      //<< "mz: " << QString("%1").arg(mz, 0, 'f', 10)
      //<< "obligatory bin index (the last bin index):" << returnIndex
      //<< "with bin's mz:" << QString("%1").arg(at(returnIndex)->m_key, 0,
      //'f',
      // 10)
      //<< "bin count: size():" << size()
      //<< "last bin's mz:" << QString("%1").arg(at(size() - 1)->m_key, 0,
      //'f',
      // 10);
    }

  m_lastBinIdx = returnIndex;

  // qDebug() << __FILE__ << __LINE__
  //<< "mz: " << QString("%1").arg(mz, 0, 'f', 10)
  //<< "found bin index:" << returnIndex
  //<< "with bin's mz:" << QString("%1").arg(at(returnIndex)->m_key, 0, 'f',
  // 10)
  //<< "bin count: size():" << size()
  //<< "last bin's mz:" << QString("%1").arg(at(size() - 1)->m_key, 0, 'f',
  // 10);

  return returnIndex;
}


void
MassSpectrum::cancelOperation()
{
  m_isOperationCancelled = true;
}

} // namespace msXpSlibmass
