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


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

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

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


/////////////////////// Local includes
#include "QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz.hpp"
#include "ProcessingStep.hpp"
#include "ProcessingType.hpp"

namespace MsXpS
{
namespace MineXpert
{


QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::
  QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz(QObject *parent)
  : QualifiedMassSpectrumVectorMassDataIntegrator(parent)
{
}

QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::
  QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz(
    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);
}

QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::
  ~QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz()
{
}

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

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

  return copy;
}

std::size_t
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::getColorMapKeyCellCount()
  const
{
  return m_colorMapKeyCellCount;
}

std::size_t
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::getColorMapMzCellCount()
  const
{
  return m_colorMapMzCellCount;
}

double
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::getColorMapMinKey() const
{
  return m_colorMapMinKey;
}

double
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::getColorMapMaxKey() const
{
  return m_colorMapMaxKey;
}

double
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::getColorMapMinMz() const
{
  return m_colorMapMinMz;
}

double
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::getColorMapMaxMz() const
{
  return m_colorMapMaxMz;
}

using Map = std::map<double, pappso::MapTrace>;

const std::shared_ptr<Map> &
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::
  getDoubleMapTraceMapSPtr() const
{
  return m_doubleMapTraceMapSPtr;
}

pappso::Enums::DataKind
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::
  getLastIntegrationDataKind() const
{
  return m_lastIntegrationDataKind;
}

void
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::integrate()
{
  // qDebug();

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

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

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

  // In this integration, we are going to craft the data to create a colormap
  // that will plot mz data against either rt or dt data. The way we know if we
  // have to plot mz data against one or the other is by looking into the
  // processing flow's processing type.

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

  ProcessingStepCstSPtr last_processing_step_csp =
    mp_processingFlow->mostRecentStep();

  ProcessingType last_processing_type =
    last_processing_step_csp->getDestProcessingType();

  // We need to know what kind of integration we deal with here, rt_mz or dt_mz
  // because we have to store it so that after the computation is performed, the
  // consumer of this integrator knows what integration was performed. It will
  // have no other means to know what kind of integration we have performed.

  if(last_processing_type.bitMatches("DT_MZ"))
    m_lastIntegrationDataKind = pappso::Enums::DataKind::dt;
  else if(last_processing_type.bitMatches("MZ_RT"))
    m_lastIntegrationDataKind = pappso::Enums::DataKind::rt;
  else
    qFatal() << "Programming error. The processing is not allowed here.";

  // Choose the right message to emit and make sanity checks.
  if(m_lastIntegrationDataKind == pappso::Enums::DataKind::rt)
    {
      emit setTaskDescriptionTextSignal(
        "Integrating to a TIC chromatogram/mass spectrum color map");
    }
  else if(m_lastIntegrationDataKind == pappso::Enums::DataKind::dt)
    {
      emit setTaskDescriptionTextSignal(
        "Integrating to a drift spectrum/mass spectrum color map");
    }
  else
    qFatal() << "Programming error.";

  // Check the mz integration params to see if the user wants binning or not.

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

  if(mz_integration_params_p->getBinningType() ==
     pappso::MzIntegrationParams::BinningType::NONE)
    {
      integrateNoBinning();
    }
  else if(mz_integration_params_p->getBinningType() ==
          pappso::MzIntegrationParams::BinningType::ARBITRARY)
    {
      integrateArbitraryBinning();
    }

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

  QString chrono_string;

  if(m_lastIntegrationDataKind == pappso::Enums::DataKind::rt)
    chrono_string = pappso::Utils::chronoIntervalDebugString(
      "Integration to RT / MZ colormap took:",
      chrono_start_time,
      chrono_end_time);
  else if(m_lastIntegrationDataKind == pappso::Enums::DataKind::dt)
    chrono_string = pappso::Utils::chronoIntervalDebugString(
      "Integration to DT / MZ colormap took:",
      chrono_start_time,
      chrono_end_time);
  else
    qFatal() << "Programming error.";

  emit logTextToConsoleSignal(chrono_string);

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

  return;
}

