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


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


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


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


/////////////////////// Local includes
#include "QualifiedMassSpectrumVectorMassDataIntegrator.hpp"
#include "ProcessingStep.hpp"
#include <pappsomspp/core/processing/combiners/integrationscoperect.h>
#include <pappsomspp/core/processing/combiners/integrationscoperhomb.h>

namespace MsXpS
{
namespace MineXpert
{

// Needed for qRegisterMetaType
QualifiedMassSpectrumVectorMassDataIntegrator::
  QualifiedMassSpectrumVectorMassDataIntegrator(QObject *parent)
  : libXpertMassCore::MonitoredTaskInterface(parent),
    mp_processingFlow(new ProcessingFlow(dynamic_cast<QObject *>(this)))
{
}

QualifiedMassSpectrumVectorMassDataIntegrator::
  QualifiedMassSpectrumVectorMassDataIntegrator(
    MsRunDataSetCstSPtr ms_run_data_set_csp,
    const ProcessingFlow &processing_flow,
    const QualifiedMassSpectraVectorSPtr
      &qualified_mass_spectra_to_integrate_sp,
    QObject *parent)
  : libXpertMassCore::MonitoredTaskInterface(parent),
    mcsp_msRunDataSet(ms_run_data_set_csp),
    mp_processingFlow(processing_flow.clone(dynamic_cast<QObject *>(this))),
    mcsp_qualifiedMassSpectraToIntegrateVector(
      qualified_mass_spectra_to_integrate_sp)
{
  mp_processingFlow->setMsRunDataSetCstSPtr(mcsp_msRunDataSet);

  // qDebug() << "Going to call setInnermostRanges().";
  setInnermostRanges();
}

QualifiedMassSpectrumVectorMassDataIntegrator::
  ~QualifiedMassSpectrumVectorMassDataIntegrator()
{
}

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

void
QualifiedMassSpectrumVectorMassDataIntegrator::setProcessingFlow(
  const ProcessingFlow &processing_flow)
{
  delete mp_processingFlow;

  mp_processingFlow = processing_flow.clone(dynamic_cast<QObject *>(this));

  // qDebug() << "Going to call setInnermostRanges().";
  setInnermostRanges();
}

const ProcessingFlow &
QualifiedMassSpectrumVectorMassDataIntegrator::getProcessingFlowCstRef() const
{
  return *mp_processingFlow;
}

ProcessingFlow &
QualifiedMassSpectrumVectorMassDataIntegrator::getProcessingFlowRef()
{
  return *mp_processingFlow;
}

ProcessingFlow *
QualifiedMassSpectrumVectorMassDataIntegrator::getProcessingFlow()
{
  return mp_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. Indeed, in a mass spectral data exploration, one
  // typically starts by looking at mass data of MS level 0 (all the levels), or
  // 1 (typically the TIC chrom). Then, one start delving into the depth of the
  // mass data, with higher MS levels. It is thus logical that we only accept
  // mass spectra that have as MS level matching that greatest MS level as
  // recorded in the steps of the processing flow.

  std::size_t greatest_ms_level = mp_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 greatest ms level:" << greatest_ms_level;

          return false;
        }
      // else
      //{
      // qDebug().noquote()
      //<< "Spectrum ms level:" << spectrum_ms_level
      //<< "*does* match greatest 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(
  ProcessingStepCstSPtr processing_step_csp,
  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";

  const MsFragmentationSpec fragmentation_spec =
    processing_step_csp->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))
            return 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()))
            return false;
        }
    }
  // else
  //{
  //// Else, fragmentation is not a criterion for filtering data. So
  //// go on.

  // qDebug() << "The fragmentation spec is *not* valid. This means that the "
  //"check succeeds because fragmentation is not a criterion for "
  //"the filtering of the data.";
  //}

  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.

  // A processing flow contains a number of processing steps.

  for(auto &&step_csp : mp_processingFlow->getStepsCstRef())
    {
      if(!checkMsFragmentation(step_csp, qualified_mass_spectrum_csp))
        return false;
    }

  return true;
}

