/* 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 <qmath.h>
#include <omp.h>

#include <QDebug>
#include <QTimer>
#include <QThread>

#include <minexpert/nongui/MassDataIntegrator.hpp>
#include <libmass/MassSpectrum.hpp>
#include <minexpert/nongui/History.hpp>

#include <minexpert/nongui/MassSpecDataFileLoaderPwiz.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderDx.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderMs1.hpp>
#include <minexpert/nongui/MassSpecDataFileLoaderSqlite3.hpp>
#include <minexpert/nongui/SavGolFilter.hpp>


namespace msXpSmineXpert
{


//! Construct an uninitialized MassDataIntegrator instance.
/*!

*/
MassDataIntegrator::MassDataIntegrator()
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "Allocating new integrator:" << this;
}


//! Construct an initialized MassDataIntegrator instance.
/*!

  \param massSpecDataSet MassSpecDataSet instance holding the mass data on
  which to perform the integrations.

*/
MassDataIntegrator::MassDataIntegrator(const MassSpecDataSet *massSpecDataSet)
  : mp_massSpecDataSet{massSpecDataSet}
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Allocating new integrator:" << this
  //<< "with mp_massSpecDataSet{massSpecDataSet}:" << mp_massSpecDataSet;
}


//! Construct an initialized MassDataIntegrator instance.
/*!

  \param massSpecDataSet MassSpecDataSet instance holding the mass data on
  which to perform the integrations.

  \param history History instance where the various integration steps
  undergone or to undergo by this MassDataIntegrator integration are recorded.

*/
MassDataIntegrator::MassDataIntegrator(const MassSpecDataSet *massSpecDataSet,
                                       const History &history)
  : mp_massSpecDataSet{massSpecDataSet}, m_history{history}
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Allocating new integrator:" << this
  //<< "with mp_massSpecDataSet{massSpecDataSet}:" << mp_massSpecDataSet
  //<< "and history:" << m_history.asText();
}


//! Destroy \c this MassDataIntegrator instance.
MassDataIntegrator::~MassDataIntegrator()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
}

void
MassDataIntegrator::setMassSpecDataSet(const MassSpecDataSet *massSpecDataSet)
{
  if(massSpecDataSet == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Pointer cannot be nullptr."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  mp_massSpecDataSet = massSpecDataSet;
}


const MassSpecDataSet *
MassDataIntegrator::massSpecDataSet() const
{
  return mp_massSpecDataSet;
}


//! Return a const reference to \c m_keyVector.
const QVector<double> &
MassDataIntegrator::keyVector() const
{
  return m_keyVector;
}


//! Return a const reference to \c m_valVector.
const QVector<double> &
MassDataIntegrator::valVector() const
{
  return m_valVector;
}


void
MassDataIntegrator::setHistory(const History &history)
{
  m_history = history;
}


//! Return a copy of \c m_history
History
MassDataIntegrator::history() const
{
  return m_history;
}


const QMap<double, double> &
MassDataIntegrator::map()
{
  return m_map;
}


const QHash<double, msXpSlibmass::MassSpectrum *> &
MassDataIntegrator::doubleMassSpecHash()
{
  return m_doubleMassSpecHash;
}


//! Compute the total ion current intensity value according to \c m_history.
double
MassDataIntegrator::ticIntensity() const
{
  return m_ticIntensity;
}


//! Perform the integration according to \c m_history.
bool
MassDataIntegrator::integrate()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< " - current thread id:" << QThread::currentThreadId();


  // Very complex process here.
  //
  // We need to get the most recent history item from m_history.

  const HistoryItem *newestHistoryItem = m_history.newestHistoryItem();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "Newest history item:" << newestHistoryItem->asText();

  // Now we need to know what is the integration type (or what are the
  // integration types) required in that history item.

  QList<int> integrationTypeList = newestHistoryItem->integrationTypes();

  if(integrationTypeList.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Cannot be that the history item  has not a single integration type."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // The list might contain more than one integration range, but whatever the
  // number of these ranges, the IntegrationType needs to point towards the
  // same kind of data. For example, from a color map plot widget, we can
  // integrate to MZ, in which case there will be two IntegrationType
  // elements, one from DT to MZ and the other from MZ to MZ. Both must point
  // to the same MZ, otherwise that is an error. So, this means that we can
  // just check one of the IntegrationType list elements for an ANY_TO_XXXX
  // value.

  int integrationType = integrationTypeList.first();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "size:" << HistoryItem::m_integrationTypeDescriptionMap.size();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "integration type:" << integrationType
  //<< "string:" <<
  //HistoryItem::m_integrationTypeDescriptionMap.value(integrationType)->brief;

  if(integrationType & ANY_TO_RT)
    return integrateToRt();
  else if(integrationType & ANY_TO_MZ)
    return integrateToMz();
  else if(integrationType & ANY_TO_DT)
    return integrateToDt();
  else if(integrationType & ANY_TO_TIC_INT)
    return integrateToTicIntensity();
  else
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "There must be an integration type for this integration."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

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

  return false;
}


//! Integrate to the TIC (total ion current chromatogram according to \c
//! m_history.
/*!

  When mass spectrometry data are loaded from a mass data file, one of the
  first computation that is performed is the production of a TIC chromatogram
  representing the mass data as a function of the acquisition time (retention
  time). This is what this function does.

  \param massSpecDataSet pointer to the MassSpecDataSet instance where the
  data were initially stored during the data file loading process.

*/
void
MassDataIntegrator::initialTic()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "() - enter"
  //<< "with this:" << this << " - current thread id:" <<
  //QThread::currentThreadId();

  if(mp_massSpecDataSet->isStreamed())
    return streamInitialTic();

  m_keyVector.clear();
  m_valVector.clear();

  // The data are fully loaded in RAM and thus we use them to calculate
  // the TIC chromatogram now. However, the history does not contain any
  // item because this function is called right after having loaded data
  // from disk. So we first need to create a history item and append it to
  // m_history.

  // To craft the history item, we need the rt range:

  const msXpSlibmass::MsMultiHash &rtHash = mp_massSpecDataSet->rtHash();

  QList<double> rtList = rtHash.uniqueKeys();
  qSort(rtList.begin(), rtList.end());

  // We now have access to the smallest/greatest retention time values.

  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::FILE_TO_RT, rtList.first(), rtList.last());
  m_history.appendHistoryItem(histItem);

  if(!integrateToRt())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Failed to calculate the initial TIC chromatogram."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Be careful that integrateToRt() emits resultsReadySignal().
  // emit resultsReadySignal();
}