void
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::integrateNoBinning()
{
  // qDebug();

  // In this integration, we are going to craft the data to create a colormap
  // that will plot mz data against either rt or dt data. The way we know if we
  // have to plot mz data against one or the other is by looking into the
  // processing flow's processing type.

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

  // Note, however, that the hassle described above only is acceptable when the
  // selection polygon is not square. If the selection polygon is square, then
  // the ranges suffice, and they have been filled-in properly upon construction
  // of this integrator.

  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.
  // We need to know the decimal places, so get them from the processing flow's
  // most sensible MzIntegrationParams.

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

  // qDebug().noquote() << "Starting integration with mz integration params:"
  //<< local_mz_integration_params.toString();

  // Instantiate a plus combiner that will be used as a template for all the
  // combiners operating concurrently below. Note that because we do not make
  // bins, we just need a TracePlusCombiner and not a MassSpectrumPlusCombiner.

  pappso::TracePlusCombiner template_plus_combiner(decimal_places);

  // Using a shared pointer make it easy to handle the copying of the map to
  // users of that map without bothering.

  if(m_doubleMapTraceMapSPtr == nullptr)
    m_doubleMapTraceMapSPtr =
      std::make_shared<std::map<double, pappso::MapTrace>>();
  else
    m_doubleMapTraceMapSPtr->clear();

  // In this function, the first key is either dt or rt and the second key does
  // not have use.

  // 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(
      mcsp_qualifiedMassSpectraToIntegrateVector->size());

  // 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 of std::map<double, pappso::MapTrace> to store retention
  // time values and the combined mass spectrum.

  // Handy aliases.
  using Map     = std::map<double, pappso::MapTrace>;
  using MapSPtr = std::shared_ptr<Map>;

  std::vector<MapSPtr> vector_of_double_maptrace_maps;
  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    vector_of_double_maptrace_maps.push_back(std::make_shared<Map>());

  // Now that we have all the parallel material, we can start the parallel work.
  std::size_t mass_spectra_count =
    mcsp_qualifiedMassSpectraToIntegrateVector->size();

  emit taskSetMaxValueSignal(mass_spectra_count);

  qDebug() << "Starting the iteration in the visitors.";
  qDebug() << "Number of teams:" << omp_get_num_teams();

  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.

      MapSPtr current_double_maptrace_map_sp =
        vector_of_double_maptrace_maps.at(iter);

      while(current_iterator != end_iterator)
        {
          // We want to be able to intercept any cancellation of the
          // operation.

          if(m_isOperationCancelled)
            break;

          pappso::TracePlusCombiner trace_plus_combiner(template_plus_combiner);

          // Make the computation.

          // For each mass spectrum all we need is compute its TIC value, that
          // is, the sum of all its intensities (the y value of the DataPoint
          // instances it contains).

          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;
            }

          // Great, at this point we actually have the mass spectrum and we need
          // to get the dt|rt value for it.

          double rt_value = qualified_mass_spectrum_csp->getRtInMinutes();

          // 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();

          bool ok = false;

          if(file_format == pappso::Enums::MsDataFormat::brukerTims)
            {
              // Start by looking if there is a OneOverK0 valid value, which
              // means we have effectively handled a genuine mobility scan
              // spectrum.
              QVariant ion_mobility_variant_value =
                qualified_mass_spectrum_csp->getParameterValue(
                  pappso::QualifiedMassSpectrumParameter::IonMobOneOverK0);

              if(ion_mobility_variant_value.isValid())
                {
                  // Yes, genuine ion mobility scan handled here.

                  ion_mobility_value = ion_mobility_variant_value.toDouble(&ok);

                  if(!ok)
                    {
                      qFatal(
                        "The data are Bruker timsTOF data but failed to "
                        "convert valid "
                        "QVariant 1/K0 value to double.");
                    }
                }
              else
                {
                  // We are not handling a genuine single ion mobility scan
                  // here. We must be handling a mass spectrum that correspond
                  // to the combination of all the ion mobility scans for a
                  // single frame. This is when the user asks that ion mobility
                  // scans be flattended. In this case, the OneOverK0 value is
                  // not valid but there are two values that are set:
                  // TimsFrameInvKoBegin and TimsFrameInvKoEnd.
                  // See TimsFramesMsRunReader::readSpectrumCollection2.

                  // Test one of these values as a sanity check. But
                  // give the value of -1 for the ion mobility because we do not
                  // have ion mobility data here.

                  ion_mobility_variant_value =
                    qualified_mass_spectrum_csp->getParameterValue(
                      pappso::QualifiedMassSpectrumParameter::
                        IonMobOneOverK0Begin);

                  if(!ion_mobility_variant_value.isValid())
                    {
                      qFatal(
                        "The data are Bruker timsTOF data but failed to get "
                        "correct "
                        "ion mobility data. Inconsistency found.");
                    }
                }
            }
          else
            {
              ion_mobility_value =
                qualified_mass_spectrum_csp->getDtInMilliSeconds();
            }

          if(ion_mobility_value == -1)
            {
              qDebug() << "Not ion mobility data.";

              ++current_iterator;
              continue;
            }

          double first_key; // that is, either rt or dt.

          if(m_lastIntegrationDataKind == pappso::Enums::DataKind::rt)
            {
              first_key = rt_value;
            }
          else if(m_lastIntegrationDataKind == pappso::Enums::DataKind::dt)
            {
              first_key = ion_mobility_value;
            }
          else
            qFatal() << "Programming error.";

          // We need to iterate in the current_map_trace_sp and find if there is
          // already a map trace by the same dt|rt value in it.

          Map::iterator double_maptrace_map_iterator;

          double_maptrace_map_iterator =
            current_double_maptrace_map_sp->find(first_key);

          // Before combining, we need to check if some filtering is
          // required as for m/z data.

          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);
            }

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

              filtered_trace = the_polygon_filter.filter(
                filtered_trace, ion_mobility_value, rt_value);
            }

          // At this point we can make the combination with the filtered trace.

          // Tells if the key was already found in the map.
          if(double_maptrace_map_iterator !=
             current_double_maptrace_map_sp->end())
            {

              // The map already contained a pair that had the key value ==
              // first_key. All we have to do is combine the new mass
              // spectrum pointed to by the map value into the MapTrace that is
              // pointed to by the map key.

              // Combine the new mass spectrum right into the found MapTrace.

              // qDebug() << "Before combination, map trace map size: "
              //<< double_maptrace_map_iterator->second.size();

              trace_plus_combiner.combine(double_maptrace_map_iterator->second,
                                          filtered_trace);
            }
          else
            {

              // No other mass spectrum was encountered by the first_key key.
              // So we need to do all the process here.

              // qDebug() << "No node for a mass spectrum having that same
              // first_key " "was found already:"
              //<< first_key;

              // Instantiate a new MapTrace in which we'll combine this and all
              // the future mass spectra having been acquired at this same dt
              // value.

              pappso::MapTrace map_trace;

              trace_plus_combiner.combine(map_trace, filtered_trace);

              // Finally, store in the map, the map_trace along with its
              // first_key.

              (*current_double_maptrace_map_sp)[first_key] = map_trace;

              // qDebug() << "After combination, map trace map size: "
              //<< map_trace.size();
            }

          // At this point we have processed the qualified mass spectrum.

          emit taskSetStatusMessageAndIncrementCurrentValueSignal(
            "Processing spectra", 1);

          // Now we can go to the next mass spectrum.
          ++current_iterator;
        }
      // End of
      // while(current_iterator != end_iterator)
    }
  // 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 std::map<double, pappso::MapTrace>
  // map was filled in with the dt|rt, MapTrace pairs.

  // If the task was cancelled, the monitor widget was locked. We need to
  // unlock it.
  emit unlockTaskMonitorCompositeWidgetSignal();
  emit taskSetMinMaxValuesSignal(0, vector_of_double_maptrace_maps.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 combiner,
  // set the bins to it and then compute the combinations using it.

  pappso::TracePlusCombiner trace_plus_combiner(decimal_places);

  int iter = 0;

  for(auto &&map_sp : vector_of_double_maptrace_maps)
    {
      // qDebug();

      ++iter;

      if(m_isOperationCancelled)
        break;

      for(auto &&pair : *map_sp)
        {
          // first is the rt|dt key
          // second is the pappso::MapTrace

          emit taskSetStatusMessageSignal(
            QString("Consolidating color map data from thread %1").arg(iter));

          emit taskSetCurrentValueSignal(iter);

          double first_key = pair.first;

          pappso::MapTrace current_maptrace = pair.second;

          // Now search in the member if a rt|dt key was found already.

          Map::iterator double_maptrace_map_iterator;

          double_maptrace_map_iterator =
            m_doubleMapTraceMapSPtr->find(first_key);

          // Tells if the key was already found in the map.
          if(double_maptrace_map_iterator != m_doubleMapTraceMapSPtr->end())
            {
              // qDebug() << "Adding new key value:" << first_key;


              // The map already contained a pair that had the key value ==
              // first_key. All we have to do is combine the new mass
              // spectrum pointed to by the map value into the MapTrace that is
              // pointed to by the map key.

              // Combine the new mass spectrum right into the found MapTrace.

              // std::size_t before_size = current_maptrace.size();

              trace_plus_combiner.combine(double_maptrace_map_iterator->second,
                                          current_maptrace);

              // std::size_t after_size = current_maptrace.size();

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

              // No other mass spectrum was encountered by the first_key key.
              // So we need to do all the process here.

              // qDebug() << "No node for a mass spectrum having that same
              // first_key " "was found already:"
              //<< first_key;

              // Instantiate a new MapTrace in which we'll combine this and all
              // the future mass spectra having been acquired at this same dt
              // value.

              pappso::MapTrace map_trace;

              // std::size_t before_size = map_trace.size();

              trace_plus_combiner.combine(map_trace, current_maptrace);

              // std::size_t after_size = map_trace.size();

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

              // Finally, store in the map, the map_trace along with its
              // first_key.

              (*m_doubleMapTraceMapSPtr)[first_key] = map_trace;

              // qDebug() << "After combination, map trace map size: " <<
              // map_trace.size();
            }
        }
    }

  // We now need to go through the double map trace map to inspect its
  // characteristics.

  if(!m_doubleMapTraceMapSPtr->size())
    {
      qDebug() << "There are no data in the double maptrace map.";

      return;
    }

  m_colorMapMinKey = m_doubleMapTraceMapSPtr->begin()->first;
  m_colorMapMaxKey = m_doubleMapTraceMapSPtr->rbegin()->first;

  m_colorMapKeyCellCount = m_doubleMapTraceMapSPtr->size();

  for(auto &&pair : *m_doubleMapTraceMapSPtr)
    {
      pappso::MapTrace map_trace = pair.second;

      if(!map_trace.size())
        continue;

      if(map_trace.size() > m_colorMapMzCellCount)
        m_colorMapMzCellCount = map_trace.size();

      if(map_trace.begin()->first < m_colorMapMinMz)
        m_colorMapMinMz = map_trace.begin()->first;

      if(map_trace.rbegin()->first > m_colorMapMaxMz)
        m_colorMapMaxMz = map_trace.rbegin()->first;
    }

  return;
}