void
QualifiedMassSpectrumVectorMassDataIntegrator::setInnermostRanges()
{
  // qDebug().noquote() << "Setting innermost ranges for processing flow:"
  //                    << mp_processingFlow->toString(0, "  ");

  m_innermostSteps2D.clear();

  std::pair<double, double> range_pair;

  // The RT_TO_ANY processing type mask is the most generic mask involving RT
  // data, that encompasses both the 1D RT_ONLY_TO_ANY, but also the 2D
  // XX_RT_TO_ANY types, with XX being DT or MZ.

  // There are two situations:
  //
  // 1. when the processing step has a source type that matches the ANY_RT mask
  // is 1D, then we only are interested in the innermost RT range values.

  // 2. when the processing step is 2D, that is, the selection polygon is
  // rectangle. In a 2D selection polygon, each
  // dimension (horizontal, X ; or vertical, Y) is of a different source kind.
  // In a RT_DT color map, one source type is RT and the other is DT, for
  // example. If any one of the source types of the 2D integration scope matches
  // the mask, then the correct integration scope axis range is used (that is
  // the dimension that has source type matching the mask).

  // Note that when asking that relevant 2D steps be stored in the vector
  // m_innermostSteps2D, we specify two options:
  //
  // 1. If only 2D steps need to be stored.
  //
  // 2. If only 2D steps that have a rhomboid shape (skewed rectangle) must be
  // stored. This option is easily understood: if a rectangle is squared, then
  // it is fully described using two ranges, one range for one axis and another
  // range for the other axis. Only rhomboid integration scopes are a challenge
  // for the analysis of the data. The check<DataKind>Range() functions do
  // monitor the m_innermostSteps2D vector for rhomboid-shaped rectangles.

  m_dtRange.first  = qQNaN();
  m_dtRange.second = qQNaN();

  // true: only 2D steps
  // true: only skewed 2D steps
  if(mp_processingFlow->innermostRange(
       "ANY_DT", range_pair, &m_innermostSteps2D, true, true))
    {
      // qDebug() << "innermost DT range:" << range_pair.first << "-"
      //<< range_pair.second;

      m_dtRange.first  = range_pair.first;
      m_dtRange.second = range_pair.second;
    }

  m_mzRange.first  = qQNaN();
  m_mzRange.second = qQNaN();

  // true: only 2D steps
  // true: only skewed 2D steps
  if(mp_processingFlow->innermostRange(
       "ANY_MZ", range_pair, &m_innermostSteps2D, true, true))
    {
      qDebug() << "innermost MZ range:" << range_pair.first << "-"
               << range_pair.second;

      m_mzRange.first  = range_pair.first;
      m_mzRange.second = range_pair.second;

      qDebug() << "m_mzRange.first:" << m_mzRange.first;
      qDebug() << "m_mzRange.second:" << m_mzRange.second;
    }

  m_rtRange.first  = qQNaN();
  m_rtRange.second = qQNaN();

  // When dealing with steps that are created for a 2D selection in a color map,
  // for example, there might be skewed selection polygons. For these selection
  // polygons, a specific procedure needs to be followed to determine if the
  // qualified mass spectrum's parameters (dt, rt, mz) are contained in the
  // skewed polygon. But we first need to actually have a list of such skewed
  // polygons out of the whole list of steps in the processing flow. This is why
  // we have the following true and true arguments to the function call.

  // See how the skewed selection polygons are searched for in the
  // checkDtRange() function for example.

  // true: only 2D steps
  // true: only skewed 2D steps
  if(mp_processingFlow->innermostRange(
       "ANY_RT", range_pair, &m_innermostSteps2D, true, true))
    {
      // qDebug() << "innermost RT range:" << range_pair.first << "-"
      //<< range_pair.second;

      m_rtRange.first  = range_pair.first;
      m_rtRange.second = range_pair.second;
    }

  // Sanity check.
  // It it not possible that not a single step provides a range for RT, given
  // that such a range should have been created upon reading the data from the
  // disk followed by the computation of the TIC chromatogram.
  else
    {
      qFatal(
        "Programming error. Not possible that a processing flow has not a "
        "single step with a retention time range-specifying step.");
    }

  // qDebug() << "The m_innermostSteps2D has now" << m_innermostSteps2D.size()
  //<< "steps";
}