void
MassDataIntegrator::streamInitialTic()
{

  //  The data are loaded in a streamed manner, that is the whole set of
  //  mass spectra from the file is not stored in RAM. While loading the
  //  data, the loader has dealt with TIC data and crafted a TIC
  //  chromatogram in the massSpecDataSet.

  m_keyVector.clear();

  // If the file was opened in the streaming mode, then, by definition,
  // the TIC chromatogram will have been calculated upon load and is
  // available in the massSpecDataSet. All we have to do is to plot these
  // data.

  m_keyVector =
    QVector<double>::fromList(mp_massSpecDataSet->ticChromMap().keys());
  m_valVector =
    QVector<double>::fromList(mp_massSpecDataSet->ticChromMap().values());

  // We now have access to the smallest/greatest retention time values.

  HistoryItem *histItem = new HistoryItem;
  histItem->newIntegrationRange(
    IntegrationType::FILE_TO_RT, m_keyVector.first(), m_keyVector.last());
  m_history.appendHistoryItem(histItem);

  emit resultsReadySignal();
}


void
MassDataIntegrator::fillInDtMzHash()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // We'll need this hash for storing the correspondence between any given dt
  // value and the corresponding combination spectrum obtained by combining
  // together all the spectra acquired for any given dt value.
  m_doubleMassSpecHash.clear();

  int parsedDtValues = 0;

  m_dtValueCount = 0;
  m_mzCells      = 0;
  m_firstKey     = 0;
  m_lastKey      = 0;
  m_firstVal     = 0;
  m_lastVal      = 0;

  if(mp_massSpecDataSet->isStreamed())
    return streamFillInDtMzHash();

  // The data are fully loaded in RAM and thus we use them to calculate
  // the color map.

  // First off, check if we have drift time data, because if not, then we need
  // not do anything here.

  // Make a list of all the dt values, we'll need that list in the same way,
  // either with streamed or full data.

  QList<double> dtList = mp_massSpecDataSet->dtHash().uniqueKeys();

  qSort(dtList.begin(), dtList.end());

  int dtListSize = dtList.size();

  if(dtListSize == 0)
    {
      // This is not intrinsically an error. It could be simply that the data
      // are not for a mobility mass spectrometry experiment.

      m_errorCode = 0;

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "The list of drift time values is empty, returning.";

      return;
    }

  // qDebug() << __FILE__ << __LINE__
  //<< "Number of drift time values:" << dtListSize
  //<< "from" << dtList.first() << "to" << dtList.last();

  int mzCellNumber = 0;
  double minimumMz = 1000000000;
  double maximumMz = 0;

  // We want to declare/define this variable here because we'll need it
  // later event if the loop is interrupted. This value is essential for
  // the proper dimensioning of the color map (see below).

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = dtListSize;
  emit updateFeedbackSignal("Color map calculation: ",
                            -1,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  // For each dt value, we'll extract from the mass spec data set all the
  // mass spectra that were acquired at that drift time. We'll combine all
  // these spectra into a single mass spectrum that will remain associated
  // to the specific dt value thanks to the m_doubleMassSpecHash declared above.

  for(parsedDtValues = 0; parsedDtValues < dtListSize; ++parsedDtValues)
    {
      if(m_isOperationCancelled)
        {

          // parsedDtValues contains the number (not the index) of the
          // actually parsed dt values at this round not counting this round.

          break;
        }

      double dt = dtList.at(parsedDtValues);

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "iterating in drift time value:" << dt ;

      // Since were had loaded the full set of data from file,
      // massSpecDataSet->m_dtHash contains all the (dt,spectra) pairs.
      // Only get the list of spectra for the current dt.

      QList<msXpSlibmass::MassSpectrum *> massSpectra =
        mp_massSpecDataSet->dtHash().values(dt);

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "The list of mass spectra for dt:" << dt << "is:" <<
      //massSpectra.size();

      msXpSlibmass::MassSpectrum *combinedMassSpectrum =
        new msXpSlibmass::MassSpectrum;
      // We compute "rough" spectra here, with type m/z resolution.
      combinedMassSpectrum->rdecimalPlaces() = 0;
      // We need to specify that we do not want any binning here.
      combinedMassSpectrum->setBinningType(
        msXpS::BinningType::BINNING_TYPE_NONE);

      combinedMassSpectrum->combine(massSpectra);

      int massSpectrumSize = combinedMassSpectrum->size();

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "after mass spectrum combination, new combine mass spectrum size:"
      //<< massSpectrumSize;

      // Update the number of m/z cells, that is of m/z bins
      if(massSpectrumSize > mzCellNumber)
        {
          mzCellNumber = massSpectrumSize;
        }

      if(combinedMassSpectrum->first()->key() < minimumMz)
        {
          minimumMz = combinedMassSpectrum->first()->key();
        }

      if(combinedMassSpectrum->last()->key() > maximumMz)
        {
          maximumMz = combinedMassSpectrum->last()->key();
        }

      // Now put the dt,spectrum pair in the hash that we'll later use to
      // fill-in the colormap.
      m_doubleMassSpecHash.insert(dt, combinedMassSpectrum);

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "The dtMsHash has " << m_doubleMassSpecHash.size() << "items";

      emit updateFeedbackSignal(QString("dt: %1").arg(dt, 0, 'f', 6),
                                parsedDtValues,
                                true /* setVisible */,
                                LogType::LOG_TO_STATUS_BAR,
                                m_progressFeedbackStartValue,
                                m_progressFeedbackEndValue);

      // qDebug() << __FILE__ << __LINE__
      //<< "Inserted dt value:" << dt << "with spectrum of" <<
      // combinedMassSpectrum->size() << "peaks";
    }
  // End of
  // for(; parsedDtValues < dtListSize; ++parsedDtValues)

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "size of the hash:" << m_doubleMassSpecHash.size();

  // We may have gotten here because the operation was cancelled. In any case,
  // reset the boolean value.
  m_isOperationCancelled = false;

  // Set to the parameter slots the proper values.

  m_dtValueCount = parsedDtValues;
  m_mzCells      = mzCellNumber;

  m_firstKey = dtList.first();
  m_lastKey  = dtList.last();

  m_firstVal = minimumMz;
  m_lastVal  = maximumMz;

  // At this point we have finished filling-in the dt/mass spectrum map.
  // Return and let the color map plot widget do the laying in of the color
  // map cells.

  HistoryItem *histItem = new HistoryItem;

  // Because we are documenting the mz = f(dt) colormap, we need two
  // integration ranges: the MZ range and the DT range.

  histItem->newIntegrationRange(
    IntegrationType::FILE_TO_DT, m_firstKey, m_lastKey);

  histItem->newIntegrationRange(
    IntegrationType::FILE_TO_MZ, m_firstVal, m_lastVal);

  m_history.appendHistoryItem(histItem);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The dtMzHash has " << m_doubleMassSpecHash.size() << "items."
  //<< "That is (in/con)firmed with parsed dt values:" << parsedDtValues;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"Going to emit resultsReadySignal" ;

  emit resultsReadySignal();
}


