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


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


/////////////////////// pappsomspp includes


/////////////////////// Local includes
#include "QualifiedMassSpectrumVectorMassDataIntegrator.hpp"


int qualifiedMassSpectrumVectorMassDataIntegratorMetaTypeId = qRegisterMetaType<
  msxps::minexpert::QualifiedMassSpectrumVectorMassDataIntegrator>(
  "msxps::minexpert::QualifiedMassSpectrumVectorMassDataIntegrator");

int qualMassSpecVecMassDataIntegratorSPtrMetaTypeId = qRegisterMetaType<
  msxps::minexpert::QualifiedMassSpectrumVectorMassDataIntegratorSPtr>(
  "msxps::minexpert::QualifiedMassSpectrumVectorMassDataIntegratorSPtr");


namespace msxps
{
namespace minexpert
{

// Needed for qRegisterMetaType
QualifiedMassSpectrumVectorMassDataIntegrator::
  QualifiedMassSpectrumVectorMassDataIntegrator()
{
}


QualifiedMassSpectrumVectorMassDataIntegrator::
  QualifiedMassSpectrumVectorMassDataIntegrator(
    MsRunDataSetCstSPtr ms_run_data_set_csp,
    const ProcessingFlow &processing_flow,
    QualifiedMassSpectraVectorSPtr &qualified_mass_spectra_to_integrate_sp)
  : mcsp_msRunDataSet(ms_run_data_set_csp),
    m_processingFlow(processing_flow),
    mcsp_qualifiedMassSpectraToIntegrateVector(
      qualified_mass_spectra_to_integrate_sp)
{
  m_processingFlow.setMsRunDataSetCstSPtr(mcsp_msRunDataSet);
  setInnermostRanges();
}


// Needed for qRegisterMetaType
QualifiedMassSpectrumVectorMassDataIntegrator::
  QualifiedMassSpectrumVectorMassDataIntegrator(
    const QualifiedMassSpectrumVectorMassDataIntegrator &other)
  : QObject(),
    mcsp_msRunDataSet(other.mcsp_msRunDataSet),
    m_processingFlow(other.m_processingFlow),
    mcsp_qualifiedMassSpectraToIntegrateVector(
      other.mcsp_qualifiedMassSpectraToIntegrateVector)
{
  m_processingFlow.setMsRunDataSetCstSPtr(mcsp_msRunDataSet);
  setInnermostRanges();
}


QualifiedMassSpectrumVectorMassDataIntegrator::
  ~QualifiedMassSpectrumVectorMassDataIntegrator()
{
}


MsRunDataSetCstSPtr
QualifiedMassSpectrumVectorMassDataIntegrator::getMsRunDataSet() const
{
  return mcsp_msRunDataSet;
}


void
QualifiedMassSpectrumVectorMassDataIntegrator::setProcessingFlow(
  const ProcessingFlow &processing_flow)
{
  m_processingFlow = processing_flow;
  setInnermostRanges();
}


const ProcessingFlow &
QualifiedMassSpectrumVectorMassDataIntegrator::getProcessingFlow() const
{
  return m_processingFlow;
}


pappso::QualifiedMassSpectrumCstSPtr
QualifiedMassSpectrumVectorMassDataIntegrator::checkQualifiedMassSpectrum(
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  if(qualified_mass_spectrum_csp == nullptr)
    qFatal("Cannot be nullptr");
  if(qualified_mass_spectrum_csp.get() == nullptr)
    qFatal("Cannot be nullptr");

  // Verify that the inner MassSpectrum in the QualifiedMassSpectrum is
  // non-nullptr.

  if(qualified_mass_spectrum_csp->getMassSpectrumSPtr() == nullptr)
    {
      // It is nullptr, which means that the binary data of the mass spectrum
      // are not available at the moment.

      // qDebug() << "Need to access the mass data right from the file.";

      std::size_t mass_spectrum_index =
        mcsp_msRunDataSet->getMsRunDataSetTreeCstSPtr()->massSpectrumIndex(
          qualified_mass_spectrum_csp);

      pappso::MsRunReaderCstSPtr ms_run_reader_csp =
        mcsp_msRunDataSet->getMsRunReaderCstSPtr();

      // Note the true bool value to indicate that we want the mz,i binary data.

      qualified_mass_spectrum_csp =
        std::make_shared<pappso::QualifiedMassSpectrum>(
          ms_run_reader_csp->qualifiedMassSpectrum(mass_spectrum_index, true));
    }

  if(qualified_mass_spectrum_csp == nullptr)
      qFatal("QualifiedMassSpectrum cannot be nullptr.");
  
  //qDebug() << "Retention time is:" << qualified_mass_spectrum_csp->getRtInMinutes();

  // In some cases this might happen, for example with the Leptocheline_MS3_DDA_IT_1.mzml
  // test file.
  
  //if(!qualified_mass_spectrum_csp->size())
    //qFatal("The qualified mass spectrum is empty (size is 0).");

  // If the parameter had effectively the binary data in it, then we return it
  // as it was. Otherwise we have created one new.

  return qualified_mass_spectrum_csp;
}


const pappso::MapTrace &
QualifiedMassSpectrumVectorMassDataIntegrator::getMapTrace() const
{
  return m_mapTrace;
}


void 
QualifiedMassSpectrumVectorMassDataIntegrator::setMaxThreadUseCount(std::size_t number)
{
  m_maxThreadUseCount = number;
}


std::size_t
QualifiedMassSpectrumVectorMassDataIntegrator::getMaxThreadUseCount()
{
  return m_maxThreadUseCount;
}


void
QualifiedMassSpectrumVectorMassDataIntegrator::cancelOperation()
{
  m_isOperationCancelled = true;

  //qDebug() << "cancellation asked, setting m_isOperationCancelled to true and "
              //"emitting cancelOperationSignal";

  emit cancelOperationSignal();
}


std::size_t
QualifiedMassSpectrumVectorMassDataIntegrator::bestThreadCount()
{

  std::size_t ideal_thread_count = QThread::idealThreadCount();
  // qDebug() << "ideal_thread_count:" << ideal_thread_count;

  // If the user has limited the count of usable threads, then we need to honour
  // that limitation. Only if the limitation value is not 0 (in which case there
  // is no limitation).
  if(m_maxThreadUseCount != 0 && ideal_thread_count > m_maxThreadUseCount)
    ideal_thread_count = m_maxThreadUseCount;

  // Depending on the number of nodes (node_count) and on the number of thread
  // available, we need to compute the right values to parallelize correctly the
  // computations. For example, if we load a single mass spectrum from an
  // XY-formatted file, then we cannot image to distribute that loading process
  // on more than one thread. This is what this function is for: ensure that the
  // proper values for thread count and nodes per thread are correct. Otherwise
  // crash !

  if(mcsp_qualifiedMassSpectraToIntegrateVector->size() < ideal_thread_count)
    {
      return 1;
    }

  return ideal_thread_count;
}


std::pair<std::size_t, std::size_t>
QualifiedMassSpectrumVectorMassDataIntegrator::bestParallelIntegrationParams([
  [maybe_unused]] std::size_t node_count)
{
  std::size_t ideal_thread_count = bestThreadCount();

  int nodes_per_thread =
    mcsp_qualifiedMassSpectraToIntegrateVector->size() / ideal_thread_count;

  // qDebug() << "ideal_thread_count:" << ideal_thread_count
  //<< "nodes_per_thread:" << nodes_per_thread;

  return std::pair<std::size_t, int>(ideal_thread_count, nodes_per_thread);
}


using Iterator =
  std::vector<pappso::QualifiedMassSpectrumCstSPtr>::const_iterator;

std::vector<std::pair<Iterator, Iterator>>
QualifiedMassSpectrumVectorMassDataIntegrator::calculateIteratorPairs(
  std::size_t thread_count, int nodes_per_thread)
{
  // qDebug();

  std::vector<std::pair<Iterator, Iterator>> iterators;

  Iterator begin_iterator = mcsp_qualifiedMassSpectraToIntegrateVector->begin();
  Iterator end_iterator   = mcsp_qualifiedMassSpectraToIntegrateVector->end();

#if 0

  // Quality check, go through all the qual mass specs and check that they are
  // fine.

  qDebug() << "Quality check: verifying that all the mass spectra in the "
              "vector are correct";

  for(auto &mass_spectrum_csp : *mcsp_qualifiedMassSpectraToIntegrateVector)
    {
      if(nullptr == mass_spectrum_csp || nullptr == mass_spectrum_csp.get())
        qFatal("Cannot be nullptr.");

      qDebug() << "Iterated mass spectrum at rt:"
               << mass_spectrum_csp.get()->getRtInMinutes();
    }

#endif

  // qDebug().noquote() << "Number of mass spectra to distribute in the
  // iterators:"
  //<< std::distance(begin_iterator, end_iterator) << "\n"
  //<< "Thread count:" << thread_count << "\n"
  //<< "nodes_per_thread:" << nodes_per_thread << "\n"
  //<< "First rt:" << begin_iterator->get()->getRtInMinutes() << "\n"
  //<< "Last rt:" << std::prev(end_iterator)->get()->getRtInMinutes() << "\n";

  for(std::size_t iter = 0; iter < thread_count; ++iter)
    {
      // qDebug() << "calculating the iterators for thread index:" << iter;

      std::size_t first_index = iter * nodes_per_thread;

      Iterator first_iterator = begin_iterator + first_index;

      // qDebug() << "First iterator is for mass spectrum at rt:"
      //<< first_iterator->get()->getRtInMinutes();

      // Declare variables
      std::size_t last_index = 0;
      Iterator last_iterator;

      if(iter == (thread_count - 1))
        {
          // qDebug() << "In the last iteration in the for loop.";

          // We are in the last iteration of the loop, so make sure we account
          // for all the remaining spectra.
          last_iterator = end_iterator;
        }
      else
        {

          // We are not yet at the last of the loop iteration. We want to
          // provide the last() iterator as the iterator that matches the
          // spectrum *after* the one we really consider to be the last spectrum
          // to be accounted for. Which is why we add one below.

          last_index = (iter + 1) * nodes_per_thread;

          // qDebug() << "last_index:" << last_index;

          if(last_index >= mcsp_qualifiedMassSpectraToIntegrateVector->size())
            {
              // qDebug() << "Too large, setting last_iterator to
              // end_iterator.";
              last_iterator = end_iterator;
            }
          else
            {
              // qDebug() << "Setting last_iterator to begin + last_index.";
              last_iterator = begin_iterator + last_index;
            }
        }

      // qDebug() << "Prev-last iterator is for mass spectrum at rt:"
      //<< std::prev(last_iterator)->get()->getRtInMinutes();

      iterators.push_back(
        std::pair<Iterator, Iterator>(first_iterator, last_iterator));
    }

#if 0

  qDebug() << "Textual output of all the iterators:";

  // Provide a textual output of all the pointers to the various mass spectra

  // The iterators are defined like this:
  // std::vector<pappso::QualifiedMassSpectrumCstSPtr>::const_iterator;

  std::size_t thread_index = 0;
  for(auto &iterator_pair : iterators)
    {
      qDebug() << "For thread index " << thread_index << "iterators:";

      qDebug() << "first mass spectrum:" << iterator_pair.first->get()
               << "at rt:" << iterator_pair.first->get()->getRtInMinutes();

      qDebug() << "last mass spectrum : "
               << std::prev(iterator_pair.second)->get()
               << "at rt:" << std::prev(iterator_pair.second)->get()->getRtInMinutes();

      ++thread_index;
    }

#endif

  return iterators;
}


std::vector<std::pair<Iterator, Iterator>>
QualifiedMassSpectrumVectorMassDataIntegrator::calculateIteratorPairs(
  Iterator begin_iterator,
  Iterator end_iterator,
  std::size_t thread_count,
  int nodes_per_thread)
{
  std::vector<std::pair<Iterator, Iterator>> iterators;

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

  // qDebug() << "The distance between start and end iterators:" << node_count;

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

      std::size_t first_index = iter * nodes_per_thread;

      Iterator first_iterator = begin_iterator + first_index;

      // Declare variables
      std::size_t last_index = 0;
      Iterator last_iterator;

      if(iter == (thread_count - 1))
        {
          // qDebug() << "In the last iteration in the for loop.";

          // We are in the last iteration of the loop, so make sure we account
          // for all the remaining spectra.
          last_iterator = end_iterator;
        }
      else
        {

          // We are not yet at the last of the loop iteration. We want to
          // provide the last() iterator as the iterator that matches the
          // spectrum *after* the one we really consider to be the last spectrum
          // to be accounted for. Which is why we add one below.

          last_index = (iter + 1) * nodes_per_thread;

          // qDebug() << "Test last_index:" << last_index;

          if(last_index >= node_count)
            {
              // qDebug() << "Too large, setting last_iterator to
              // end_iterator.";
              last_iterator = end_iterator;
            }
          else
            {
              // qDebug() << "Setting last_itertor to begin + last_index.";
              last_iterator = begin_iterator + last_index;
            }
        }

      // qDebug().noquote() << QString(
      //"Prepared visitor index %1 for interval [%2-%3]")
      //.arg(iter)
      //.arg(first_iterator - begin_iterator)
      //.arg(last_iterator - begin_iterator)
      //<< "with distance:"
      //<< std::distance(first_iterator, last_iterator);

      iterators.push_back(
        std::pair<Iterator, Iterator>(first_iterator, last_iterator));
    }