std::size_t
QualifiedMassSpectrumVectorMassDataIntegrator::fillInIntegrationScopeSpecs(
  std::vector<pappso::IntegrationScopeSpec> &integration_scope_specs)
{
  // qDebug()
  //<< "Fill in the selection polygon specs (MZ-related stuff) vector from "
  //"m_innermostSteps2D with size:"
  //<< m_innermostSteps2D.size();

  // This function is responsible for configuring the integration scope specs by
  // looking at m_innermostSteps2D steps that contains MZ data kind. These specs
  // are then used by filters to determine if Trace or Mass Spectrum m/z data
  // points are to be used or not for the integration. All this is required to
  // abide by selection polygons that are not rectangle but are oblique (a.k.a.
  // as rhomboid or skewed).

  for(auto &&step_csp : m_innermostSteps2D)
    {
      if(step_csp->getIntegrationScope()->isRectangle())
        {
          // The selection polygon is rectangle, which means that the ranges
          // extracted from the processing flow at construction time are fully
          // sufficient to characterize the combination work. Indeed, a
          // rectangle can fully be described by two simple ranges: the x-axis
          // range and the y-axis range. This is not the case with the rhomboid
          // shape, where it is necessary to look for each m/z value if it is
          // inside the rhomboid shape or not.

          qDebug()
            << "20250904 Currently iterated step's selection polygon *is* "
               "rectangle. Skipping it."
            << step_csp->toString();

          continue;
        }
      else if(!step_csp->getIntegrationScope()->isRhomboid())
        {
          // At this point we know that the integration scope *must* be of type
          // Rhomb.
          qFatal(
            "The 2D integration scope must be Rhomboid if it is not "
            "Rectangle.");
        }

      qDebug() << "20250904 Iterating in inner most step 2D:"
               << step_csp->toString();

      // There are two kinds of processing types of interest: DT_MZ_TO_ANY and
      // MZ_RT_TO_ANY.

      if(step_csp->getSrcProcessingType(pappso::Enums::Axis::x)
           .bitMatches("MZ"))
        {
          // qDebug() << "Step's x source type matches MZ";

          // We want the integration scope to have MZ as the y-axis dimension.
          // If this is not the case, we need to transpose the rectangle.

          pappso::IntegrationScopeRhombSPtr local_integration_scope_sp =
            std::make_shared<pappso::IntegrationScopeRhomb>(
              static_cast<pappso::IntegrationScopeRhomb>(
                *(dynamic_cast<const pappso::IntegrationScopeRhomb *>(
                  step_csp->getIntegrationScope().get()))));

          local_integration_scope_sp->transpose();

          // Now, y:MZ in the transposed scope.

          // But in the original scope, y is still either DT or RT.
          if(step_csp->getSrcProcessingType(pappso::Enums::Axis::y)
               .bitMatches("DT"))
            {
              // qDebug() << "We are handling a DT_MZ step source type";

              // We want the selection polygon to have x:DT and y:MZ.
              integration_scope_specs.push_back(pappso::IntegrationScopeSpec(
                local_integration_scope_sp, pappso::Enums::DataKind::dt));
            }
          else if(step_csp->getSrcProcessingType(pappso::Enums::Axis::y)
                    .bitMatches("RT"))
            {
              // qDebug() << "We are handling a MZ_RT step source type";

              // We want the selection polygon to have x:RT and y:MZ.
              integration_scope_specs.push_back(pappso::IntegrationScopeSpec(
                local_integration_scope_sp, pappso::Enums::DataKind::rt));
            }
          else
            qFatal() << "Programming error.";
        }
      else if(step_csp->getSrcProcessingType(pappso::Enums::Axis::y)
                .bitMatches("MZ"))
        {
          // qDebug() << "Step's y source type matches MZ";

          // We want the integration scope to have MZ as the y-axis dimension.
          // No need to transpose.

          if(step_csp->getSrcProcessingType(pappso::Enums::Axis::x)
               .bitMatches("DT"))
            {
              // qDebug() << "We are handling a DT_MZ step source type";

              // We want the selection polygon to have x:DT and y:MZ.
              integration_scope_specs.push_back(pappso::IntegrationScopeSpec(
                std::const_pointer_cast<pappso::IntegrationScopeBase>(
                  step_csp->getIntegrationScope()),
                pappso::Enums::DataKind::dt));
            }
          else if(step_csp->getSrcProcessingType(pappso::Enums::Axis::x)
                    .bitMatches("RT"))
            {
              // qDebug() << "We are handling a MZ_RT step source type";

              // We want the selection polygon to have x:RT and y:MZ.
              integration_scope_specs.push_back(pappso::IntegrationScopeSpec(
                std::const_pointer_cast<pappso::IntegrationScopeBase>(
                  step_csp->getIntegrationScope()),
                pappso::Enums::DataKind::rt));
            }
          else
            qFatal() << "Programming error.";
        }
      // else
      // qDebug() << "The step a no source type involving MZ data.";
    }
  // End of
  // for(auto &&step_csp: m_innermostSteps2D)

  return integration_scope_specs.size();
}