void
MassDataIntegrator::streamFillInDtMzHash()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // The data were loaded in a streamed manner, that is the whole set of
  // mass spectra from the file is NOT stored in RAM. While loading the
  // data, the loader has dealt with the color map data and crafted data
  // usable for creating the map.

  // First off, check if we have drift time data, because if not, then we need
  // not do anything here.

  // Make a list of all the dt values, we'll need that list in the same way,
  // either with streamed or full data.
  QList<double> dtList = mp_massSpecDataSet->dtHash().uniqueKeys();
  qSort(dtList.begin(), dtList.end());

  int dtListSize = dtList.size();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The list of drift time values has " << dtListSize << "items";

  if(dtListSize == 0)
    {
      // This is not intrinsically an error. It could be simply that the data
      // are not for a mobility mass spectrometry experiment.
      m_dtValueCount = 0;

      m_errorCode = 0;
      return;
    }

  m_dtValueCount = dtListSize;

  // Also, since we are in streamed mode, the data were already crunched
  // on file load. Get a local handle by assignment (not deep copy).

  m_doubleMassSpecHash = mp_massSpecDataSet->dtHash();

  // qDebug() << "size of the hash:" << m_doubleMassSpecHash.size();

  // Fill in the slots with data that were computed real time while
  // loading the data in streamed mode.
  m_mzCells = mp_massSpecDataSet->mzCellCount();

  m_firstKey = dtList.first();
  m_lastKey  = dtList.last();

  m_firstVal = mp_massSpecDataSet->mzRangeStart();
  m_lastVal  = mp_massSpecDataSet->mzRangeEnd();

  // At this point we have finished filling-in the dt/mass spectrum map.
  // Return and let the color map plot widget do the laying in of the color
  // map cells.

  HistoryItem *histItem = new HistoryItem;

  // Because we are documenting the mz = f(dt) colormap, we need two
  // integration ranges: the MZ range and the DT range.

  histItem->newIntegrationRange(
    IntegrationType::FILE_TO_DT, m_firstKey, m_lastKey);

  histItem->newIntegrationRange(
    IntegrationType::FILE_TO_MZ, m_firstVal, m_lastVal);

  m_history.appendHistoryItem(histItem);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The dtMsHash has " << m_doubleMassSpecHash.size() << "items."
  //<< "That is (in/con)firmed with parsed dt values:" << m_dtValueCount;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"Going to emit resultsReadySignal" ;

  emit resultsReadySignal();

  return;
}