  return iterators;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkMsLevel(
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{

  // Immediately get a pointer to  the qualified mass spectrum that is stored
  // in the node.

  // qDebug().noquote() << "Visiting node:" << &node << "text format:" <<
  // node.toString()
  //<< "with quaified mass spectrum:"
  //<< node.getQualifiedMassSpectrum()->toString();

  uint spectrum_ms_level = qualified_mass_spectrum_csp->getMsLevel();

  // qDebug().noquote() << "Current mass spectrum has ms level:"
  //<< spectrum_ms_level;

  // Get the greatest MS level that is requested amongst all the steps/specs in
  // the processing flow. We are not going going to accept a mass spectrum with
  // a differing MS level.

  size_t greatest_ms_level = m_processingFlow.greatestMsLevel();

  // qDebug().noquote() << "Greatest ms level in the processing flow:"
  //<< greatest_ms_level
  //<< "spectrum_ms_level:" << spectrum_ms_level;

  // Check if the mass spectrum matches the requirement about the MS level. Note
  // that if the greatest MS level is 0, then we account for all data because
  // that means that the MS level is not a criterion for filtering mass spectra
  // during the visit of the ms run data set tree.

  if(greatest_ms_level)
    {
      if(spectrum_ms_level != greatest_ms_level)
        {
          // qDebug().noquote()
          //<< "Spectrum ms level:" << spectrum_ms_level
          //<< "does *not* match greatestest ms level:" << greatest_ms_level;

          return false;
        }
      else
        {
          // qDebug().noquote()
          //<< "Spectrum ms level:" << spectrum_ms_level
          //<< "*does* match greatestest ms level:" << greatest_ms_level;
        }
    }
  else
    {
      // qDebug() << "The greatest ms level is 0, accounting all the spectra.";

      // Just go on, we do take into consideration any MS level.
    }

  // qDebug() << "Returning true.";

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkMsFragmentation(
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  // qDebug();


  if(qualified_mass_spectrum_csp == nullptr)
    qFatal("Cannot be nullptr");

  if(qualified_mass_spectrum_csp.get() == nullptr)
    qFatal("Cannot be nullptr");

  // We want to go through all the ProcessingStep_s in the Flow and for each
  // Step check each Spec's MsFragmentationSpec, if any.

  // As soon as there is a single fragmentation spec that does not match the
  // requirement, we set this variable to false.
  bool should_account_mass_spectrum = true;

  // A processing flow contains a number of processing steps.
  for(auto &&step : m_processingFlow)
    {

      // Each processing step contains a map relating processing types with
      // processing specs.
      for(auto &&type_spec_pair : step->getProcessingTypeSpecMap())
        {

          const ProcessingSpec *spec_p = type_spec_pair.second;

          // Each processing spec might contain a ms fragmentation spec in the
          // form of a pointer to such an instance. If no such fragspec is
          // available (the pointer is nullptr), then an invalid
          // MsFragmentationSpec is returned, which is why we test it here.
          const MsFragmentationSpec fragmentation_spec =
            spec_p->getMsFragmentationSpec();

          if(fragmentation_spec.isValid())
            {
              // qDebug().noquote()
              //<< "ProcessingSpec's MsFragmentationSpec *is*  valid:"
              //<< fragmentation_spec.toString();

              // Check if the fragmentation spec contains at least one
              // precursor. There are not necessarily because the fragmentation
              // spec may simply stipulate a ms level or define one or more
              // precursor spectrum indices.

              if(fragmentation_spec.precursorMzValuesCount())
                {
                  // Get the list of precursor ion data that were recorded while
                  // acquiring the mass data.

                  const std::vector<pappso::PrecursorIonData>
                    &precursor_ion_data_vector =
                      qualified_mass_spectrum_csp->getPrecursorIonData();

                  std::size_t precursor_ion_data_vector_size =
                    precursor_ion_data_vector.size();

                  // There are two situations:
                  //
                  // 1. Evidently, since the fragmentation spec contains actual
                  // precursor mz values, if the mass spectrum contains no
                  // precursor ion data, then it should be dismissed
                  // immediately, without even calling the
                  // fragmentation_spec.containsMzPrecursors() function.

                  // 2. We need to actually check if the fragmentation spec also
                  // lists precursor mz values. The function below checks if
                  // there are matches between the precursor mz values from the
                  // mass spectrum acquisition and the precursor mz values
                  // listed in the MsFragmentationSpec. The function returns
                  // true if at least one match could be performed.

                  if(!precursor_ion_data_vector_size ||
                     !fragmentation_spec.containsMzPrecursors(
                       precursor_ion_data_vector))
                    {
                      should_account_mass_spectrum = false;
                    }
                  // else
                  // qDebug().noquote()
                  //<< "Mass spectrum:" << qualified_mass_spectrum_csp.get()
                  //<< "matched the MsFragmentationSpec's mz precursor "
                  //"values. "
                  //<< "Mass spectrum prec mz values: "
                  //<< qualified_mass_spectrum_csp
                  //->getPrecursorDataMzValuesAsString()
                  //<< "and MsFragmentationSpec prec mz values: "
                  //<< fragmentation_spec.mzPrecursorsToString();
                }

              if(fragmentation_spec.precursorSpectrumIndicesCount())
                {
                  // qDebug() << "There are precursor spectrum indices.";

                  if(!fragmentation_spec.containsSpectrumPrecursorIndex(
                       qualified_mass_spectrum_csp
                         ->getPrecursorSpectrumIndex()))
                    {
                      should_account_mass_spectrum = false;
                    }
                }
            }
          else
            {
              // Else, fragmentation is not a criterion for filtering data. So
              // go on.

              // qDebug() << "The fragmentation spec is *not* valid.";
            }
        }
    }

  // qDebug() << "The MsFragmentation ends out to check:"
  //<< should_account_mass_spectrum << "for qualified mass spectrum: "
  //<< qualified_mass_spectrum_csp.get()
  //<< "at rt:" << qualified_mass_spectrum_csp.get()->getRtInMinutes();

  return should_account_mass_spectrum;
}


void
QualifiedMassSpectrumVectorMassDataIntegrator::setInnermostRanges()
{

  double start = std::numeric_limits<double>::max();
  double end   = std::numeric_limits<double>::min();

  // The function below returns true only if a spec has been found that that
  // type "RT_TO_ANY"
  if(m_processingFlow.innermostRtRange(start, end))
    {
      m_rtRange.first  = start;
      m_rtRange.second = end;
    }
  else
    {
      m_rtRange.first  = std::numeric_limits<double>::max();
      m_rtRange.second = std::numeric_limits<double>::min();
    }

  start = std::numeric_limits<double>::max();
  end   = std::numeric_limits<double>::min();

  // The function below returns true only if a spec has been found that that
  // type "DT_TO_ANY"
  if(m_processingFlow.innermostDtRange(start, end))
    {
      m_dtRange.first  = start;
      m_dtRange.second = end;
    }
  else
    {
      m_dtRange.first  = std::numeric_limits<double>::max();
      m_dtRange.second = std::numeric_limits<double>::min();
    }

  start = std::numeric_limits<double>::max();
  end   = std::numeric_limits<double>::min();

  // The function below returns true only if a spec has been found that that
  // type "DT_TO_ANY"
  if(m_processingFlow.innermostMzRange(start, end))
    {
      m_mzRange.first  = start;
      m_mzRange.second = end;
    }
  else
    {
      m_mzRange.first  = std::numeric_limits<double>::max();
      m_mzRange.second = std::numeric_limits<double>::min();
    }

  // qDebug() << "Innermost RT range:" << m_rtRange.first << "->"
  //<< m_rtRange.second << "Innermost DT range:" << m_dtRange.first
  //<< "->" << m_dtRange.second;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkRtRange(
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  double rt = qualified_mass_spectrum_csp->getRtInMinutes();

  bool res = (rt >= m_rtRange.first && rt <= m_rtRange.second);

  // if(!res)
  //{
  // qDebug() << "Rt range did not check:" << m_rtRange.first << "->"
  //<< m_rtRange.second << "with value:" << rt;
  //}
  // else
  //{
  // qDebug() << "Rt range did check:" << m_rtRange.first << "->"
  //<< m_rtRange.second << "with value:" << rt;
  //}

  return res;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkDtRange(
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  // Only try to actually check the values if that the dt time values were
  // intented to be accounted for. Otherwise just return true because the dt
  // times are not a criterion.

  if(m_dtRange.first == std::numeric_limits<double>::max() ||
     m_dtRange.second == std::numeric_limits<double>::min())
    {
      // qDebug()
      //<< "m_dtRange.first is max() and m_dtRange.second is min(). This means
      //" "that DT range is not a criterion. Returning true.";
      return true;
    }

  double dt = qualified_mass_spectrum_csp->getDtInMilliSeconds();

  if(dt != -1)
    {
      // qDebug() << "dt:" << dt << "m_dtRange.first:" << m_dtRange.first
      //<< "m_dtRange.second:" << m_dtRange.second;

      return (dt >= m_dtRange.first && dt <= m_dtRange.second);
    }

  // qDebug() << "Returning true.";

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingStep(
  const ProcessingStep &step,
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  //  qDebug();

  // A processing step is a collection of mapped items:
  // std::map<ProcessingType, ProcessingSpec> m_processingTypeSpecMap;
  //
  // For each item, we need to check if the node matches the spec.

  for(auto &&pair : step.getProcessingTypeSpecMap())
    {
      if(checkProcessingSpec(pair, qualified_mass_spectrum_csp))
        {
          // qDebug() << "The node:" << &node << "matches the iterated spec.";
        }
      else
        {
          // qDebug() << "The node:" << &node
          //<< "does not match the iterated spec. Returning false.";

          return false;
        }
    }

  // qDebug() << "The node:" << &node
  //<< "matched all the processing specs: matching the step: returning "
  //"true.";

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingStep(
  const ProcessingStep &step,
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp,
  pappso::MassDataCombinerInterface &mass_spectrum_combiner)
{
  //  qDebug();

  // A processing step is a collection of mapped items:
  // std::map<ProcessingType, ProcessingSpec> m_processingTypeSpecMap;
  //
  // For each item, we need to check if the node matches the spec.

  for(auto &&pair : step.getProcessingTypeSpecMap())
    {
      if(checkProcessingSpec(
           pair, qualified_mass_spectrum_csp, mass_spectrum_combiner))
        {
          // qDebug() << "The node:" << &node << "matches the iterated spec.";
        }
      else
        {
          // qDebug() << "The node:" << &node
          //<< "does not match the iterated spec. Returning false.";

          return false;
        }
    }

  // qDebug() << "The node:" << &node
  //<< "matched all the processing specs: matching the step: returning "
  //"true.";

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingSpec(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  //  qDebug();

  // This is the most elemental component of a ProcessingFlow, so here we
  // actually look into the node.

  // A Processing spec defines a range of acceptable values and holds a
  // pointer to a MsFragmentationSpec :

  // MsFragmentationSpec *mpa_msFragmentationSpec = nullptr;
  // double m_start = std::numeric_limits<double>::max();
  // double m_end   = std::numeric_limits<double>::min();

  // qDebug() << "Node:" << &node << "with qualified mass spectrum:"
  //<< node.getQualifiedMassSpectrum()->toString();

  // In the step, each processing spec is associated to a processing type in a
  // map. We get the pair of both to check the compliance of the node to the
  // range and the processing type.

  const ProcessingSpec *spec_p = type_spec_pair.second;

  if(qualified_mass_spectrum_csp == nullptr)
    qFatal("Cannot be nullptr");

  if(qualified_mass_spectrum_csp.get() == nullptr)
    qFatal("Cannot be nullptr");

  const MsFragmentationSpec fragmentation_spec =
    spec_p->getMsFragmentationSpec();

  if(fragmentation_spec.isValid())
    {
      if(fragmentation_spec.precursorMzValuesCount())
        {
          const std::vector<pappso::PrecursorIonData>
            &precursor_ion_data_vector =
              qualified_mass_spectrum_csp->getPrecursorIonData();

          if(!fragmentation_spec.containsMzPrecursors(
               precursor_ion_data_vector))
            {
              return false;
            }
        }

      if(fragmentation_spec.precursorSpectrumIndicesCount())
        {
          // qDebug() << "There are precursor spectrum indices.";

          if(!fragmentation_spec.containsSpectrumPrecursorIndex(
               qualified_mass_spectrum_csp->getPrecursorSpectrumIndex()))
            {
              // qDebug() << "Node:" << &node
              //<< "The precursor spectrum index does not match.";

              return false;
            }
          else
            {
              // qDebug()
              //<< "The mass spectrum has a precursor spectrum index of:"
              //<< qualified_mass_spectrum_csp->getPrecursorSpectrumIndex()
              //<< "and it matches one of the requested list.";
            }
        }
    }
  else
    {
      // Else, fragmentation is not a criterion for filtering data. So go
      // on.

      // qDebug() << "Node:" << &node
      //<< "The fragmentation spec is *not* valid. Accounting the "
      //"spectrum.";
    }

  // qDebug() << "Node:" << &node << "Frag spec ok, going on with the spec
  // check.";

  ProcessingType type = type_spec_pair.first;

  if(type.bitMatches("RT_TO_ANY") || type.bitMatches("FILE_TO_MZ"))
    {
      //      qDebug();
      if(!checkProcessingTypeByRt(type_spec_pair, qualified_mass_spectrum_csp))
        {
          // qDebug() << "Node:" << &node << "Checking by Rt failed.";

          return false;
        }
    }
  else if(type.bitMatches("MZ_TO_ANY"))
    {
      //      qDebug();
      if(!checkProcessingTypeByMz(type_spec_pair, qualified_mass_spectrum_csp))
        {
          // qDebug() << "Node:" << &node << "Checking by Mz failed.";

          return false;
        }
    }
  else if(type.bitMatches("DT_TO_ANY"))
    {
      if(!checkProcessingTypeByDt(type_spec_pair, qualified_mass_spectrum_csp))
        {
          return false;
        }
    }

  // At this point we seem to understand that the node matched!

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingSpec(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp,
  pappso::MassDataCombinerInterface &mass_spectrum_combiner_sp)
{
  //  qDebug();

  // This is the most elemental component of a ProcessingFlow, so here we
  // actually look into the node.

  // qDebug() << "Node:" << &node << "with qualified mass spectrum:"
  //<< node.getQualifiedMassSpectrum()->toString();

  const ProcessingSpec *spec_p = type_spec_pair.second;

  if(qualified_mass_spectrum_csp == nullptr)
    qFatal("Cannot be nullptr");

  if(qualified_mass_spectrum_csp.get() == nullptr)
    qFatal("Cannot be nullptr");

  const MsFragmentationSpec fragmentation_spec =
    spec_p->getMsFragmentationSpec();

  if(fragmentation_spec.isValid())
    {
      // qDebug() << "Node:" << &node << "Spec's fragmentation spec *is*
      // valid:"
      //<< fragmentation_spec.toString();

      if(fragmentation_spec.precursorMzValuesCount())
        {
          if(!fragmentation_spec.containsMzPrecursor(
               qualified_mass_spectrum_csp->getPrecursorMz()))
            {
              // qDebug() << "Node:" << &node
              //<< "The precursor mz does not match.";

              return false;
            }
        }

      if(fragmentation_spec.precursorSpectrumIndicesCount())
        {
          // qDebug() << "There are precursor spectrum indices.";

          if(!fragmentation_spec.containsSpectrumPrecursorIndex(
               qualified_mass_spectrum_csp->getPrecursorSpectrumIndex()))
            {
              // qDebug() << "Node:" << &node
              //<< "The precursor spectrum index does not match.";

              return false;
            }
          else
            {
              // qDebug()
              //<< "The mass spectrum has a precursor spectrum index of:"
              //<< qualified_mass_spectrum_csp->getPrecursorSpectrumIndex()
              //<< "and it matches one of the requested list.";
            }
        }
    }
  else
    {
      // Else, fragmentation is not a criterion for filtering data. So go
      // on.

      // qDebug() << "Node:" << &node
      //<< "The fragmentation spec is *not* valid. Accounting the "
      //"spectrum.";
    }

  // qDebug() << "Node:" << &node << "Frag spec ok, going on with the spec
  // check.";

  ProcessingType type = type_spec_pair.first;

  if(type.bitMatches("RT_TO_ANY") || type.bitMatches("FILE_TO_MZ"))
    {
      //      qDebug();
      if(!checkProcessingTypeByRt(type_spec_pair, qualified_mass_spectrum_csp))
        {
          // qDebug() << "Node:" << &node << "Checking by Rt failed.";

          return false;
        }
    }
  else if(type.bitMatches("MZ_TO_ANY"))
    {
      //      qDebug();
      if(!checkProcessingTypeByMz(type_spec_pair,
                                  qualified_mass_spectrum_csp,
                                  mass_spectrum_combiner_sp))
        {
          // qDebug() << "Node:" << &node << "Checking by Mz failed.";

          return false;
        }
    }
  else if(type.bitMatches("DT_TO_ANY"))
    {
      // qDebug();
      if(!checkProcessingTypeByDt(type_spec_pair, qualified_mass_spectrum_csp))
        {
          return false;
        }
    }

  // At this point we seem to understand that the node matched!

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingTypeByRt(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  //  qDebug();

  const ProcessingSpec *spec_p = type_spec_pair.second;

  if(spec_p->hasValidRange())
    {
      double rt = qualified_mass_spectrum_csp->getRtInMinutes();

      if(rt >= spec_p->getStart() && rt <= spec_p->getEnd())
        {
          //      qDebug() << "Returning true for Rt.";
          return true;
        }
      else
        {
          return false;
        }
    }

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingTypeByMz(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  [[maybe_unused]] pappso::QualifiedMassSpectrumCstSPtr
    &qualified_mass_spectrum_csp)
{
  //  qDebug();

  // Are there specs about the m/z range to be accounted for in the
  // computation?

  const ProcessingSpec *spec_p = type_spec_pair.second;

  if(spec_p->hasValidRange())
    {
      // qDebug() << "Needed a m/z range filtering:" << spec_p->toString();

      // The point is that if there are more than one spec in the the flow's
      // steps that limit the m/z range, we want to be sure to perform the
      // combination using the innermost range. So ask the ProcessingFlow to
      // actually compute that innermost m/z range.

      // Make a local copy of the type_spec_pair

      std::pair<ProcessingType, ProcessingSpec *> local_type_spec_pair(
        type_spec_pair);

      if(m_processingFlow.innermostMzRange(local_type_spec_pair))
        {
          // qDebug() << "MZ range limitations were found.";

          m_limitMzRangeStart = local_type_spec_pair.second->getStart();
          m_limitMzRangeEnd   = local_type_spec_pair.second->getEnd();
        }
      else
        qFatal(
          "Cannot be that there is no innermost m/z range if there is a "
          "valid "
          "m/z range in a spec.");
    }

  // Return true because we'll account that node whatever the mz range
  // criterion. But, the limits will be accounted for in the visit() function
  // at the moment of TIC calculation.

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingTypeByMz(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  [[maybe_unused]] pappso::QualifiedMassSpectrumCstSPtr
    &qualified_mass_spectrum_csp,
  pappso::MassDataCombinerInterface &mass_spectrum_combiner)
{
  //  qDebug();

  // Are there specs about the m/z range to be accounted for in the
  // computation?

  const ProcessingSpec *spec_p = type_spec_pair.second;

  if(spec_p->hasValidRange())
    {
      // qDebug() << "Needed a m/z range filtering:" << spec_p->toString();

      // The point is that if there are more than one spec in the the flow's
      // steps that limit the m/z range, we want to be sure to perform the
      // combination using the innermost range. So ask the ProcessingFlow to
      // actually compute that innermost m/z range.

      // Make a local copy of the type_spec_pair

      std::pair<ProcessingType, ProcessingSpec *> local_type_spec_pair(
        type_spec_pair);

      if(m_processingFlow.innermostMzRange(local_type_spec_pair))
        {
          mass_spectrum_combiner.setFilterResampleKeepXRange(
            pappso::FilterResampleKeepXRange(
              local_type_spec_pair.second->getStart(),
              local_type_spec_pair.second->getEnd()));

          m_limitMzRangeStart = local_type_spec_pair.second->getStart();
          m_limitMzRangeEnd   = local_type_spec_pair.second->getEnd();
        }
      else
        qFatal(
          "Cannot be that there is no innermost m/z range if there is a "
          "valid "
          "m/z range in a spec.");
    }

  // Return true because we'll account that node whatever the mz range
  // criterion. But, the limits will be accounted for in the visit() function
  // at the moment of TIC calculation.

  return true;
}


bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkProcessingTypeByDt(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  //  qDebug();

  const ProcessingSpec *spec_p = type_spec_pair.second;

  if(spec_p->hasValidRange())
    {
      // qDebug() << "spec_p has valid range:" << spec_p->getStart() << "->"
      //<< spec_p->getEnd();

      double dt = qualified_mass_spectrum_csp->getDtInMilliSeconds();

      if(dt >= spec_p->getStart() && dt <= spec_p->getEnd())
        return true;
      else
        return false;
    }

  return true;
}


} // namespace minexpert

} // namespace msxps