bool
QualifiedMassSpectrumVectorMassDataIntegrator::checkInnermostDtRt2DSteps(
  pappso::QualifiedMassSpectrumCstSPtr &qualified_mass_spectrum_csp)
{
  double rt = 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())
        {
          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();
    }

  // qDebug() << "The vector of innermost 2D steps has size:"
  //<< m_innermostSteps2D.size();

  // Now see if we have to also account for 2D selection skewed polygons
  // involving dt and rt.

  for(auto &&step_csp : m_innermostSteps2D)
    {
      // qDebug() << "Iterating in step:" << step.toString();

      // A skewed selection polygon was found.

      if(step_csp->getSrcProcessingType(pappso::Enums::Axis::x)
           .bitMatches("DT") &&
         step_csp->getSrcProcessingType(pappso::Enums::Axis::y)
           .bitMatches("RT"))
        {
          // That had DT and RT axes.

          // if(!step_csp->getSelectionPolygon().contains(
          //      QPointF(ion_mobility_value, rt)))
          if(!step_csp->getIntegrationScope()->contains(
               QPointF(ion_mobility_value, rt)))
            {
              // qDebug() << "Checking step:" << step.toString()
              //<< "for qualified mass spectrum with dt|rt:" << dt << "|"
              //<< rt << "failed";
              return false;
            }
          // else
          // qDebug() << "Checking step:" << step.toString()
          //<< "for qualified mass spectrum with dt|rt:" << dt << "|"
          //<< rt << "succeeded.";
        }
      else if(step_csp->getSrcProcessingType(pappso::Enums::Axis::x)
                .bitMatches("RT") &&
              step_csp->getSrcProcessingType(pappso::Enums::Axis::y)
                .bitMatches("DT"))
        {
          // That had DT and RT axes.

          qWarning() << "The selection polygon should have x:DT and y:RT";

          // if(!step_csp->getSelectionPolygon().contains(
          //      QPointF(rt, ion_mobility_value)))
          if(!step_csp->getIntegrationScope()->contains(
               QPointF(rt, ion_mobility_value)))
            {
              // qDebug() << "Checking step:" << step.toString()
              //<< "for qualified mass spectrum with dt|rt:" << dt << "|"
              //<< rt << "failed";
              return false;
            }
          // else
          // qDebug() << "Checking step:" << step.toString()
          //<< "for qualified mass spectrum with dt|rt:" << dt << "|"
          //<< rt << "succeeded.";
        }
      // else
      // qDebug() << "The step" << step.toString()
      //<< "source types did not match DT|RT nor RT|DT. "
      //"Skipping it.";
    }

  return true;
}

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

  // We are asked to check if the mass spectrum matches the DT range. This
  // means that we can quickly check that agains all DT range that was
  // determined during the construction time of this integrator.

  // Attention, all this is useful only if the processing flow was seen
  // containings steps involving the DT data kind.

  if(std::isnan(m_dtRange.first) || std::isnan(m_dtRange.second))
    {
      // There is no DT range data, which means DT is not a criterion to
      // filter mass spectra. Return true so that the mass spectrum is
      // accounted for.
      return true;
    }

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

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

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

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

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

  // qDebug() << qSetRealNumberPrecision(10) << "dt range:" << m_dtRange.first
  //<< "-" << m_dtRange.second << "The mass spectrum dt value:" << dt;

  if(ion_mobility_value == -1)
    {
      // There is no DT range data in the mass spectrum, which means DT cannot
      // be  a criterion to filter mass spectra. Return true so that the mass
      // spectrum is accounted for.
      return true;
    }

  if(!(ion_mobility_value >= m_dtRange.first &&
       ion_mobility_value <= m_dtRange.second))
    {
      // qDebug() << "Returning false on the check of the dt range.";
      return false;
    }

  // Now see if we have to also account for 2D selection skewed polygons
  // involving dt and rt.

  return checkInnermostDtRt2DSteps(qualified_mass_spectrum_csp);
}

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

  // We are asked to check if the mass spectrum matches the RT range. This
  // means that we can quickly check that agains all RT range that was
  // determined during the construction time of this integrator.

  double rt = qualified_mass_spectrum_csp->getRtInMinutes();

  if(!(rt >= m_rtRange.first && rt <= m_rtRange.second))
    {
      // qDebug() << "rt:" << rt << "failed to check range:"
      //<< "[" << m_rtRange.first << "-" << m_rtRange.second << "]";

      return false;
    }

  // Now see if we have to also account for 2D selection skewed polygons
  // involving dt and rt.

  return checkInnermostDtRt2DSteps(qualified_mass_spectrum_csp);
}


} // namespace MineXpert

} // namespace MsXpS
