/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright (C) 2009--2020 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes
#include <vector>
#include <cmath>


/////////////////////// OPENMP include
#include <omp.h>

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


/////////////////////// pappsomspp includes
#include <pappsomspp/core/processing/combiners/tracepluscombiner.h>


/////////////////////// Local includes
#include "QualifiedMassSpectrumVectorMassDataIntegrator.hpp"
#include "QualifiedMassSpectrumVectorMassDataIntegratorToMz.hpp"
#include "ProcessingFlow.hpp"
#include "ProcessingStep.hpp"

namespace MsXpS
{
namespace MineXpert
{

// Needed for qRegisterMetaType
QualifiedMassSpectrumVectorMassDataIntegratorToMz::
  QualifiedMassSpectrumVectorMassDataIntegratorToMz(QObject *parent)
  : QualifiedMassSpectrumVectorMassDataIntegrator(parent)
{
}

QualifiedMassSpectrumVectorMassDataIntegratorToMz::
  QualifiedMassSpectrumVectorMassDataIntegratorToMz(
    MsRunDataSetCstSPtr ms_run_data_set_csp,
    const ProcessingFlow &processing_flow,
    const QualifiedMassSpectraVectorSPtr
      &qualified_mass_spectra_to_integrate_sp,
    QObject *parent)
  : QualifiedMassSpectrumVectorMassDataIntegrator(
      ms_run_data_set_csp,
      processing_flow,
      qualified_mass_spectra_to_integrate_sp,
      parent)
{
  mp_processingFlow->setMsRunDataSetCstSPtr(mcsp_msRunDataSet);
}

QualifiedMassSpectrumVectorMassDataIntegratorToMz::
  ~QualifiedMassSpectrumVectorMassDataIntegratorToMz()
{
}

QualifiedMassSpectrumVectorMassDataIntegrator *
QualifiedMassSpectrumVectorMassDataIntegratorToMz::clone(QObject *parent) const
{
  QualifiedMassSpectrumVectorMassDataIntegratorToMz *copy_p =
    new QualifiedMassSpectrumVectorMassDataIntegratorToMz(
      mcsp_msRunDataSet,
      *mp_processingFlow,
      mcsp_qualifiedMassSpectraToIntegrateVector,
      parent);

  copy_p->m_maxThreadUseCount = m_maxThreadUseCount;
  copy_p->m_innermostSteps2D.assign(m_innermostSteps2D.begin(),
                                    m_innermostSteps2D.end());
  copy_p->m_dtRange = m_dtRange;
  copy_p->m_mzRange = m_mzRange;
  copy_p->m_rtRange = m_rtRange;

  return copy_p;
}

void
QualifiedMassSpectrumVectorMassDataIntegratorToMz::integrate()
{
  std::chrono::system_clock::time_point chrono_start_time =
    std::chrono::system_clock::now();

  // Nothing special to do about processing flow info because that must have
  // been set before calling this function.

  // We need to clear the map trace!
  m_mapTrace.clear();

  // Heap-allocated
  pappso::MzIntegrationParams *mz_integration_params_p =
    mp_processingFlow->getSensibleMzIntegrationParams();

  // qDebug().noquote() << "Got sensible mz integration params:"
  // << mz_integration_params_p->toString();

  if(mz_integration_params_p->getBinningType() ==
     pappso::MzIntegrationParams::BinningType::NONE)
    {
      // qDebug() << "Integrating to MZ without binning:"
      //          << mz_integration_params_p->toString();
      integrateToMzNoBinning();
    }
  else if(mz_integration_params_p->getBinningType() ==
          pappso::MzIntegrationParams::BinningType::ARBITRARY)
    {
      qDebug() << "Integrating to MZ with arbitrary binning:"
               << mz_integration_params_p->toString();
      integrateToMzArbitraryBinning();
    }

  delete mz_integration_params_p;

  std::chrono::system_clock::time_point chrono_end_time =
    std::chrono::system_clock::now();

  QString chrono_string = pappso::Utils::chronoIntervalDebugString(
    "Integration to mass spectrum took:", chrono_start_time, chrono_end_time);

  emit logTextToConsoleSignal(chrono_string);

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

  return;
}

bool
QualifiedMassSpectrumVectorMassDataIntegratorToMz::integrateToMzNoBinning()
{
  // qDebug();

  // If there are no data, nothing to do.
  std::size_t mass_spectra_count =
    mcsp_qualifiedMassSpectraToIntegrateVector->size();

  if(!mass_spectra_count)
    {
      // qDebug() << "The qualified mass spectrum vector is empty, nothing to
      // do.";

      emit cancelOperationSignal();
    }

  // qDebug() << "The number of mass spectra to handle:" << mass_spectra_count;

  emit setTaskDescriptionTextSignal(
    "Integrating to a mass spectrum (no binning)");

  // We need a processing flow to work, and, in particular, a processing step
  // from which to find the specifics of the calculation.

  if(!mp_processingFlow->getStepsCstRef().size())
    qFatal() << "The processing flow cannot be empty. Program aborted.";

  // When constructing this integrator, the processing flow passed as parameter
  // was used to gather innermost ranges data. When the steps that match
  // the innermost criterion, these are stored in m_innermostSteps2D. When the
  // steps are non-2D steps, then only the m_xxRange values are updated.

  // If the m_innermostSteps2D vector of ProcessingSteps is non-empty, then,
  // that means that we may have some MZ-related 2D processing steps to account
  // at combination time, because that is precisely the only moment that we can
  // do that work. To be able to provide selection polygon an mass
  // spectrum-specific dt|rt data, we have the SelectionPolygonSpec class. There
  // can be as many such class instances as required to document the integration
  // boundaries rt/mz and dt/mz for example. This is why we need to fill in the
  // vector of SelectionPolygonSpec and then set that to the combiner.

  // If the m_innermostSteps2D vector of ProcessingSteps is non-empty, then,
  // that means that we may have some MZ-related 2D processing steps to account
  // at combination time, because that is precisely the only moment that we can
  // do that work.

  std::vector<pappso::IntegrationScopeSpec> integration_scope_specs;

  fillInIntegrationScopeSpecs(integration_scope_specs);

  // Prepare the template for all the combiners we'll use in the different
  // threads.

  // Heap-allocated
  pappso::MzIntegrationParams *mz_integration_params_p =
    mp_processingFlow->getSensibleMzIntegrationParams();

  int decimal_places = mz_integration_params_p->getDecimalPlaces();

  pappso::TracePlusCombiner template_plus_combiner(decimal_places);

  // Now we need to check a number of things:
  //
  // 1. If there are no selection polygon specs, then all we need to check is if
  // there is some m/z range limitation. If so, create the right filter.
  //
  // 2. If there is at least one selection polygon spec, then create the right
  // filter.

  // if(!selection_polygon_specs.size())
  //{
  // if(!std::isnan(m_mzRange.first) && !std::isnan(m_mzRange.second))
  //{
  // pappso::FilterInterfaceCstSPtr filter_csp =
  // std::make_shared<const pappso::FilterResampleKeepXRange>(
  // m_mzRange.first, m_mzRange.second);

  // qDebug() << "Adding pappso::FilterResampleKeepXRange.";

  // template_plus_combiner.addFilter(filter_csp);
  //}
  //}
  // else
  //{
  // pappso::FilterInterfaceCstSPtr filter_csp =
  // std::make_shared<const pappso::FilterResampleKeepPointInPolygon>(
  // pappso::FilterResampleKeepPointInPolygon(selection_polygon_specs));

  // qDebug() << "Adding pappso::FilterResampleKeepPointInPolygon.";

  // template_plus_combiner.addFilter(filter_csp);
  //}

  // qDebug();

  // At this point, allocate a visitor that is specific for the calculation of
  // the mass spectrum. Since we are not binning, we can use a visitor that
  // integrates to a pappso::Trace, not a pappso::MassSpectrum.

  // But we want to parallelize the computation. Se we will allocate a vector of
  // visitors (as many as possible for the available processor threads), each
  // visitor having a subset of the initial data to integrate.

  // Determine the best number of threads to use and how many spectra to provide
  // each of the threads.

  // In the pair below, first is the ideal number of threads and second is the
  // number of mass spectra per thread.
  std::pair<std::size_t, std::size_t> best_parallel_params =
    bestParallelIntegrationParams(mass_spectra_count);

  // Handy alias.
  using MassSpecVector = std::vector<pappso::QualifiedMassSpectrumCstSPtr>;
  using VectorIterator = MassSpecVector::const_iterator;

  // Prepare a set of iterators that will distribute all the mass spectra
  // uniformly.
  std::vector<std::pair<VectorIterator, VectorIterator>> iterators =
    calculateIteratorPairs(best_parallel_params.first,
                           best_parallel_params.second);

  // Set aside a vector pappso::MapTrace shared pointers that will be getting
  // the result of the integrations. There will be one such instance per thread
  // executing.

  std::vector<pappso::MapTraceSPtr> vector_of_map_trace;
  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    vector_of_map_trace.push_back(std::make_shared<pappso::MapTrace>());

  // Now that we have all the parallel material, we can start the parallel work.
  emit taskSetMaxValueSignal(mass_spectra_count);

  // qDebug() << "Starting the iteration in the visitors.";

  omp_set_num_threads(iterators.size());
#pragma omp parallel for ordered
  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    {
      // qDebug() << "thread index:" << iter;

      // Get the iterator range that we need to deal in this thread.
      VectorIterator current_iterator = iterators.at(iter).first;
      VectorIterator end_iterator     = iterators.at(iter).second;

      // Get the map for this thread.

      pappso::MapTraceSPtr current_map_trace_sp = vector_of_map_trace.at(iter);

      // std::size_t qualified_mass_spectrum_count =
      // std::distance(current_iterator, end_iterator);

      // qDebug() << "For thread index:" << iter
      //<< "the number of mass spectra to process:"
      //<< qualified_mass_spectrum_count;

      // Allocate the combiner because we'll need it for the checking of the
      // steps (will set eventual processing to the Trace if there are
      // start-end ranges for the mz values, for example with
      // setFilterResampleKeepXRange.

      pappso::TracePlusCombiner trace_plus_combiner(template_plus_combiner);

      // Now process each qualified mass spectrum in the iterator range.
      // Remember the begin and end iterators point to a range of mass
      // spectra in the mass spectra vector that holds *all* the mass
      // spectra to be processed.
      while(current_iterator != end_iterator)
        {
          // We want to be able to intercept any cancellation of the
          // operation.

          if(m_isOperationCancelled)
            break;

          // Make the computation.

          pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
            *current_iterator;

          // Ensure that the qualified mass spectrum contains a mass spectrum
          // that effectively contains the binary (mz,i) data.

          qualified_mass_spectrum_csp =
            checkQualifiedMassSpectrum(qualified_mass_spectrum_csp);

          if(qualified_mass_spectrum_csp == nullptr)
            qFatal(
              "Programming error. Not possible that shared pointer to "
              "qualified mass spectrum be nullptr.");

          // Great, at this point we actually have the mass spectrum!

          // We now need to ensure that the processing flow allows for this mass
          // spectrum to be combined.

          // Immediately check if the qualified mass spectrum is of a MS level
          // that matches the greatest MS level found in the member processing
          // flow instance.

          if(!checkMsLevel(qualified_mass_spectrum_csp))
            {
              // qDebug() << "The MsLevel check returned false";
              ++current_iterator;
              continue;
            }
          if(!checkMsFragmentation(qualified_mass_spectrum_csp))
            {
              // qDebug() << "The MsFragmentation check returned false";
              ++current_iterator;
              continue;
            }

          // Make easy check about RT and DT.

          if(!checkRtRange(qualified_mass_spectrum_csp))
            {
              // qDebug() << "checkRtRange failed check.";
              ++current_iterator;
              continue;
            }
          if(!checkDtRange(qualified_mass_spectrum_csp))
            {
              // qDebug() << "checkDtRange failed check.";
              ++current_iterator;
              continue;
            }

          pappso::Trace filtered_trace(
            *qualified_mass_spectrum_csp->getMassSpectrumSPtr());

          // It may be possible that the 2D steps limiting the m/z range
          // have been followed by 1D steps further limiting the m/Z range.
          // The point is, the 1D steps are not stored in
          // selection_polygon_specs. However, by necessity, if there is at
          // least one selection_polygon_spec, then the m_mzRange holds
          // values. Futher, if there had been a 1D step limiting even more
          // the m/z range, then that step would have been accounted for in
          // m_mzRange. So we can account that limit right away.

          if(!std::isnan(m_mzRange.first) && !std::isnan(m_mzRange.second))
            {
              pappso::FilterResampleKeepXRange the_keep_filter(
                m_mzRange.first, m_mzRange.second);

              filtered_trace = the_keep_filter.filter(filtered_trace);
            }

          // Handle the mobility data, either drift time or 1/K0 depending
          // on the file type.
          double ion_mobility_value = -1;

          // Get the format of the file, because that will condition the
          // way we provide mobility values.

          pappso::Enums::MsDataFormat file_format =
            qualified_mass_spectrum_csp->getMassSpectrumId()
              .getMsRunIdCstSPtr()
              ->getMsDataFormat();

          if(file_format == pappso::Enums::MsDataFormat::brukerTims)
            {
              QVariant ion_mobility_variant_value =
                qualified_mass_spectrum_csp->getParameterValue(
                  pappso::QualifiedMassSpectrumParameter::IonMobOneOverK0);

              if(ion_mobility_variant_value.isValid())
                {
                  bool ok            = false;
                  ion_mobility_value = ion_mobility_variant_value.toDouble(&ok);
                  if(!ok)
                    {
                      qFatal(
                        "Failed to convert QVariant 1/K0 value to double.");
                    }
                }
            }
          else
            {
              ion_mobility_value =
                qualified_mass_spectrum_csp->getDtInMilliSeconds();
            }

          if(integration_scope_specs.size())
            {
              pappso::FilterResampleKeepPointInPolygon the_polygon_filter(
                integration_scope_specs);

              filtered_trace = the_polygon_filter.filter(
                filtered_trace,
                ion_mobility_value,
                qualified_mass_spectrum_csp->getRtInMinutes());
            }

          // No need to configure the combiner here, because we already have
          // done that above.

          // qDebug() << "Going to combine a new qualified mass spectrum.";

          // std::size_t before_size = current_map_trace_sp->size();

          trace_plus_combiner.combine(*current_map_trace_sp, filtered_trace);

          // std::size_t after_size = current_map_trace_sp->size();

          // qDebug() << "Before combination, map trace map size: " <<
          // before_size
          //<< "after combination:" << after_size;

          // qDebug() << "Processed one more spectrum.";

          emit taskSetStatusMessageAndIncrementCurrentValueSignal(
            "Processing spectra", 1);

          // Now we can go to the next mass spectrum.
          ++current_iterator;
        }
        // End of
        // while(current_iterator != end_iterator)

#if 0
			qDebug() << "At the end of the current iterator pair, current map:";
			for(auto &&pair : *current_tic_chrom_map_sp)
				qDebug().noquote() << "(" << pair.first << "," << pair.second << ")";
#endif
    }
  // End of
  // #pragma omp parallel for ordered
  // for(std::size_t iter = 0; iter < iterators.size(); ++iter)


  // In the loop above, for each thread, a pappso::MapTrace was
  // filled in with the m/z,intensity pairs.

  // If the task was cancelled, the monitor widget was locked. We need to
  // unlock it.
  emit unlockTaskMonitorCompositeWidgetSignal();
  emit taskSetMinMaxValuesSignal(0, vector_of_map_trace.size() - 1);

  // We might be here because the user cancelled the operation in the for loop
  // above (visiting all the visitors). In this case m_isOperationCancelled is
  // true. We want to set it back to false, so that the following loop is gone
  // through. The user can ask that the operation be cancelled once more. But
  // we want that at least the performed work be used to show the trace.

  pappso::TracePlusCombiner trace_plus_combiner;

  trace_plus_combiner.setDecimalPlaces(decimal_places);

  m_isOperationCancelled = false;
  int iter               = 0;

  // qDebug() << "The vector of map traces has:" << vector_of_map_trace.size()
  //<< "items.";

  for(auto &&current_map_trace_sp : vector_of_map_trace)
    {
      if(m_isOperationCancelled)
        break;

      ++iter;

      emit taskSetStatusMessageSignal(
        QString("Consolidating mass spectra from thread %1").arg(iter));

      // qDebug() << "In the consolidating loop, iter:" << iter
      //<< "with a MapTrace of this size : "
      //<< current_map_trace_sp->size();

      trace_plus_combiner.combine(m_mapTrace, *current_map_trace_sp);

      emit taskSetCurrentValueSignal(iter);

      // qDebug() << "Emitted current value:" << iter;
    }

  // Write the data to file so that we can check. It works !!!
  // pappso::Utils::appendToFile(m_mapTrace.toString(), "/tmp/prova.txt");

  // At this point, we have really combined all the traces into a single map
  // trace!

  return true;
}

bool
QualifiedMassSpectrumVectorMassDataIntegratorToMz::
  integrateToMzArbitraryBinning()
{
  // If there are no data, nothing to do.
  std::size_t mass_spectra_count =
    mcsp_qualifiedMassSpectraToIntegrateVector->size();

  qDebug() << "Integration in arbitrary binning mode of" << mass_spectra_count
           << "spectr[um,a].";

  if(!mass_spectra_count)
    {
      // qDebug() << "The qualified mass spectrum vector is empty, nothing to
      // do.";

      emit cancelOperationSignal();
    }

  // qDebug() << "The number of mass spectra to handle:" << mass_spectra_count;

  emit setTaskDescriptionTextSignal("Integrating to a mass spectrum (binning)");

  //  This function needs to be called by integrateToMz() that has setup all the
  //  required parameters.

  // We need a processing flow to work, and, in particular, a processing step
  // from which to find the specifics of the calculation.

  if(!mp_processingFlow->getStepsCstRef().size())
    qFatal() << "The processing flow cannot be empty. Program aborted.";

  // In this case where binning is requested, we need to craft the bins, and for
  // that we need proper mz integration params.

  // Heap-allocated
  pappso::MzIntegrationParams *sensible_mz_integration_params_p =
    mp_processingFlow->getSensibleMzIntegrationParams();

  qDebug().noquote() << "Sensible MzIntegrationParams:"
                     << sensible_mz_integration_params_p->toString(0, "  ");

  qDebug().noquote() << "The ms run data set stats:"
                     << mcsp_msRunDataSet->getMsRunDataSetStats().toString();

  // Now we can create the bins!
  std::vector<double> bins = sensible_mz_integration_params_p->createBins();

  qDebug() << "The bins were created, count:" << bins.size();

#if 0
  QString bins_before = "Bins:\n";
  for(auto &&bin : bins)
    bins_before += QString("%1\n").arg(bin, 0, 'f', 6);

  QString date_time =
    QDateTime::currentDateTime().toString("yyyyMMdd-HH-mm-ss");
  QString fileName = "/tmp/massSpecArbitraryBins.txt-at-" + date_time;

  qDebug() << "Writing the list of bins setup in the "
              "mass spectrum in file "
           << fileName;

  QFile file(fileName);
  file.open(QIODevice::WriteOnly);

  QTextStream fileStream(&file);

  for(auto &&bin : bins)
    fileStream << QString("%1\n").arg(bin, 0, 'f', 10);

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


  // When constructing this integrator, the processing flow passed as parameter
  // was used to gather innermost ranges data. When the steps that match
  // the innermost criterion, these are stored in m_innermostSteps2D. When the
  // steps are non-2D steps, then only the m_xxRange values are updated.

  // If the m_innermostSteps2D vector of ProcessingSteps is non-empty, then,
  // that means that we may have some MZ-related 2D processing steps to account
  // at combination time, because that is precisely the only moment that we can
  // do that work. To be able to provide selection polygon an mass
  // spectrum-specific dt|rt data, we have the SelectionPolygonSpec class. There
  // can be as many such class instances as required to document the integration
  // boundaries rt/mz and dt/mz for example. This is why we need to fill in the
  // vector of SelectionPolygonSpec and then set that to the combiner.

  // If the m_innermostSteps2D vector of ProcessingSteps is non-empty, then,
  // that means that we may have some MZ-related 2D processing steps to account
  // at combination time, because that is precisely the only moment that we can
  // do that work.

  std::vector<pappso::IntegrationScopeSpec> integration_scope_specs;

  fillInIntegrationScopeSpecs(integration_scope_specs);

  // Prepare the template for all the combiners we'll use in the different
  // threads.

  pappso::MassSpectrumPlusCombiner template_plus_combiner(
    sensible_mz_integration_params_p->getDecimalPlaces());

  template_plus_combiner.setBins(bins);


#if 0
  QString bins_after = "Bins:\n";
  for(auto &&bin : template_plus_combiner.getBins())
    bins_after += QString("%1\n").arg(bin, 0, 'f', 6);

  if(bins_before == bins_after)
    qDebug() << "The copying of the bins in the template combiner worked.";
  else
    qFatal() << "Programming error.";

#endif

  // At this point, allocate a visitor that is specific for the calculation
  // of the mass spectrum. Since we are not binning, we can use a visitor
  // that integrates to a pappso::Trace, not a pappso::MassSpectrum.

  // But we want to parallelize the computation. Se we will allocate a
  // vector of visitors (as many as possible for the available processor
  // threads), each visitor having a subset of the initial data to
  // integrate.

  // Determine the best number of threads to use and how many spectra to
  // provide each of the threads.

  // In the pair below, first is the ideal number of threads and second is
  // the number of mass spectra per thread.
  std::pair<std::size_t, std::size_t> best_parallel_params =
    bestParallelIntegrationParams(mass_spectra_count);

  using MassSpecVector = std::vector<pappso::QualifiedMassSpectrumCstSPtr>;
  using VectorIterator = MassSpecVector::const_iterator;

  // Prepare a set of iterators that will distribute all the mass spectra
  // uniformly.
  std::vector<std::pair<VectorIterator, VectorIterator>> iterators =
    calculateIteratorPairs(best_parallel_params.first,
                           best_parallel_params.second);

  // Set aside a vector pappso::MapTrace shared pointers that will be
  // getting the result of the integrations. There will be one such instance
  // per thread executing.

  std::vector<pappso::MapTraceSPtr> vector_of_map_trace;
  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    vector_of_map_trace.push_back(std::make_shared<pappso::MapTrace>());

  // Now that we have all the parallel material, we can start the parallel work.
  emit taskSetMaxValueSignal(mass_spectra_count);

  // qDebug() << "Starting the iteration in the visitors.";

  omp_set_num_threads(iterators.size());
#pragma omp parallel for ordered
  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    {
      // qDebug() << "thread index:" << iter;

      // Get the iterator range that we need to deal in this thread.
      VectorIterator current_iterator = iterators.at(iter).first;
      VectorIterator end_iterator     = iterators.at(iter).second;

      // Get the map for this thread.

      pappso::MapTraceSPtr current_map_trace_sp = vector_of_map_trace.at(iter);

      // std::size_t qualified_mass_spectrum_count =
      // std::distance(current_iterator, end_iterator);

      // qDebug() << "For thread index:" << iter
      //<< "the number of mass spectra to process:"
      //<< qualified_mass_spectrum_count;

      // Allocate the combiner because we'll need it for the checking of the
      // steps (will set eventual processing to the Trace if there are
      // start-end ranges for the mz values, for example with
      // setFilterResampleKeepXRange.

      pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner(
        template_plus_combiner);

      // Now process each qualified mass spectrum in the iterator range.
      // Remember the begin and end iterators point to a range of mass
      // spectra in the mass spectra vector that holds *all* the mass
      // spectra to be processed.
      while(current_iterator != end_iterator)
        {
          // We want to be able to intercept any cancellation of the
          // operation.

          if(m_isOperationCancelled)
            break;

          // Make the computation.

          pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
            *current_iterator;

          // Ensure that the qualified mass spectrum contains a mass spectrum
          // that effectively contains the binary (mz,i) data.

          qualified_mass_spectrum_csp =
            checkQualifiedMassSpectrum(qualified_mass_spectrum_csp);

          if(qualified_mass_spectrum_csp == nullptr)
            qFatal(
              "Programming error. Not possible that shared pointer to "
              "qualified mass spectrum be nullptr.");

          // Great, at this point we actually have the mass spectrum!

          // We now need to ensure that the processing flow allows for this
          // mass spectrum to be combined.

          // Immediately check if the qualified mass spectrum is of a MS
          // level that matches the greatest MS level found in the member
          // processing flow instance.

          if(!checkMsLevel(qualified_mass_spectrum_csp))
            {
              // qDebug() << "The MsLevel check returned false";
              ++current_iterator;
              continue;
            }
          if(!checkMsFragmentation(qualified_mass_spectrum_csp))
            {
              // qDebug() << "The MsFragmentation check returned false";
              ++current_iterator;
              continue;
            }

          // Make easy check about RT and DT.

          if(!checkRtRange(qualified_mass_spectrum_csp))
            {
              // qDebug() << "checkRtRange failed check.";
              ++current_iterator;
              continue;
            }
          if(!checkDtRange(qualified_mass_spectrum_csp))
            {
              // qDebug() << "checkDtRange failed check.";
              ++current_iterator;
              continue;
            }

          pappso::Trace filtered_trace;

          // It may be possible that the 2D steps limiting the m/z range
          // have been followed by 1D steps further limiting the m/Z range.
          // The point is, the 1D steps are not stored in
          // selection_polygon_specs. However, by necessity, if there is at
          // least one selection_polygon_spec, then the m_mzRange holds
          // values. Futher, if there had been a 1D step limiting even more
          // the m/z range, then that step would have been accounted for in
          // m_mzRange. So we can account that limit right away.

          if(!std::isnan(m_mzRange.first) && !std::isnan(m_mzRange.second))
            {
              qDebug() << "There is a mz range for the integration:"
                       << "[" << m_mzRange.first << "-" << m_mzRange.second
                       << "]";

              filtered_trace.initialize(
                *qualified_mass_spectrum_csp->getMassSpectrumSPtr());

              pappso::FilterResampleKeepXRange the_keep_filter(
                m_mzRange.first, m_mzRange.second);

              filtered_trace = the_keep_filter.filter(filtered_trace);
            }

          if(integration_scope_specs.size())
            {
              pappso::FilterResampleKeepPointInPolygon the_polygon_filter(
                integration_scope_specs);

              if(!filtered_trace.size())
                {
                  filtered_trace.initialize(
                    *qualified_mass_spectrum_csp->getMassSpectrumSPtr());
                }


              // Handle the mobility data, either drift time or 1/K0 depending
              // on the file type.
              double ion_mobility_value = -1;

              // Get the format of the file, because that will condition the
              // way we provide mobility values.

              pappso::Enums::MsDataFormat file_format =
                qualified_mass_spectrum_csp->getMassSpectrumId()
                  .getMsRunIdCstSPtr()
                  ->getMsDataFormat();

              if(file_format == pappso::Enums::MsDataFormat::brukerTims)
                {
                  QVariant ion_mobility_variant_value =
                    qualified_mass_spectrum_csp->getParameterValue(
                      pappso::QualifiedMassSpectrumParameter::IonMobOneOverK0);

                  if(ion_mobility_variant_value.isValid())
                    {
                      bool ok = false;
                      ion_mobility_value =
                        ion_mobility_variant_value.toDouble(&ok);
                      if(!ok)
                        {
                          qFatal(
                            "Failed to convert QVariant 1/K0 value to double.");
                        }
                    }
                }
              else
                {
                  ion_mobility_value =
                    qualified_mass_spectrum_csp->getDtInMilliSeconds();
                }

              filtered_trace = the_polygon_filter.filter(
                filtered_trace,
                ion_mobility_value,
                qualified_mass_spectrum_csp->getRtInMinutes());
            }

          // No need to configure the combiner here, because we already have
          // done that above.

          // qDebug() << "Going to combine a new qualified mass spectrum.";

          if(filtered_trace.size())
            mass_spectrum_plus_combiner.combine(*current_map_trace_sp,
                                                filtered_trace);
          else
            mass_spectrum_plus_combiner.combine(
              *current_map_trace_sp,
              *qualified_mass_spectrum_csp->getMassSpectrumSPtr());

          // qDebug() << "Processed one more qualified mass spectrum:"
          //<< qualified_mass_spectrum_csp->getMassSpectrumSPtr().get()
          //<< "with size:" << qualified_mass_spectrum_csp->size();

          emit taskSetStatusMessageAndIncrementCurrentValueSignal(
            "Processing spectra", 1);

          // Now we can go to the next mass spectrum.
          ++current_iterator;
        }
        // End of
        // while(current_iterator != end_iterator)

#if 0
			qDebug() << "At the end of the current iterator pair, current map:";
			for(auto &&pair : *current_tic_chrom_map_sp)
				qDebug().noquote() << "(" << pair.first << "," << pair.second << ")";
#endif
    }
  // End of
  // #pragma omp parallel for ordered
  // for(std::size_t iter = 0; iter < iterators.size(); ++iter)


  // In the loop above, for each thread, a pappso::MapTrace was
  // filled in with the m/z,intensity pairs.

  // If the task was cancelled, the monitor widget was locked. We need to
  // unlock it.
  emit unlockTaskMonitorCompositeWidgetSignal();
  emit taskSetMinMaxValuesSignal(0, vector_of_map_trace.size() - 1);

  // We might be here because the user cancelled the operation in the for
  // loop above (visiting all the visitors). In this case
  // m_isOperationCancelled is true. We want to set it back to false, so
  // that the following loop is gone through. The user can ask that the
  // operation be cancelled once more. But we want that at least the
  // performed work be used to show the trace.

  m_isOperationCancelled = false;

  // Now we need to combine all the map traces processed concurrently into a
  // single map trace. For that we instanciate another mass spectrum combiner,
  // set the bins to it and then compute the combinations using it.

  pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner(
    sensible_mz_integration_params_p->getDecimalPlaces());

  mass_spectrum_plus_combiner.setBins(bins);

  // qDebug() << "The vector of map traces has:" <<
  // vector_of_map_trace.size()
  //<< "items.";

  int iter = 0;

  for(auto &&current_map_trace_sp : vector_of_map_trace)
    {
      if(m_isOperationCancelled)
        break;

      ++iter;

      QString message =
        QString("Consolidating mass spectra from thread %1").arg(iter);

      emit taskSetStatusMessageSignal(message);

      // qDebug() << message;

      mass_spectrum_plus_combiner.combine(m_mapTrace, *current_map_trace_sp);

      emit taskSetCurrentValueSignal(iter);

      // qDebug() << "Emitted current value:" << iter;
    }

  // Write the data to file so that we can check. It works !!!
  // pappso::Utils::appendToFile(m_mapTrace.toString(), "/tmp/prova.txt");

  // At this point, we have really combined all the traces into a single map
  // trace!
  return true;
}


} // namespace MineXpert

} // namespace MsXpS