//! Integrate to a XIC (extracted ion chromatogram) according to \c m_history.
bool
MassDataIntegrator::integrateToRt()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"executing from thread id:" << QThread::currentThreadId();

  m_keyVector.clear();
  if(mp_massSpecDataSet->isStreamed())
    return streamIntegrateToRt();

  m_keyVector.clear();
  m_valVector.clear();

  // Sanity check

  const HistoryItem *newestHistoryItem = m_history.newestHistoryItem();
  QList<int> integrationTypes          = newestHistoryItem->integrationTypes();
  if(integrationTypes.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Cannot be that the history item  has not a single integration type."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int integrationType = integrationTypes.first();

  if(!(integrationType & IntegrationType::ANY_TO_RT))
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Prepare some variables.

  bool rtIntegration = false;
  double rtStart     = qSNaN();
  double rtEnd       = qSNaN();

  bool mzIntegration = false;
  double mzStart     = qSNaN();
  double mzEnd       = qSNaN();

  bool dtIntegration = false;
  double dtStart     = qSNaN();
  double dtEnd       = qSNaN();

  rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
  mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);
  dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
  // qDebug("rtIntegration: [%.6f-%.6f] \n"
  //"mzIntegratin: [%.6f-%.6f] \n"
  //"dtIntegration: [%.6f-%.6f] \n",
  // rtStart, rtEnd,
  // mzStart, mzEnd,
  // dtStart, dtEnd);

  // At this point we should have qualified fully the integration ranges for
  // the three dimensions: RT, MZ and DT.

  const msXpSlibmass::MsMultiHash &rtHash = mp_massSpecDataSet->rtHash();

  QList<double> rtList;

  if(rtIntegration)
    rtHash.rangedKeys(rtList, rtStart, rtEnd);
  else
    rtList = rtHash.uniqueKeys();

  qSort(rtList.begin(), rtList.end());

  int rtListSize = rtList.size();

  QString msg("Integrating to XIC");

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = rtListSize;
  emit updateFeedbackSignal(msg,
                            -1,
                            true /* setVisible */,
                            LogType::LOG_TO_BOTH,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  QString integrationDetails = "Integrating to XIC\n\tIntegration details:\n";

  if(rtIntegration)
    integrationDetails += QString("RT range [%1-%2]\n")
                            .arg(rtStart, 0, 'f', 6)
                            .arg(rtEnd, 0, 'f', 6);
  if(mzIntegration)
    integrationDetails += QString("MZ range [%1-%2]\n")
                            .arg(mzStart, 0, 'f', 6)
                            .arg(mzEnd, 0, 'f', 6);
  if(dtIntegration)
    integrationDetails += QString("DT range [%1-%2]\n")
                            .arg(dtStart, 0, 'f', 6)
                            .arg(dtEnd, 0, 'f', 6);

  emit updateFeedbackSignal(integrationDetails, -1, LogType::LOG_TO_CONSOLE);

  QMap<double, double> rtTicMap;

  int iterationCount = 0;

#pragma omp parallel for
  for(int iter = 0; iter < rtListSize; ++iter)
    {

      if(m_isOperationCancelled)
        continue;

      double rt = rtList.at(iter);

      QList<msXpSlibmass::MassSpectrum *> rtMassSpectra(rtHash.values(rt));

      if(dtIntegration)
        {
          for(int jter = 0; jter < rtMassSpectra.size(); ++jter)
            {
              msXpSlibmass::MassSpectrum *spectrum = rtMassSpectra.at(jter);

              if(spectrum->dt() < dtStart || spectrum->dt() > dtEnd)
                {
                  rtMassSpectra.removeAt(jter);
                  --jter;
                  continue;
                }
            }
        }

      for(int kter = 0; kter < rtMassSpectra.size(); ++kter)
        {
          double newTic = 0.0;

          if(mzIntegration)
            newTic = rtMassSpectra.at(kter)->valSum(mzStart, mzEnd);
          else
            newTic = rtMassSpectra.at(kter)->valSum();

          // Had another spectrum by the same rt already been iterated in?

          double oldTic = rtTicMap.value(rt, qSNaN());

          if(!qIsNaN(oldTic))
            {

              // A rt by the same value alreay existed, update the tic
              // value and insert it, that will erase the old value,
              // because the map is NOT a multimap.

              newTic += oldTic;
            }

#pragma omp critical
          rtTicMap.insert(rt, newTic);
        }
        // End of for(int kter = 0; kter < massSpectra.size(); ++kter)
        // End of
        // #pragma omp parallel for

#pragma omp critical
      {
        ++iterationCount;

        // The feedback was setup by the master thread, so we can only
        // update it(actually emitting the signal) from that same master
        // thread:

        if(omp_get_thread_num() == 0)
          emit updateFeedbackSignal(msg,
                                    iterationCount,
                                    true /* setVisible */,
                                    LogType::LOG_TO_STATUS_BAR,
                                    m_progressFeedbackStartValue,
                                    m_progressFeedbackEndValue);
      }
    }
  // End of for(int iter = 0; iter < rtList.size(); ++iter)

  // The two commands below work because QMap is ordered along the key and
  // returns the keys in ascending order and the values in ascending order
  // *of the keys*.

  m_keyVector = QVector<double>::fromList(rtTicMap.keys());
  m_valVector = QVector<double>::fromList(rtTicMap.values());

  // Sanity check
  if(m_keyVector.size() != m_valVector.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  if(m_keyVector.size() == 1)
    {

      // It might be possible that the key/val vectors have only one value
      // if the user loaded an asc|txt|xy file where the data are only in
      // the form of two columns of numbers. In this case the single point
      // present in the vectors is at key=0 and val the summed value of all
      // the numbers in the second column of the data. We should show
      // meaninful data.

      // qDebug() << __FILE__ << __LINE__ << "Need to fake some data";

      const msXpSlibmass::MassSpectrum *massSpectrum =
        mp_massSpecDataSet->massSpectrum(0);
      double ticIntensity = massSpectrum->valSum();

      m_keyVector.append(1.0);
      m_valVector.append(ticIntensity);
    }

  m_progressFeedbackStartValue = -1;
  m_progressFeedbackEndValue   = -1;
  emit updateFeedbackSignal("",
                            -1,
                            true /* setVisible */,
                            LogType::LOG_TO_STATUS_BAR,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  // We may have gotten here because the operation was cancelled. In any case,
  // reset the boolean value.

  m_isOperationCancelled = false;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "going to emit resultsReadySignal with this:" << this;

  emit resultsReadySignal();

  return true;
}


//! Integrate (streamed mode) to a XIC (extracted ion chromatogram) according to
//! \c m_history.
bool
MassDataIntegrator::streamIntegrateToRt()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"executing from thread id:" << QThread::currentThreadId();

  m_keyVector.clear();
  m_valVector.clear();

  // Depending on the format of the data file from which the data were
  // loaded in stream mode, it will be possible or not to perform the
  // required integration.

  if(mp_massSpecDataSet->fileFormat() ==
     MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_SQLITE3)
    {
      MassSpecDataFileLoaderSqlite3 loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot

      connect(&loader,
              &MassSpecDataFileLoaderSqlite3::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderSqlite3::cancelOperation);

      int res = loader.streamedIntegration(
        m_history, IntegrationType::ANY_TO_RT, &m_keyVector, &m_valVector);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZML)
    {
      MassSpecDataFileLoaderPwiz loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot

      connect(&loader,
              &MassSpecDataFileLoaderPwiz::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderPwiz::cancelOperation);

      int res = loader.streamedIntegration(
        m_history, IntegrationType::ANY_TO_RT, &m_keyVector, &m_valVector);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else
    {
      emit resultsReadySignal();
      return false;
    }

  // Sanity check:
  if(m_keyVector.size() != m_valVector.size())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(m_keyVector.size() == 1)
    {

      // It might be possible that the key/val vectors have only one value if
      // the user loaded an asc|txt|xy file where the data are only in the
      // form of two columns of numbers. In this case the single point present
      // in the vectors is at key=0 and val the summed value of all the
      // numbers in the second column of the data. We should show meaninful
      // data.

      // qDebug() << __FILE__ << __LINE__ << "Need to fake some data";

      const msXpSlibmass::MassSpectrum *massSpectrum =
        mp_massSpecDataSet->massSpectrum(0);
      double ticIntensity = massSpectrum->valSum();

      m_keyVector.append(1.0);
      m_valVector.append(ticIntensity);
    }

  emit resultsReadySignal();
  return true;
}


//! Integrate to a mass spectrum according to \c m_history.
bool
MassDataIntegrator::integrateToMz()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"executing from thread id:" << QThread::currentThreadId();

  m_keyVector.clear();
  if(mp_massSpecDataSet->isStreamed())
    return streamIntegrateToMz();

  m_valVector.clear();

  // Sanity check

  const HistoryItem *newestHistoryItem = m_history.newestHistoryItem();
  QList<int> integrationTypes          = newestHistoryItem->integrationTypes();
  if(integrationTypes.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Cannot be that the history item  has not a single integration type."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int integrationType = integrationTypes.first();

  if(!(integrationType & IntegrationType::ANY_TO_MZ))
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Prepare some variables.

  bool rtIntegration = false;
  double rtStart     = qSNaN();
  double rtEnd       = qSNaN();

  bool mzIntegration = false;
  double mzStart     = qSNaN();
  double mzEnd       = qSNaN();

  bool dtIntegration = false;
  double dtStart     = qSNaN();
  double dtEnd       = qSNaN();

  rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
  mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);
  dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);

  // qDebug("rtIntegration: [%.6f-%.6f] \n"
  //"mzIntegratin: [%.6f-%.6f] \n"
  //"dtIntegration: [%.6f-%.6f] \n",
  // rtStart, rtEnd,
  // mzStart, mzEnd,
  // dtStart, dtEnd);

  // At this point we should have qualified fully the integration ranges for
  // the three dimensions: RT, MZ and DT.

  const msXpSlibmass::MsMultiHash &rtHash = mp_massSpecDataSet->rtHash();

  QList<double> rtList;

  if(rtIntegration)
    rtHash.rangedKeys(rtList, rtStart, rtEnd);
  else
    rtList = rtHash.uniqueKeys();

  qSort(rtList.begin(), rtList.end());

  int rtListSize = rtList.size();

  m_feedbackMsgSkeleton = "Integrating to MZ";

  QString integrationDetails = "Integrating to MZ\n\tIntegration details:\n";

  if(rtIntegration)
    integrationDetails += QString("RT range [%1-%2]\n")
                            .arg(rtStart, 0, 'f', 6)
                            .arg(rtEnd, 0, 'f', 6);
  if(mzIntegration)
    integrationDetails += QString("MZ range [%1-%2]\n")
                            .arg(mzStart, 0, 'f', 6)
                            .arg(mzEnd, 0, 'f', 6);
  if(dtIntegration)
    integrationDetails += QString("DT range [%1-%2]\n")
                            .arg(dtStart, 0, 'f', 6)
                            .arg(dtEnd, 0, 'f', 6);

  QList<msXpSlibmass::MassSpectrum *> massSpectrumList;
  msXpSlibmass::MassSpectrum combinedMassSpectrum;

  // When integrating to m/z, we must take into account the bin
  // parameters, the nominal value and the type and other parameters.

  MzIntegrationParams mzIntegrationParams = m_history.mzIntegrationParams();
  combinedMassSpectrum.setBinningType(mzIntegrationParams.m_binningType);
  combinedMassSpectrum.setBinSize(mzIntegrationParams.m_binSize);
  combinedMassSpectrum.setBinSizeType(mzIntegrationParams.m_binSizeType);
  combinedMassSpectrum.applyMzShift(mzIntegrationParams.m_applyMzShift);
  combinedMassSpectrum.setRemoveZeroValDataPoints(
    mzIntegrationParams.m_removeZeroValDataPoints);

  // And now other data from the mass spec data stats:
  MassSpecDataStats statistics = mp_massSpecDataSet->statistics();

  combinedMassSpectrum.setMinMz(statistics.m_minMz);
  combinedMassSpectrum.setMaxMz(statistics.m_maxMz);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "The retention time list has " << rtListSize << "time values";

  for(int iter = 0; iter < rtListSize; ++iter)
    {

      if(m_isOperationCancelled)
        continue;

      double rt = rtList.at(iter);

      // qDebug() << __FILE__ << __LINE__ << "integrating for rt:" << rt;

      // Get a list of all the mass spectra having the same key value.
      // For MS1 spectra, there is only one spectrum per rt for non-mobility
      // mass spectrometry, of course.

      QList<msXpSlibmass::MassSpectrum *> rtMassSpectra(rtHash.values(rt));

      if(dtIntegration)
        {
          for(int jter = 0; jter < rtMassSpectra.size(); ++jter)
            {
              msXpSlibmass::MassSpectrum *spectrum = rtMassSpectra.at(jter);

              if(spectrum->dt() < dtStart || spectrum->dt() > dtEnd)
                {
                  rtMassSpectra.removeAt(jter);
                  --jter;
                  continue;
                }
            }
        }

      // At this point we have the accounted mass spectra remaining in the
      // rtMassSpectra list. Append these spectra to the main list.

      massSpectrumList.append(rtMassSpectra);
    }

  // Now perform the integration.

  int specCount = massSpectrumList.size();

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = specCount;
  emit updateFeedbackSignal(m_feedbackMsgSkeleton,
                            -1,
                            true /* setVisible */,
                            LogType::LOG_TO_STATUS_BAR,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  emit updateFeedbackSignal(integrationDetails, -1, LogType::LOG_TO_CONSOLE);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Size of the list of all the spectra of the integration to MZ : "
  //<< massSpectrumList.size();

  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.";

      if(mzIntegration)
        combinedMassSpectrum.combine(massSpectrumList, mzStart, mzEnd);
      else
        combinedMassSpectrum.combine(massSpectrumList);
    }
  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<msXpSlibmass::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 the one that was
          // configured above for the various mz integration params, so that it
          // too benefits from the binning configuration data as determined by
          // the call to setupBinnability above.

          msXpSlibmass::MassSpectrum *newMassSpectrum =
            new msXpSlibmass::MassSpectrum(combinedMassSpectrum);

          connect(newMassSpectrum,
                  &msXpSlibmass::MassSpectrum::counterSignal,
                  this,
                  &MassDataIntegrator::incrementCounter);

          connect(this,
                  &MassDataIntegrator::cancelOperationSignal,
                  newMassSpectrum,
                  &msXpSlibmass::MassSpectrum::cancelOperation);

          combinedMassSpectrumList.append(newMassSpectrum);

          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
      for(int iter = 0; iter < maxThreads; ++iter)
        {

          msXpSlibmass::MassSpectrum *p_combinedMassSpectrum =
            combinedMassSpectrumList.at(iter);
          QPair<int, int> *p_indexRange = indexRangeList.at(iter);

          p_combinedMassSpectrum->combineSlice(massSpectrumList,
                                               p_indexRange->first,
                                               p_indexRange->second,
                                               mzStart,
                                               mzEnd);
        }
      // 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 combined mass spectra. Take
      // advantage of the loop to also free each combined mass spectrum in
      // turn.
      //
      // At first, copy one of the mass spectra in the list right into
      // combinedMassSpectrum, so that the bins are copied in there if there
      // were bins. Remember, a binned combine of a spectrum cannot be
      // performed by an empty mass spectrum.

      combinedMassSpectrum = *combinedMassSpectrumList.takeFirst();
      delete combinedMassSpectrumList.takeFirst();

      // And now combine all the remaining spectra.
      while(combinedMassSpectrumList.size())
        {
          msXpSlibmass::MassSpectrum *massSpectrum =
            combinedMassSpectrumList.takeFirst();

          // No need to check mz range because we did that already above.
          combinedMassSpectrum.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(combinedMassSpectrum.isRemoveZeroValDataPoints())
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Now removing all 0-m/z data points.";

      combinedMassSpectrum.removeZeroValDataPoints();
    }

  m_keyVector = QVector<double>::fromList(combinedMassSpectrum.keyList());
  m_valVector = QVector<double>::fromList(combinedMassSpectrum.valList());

  if(mzIntegrationParams.m_applySavGolFilter)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "savGol is to be computed";

      SavGolFilter savGolFilter(mzIntegrationParams.m_savGolParams);

      savGolFilter.initializeData(m_keyVector, m_valVector);

      savGolFilter.filter();

      QVector<double> filtered;
      savGolFilter.filteredData(filtered);

      m_valVector.clear();
      m_valVector.append(filtered);
    }

  // Sanity check
  if(m_keyVector.size() != m_valVector.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "data:" << m_keyVector.size() << m_valVector.size();

  // We may have gotten here because the operation was cancelled. In any case,
  // reset the boolean value.

  m_isOperationCancelled = false;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "going to emit resultsReadySignal";

  emit resultsReadySignal();

  return true;
}


