/* 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 <iostream>
#include <iomanip>
#include <cmath>
#include <chrono>
#include <vector>
#include <iterator>


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

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

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


/////////////////////// Local includes
#include "MassDataIntegrator.hpp"
#include "MsRunDataSetTreeMassDataIntegratorToRt.hpp"
#include "ProcessingStep.hpp"
#include "ProcessingType.hpp"
#include "BaseMsRunDataSetTreeNodeVisitor.hpp"
#include "TicChromTreeNodeCombinerVisitor.hpp"
#include "MsRunStatisticsTreeNodeVisitor.hpp"
#include "MultiTreeNodeCombinerVisitor.hpp"

namespace MsXpS
{
namespace MineXpert
{


MsRunDataSetTreeMassDataIntegratorToRt::MsRunDataSetTreeMassDataIntegratorToRt(
  QObject *parent)
  : MassDataIntegrator(parent)
{
}

MsRunDataSetTreeMassDataIntegratorToRt::MsRunDataSetTreeMassDataIntegratorToRt(
  MsRunDataSetCstSPtr ms_run_data_set_csp, QObject *parent)
  : MassDataIntegrator(ms_run_data_set_csp, parent)
{
  // Essential that the mp_processingFlow member is configured to have the right
  // pointer to the ms run data set.

  mp_processingFlow->setMsRunDataSetCstSPtr(ms_run_data_set_csp);
}

MsRunDataSetTreeMassDataIntegratorToRt::MsRunDataSetTreeMassDataIntegratorToRt(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const ProcessingFlow &processing_flow,
  QObject *parent)
  : MassDataIntegrator(ms_run_data_set_csp, processing_flow, parent)
{
  // Essential that the mp_processingFlow member is configured to have the right
  // pointer to the ms run data set.
  if(ms_run_data_set_csp != mp_processingFlow->getMsRunDataSetCstSPtr())
    qFatal() << "The pointers should be identical.";
}

MsRunDataSetTreeMassDataIntegratorToRt::
  ~MsRunDataSetTreeMassDataIntegratorToRt()
{
  // qDebug() << "Destroying integrator:" << this;
}

MassDataIntegrator *
MsRunDataSetTreeMassDataIntegratorToRt::clone(QObject *parent) const
{
  MsRunDataSetTreeMassDataIntegratorToRt *copy =
    new MsRunDataSetTreeMassDataIntegratorToRt(
      mcsp_msRunDataSet, *mp_processingFlow, parent);

  copy->m_mapTrace              = m_mapTrace;
  copy->m_doubleMapTraceMapSPtr = m_doubleMapTraceMapSPtr;
  copy->m_isOperationCancelled  = m_isOperationCancelled;

  copy->m_msRunDataSetStats = m_msRunDataSetStats;

  return copy;
}

const MsRunDataSetStats &
MsRunDataSetTreeMassDataIntegratorToRt::getMsRunDataSetStats() const
{
  return m_msRunDataSetStats;
}

void
MsRunDataSetTreeMassDataIntegratorToRt::
  seedInitialTicChromatogramAndMsRunDataSetStatistics()
{
  // qDebug() << "From thread" << QThread::currentThreadId()
  //          << "starting the calculation.";

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

  // The number of nodes we want is that of the root nodes, not of all the
  // nodes, that is, we want the MS1 nodes. Indeed, the division of the nodes
  // into the various threads can only be performed with the root nodes.
  std::size_t node_count =
    mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr()->getRootNodes().size();

  // If there are no data, nothing to do.
  if(!node_count)
    {
      qDebug() << "The ms run data set is empty, nothing to do.";

      emit cancelOperationSignal();

      return;
    }

  // qDebug() << "root nodes count:" << node_count;

  // Document that we only care of the MS1 data, because we are computing an
  // initial TIC chromatogram.
  MsFragmentationSpec ms_frag_spec;
  ms_frag_spec.setMsLevel(1);

  // We need to craft a new processing step, push it back to the processing
  // flow and then call the proper integration function.

  ProcessingStepSPtr step_sp = std::make_shared<ProcessingStep>(ms_frag_spec);

  double range_start = mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr()
                         ->getRootNodes()
                         .front()
                         ->getQualifiedMassSpectrum()
                         ->getRtInMinutes();
  double range_end = mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr()
                       ->getRootNodes()
                       .back()
                       ->getQualifiedMassSpectrum()
                       ->getRtInMinutes();

  // qDebug() << "Right from the ms run data set, get rt range start:"
  //<< range_start << "and rt range end:" << range_end;

  //**pappso::SelectionPolygon selection_polygon;
  //**selection_polygon.set1D(range_start, range_end);
  //**step_sp->setSelectionPolygon(selection_polygon);

  pappso::IntegrationScopeCstSPtr integration_scope_csp =
    std::make_shared<const pappso::IntegrationScope>(
      QPointF(range_start, 0),
      range_end - range_start,
      pappso::Enums::DataKind::rt);
  step_sp->setIntegrationScope(integration_scope_csp);
  // qDebug() << "Step is:" << step_sp->toString();

  // qDebug() << "Created 1D selection polygon:"
  //<< step_p->getSelectionPolygon().toString();

  // qDebug() << "Is1D:" << step_p->getSelectionPolygon().is1D();

  step_sp->setSrcProcessingType(pappso::Enums::Axis::x, "FILE_RT");
  step_sp->setDataKind(pappso::Enums::Axis::x, pappso::Enums::DataKind::rt);

  step_sp->setSrcProcessingType(pappso::Enums::Axis::y, "NOT_SET");
  step_sp->setDataKind(pappso::Enums::Axis::y, pappso::Enums::DataKind::unset);

  // Now document the destination processing type:
  step_sp->setDestProcessingType("RT");

  // At this point we have document all the necessary to perform the
  // integration.

  // There should be auto conversion from non-const to const.
  mp_processingFlow->getStepsRef().push_back(step_sp);

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

  // Create as many multi-visitors as there are available threads.

  // emit taskSetMaxValueSignal(node_count);

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

  // qDebug() << "ideal thread count:" << best_parallel_params.first << "with"
  //          << best_parallel_params.second << "mass spec nodes per thread";

  std::vector<MultiTreeNodeCombinerVisitor *> visitors;

  // Determine the number of parallel loops that will perform
  // the calculation.
  using Iterator = std::vector<pappso::MsRunDataSetTreeNode *>::const_iterator;

  // qDebug() << "Now computing calculateMsRunDataSetTreeNodeIteratorPairs,
  // thread"
  //          << QThread::currentThreadId();

  std::vector<std::pair<Iterator, Iterator>> iterators =
    calculateMsRunDataSetTreeNodeIteratorPairs(
      node_count, best_parallel_params.first, best_parallel_params.second);

  // qDebug()
  //   << "Done computing calculateMsRunDataSetTreeNodeIteratorPairs, thread"
  //   << QThread::currentThreadId();

  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    {
      // qDebug() << "Now preparing visitors for new thread - index:" << iter
      //          << "thread" << QThread::currentThreadId()
      //          << "with parent's (this) thread:" << this->thread();

      MultiTreeNodeCombinerVisitor *multi_visitor_p =
        new MultiTreeNodeCombinerVisitor(
          mcsp_msRunDataSet,
          *mp_processingFlow,
          /*this not passed because of different threads.*/ nullptr);

      // qDebug() << "Done preparing visitors for new thread - index:" << iter
      //          << "thread" << QThread::currentThreadId()
      //          << "with parent's (this) thread:" << this->thread();

      // Now configure the visitor to hold as many visitors as we need. We want
      // one visitor for initial TIC chrom calculation and one for the initial
      // ms run data set statistics.

      // qDebug() << "Now heap-allocating TicChromTreeNodeCombinerVisitor,
      // thread"
      //          << QThread::currentThreadId();

      // Start with first visitor that will compute the TIC chromatogram
      TicChromTreeNodeCombinerVisitor *tic_visitor_p =
        new TicChromTreeNodeCombinerVisitor(
          mcsp_msRunDataSet,
          *mp_processingFlow,
          /* When we delete the multi_visitor_sp,
           this tic_visitor_p will be* deleted
           thanks to this parentship.*/
          multi_visitor_p);

      // qDebug() << "Done heap-allocating TicChromTreeNodeCombinerVisitor,
      // thread"
      //          << QThread::currentThreadId();

      multi_visitor_p->addVisitor(tic_visitor_p);

      // And now the other visitor that will compute some statistics for the ms
      // run data set.

      // Set aside a stats holder instance.

      MsRunDataSetStats ms_run_data_set_stats;

      MsRunStatisticsTreeNodeVisitor *stats_visitor_p =
        new MsRunStatisticsTreeNodeVisitor(
          mcsp_msRunDataSet,
          *mp_processingFlow,
          ms_run_data_set_stats,
          /* When we delete the multi_visitor_sp,
            this stats_visitor_p will be deleted
            thanks to this parentship.*/
          multi_visitor_p);

      multi_visitor_p->addVisitor(stats_visitor_p);

      // We want to be able to intercept any cancellation of the operation.

      // qDebug() << "Making connection for the cancel operation signal";

      connect(this,
              &MsRunDataSetTreeMassDataIntegratorToRt::cancelOperationSignal,
              [multi_visitor_p]() {
                multi_visitor_p->cancelOperation();
              });

      // The visitor gets the number of nodes to process from the data set tree
      // node. This signal tells the final user of the signal to set the max
      // value of the progress bar to number_of_nodes_to_process.

      // qDebug() << "Making connection for the progress bar signal.";

      connect(multi_visitor_p,
              &MultiTreeNodeCombinerVisitor::taskSetMaxValueSignal,
              [this](std::size_t number_of_nodes_to_process) {
                emit taskSetMaxValueSignal(number_of_nodes_to_process);
              });

      // The visitor emits the signal to tell that the currently iterated node
      // has number_of_nodes_to_process children to process. Because the visitor
      // might operate in a thread and that there might be other threads
      // handling other nodes, we do not consider that the
      // number_of_nodes_to_process value is the total number of nodes to
      // process but only the number of nodes that the visitor is handling. We
      // thus update the max progress bar value by number_of_nodes_to_process.
      // In a typical setting, the user of the signal is the monitoring object,
      // be it a non-gui object or the TaskMonitorCompositeWidget.

      // qDebug()
      // << "Making connection for the progress bar incrementation signal.";

      connect(multi_visitor_p,
              &TicChromTreeNodeCombinerVisitor::taskIncrementMaxValueSignal,
              [this](std::size_t number_of_nodes_to_process) {
                emit taskIncrementMaxValueSignal(number_of_nodes_to_process);
              });

      connect(multi_visitor_p,
              &TicChromTreeNodeCombinerVisitor::taskSetCurrentValueSignal,
              [this](std::size_t number_of_processed_nodes) {
                emit taskSetCurrentValueSignal(number_of_processed_nodes);
              });

      connect(multi_visitor_p,
              &TicChromTreeNodeCombinerVisitor::
                taskSetStatusMessageAndIncrementCurrentValueSignal,
              [this](QString text, std::size_t increment) {
                emit taskSetStatusMessageAndIncrementCurrentValueSignal(
                  text, increment);
              });

      connect(multi_visitor_p,
              &TicChromTreeNodeCombinerVisitor::taskSetStatusMessageSignal,
              [this](QString text) {
                emit taskSetStatusMessageSignal(text);
              });

      connect(multi_visitor_p,
              &TicChromTreeNodeCombinerVisitor::
                taskSetStatusMessageAndCurrentValueSignal,
              [this](QString text, std::size_t value) {
                emit taskSetStatusMessageAndCurrentValueSignal(text, value);
              });

      visitors.push_back(multi_visitor_p);
    }

  // qDebug() << "Done preparing all the visitors" << visitors.size()
  //          << "in their container.";

  // Now that we have configured all the visitors, perform a parallel iteration
  // in these and ask them to work on the matching ms run data
  // set iterators pair.
  omp_set_num_threads(visitors.size());