void
QualifiedMassSpectrumVectorMassDataIntegratorToDtRtMz::
  integrateArbitraryBinning()
{
  // qDebug();

  // In this integration, we are going to craft the data to create a colormap
  // that will plot mz data against either rt or dt data. The way we know if we
  // have to plot mz data against one or the other is by looking into the
  // processing flow's processing type.

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

  // Note, however, that the hassle described above only is acceptable when the
  // selection polygon is not square. If the selection polygon is square, then
  // the ranges suffice, and they have been filled-in properly upon construction
  // of this integrator.

  // We need this in some places, for debugging the bin creation and the mass
  // spectrum combination.
  // QString debug_string = "";

  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.

  // Prepare the template for all the combiners we'll use in the different
  // threads.
  // We need to know the decimal places, so get them from the processing flow's
  // most sensible MzIntegrationParams.

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

  // Instanciate a plus combiner that will be used as a template for all the
  // combiners operating concurrently below. Note that because we are computing
  // with binning, we need a MassSpectrumPlusCombiner and not a
  // TracePlusCombiner.

  pappso::MassSpectrumPlusCombiner template_plus_combiner(decimal_places);

  // To create the bins, we need the smallest and greatest m/z values in the
  // data set. Fortunately, the getSensibleMzIntegrationParams() does just that:
  // start by seeking these values from the data set.

  // qDebug() << "Now starting creation of the bins.";

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

  // debug_string = QString(
  //"Number of bins: %1\n"
  //"First bins: %2 %3 %4 -- Last bins: %5 %6 %7\n")
  //.arg(bins.size())
  //.arg(bins[0], 0, 'f', 6)
  //.arg(bins[1], 0, 'f', 6)
  //.arg(bins[2], 0, 'f', 6)
  //.arg(bins[bins.size() - 3], 0, 'f', 6)
  //.arg(bins[bins.size() - 2], 0, 'f', 6)
  //.arg(bins[bins.size() - 1], 0, 'f', 6);

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

  template_plus_combiner.setBins(bins);

  // Using a shared pointer make it easy to handle the copying of the map to
  // users of that map without bothering.

  if(m_doubleMapTraceMapSPtr == nullptr)
    m_doubleMapTraceMapSPtr =
      std::make_shared<std::map<double, pappso::MapTrace>>();
  else
    m_doubleMapTraceMapSPtr->clear();

  // In this function, the first key is either dt or rt and the second key does
  // not have use.

  // 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(
      mcsp_qualifiedMassSpectraToIntegrateVector->size());

  // 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 of std::map<double, pappso::MapTrace> to store retention
  // time values and the combined mass spectrum.

  // Handy aliases.
  using Map     = std::map<double, pappso::MapTrace>;
  using MapSPtr = std::shared_ptr<Map>;

  std::vector<MapSPtr> vector_of_double_maptrace_maps;
  for(std::size_t iter = 0; iter < iterators.size(); ++iter)
    vector_of_double_maptrace_maps.push_back(std::make_shared<Map>());

  // Now that we have all the parallel material, we can start the parallel work.
  std::size_t mass_spectra_count =
    mcsp_qualifiedMassSpectraToIntegrateVector->size();

  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.

      MapSPtr current_double_maptrace_map_sp =
        vector_of_double_maptrace_maps.at(iter);

      while(current_iterator != end_iterator)
        {
          // We want to be able to intercept any cancellation of the
          // operation.

          if(m_isOperationCancelled)
            break;

          pappso::MassSpectrumPlusCombiner mass_spectrum_plus_combiner(
            template_plus_combiner);

          // std::vector<double> local_bins =
          // mass_spectrum_plus_combiner.getBins();

          // debug_string =
          // QString(
          //"Number of bins: %1\n"
          //"First bins: %2 %3 %4 -- Last bins: %5 %6 %7\n")
          //.arg(bins.size())
          //.arg(bins[0], 0, 'f', 6)
          //.arg(bins[1], 0, 'f', 6)
          //.arg(bins[2], 0, 'f', 6)
          //.arg(bins[bins.size() - 3], 0, 'f', 6)
          //.arg(bins[bins.size() - 2], 0, 'f', 6)
          //.arg(bins[bins.size() - 1], 0, 'f', 6);

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

          // Make the computation.

          // For each mass spectrum all we need is compute its TIC value, that
          // is, the sum of all its intensities (the y value of the DataPoint
          // instances it contains).

          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;
            }

          // Great, at this point we actually have the mass spectrum and we need
          // to get the dt|rt value for it.

          double rt_value = qualified_mass_spectrum_csp->getRtInMinutes();

          // 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())
                {
                  qFatal(
                    "The data are not Bruker timsTOF data. Inconsistency "
                    "found.");
                }

              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();
            }

          double first_key; // that is, either rt or dt.

          if(m_lastIntegrationDataKind == pappso::Enums::DataKind::rt)
            {
              first_key = rt_value;
              // qDebug() << "The dt|rt key is actually a RT key with value:"
              //<< first_key;
            }
          else if(m_lastIntegrationDataKind == pappso::Enums::DataKind::dt)
            {
              first_key = ion_mobility_value;
              // qDebug() << "The dt|rt key is actually a DT key with value:"
              //<< first_key;
            }
          else
            qFatal() << "Programming error.";

          // Before combining, we need to check if some filtering is
          // required as for m/z data.

          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))
            {
              // qDebug() << "There appears to be an m/z-based filtering need.";

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

              filtered_trace = the_keep_filter.filter(filtered_trace);
            }

          if(integration_scope_specs.size())
            {
              // qDebug() << "There appears to be an selection polygon-based "
              //"filtering need.";

              pappso::FilterResampleKeepPointInPolygon the_polygon_filter(
                integration_scope_specs);

              filtered_trace = the_polygon_filter.filter(
                filtered_trace, ion_mobility_value, rt_value);
            }

          // At this point we can make the combination with the filtered trace.

          // We need to iterate in the current_map_trace_sp and find if there is
          // already a map trace by the same dt|rt value in it.

          Map::iterator double_maptrace_map_iterator;

          double_maptrace_map_iterator =
            current_double_maptrace_map_sp->find(first_key);

          // Tells if the key was already found in the map.
          if(double_maptrace_map_iterator !=
             current_double_maptrace_map_sp->end())
            {

              // The map already contained a pair that had the key value ==
              // first_key (remember, either dt or rt). All we have to do is
              // combine the new mass spectrum pointed to by the map value into
              // the MapTrace that is pointed to by the map key.

              // Combine the new mass spectrum right into the found MapTrace.

              // qDebug() << "Before combination, map trace map size: "
              //<< double_maptrace_map_iterator->second.size();

              mass_spectrum_plus_combiner.combine(
                double_maptrace_map_iterator->second, filtered_trace);

              // QString debug_string =
              // QString(
              //"The current combination MapTrace's first element: (%1,%2)\n")
              //.arg(double_maptrace_map_iterator->second.begin()->first,
              // 0,
              //'f',
              // 6)
              //.arg(double_maptrace_map_iterator->second.begin()->second,
              // 0,
              //'f',
              // 6);

              // qDebug().noquote() << debug_string;
            }
          else
            {

              // No other mass spectrum was encountered by the first_key key.
              // So we need to do all the process here.

              // qDebug() << "No node for a mass spectrum having that same
              // first_key " "was found already:"
              //<< first_key;

              // Instantiate a new MapTrace in which we'll combine this and all
              // the future mass spectra having been acquired at this same dt|rt
              // value.

              pappso::MapTrace map_trace;

              // 20220812 See issue #6 at
              // https://gitlab.com/msxpertsuite/minexpert2/-/issues/6
              // Where Kevin Hooijschuur explains that with
              // multiplexed/demultiplexed mobility data from the Agilent 6560
              // IMS-QTOF instrument, the color map that is computed is kind of
              // blurred.

              // I discovered that the example file that Kevin provided me with,
              // produced a final color map with a number of cells for the m/z
              // dimension that did not match the number of bins.
              //
              // I thus understood that that might be the problem and decided to
              // force the final color map to have as many cells in the m/z
              // dimension as there are bins by initializing each newly created
              // map_trace with as many keys as there are bins all with 0-valued
              // intensities.

              for(double bin : bins)
                map_trace[bin] = 0.0;

              mass_spectrum_plus_combiner.combine(map_trace, filtered_trace);

              // QString debug_string =
              // QString(
              //"The brand new rt|dt value: %1 combination MapTrace's first "
              //"element: "
              //"(%2,%3)\n")
              //.arg(first_key)
              //.arg(map_trace.begin()->first, 0, 'f', 6)
              //.arg(map_trace.begin()->second, 0, 'f', 6);

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

              // Finally, store in the map, the map_trace along with its
              // first_key.

              (*current_double_maptrace_map_sp)[first_key] = map_trace;

              // qDebug() << "After combination, map trace map size: "
              //<< map_trace.size();
            }

          // At this point we have processed the qualified mass spectrum.

          emit taskSetStatusMessageAndIncrementCurrentValueSignal(
            "Processing spectra", 1);

          // Now we can go to the next mass spectrum.
          ++current_iterator;
        }
      // End of
      // while(current_iterator != end_iterator)
    }
  // 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 std::map<double, pappso::MapTrace>
  // map was filled in with the dt|rt, MapTrace pairs.

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

  if(mz_integration_params_p->getBinningType() ==
     pappso::MzIntegrationParams::BinningType::ARBITRARY)
    {
      mass_spectrum_plus_combiner.setBins(bins);
    }

  std::vector<double> local_bins = mass_spectrum_plus_combiner.getBins();

  // debug_string = QString(
  //"Number of bins: %1\n"
  //"First bins: %2 %3 %4 -- Last bins: %5 %6 %7\n")
  //.arg(bins.size())
  //.arg(bins[0], 0, 'f', 6)
  //.arg(bins[1], 0, 'f', 6)
  //.arg(bins[2], 0, 'f', 6)
  //.arg(bins[bins.size() - 3], 0, 'f', 6)
  //.arg(bins[bins.size() - 2], 0, 'f', 6)
  //.arg(bins[bins.size() - 1], 0, 'f', 6);

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

  int iter = 0;

  for(auto &&map_sp : vector_of_double_maptrace_maps)
    {
      // qDebug();

      ++iter;

      if(m_isOperationCancelled)
        break;

      for(auto &&pair : *map_sp)
        {
          // first is the rt|dt key
          // second is the pappso::MapTrace

          emit taskSetStatusMessageSignal(
            QString("Consolidating color map data from thread %1").arg(iter));

          emit taskSetCurrentValueSignal(iter);

          double first_key = pair.first;

          pappso::MapTrace current_maptrace = pair.second;

          // Now search in the member if a rt|dt key was found already.

          Map::iterator double_maptrace_map_iterator;

          double_maptrace_map_iterator =
            m_doubleMapTraceMapSPtr->find(first_key);

          // Tells if the key was already found in the map.
          if(double_maptrace_map_iterator != m_doubleMapTraceMapSPtr->end())
            {
              // qDebug() << "Adding new key value:" << first_key;


              // The map already contained a pair that had the key value ==
              // first_key. All we have to do is combine the new mass
              // spectrum pointed to by the map value into the MapTrace that is
              // pointed to by the map key.

              // Combine the new mass spectrum right into the found MapTrace.

              // std::size_t before_size = current_maptrace.size();

              mass_spectrum_plus_combiner.combine(
                double_maptrace_map_iterator->second, current_maptrace);

              // std::size_t after_size = current_maptrace.size();

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

              // No other mass spectrum was encountered by the first_key key.
              // So we need to do all the process here.

              // qDebug() << "No node for a mass spectrum having that same
              // first_key " "was found already:"
              //<< first_key;

              // Instantiate a new MapTrace in which we'll combine this and all
              // the future mass spectra having been acquired at this same dt
              // value.

              pappso::MapTrace map_trace;

              // std::size_t before_size = map_trace.size();

              mass_spectrum_plus_combiner.combine(map_trace, current_maptrace);

              // std::size_t after_size = map_trace.size();

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

              // Finally, store in the map, the map_trace along with its
              // first_key.

              (*m_doubleMapTraceMapSPtr)[first_key] = map_trace;

              // qDebug() << "After combination, map trace map size: " <<
              // map_trace.size();
            }
        }
    }

  // We now need to go through the double map trace map to inspect its
  // characteristics.

  if(!m_doubleMapTraceMapSPtr->size())
    {
      qDebug() << "There are no data in the double maptrace map.";

      return;
    }

  // debug_string =
  // QString(
  //"For the first key (rt or dt) value (%1) of the double|maptrace map, "
  //"this is the MapTrace content: %2\n")
  //.arg(m_doubleMapTraceMapSPtr->begin()->first)
  //.arg(m_doubleMapTraceMapSPtr->begin()->second.toString());

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

  // for(auto iter = m_doubleMapTraceMapSPtr->begin();
  // iter != m_doubleMapTraceMapSPtr->end();
  //++iter)
  //{
  // debug_string =
  // QString(
  //"For the last key (rt or dt) value (%1) of the double|maptrace map, "
  //"this is the MapTrace content: %2\n\n")
  //.arg(iter->first)
  //.arg(iter->second.toString());

  // qDebug().noquote() << debug_string;
  //}

  m_colorMapMinKey = m_doubleMapTraceMapSPtr->begin()->first;
  m_colorMapMaxKey = m_doubleMapTraceMapSPtr->rbegin()->first;

  m_colorMapKeyCellCount = m_doubleMapTraceMapSPtr->size();

  m_colorMapMzCellCount = std::numeric_limits<double>::min();

  // qDebug() << "Initial data to configure the color map:"
  //<< "m_colorMapMinKey: " << m_colorMapMinKey << "m_colorMapMaxKey"
  //<< m_colorMapMaxKey << "m_colorMapKeyCellCount"
  //<< m_colorMapKeyCellCount;

  for(auto &&key_maptrace_pair : *m_doubleMapTraceMapSPtr)
    {
      pappso::MapTrace map_trace = key_maptrace_pair.second;

      if(!map_trace.size())
        continue;

      if(map_trace.size() > m_colorMapMzCellCount)
        m_colorMapMzCellCount = map_trace.size();

      if(map_trace.begin()->first < m_colorMapMinMz)
        m_colorMapMinMz = map_trace.begin()->first;

      if(map_trace.rbegin()->first > m_colorMapMaxMz)
        m_colorMapMaxMz = map_trace.rbegin()->first;
    }

  // qDebug() << "At the end of the map data scrutinization, the following data
  // " "have emerged"
  //<< "m_colorMapMinMz:" << m_colorMapMinMz
  //<< "m_colorMapMaxMz:" << m_colorMapMaxMz
  //<< "m_colorMapMzCellCount:" << m_colorMapMzCellCount;

  return;
}
} // namespace MineXpert

} // namespace MsXpS