//! Integrate (streamed mode) to a mass spectrum according to \c m_history.
bool
MassDataIntegrator::streamIntegrateToMz()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"executing from thread id:" << QThread::currentThreadId();

  m_keyVector.clear();
  m_valVector.clear();

  // Depending on the format of the data file from which the data were
  // loaded in stream mode, it will be possible or not to perform the
  // required integration.

  if(mp_massSpecDataSet->fileFormat() ==
     MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_SQLITE3)
    {
      MassSpecDataFileLoaderSqlite3 loader(mp_massSpecDataSet->fileName());

      connect(&loader,
              &MassSpecDataFileLoaderSqlite3::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderSqlite3::cancelOperation);

      // We are going to load data in streamed mode. For the specific case of
      // m/z integration, and potentially using binning, we need to have a
      // statistics knowledge of the initial data.

      loader.setMassSpecDataStats(mp_massSpecDataSet->statistics());

      int res = loader.streamedIntegration(
        m_history, IntegrationType::ANY_TO_MZ, &m_keyVector, &m_valVector);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZML)
    {
      MassSpecDataFileLoaderPwiz loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot

      connect(&loader,
              &MassSpecDataFileLoaderPwiz::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderPwiz::cancelOperation);

      // We are going to load data in streamed mode. For the specific case of
      // m/z integration, and potentially using binning, we need to have a
      // statistics knowledge of the initial data.

      loader.setMassSpecDataStats(mp_massSpecDataSet->statistics());

      int res = loader.streamedIntegration(
        m_history, IntegrationType::ANY_TO_MZ, &m_keyVector, &m_valVector);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_DX)
    {
      MassSpecDataFileLoaderDx loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderDx::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);


      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderDx::cancelOperation);

      int res = loader.streamedIntegration(
        m_history, &m_keyVector, &m_valVector, Q_NULLPTR /* intensity */);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MS1)
    {
      MassSpecDataFileLoaderMs1 loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderMs1::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderMs1::cancelOperation);

      int res = loader.streamedIntegration(
        m_history, &m_keyVector, &m_valVector, Q_NULLPTR /* intensity */);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else
    {
      emit resultsReadySignal();
      return false;
    }

  emit resultsReadySignal();
  return true;
}