#pragma omp parallel for ordered
  for(std::size_t iter = 0; iter < visitors.size(); ++iter)
    {
      auto multi_visitor_p = visitors.at(iter);

      // qDebug() << "Multi-visitor:" << multi_visitor_p << "at index:" << iter
      //          << "Executing from thread:" << QThread::currentThreadId();

      mcsp_msRunDataSet->msp_msRunDataSetTree->accept(
        *multi_visitor_p, iterators.at(iter).first, iterators.at(iter).second);

      // qDebug() << "Finished running the visitor at index:" << iter;
    }
  // End of
  // #pragma omp parallel for

  // In the loop above, the m_ticChromMapTrace object (map <double, double>,
  // that is, (rt, tic)) has been filled with the various TIC values for the
  // different retention times.
  //
  // At this point we need to combine all the visitor-contained map traces
  // into a single trace. We make the combination into the member MapTrace
  // object. We choose the Trace combiner because we do not have to do any
  // binning. On the contrary we want absolutely all the RT values encountered
  // throughout all of the spectra.

  // qDebug() << "End of the iteration in the visitors";

  // If the task was cancelled, the monitor widget was locked. We need to
  // unlock it.
  emit unlockTaskMonitorCompositeWidgetSignal();
  emit taskSetMinMaxValuesSignal(0, visitors.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;

  pappso::TracePlusCombiner trace_plus_combiner;
  MsRunDataSetStats ms_run_data_set_stats_merge;

  for(std::size_t iter = 0; iter < visitors.size(); ++iter)
    {
      if(m_isOperationCancelled)
        break;

      auto multi_visitor_p = visitors.at(iter);

      // We know that the first visitor is the visitor for the TIC chromatogram.
      // So, handle this one first.

      TicChromTreeNodeCombinerVisitor *tic_visitor_p =
        dynamic_cast<TicChromTreeNodeCombinerVisitor *>(
          multi_visitor_p->m_visitors.front());
      // I do not understand why a new visitor needs to be allocated ?
      // multi_visitor_p->m_visitors.front()->clone();

      const pappso::MapTrace &map_trace = tic_visitor_p->getTicChromMapTrace();

      // Use the trace plus combiner instance to combine the iterated trace into
      // the member map trace that the integrator users will query after that
      // that calculation is finished.
      trace_plus_combiner.combine(m_mapTrace, map_trace.toTrace());

      // Now we know that the second visitor is for the statistics. Handle this
      // now.

      MsRunStatisticsTreeNodeVisitor *stats_visitor_p =
        dynamic_cast<MsRunStatisticsTreeNodeVisitor *>(
          multi_visitor_p->m_visitors.back());
      // I do not understand why a new visitor needs to be allocated ?
      // multi_visitor_p->m_visitors.back()->clone();

      // qDebug() << "In the consolidating loop, iter:" << iter
      //<< "with a map trace of this size : " << map_trace.size();

      emit taskSetStatusMessageSignal(
        QString("Consolidating TIC chromatograms and statistics from thread %1")
          .arg(iter + 1));

      emit taskSetCurrentValueSignal(iter + 1);

      // qDebug() << "Going to merge a visitor's MsRunDataSetStats and the "
      //"m_maxMz for the data stats is:"
      //<< dynamic_cast<const MsRunStatisticsTreeNodeVisitor *>(
      // multi_visitor_sp->m_visitors.back().get())
      //->getMsRunDataSetStats()
      //.m_maxMz;

      // qDebug() << "We are merging one visitor's ms run data set stats:"
      //<< stats_visitor_sp.get()->getMsRunDataSetStats().toString();

      // qDebug()
      //<< "Before the merge, the ms_run_data_set_stats_merge has m_maxMz:"
      //<< ms_run_data_set_stats_merge.m_maxMz;

      ms_run_data_set_stats_merge.merge(
        stats_visitor_p->getMsRunDataSetStats());

      // qDebug()
      //<< "After the merge, the ms_run_data_set_stats_merge has m_maxMz:"
      //<< ms_run_data_set_stats_merge.m_maxMz;
    }

  // At this point we finally have the statistical data merged into a single
  // instance. We now need to consolidate.

  // qDebug() << "Final consolidation of the statistics.";

  ms_run_data_set_stats_merge.consolidate();

  // At this point we can set the statistics to the ms run data set. We need to
  // un-const_cast the shared pointer (this is one of the rarest situations
  // where we need to do this).

  // qDebug() << "Setting the calculated statistics to the ms run data set.";

  std::const_pointer_cast<MsRunDataSet>(mcsp_msRunDataSet)
    ->setMsRunDataSetStats(ms_run_data_set_stats_merge);

  // qDebug().noquote() << mcsp_msRunDataSet->getMsRunDataSetStats().toString();

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

  QString chrono_string = pappso::Utils::chronoIntervalDebugString(
    "Integration to initial TIC chromatogram and calculation of the ms run "
    "data set statistics took:",
    chrono_start_time,
    chrono_end_time);

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

  // Free all the visitors (we can do this because we have passed nullptr as the
  // parent):
  foreach(BaseMsRunDataSetTreeNodeVisitor *visitor_p, visitors)
    delete visitor_p;

  emit finishedIntegratingToInitialTicChromatogramSignal(this);
}