//! Integrate to a drift spectrum according to \c m_history.
bool
MassDataIntegrator::integrateToDt()
{

  // Only do something if the data set contains ion mobility data.

  if(!mp_massSpecDataSet->isMobilityExperiment())
    return false;

  if(mp_massSpecDataSet->isStreamed())
    return streamIntegrateToDt();

  m_keyVector.clear();
  m_valVector.clear();

  // Sanity check

  const HistoryItem *newestHistoryItem = m_history.newestHistoryItem();
  QList<int> integrationTypes          = newestHistoryItem->integrationTypes();
  if(integrationTypes.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Cannot be that the history item  has not a single integration type."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int integrationType = integrationTypes.first();

  if(!(integrationType & IntegrationType::ANY_TO_DT))
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Prepare some variables.

  bool rtIntegration = false;
  double rtStart     = qSNaN();
  double rtEnd       = qSNaN();

  bool mzIntegration = false;
  double mzStart     = qSNaN();
  double mzEnd       = qSNaN();

  bool dtIntegration = false;
  double dtStart     = qSNaN();
  double dtEnd       = qSNaN();

  rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
  mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);
  dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);

  // qDebug("rtIntegration: [%.6f-%.6f] \n"
  //"mzIntegration: [%.6f-%.6f] \n"
  //"dtIntegration: [%.6f-%.6f] \n",
  // rtStart, rtEnd,
  // mzStart, mzEnd,
  // dtStart, dtEnd);

  // At this point we should have qualified fully the integration ranges for
  // the three dimensions: RT, MZ and DT.

  const msXpSlibmass::MsMultiHash &rtHash = mp_massSpecDataSet->rtHash();

  QList<double> rtList;
  if(rtIntegration)
    rtHash.rangedKeys(rtList, rtStart, rtEnd);
  else
    rtList = rtHash.uniqueKeys();

  qSort(rtList.begin(), rtList.end());

  int rtListSize = rtList.size();

  m_feedbackMsgSkeleton = "Integrating to DT";

  QString integrationDetails = "Integrating to DT\n\tIntegration details:\n";

  if(rtIntegration)
    integrationDetails += QString("RT range [%1-%2]\n")
                            .arg(rtStart, 0, 'f', 6)
                            .arg(rtEnd, 0, 'f', 6);
  if(mzIntegration)
    integrationDetails += QString("MZ range [%1-%2]\n")
                            .arg(mzStart, 0, 'f', 6)
                            .arg(mzEnd, 0, 'f', 6);
  if(dtIntegration)
    integrationDetails += QString("DT range [%1-%2]\n")
                            .arg(dtStart, 0, 'f', 6)
                            .arg(dtEnd, 0, 'f', 6);


  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = rtListSize;

  emit updateFeedbackSignal(m_feedbackMsgSkeleton,
                            0,
                            true /*setVisible */,
                            LogType::LOG_TO_STATUS_BAR,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  emit updateFeedbackSignal(integrationDetails, -1, LogType::LOG_TO_CONSOLE);

  // For each drift time, we'll want to store the combined total ion
  // current. A map is a good option to increment previously existing values
  // or to just add new values. Then it will be easy to ask for a list of
  // keys (ordered) and of values (ordered according to the order of the
  // keys).

  QMap<double, double> dtTicMap;

  int iterationCount = 0;

  for(int iter = 0; iter < rtListSize; ++iter)
    {

      if(m_isOperationCancelled)
        continue;

      double rt = rtList.at(iter);

      QList<msXpSlibmass::MassSpectrum *> rtMassSpectra(rtHash.values(rt));

      if(dtIntegration)
        {
          for(int jter = 0; jter < rtMassSpectra.size(); ++jter)
            {
              msXpSlibmass::MassSpectrum *spectrum = rtMassSpectra.at(jter);

              if(spectrum->dt() < dtStart || spectrum->dt() > dtEnd)
                {
                  rtMassSpectra.removeAt(jter);
                  --jter;
                }
            }
        }

      for(int kter = 0; kter < rtMassSpectra.size(); ++kter)
        {
          msXpSlibmass::MassSpectrum *spectrum = rtMassSpectra.at(kter);

          double newTic = 0.0;

          if(mzIntegration)
            newTic = spectrum->valSum(mzStart, mzEnd);
          else
            newTic = spectrum->valSum();

          // Had another spectrum by the same dt already been iterated in?

          double oldTic = dtTicMap.value(spectrum->dt(), qSNaN());

          if(!qIsNaN(oldTic))
            {

              // A dt by the same value alreay existed, update the tic value and
              // insert it, that will erase the old value, because the hash is
              // NOT a multihash.

              newTic += oldTic;
            }

          dtTicMap.insert(spectrum->dt(), newTic);
        }

      ++iterationCount;

      emit updateFeedbackSignal(m_feedbackMsgSkeleton,
                                iterationCount,
                                true /* setVisible */,
                                LogType::LOG_TO_STATUS_BAR);
    }
  // End of
  // for(int iter = 0; iter < rtListSize; ++iter)

  m_keyVector = QVector<double>::fromList(dtTicMap.keys());
  m_valVector = QVector<double>::fromList(dtTicMap.values());

  // Sanity check
  if(m_keyVector.size() != m_valVector.size())
    qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);

  emit updateFeedbackSignal(
    "", -1, false /* setVisible */, LogType::LOG_TO_STATUS_BAR);

  // We may have gotten here because the operation was cancelled. In any case,
  // reset the boolean value.

  m_isOperationCancelled = false;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "going to emit resultsReadySignal";

  emit resultsReadySignal();

  return true;
}