bool
MsRunDataSetTreeMassDataIntegratorToRt::integrateToRt()
{
  // qDebug();

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

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

  // We need a processing flow to work, and, in particular, a processing step
  // from which to find the specifics of the calculation. The processing flow
  // must have been set by the caller either at construction time or later
  // before using the integrator, that is, calling this function.

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

  // Get the most recent step that holds all the specifics of this integration.
  ProcessingStepCstSPtr processing_step_csp =
    mp_processingFlow->mostRecentStep();

  if(!processing_step_csp->srcMatches("ANY_RT"))
    qFatal(
      "There should be one source ProcessingType = ANY_RT. Program aborted.");

  // Try to limit the range of MS run data set tree nodes to be iterated through
  // by looking from what node to what other node we need to go to ensure that
  // our integration encompasses the right RT range.

  std::vector<pappso::MsRunDataSetTreeNode *> root_nodes =
    mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr()->getRootNodes();

  std::pair<double, double> range_pair;

  bool integration_rt = mp_processingFlow->innermostRange("ANY_RT", range_pair);

  using Iterator = std::vector<pappso::MsRunDataSetTreeNode *>::const_iterator;
  using Pair     = std::pair<Iterator, Iterator>;

  Pair pair;

  Iterator begin_iterator = root_nodes.begin();
  Iterator end_iterator   = root_nodes.end();

  std::size_t node_count = 0;

  if(integration_rt)
    {
      pair = mcsp_msRunDataSet->treeNodeIteratorRangeForRtRange(
        range_pair.first, range_pair.second);

      begin_iterator = pair.first;
      end_iterator   = pair.second;

      node_count = std::distance(begin_iterator, end_iterator);

      // qDebug() << "node_count:" << node_count;
    }
  // else
  // qDebug() << "Not integration_rt";

  if(begin_iterator == root_nodes.end())
    {
      qDebug() << "There is nothing to integrate.";
      return false;
    }

  // Now that we know the non-0 count of nodes to be processed:
  emit taskSetMaxValueSignal(node_count);

  // At this point, allocate a visitor that is specific for the calculation of
  // the TIC chromatogram.

  // But we want to parallelize the computation.

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

  // qDebug() << "ideal_thread_count:" << best_parallel_params.first
  //<< "nodes_per_thread:" << best_parallel_params.second;

  using Iterator = std::vector<pappso::MsRunDataSetTreeNode *>::const_iterator;

  std::vector<TicChromTreeNodeCombinerVisitor *> visitors;
  std::vector<std::pair<Iterator, Iterator>> iterators =
    calculateMsRunDataSetTreeNodeIteratorPairs(begin_iterator,
                                               end_iterator,
                                               best_parallel_params.first,
                                               best_parallel_params.second);

  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    {
      // qDebug() << "thread index:" << iter;

      TicChromTreeNodeCombinerVisitor *visitor_p =
        new TicChromTreeNodeCombinerVisitor(
          mcsp_msRunDataSet, *mp_processingFlow, this);

      // We want to be able to intercept any cancellation of the operation.

      connect(this,
              &MsRunDataSetTreeMassDataIntegratorToRt::cancelOperationSignal,
              [visitor_p]() {
                visitor_p->cancelOperation();
              });
      // visitor_sp.get(),
      //&TicChromTreeNodeCombinerVisitor::cancelOperation,
      // Qt::QueuedConnection);

      // The visitor gets the number of nodes to process from the data set tree
      // node. This signal tells the final user of the signal to set the max
      // value of the progress bar to number_of_nodes_to_process.

      connect(visitor_p,
              &TicChromTreeNodeCombinerVisitor::taskSetMaxValueSignal,
              [this](std::size_t number_of_nodes_to_process) {
                emit taskSetMaxValueSignal(number_of_nodes_to_process);
              });

      // The visitor emits the signal to tell that the currently iterated node
      // has number_of_nodes_to_process children to process. Because the visitor
      // might operate in a thread and that there might be other threads
      // handling other nodes, we do not consider that the
      // number_of_nodes_to_process value is the total number of nodes to
      // process but only the number of nodes that the visitor is handling. We
      // thus update the max progress bar value by number_of_nodes_to_process.
      // In a typical setting, the user of the signal is the monitoring object,
      // be it a non-gui object or the TaskMonitorCompositeWidget.
      connect(visitor_p,
              &TicChromTreeNodeCombinerVisitor::taskIncrementMaxValueSignal,
              [this](std::size_t number_of_nodes_to_process) {
                emit taskIncrementMaxValueSignal(number_of_nodes_to_process);
              });

      connect(visitor_p,
              &TicChromTreeNodeCombinerVisitor::taskSetCurrentValueSignal,
              [this](std::size_t number_of_processed_nodes) {
                emit taskSetCurrentValueSignal(number_of_processed_nodes);
              });

      connect(visitor_p,
              &TicChromTreeNodeCombinerVisitor::
                taskSetStatusMessageAndIncrementCurrentValueSignal,
              [this](QString text, std::size_t increment) {
                emit taskSetStatusMessageAndIncrementCurrentValueSignal(
                  text, increment);
              });

      connect(visitor_p,
              &TicChromTreeNodeCombinerVisitor::taskSetStatusMessageSignal,
              [this](QString text) {
                emit taskSetStatusMessageSignal(text);
              });

      connect(visitor_p,
              &TicChromTreeNodeCombinerVisitor::
                taskSetStatusMessageAndCurrentValueSignal,
              [this](QString text, std::size_t value) {
                emit taskSetStatusMessageAndCurrentValueSignal(text, value);
              });

      visitors.push_back(visitor_p);
    }

  // Now perform a parallel iteration in the various visitors and ask them to
  // work on the matching ms run data set iterators pair.
  omp_set_num_threads(visitors.size());
#pragma omp parallel for ordered
  for(std::size_t iter = 0; iter < visitors.size(); ++iter)
    {
      auto visitor_p = visitors.at(iter);

      // qDebug() << "Visitor:" << visitor_sp.get() << "at index:" << iter
      //<< "Executing from thread:" << QThread::currentThreadId();

      mcsp_msRunDataSet->msp_msRunDataSetTree->accept(
        *(visitor_p), iterators.at(iter).first, iterators.at(iter).second);
    }
  // End of
  // #pragma omp parallel for

  // In the loop above, the m_ticChromMapTrace object (map <double, double>,
  // that is, (rt, tic)) has been filled with the various TIC values for the
  // different retention times.
  //
  // At this point we need to combine all the visitor-contained map traces
  // into a single trace. We make the combination into the member MapTrace
  // object.

  // qDebug() << "End of the iteration in the visitors";

  pappso::TracePlusCombiner trace_plus_combiner;

  // If the task was cancelled, the monitor widget was locked. We need to
  // unlock it.
  emit unlockTaskMonitorCompositeWidgetSignal();
  emit taskSetMinMaxValuesSignal(0, visitors.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;

  for(std::size_t iter = 0; iter < visitors.size(); ++iter)
    {
      if(m_isOperationCancelled)
        break;

      auto &&visitor_sp = visitors.at(iter);

      const pappso::MapTrace &map_trace = visitor_sp->getTicChromMapTrace();

      // qDebug() << "In the consolidating loop, iter:" << iter
      //<< "with a map trace of this size : " << map_trace.size();

      emit taskSetStatusMessageSignal(
        QString("Consolidating TIC chromatograms from thread %1")
          .arg(iter + 1));

      emit taskSetCurrentValueSignal(iter + 1);

      trace_plus_combiner.combine(m_mapTrace, map_trace.toTrace());
    }

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

  QString chrono_string = pappso::Utils::chronoIntervalDebugString(
    "Integration to TIC/XIC chromatogram took:",
    chrono_start_time,
    chrono_end_time);

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

  return true;
}


} // namespace MineXpert

} // namespace MsXpS