//! Integrate (streamed mode) to a drift spectrum according to \c m_history.
bool
MassDataIntegrator::streamIntegrateToDt()
{
  // Depending on the format of the data file from which the data were
  // loaded in stream mode, it will be possible or not to perform the
  // required integration.

  if(mp_massSpecDataSet->fileFormat() ==
     MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_SQLITE3)
    {
      MassSpecDataFileLoaderSqlite3 loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderSqlite3::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderSqlite3::cancelOperation);

      int res = loader.streamedIntegration(
        m_history, IntegrationType::ANY_TO_DT, &m_keyVector, &m_valVector);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZML)
    {
      MassSpecDataFileLoaderPwiz loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderPwiz::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderPwiz::cancelOperation);

      int res = loader.streamedIntegration(
        m_history, IntegrationType::ANY_TO_DT, &m_keyVector, &m_valVector);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else
    {
      emit resultsReadySignal();
      return false;
    }

  emit resultsReadySignal();
  return true;
}


//! Integrate to a single TIC intensity value according to \c m_history.
bool
MassDataIntegrator::integrateToTicIntensity()
{
  if(mp_massSpecDataSet == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The mass spec data set pointer cannot be nullptr here."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  if(mp_massSpecDataSet->isStreamed())
    return streamIntegrateToTicIntensity();

  m_keyVector.clear();
  m_valVector.clear();

  // Sanity check

  const HistoryItem *newestHistoryItem = m_history.newestHistoryItem();
  QList<int> integrationTypes          = newestHistoryItem->integrationTypes();
  if(integrationTypes.isEmpty())
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Cannot be that the history item  has not a single integration type."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  int integrationType = integrationTypes.first();

  if(!(integrationType & IntegrationType::ANY_TO_TIC_INT))
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // Prepare some variables.

  bool mzIntegration = false;
  double mzStart     = qSNaN();
  double mzEnd       = qSNaN();

  bool rtIntegration = false;
  double rtStart     = qSNaN();
  double rtEnd       = qSNaN();

  bool dtIntegration = false;
  double dtStart     = qSNaN();
  double dtEnd       = qSNaN();

  rtIntegration = m_history.innermostRtRange(&rtStart, &rtEnd);
  mzIntegration = m_history.innermostMzRange(&mzStart, &mzEnd);
  dtIntegration = m_history.innermostDtRange(&dtStart, &dtEnd);

  // At this point we should have qualified fully the integration ranges for
  // the three dimensions: RT, MZ and DT.

  const msXpSlibmass::MsMultiHash &rtHash = mp_massSpecDataSet->rtHash();

  QList<double> rtList;

  if(rtIntegration)
    rtHash.rangedKeys(rtList, rtStart, rtEnd);
  else
    rtList = rtHash.uniqueKeys();

  qSort(rtList.begin(), rtList.end());

  int rtListSize = rtList.size();

  QString msg = QString("Integrating to TIC intensity");

  m_progressFeedbackStartValue = 0;
  m_progressFeedbackEndValue   = rtListSize;
  emit updateFeedbackSignal(msg,
                            0,
                            true /* setVisible */,
                            LogType::LOG_TO_STATUS_BAR,
                            m_progressFeedbackStartValue,
                            m_progressFeedbackEndValue);

  QString integrationDetails =
    "Integrating to TIC intensity\n\tIntegration details:\n";

  if(rtIntegration)
    integrationDetails += QString("RT range [%1-%2]\n")
                            .arg(rtStart, 0, 'f', 6)
                            .arg(rtEnd, 0, 'f', 6);
  if(mzIntegration)
    integrationDetails += QString("MZ range [%1-%2]\n")
                            .arg(mzStart, 0, 'f', 6)
                            .arg(mzEnd, 0, 'f', 6);
  if(dtIntegration)
    integrationDetails += QString("DT range [%1-%2]\n")
                            .arg(dtStart, 0, 'f', 6)
                            .arg(dtEnd, 0, 'f', 6);

  emit updateFeedbackSignal(integrationDetails, -1, LogType::LOG_TO_CONSOLE);

  double localIntensity = 0.0;
  int iterationCount    = 0;

#pragma omp parallel for
  for(int iter = 0; iter < rtListSize; ++iter)
    {

      if(m_isOperationCancelled)
        continue;

      double rt = rtList.at(iter);

      QList<msXpSlibmass::MassSpectrum *> rtMassSpectra(rtHash.values(rt));

      if(dtIntegration)
        {
          for(int jter = 0; jter < rtMassSpectra.size(); ++jter)
            {
              msXpSlibmass::MassSpectrum *spectrum = rtMassSpectra.at(jter);

              if(spectrum->dt() < dtStart || spectrum->dt() > dtEnd)
                {
                  rtMassSpectra.removeAt(jter);
                  --jter;
                  continue;
                }
            }
        }

      for(int kter = 0; kter < rtMassSpectra.size(); ++kter)
        {
          double newTic = 0.0;

          if(mzIntegration)
            newTic = rtMassSpectra.at(kter)->valSum(mzStart, mzEnd);
          else
            newTic = rtMassSpectra.at(kter)->valSum();

#pragma omp critical
          localIntensity += newTic;
        }
        // End of for(int kter = 0; kter < massSpectra.size(); ++kter)

#pragma omp critical
      {
        ++iterationCount;

        // The feedback was setup by the master thread, so we can only
        // update it(actually emitting the signal) from that same master
        // thread:

        if(omp_get_thread_num() == 0)
          emit updateFeedbackSignal(msg,
                                    iterationCount,
                                    true /* setVisible */,
                                    LogType::LOG_TO_STATUS_BAR,
                                    m_progressFeedbackStartValue,
                                    m_progressFeedbackEndValue);
      }
    }
  // End of
  // #pragma omp parallel for
  // for(int iter = 0; iter < rtList.size(); ++iter)

  // Finally, update the value passed to the function:
  m_ticIntensity = localIntensity;

  // We may have gotten here because the operation was cancelled. In any
  // case, reset the boolean value.

  m_isOperationCancelled = false;

  emit resultsReadySignal();

  return true;
}


//! Integrate (streamed mode) to a single TIC intensity value according to \c
//! m_history.
bool
MassDataIntegrator::streamIntegrateToTicIntensity()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //"executing from thread id:" << QThread::currentThreadId();

  if(mp_massSpecDataSet == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "The mass spec data set pointer cannot be nullptr here."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  m_keyVector.clear();
  m_valVector.clear();

  // Depending on the format of the data file from which the data were
  // loaded in stream mode, it will be possible or not to perform the
  // required integration.

  if(mp_massSpecDataSet->fileFormat() ==
     MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_SQLITE3)
    {
      MassSpecDataFileLoaderSqlite3 loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderSqlite3::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderSqlite3::cancelOperation);

      int res = loader.streamedTicIntensity(m_history, &m_ticIntensity);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MZML)
    {
      MassSpecDataFileLoaderPwiz loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderPwiz::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderPwiz::cancelOperation);

      int res = loader.streamedTicIntensity(m_history, &m_ticIntensity);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_DX)
    {
      MassSpecDataFileLoaderDx loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderDx::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);


      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderDx::cancelOperation);

      int res = loader.streamedIntegration(m_history,
                                           Q_NULLPTR /* keyVector */,
                                           Q_NULLPTR /* valVector */,
                                           &m_ticIntensity);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else if(mp_massSpecDataSet->fileFormat() ==
          MassSpecDataFileFormat::MASS_SPEC_DATA_FILE_FORMAT_MS1)
    {
      MassSpecDataFileLoaderMs1 loader(mp_massSpecDataSet->fileName());

      // Make sure we connect the feedback signal/slot
      connect(&loader,
              &MassSpecDataFileLoaderMs1::updateFeedbackSignal,
              this,
              &MassDataIntegrator::updateFeedbackRelay);

      connect(this,
              &MassDataIntegrator::cancelOperationSignal,
              &loader,
              &MassSpecDataFileLoaderMs1::cancelOperation);

      int res = loader.streamedIntegration(m_history,
                                           Q_NULLPTR /* keyVector */,
                                           Q_NULLPTR /* valVector */,
                                           &m_ticIntensity);

      if(res == -1)
        qFatal("Fatal error at %s@%d. Program aborted.", __FILE__, __LINE__);
    }
  else
    {
      emit resultsReadySignal();
      return false;
    }

  emit resultsReadySignal();
  return true;
}


//! React to the signal that the user cancelled the calculation. Relay the
//! signal.
void
MassDataIntegrator::cancelOperation()
{
  m_isOperationCancelled = true;

  // Relay that cancel operation signal to the streamed integration
  // functions.

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

  emit cancelOperationSignal();
}


//! Relay the \c updateFeedback signal.
void
MassDataIntegrator::updateFeedbackRelay(QString msg,
                                        int currentValue,
                                        bool setVisible,
                                        int logType,
                                        int startValue,
                                        int endValue)
{
  // qDebug() << "Relaying update feedback signal:" << msg;
  emit updateFeedbackSignal(
    msg, currentValue, setVisible, logType, startValue, endValue);
}


void
MassDataIntegrator::incrementCounter(int count)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "with count param:" << count;

  if(count == -1)
    {
      cancelOperation();
      return;
    }

  m_counter += count;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "new counter value:" << m_counter;

  if(++m_progressCounter > 50)
    {
      emit updateFeedbackSignal(
        m_feedbackMsgSkeleton +
          QString(" - %1 spectra integrated...").arg(m_counter),
        m_counter,
        true /* setVisible */,
        LogType::LOG_TO_STATUS_BAR,
        m_progressFeedbackStartValue,
        m_progressFeedbackEndValue);

      m_progressCounter = 0;

      QCoreApplication::processEvents();
    }
}

} // namespace msXpSmineXpert
