/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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 <cmath>
#include <vector>


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

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


/////////////////////// Local includes
#include "ProcessingFlow.hpp"
#include "ProcessingStep.hpp"
#include "Application.hpp"

namespace MsXpS
{
namespace MineXpert
{


ProcessingFlow::ProcessingFlow(QObject *parent): QObject(parent)
{
}

ProcessingFlow::ProcessingFlow(MsRunDataSetCstSPtr ms_run_data_set_csp,
                               QObject *parent)
  : QObject(parent), mcsp_msRunDataSet(ms_run_data_set_csp)
{
}

ProcessingFlow::~ProcessingFlow()
{
  // Now clear the vector.
  m_steps.clear();
}

ProcessingFlow *
ProcessingFlow::clone(QObject *parent) const
{
  ProcessingFlow *copy = new ProcessingFlow(parent);
  copy->initialize(*this);

  return copy;
}

void
ProcessingFlow::initialize(const ProcessingFlow &other)
{

  mcsp_msRunDataSet = other.mcsp_msRunDataSet;

  if(other.mp_defaultMzIntegrationParams != nullptr)
    mp_defaultMzIntegrationParams =
      other.mp_defaultMzIntegrationParams->clone(this);

  m_defaultMsFragmentationSpec = other.m_defaultMsFragmentationSpec;

  m_steps.assign(other.m_steps.begin(), other.m_steps.end());

  m_filterMap = other.m_filterMap;
}

void
ProcessingFlow::setMsRunDataSetCstSPtr(MsRunDataSetCstSPtr ms_run_data_set_csp)
{
  mcsp_msRunDataSet = ms_run_data_set_csp;
}

MsRunDataSetCstSPtr
ProcessingFlow::getMsRunDataSetCstSPtr() const
{
  return mcsp_msRunDataSet;
}

const std::vector<ProcessingStepCstSPtr> &
ProcessingFlow::getStepsCstRef() const
{
  return m_steps;
}

std::vector<ProcessingStepCstSPtr> &
ProcessingFlow::getStepsRef()
{
  return m_steps;
}

void
ProcessingFlow::setDefaultMzIntegrationParams(
  const pappso::MzIntegrationParams &mz_integration_params)
{
  if(mp_defaultMzIntegrationParams)
    delete mp_defaultMzIntegrationParams;
  mp_defaultMzIntegrationParams = mz_integration_params.clone(this);
}

pappso::MzIntegrationParams *
ProcessingFlow::getDefaultMzIntegrationParams()
{
  return mp_defaultMzIntegrationParams;
}

/* Heap-allocated*/
pappso::MzIntegrationParams *
ProcessingFlow::getSensibleMzIntegrationParams() const
{
  // pappso::MzIntegrationParams are a set of parameters that are required
  // to configure the way mass spectra (m/z data) are handled mathematically.
  // In particular, if two mass spectra are to be combined into a new third
  // mass spectrum, a number of criteria can influence the result.
  //
  // 1. Do all the points from both spectra (as long as they have distinct m/z
  // values) need to be found as such in the combined spectrum ?
  //
  // 2. Are bins required to pool together points that have their m/z value so
  // close that they are actually a single point in a trace ? If so, the
  // MzIntegrationParams allow the user to define how bins are going to be
  // crafted for binning the m/z values in a meaningful manner depending on the
  // resolving power of the instrument.

  // Because users do most often work on many mass spectra acquired on the same
  // mass spectrometer, the MzIntegrationParams do not change for a while. It is
  // thus desirable to establish a mechanism by which the user can trigger
  // the storage on disk of the binning logic parameters that do no change.
  //
  // The MzIntegrationParams are dual:
  //
  // 1. The binning logic params are those that
  // configure the size of the bins and it is desirable that they be stored
  // on disk because they are the same over a large analysis session.
  // One member, m_binSizeDivisor is set by default at a value of 6, because
  // empirically this is the best value to have the best shaped peaks. The
  // bottom line of this is that 6 points are required to nicely shape
  // a peak with a calculated FWHM value.
  //
  // 2. The other two members m_smallestMz and m_greatestMz values describe
  // the very first and very last points of the spectrum. If binning is
  // required, the generated bins span the [ m_smallestMz -- m_greatestMz ] m/z
  // range. These two values are specific, by definition, of the data set
  // at hand and are thus not useful to be stored on disk. Indeed, these
  // two values are not stored on disk.
  //
  // There is a last parameter that is not directly involved in the binning
  // work, the bit that indicates if the 0-value points need to be removed or
  // not.

  // Get the MzIntegrationParams from the MsRunDataSet because that one
  // will contain the proper m/z values that we can store for later use.
  // HEAP ALLOCATED
  pappso::MzIntegrationParams *mz_integration_params_from_data_set_p =
    mcsp_msRunDataSet->craftMzIntegrationParams();

  // This call cannot fail.
  Q_ASSERT(mz_integration_params_from_data_set_p);

  double smallest_mz = mz_integration_params_from_data_set_p->getSmallestMz();
  double greatest_mz = mz_integration_params_from_data_set_p->getGreatestMz();

  // The first approach is to check if there are MzIntegrationParams stored
  // as settings on disk.
  pappso::MzIntegrationParams *heap_allocated_mz_integration_params_p = nullptr;

  pappso::MzIntegrationParams settings_mz_integ_params;
  pappso::MzIntegrationParams::InitializationResult saved_settings_result =
    static_cast<Application *>(qApp)->getMzIntegrationParamsFromSavedSettings(
      settings_mz_integ_params);

  if(saved_settings_result ==
     pappso::MzIntegrationParams::InitializationResult::FULL)
    {
      // Good, all the parameters are set.
      heap_allocated_mz_integration_params_p =
        new pappso::MzIntegrationParams();
      heap_allocated_mz_integration_params_p->initialize(
        settings_mz_integ_params);
      heap_allocated_mz_integration_params_p->setMzValues(smallest_mz,
                                                          greatest_mz);
      return heap_allocated_mz_integration_params_p;
    }

  // If the binning logic parameters were not full, that might be because
  // some values were not set because they are not needed, in particular, if
  // there is no binning, then the other binning logic parameters are not
  // needed, which in fact makes the binning logic parameters full if
  // decimal places and remove zero-val data ponts were also set.
  if(static_cast<bool>(
       saved_settings_result &
       pappso::MzIntegrationParams::InitializationResult::BINNING_TYPE))
    {
      if(settings_mz_integ_params.getBinningType() ==
         pappso::MzIntegrationParams::BinningType::NONE)
        {
          if(static_cast<bool>(saved_settings_result &
                               pappso::MzIntegrationParams::
                                 InitializationResult::DECIMAL_PLACES))
            {
              if(static_cast<bool>(
                   saved_settings_result &
                   pappso::MzIntegrationParams::InitializationResult::
                     REMOVE_ZERO_DATA_POINTS))
                {
                  // This is enough to return these values.
                  heap_allocated_mz_integration_params_p =
                    new pappso::MzIntegrationParams();
                  heap_allocated_mz_integration_params_p->initialize(
                    settings_mz_integ_params, saved_settings_result);
                  heap_allocated_mz_integration_params_p->setMzValues(
                    smallest_mz, greatest_mz);
                  return heap_allocated_mz_integration_params_p;
                }
            }
        }
    }

  // Are there non-nullptr default MzIntegrationParams ?

  if(mp_defaultMzIntegrationParams != nullptr)
    {
      heap_allocated_mz_integration_params_p =
        new pappso::MzIntegrationParams();
      heap_allocated_mz_integration_params_p->initialize(
        *mp_defaultMzIntegrationParams);
      heap_allocated_mz_integration_params_p->setMzValues(smallest_mz,
                                                          greatest_mz);
      return heap_allocated_mz_integration_params_p;
    }

  // Try to get the MzIntegrationParams from the most recent
  // processing flow step.
  const pappso::MzIntegrationParams *recent_mz_integration_params_p =
    mostRecentMzIntegrationParams();

  if(recent_mz_integration_params_p != nullptr)
    {
      heap_allocated_mz_integration_params_p =
        new pappso::MzIntegrationParams();
      heap_allocated_mz_integration_params_p->initialize(
        *recent_mz_integration_params_p);
      heap_allocated_mz_integration_params_p->setMzValues(smallest_mz,
                                                          greatest_mz);
      return heap_allocated_mz_integration_params_p;
    }

  // Last resort... use the MzIntegrationParams gotten from the MsRunDataSet.
  return mz_integration_params_from_data_set_p;
}

void
ProcessingFlow::setDefaultMsFragmentationSpec(
  const MsFragmentationSpec &ms_fragmentation_spec)
{
  // qDebug();

  m_defaultMsFragmentationSpec = ms_fragmentation_spec;
}

const MsFragmentationSpec &
ProcessingFlow::getDefaultMsFragmentationSpec() const
{
  return m_defaultMsFragmentationSpec;
}

const std::map<QString, pappso::FilterNameInterfaceCstSPtr> &
ProcessingFlow::getFilterMapCstRef() const
{
  return m_filterMap;
}

std::map<QString, pappso::FilterNameInterfaceCstSPtr> &
ProcessingFlow::getFilterMapRef()
{
  return m_filterMap;
}

void
ProcessingFlow::addFilter(pappso::FilterNameInterfaceCstSPtr filter_csp)
{
  QString filter_name = filter_csp->name();

  // qDebug() << "The filter has name:" << filter_name
  //          << "and is:" << filter_csp->toString();

  using Pair     = std::pair<QString, pappso::FilterNameInterfaceCstSPtr>;
  using Map      = std::map<QString, pappso::FilterNameInterfaceCstSPtr>;
  using Iterator = Map::iterator;

  // qDebug() << "Now inserting the new filter in the map.";

  std::pair<Iterator, bool> res =
    m_filterMap.insert(Pair(filter_name, filter_csp));

  if(!res.second)
    {
      // Insertion failed because one other same filter by that name was
      // already in the map. Set the new value.
      // qDebug() << "An instance by that name existed already.";

      res.first->second = filter_csp;
    }
  // else
    // qDebug() << "The insertion of the filter went fine.";
}

std::size_t
ProcessingFlow::removeFilter(const QString &filter_name)
{
  return m_filterMap.erase(filter_name);
}

pappso::FilterNameInterfaceCstSPtr
ProcessingFlow::filter(const QString &filter_name) const
{
  // qDebug() << "In ProcessingFlow:" << this;
  // qDebug() << "Searching for filter:" << filter_name;

  std::map<QString, pappso::FilterNameInterfaceCstSPtr>::const_iterator
    found_iterator = m_filterMap.find(filter_name);

  if(found_iterator == m_filterMap.end())
    return nullptr;

  return found_iterator->second;
}

bool
ProcessingFlow::innermostRange(
  ProcessingType src_processing_type_mask,
  std::pair<double, double> &out_range,
  std::vector<ProcessingStepCstSPtr> *out_processing_steps_p,
  bool store_only_2d_steps,
  bool store_only_rhomboid) const
{
  // qDebug() << "Searching for innermost range for source ProcessingType mask:
  // "
  //          << src_processing_type_mask.toBriefDesc()
  //          << "in a ProcessingFlow holding" << getStepsCstRef().size()
  //          << "steps";

  // We are asked to get the innermost range for all steps having their
  // processing type matching the processing_type_mask.

  // processing_type_mask specifies a source processing type, that might
  // be someghing like "ANY_RT" or "ANY_DT" or "ANY_MZ".

  // We iterate in the steps of this processing flow and check that their
  // source type matches processing_type_mask. If so, we look at the axis
  // data kind and check for the innermost range.

  ProcessingType src_processing_type("NOT_SET");
  ProcessingType x_src_processing_type("NOT_SET");
  ProcessingType y_src_processing_type("NOT_SET");

  double inner_range_start = qQNaN();
  double inner_range_end   = qQNaN();

  // if(qIsNaN(inner_range_start))
  //   qDebug() << "It is indeed NaN!";

  bool is_first_iterated_item = true;

  ProcessingStepCstSPtr innermost_step_csp = nullptr;

  // A ProcessingFlow is a vector of ProcessingStepCstSPtr.

  for(const ProcessingStepCstSPtr &step_csp : m_steps)
    {
      // qDebug().noquote() << "Iterating in step:\n"
      //                    << step_csp->toString(0, "  ") << step_csp.get();

      double range_start;
      double range_end;

      ProcessingType iter_x_processing_type = step_csp->m_xSrcType;
      ProcessingType iter_y_processing_type = step_csp->m_ySrcType;

      // qDebug().noquote() << "This step has X axis source type:"
      //                    << iter_x_processing_type.toBriefDesc()
      //                    << "and Y axis source type:"
      //                    << iter_y_processing_type.toBriefDesc();

      // Check the processing type. We may get here any kind of mono- or
      // bi-dimensional processing type, like MZ_TO_ANY or DT_RT_TO_ANY.

      if(!iter_x_processing_type.bitMatches(src_processing_type_mask) &&
         !iter_y_processing_type.bitMatches(src_processing_type_mask))
        {
          // qDebug() << "The iterated step has none of its source types
          // matching " "the searched type. Skipping it.";

          continue;
        }

      // Is the currently iterated step mono- or two-dimensional ?
      if(step_csp->msp_integrationScope->is1D())
        {
          // This is the simplest situation, there is only one dimension,
          // the x dimension, that is of interest.

          // qDebug() << "This step is 1D";

          step_csp->msp_integrationScope->range(
            pappso::Enums::Axis::x, range_start, range_end);

          if(is_first_iterated_item)
            {
              inner_range_start = range_start;
              inner_range_end   = range_end;

              // qDebug() << "First iterated step, with inner range:"
              //          << inner_range_start << "-" << inner_range_end;

              if(!store_only_2d_steps)
                {
                  // qDebug() << "Step is 1D, setting step as "
                  //             "innermost step as requested.";
                  innermost_step_csp = step_csp;
                }

              is_first_iterated_item = false;
            }
          else
            {
              // qDebug() << "Not first iterated step.";

              // This is not the first step we iterate into,
              // so there are already range values, that we
              // need to compare with those of this last step so as
              // to update the innermost range values if necessary.

              // This variable will report if the currently iterated-into
              // step has range values that make them innermost with
              // respect to the previous got ones.
              bool was_at_least_one_range_value_inner = false;

              // If the new x-axis value is greater than the previous one,
              // then that means that we are reducing the range at the
              // left side, so this new range start value has to become
              // the new innermost range start value.

              if(range_start > inner_range_start)
                {
                  inner_range_start = range_start;

                  was_at_least_one_range_value_inner = true;

                  // qDebug() << "New inner range start:" <<
                  // inner_range_start;
                }

              // If the new x-axis value is smaller that the previous one,
              // then that means that we are reducing the range at the
              // right side, so this new range end value has to become the
              // new innmermost range end value.
              if(range_end < inner_range_end)
                {
                  inner_range_end = range_end;

                  was_at_least_one_range_value_inner = true;
                  // qDebug() << "New inner range end:" <<
                  // inner_range_end;
                }

              if(was_at_least_one_range_value_inner && !store_only_2d_steps)
                {
                  // qDebug() << "Even if 1D, setting step as innermost
                  // step as " "requested.";
                  innermost_step_csp = step_csp;
                }
            }

          continue;
        }
      else
        {
          // qDebug() << "This step is 2D, either Rect or Rhomb";

          // At this point, we know the selection polygon is either
          // Rectangle or Rhomboid, which means we have to check what
          // actual source types the iterated step has because we need to
          // know what is the type of the different selection polygon
          // axes. Indeed, we will compare only range values from the
          // iterated step for the right either horizontal (X) or vertical
          // (Y) dimension to the ones already encountered in this
          // iteration over all the steps of the processing flow.

          if(step_csp->m_xSrcType.bitMatches(src_processing_type_mask))
            {
              // qDebug() << "Step's m_xSrcType.bitMatches"
              //          << src_processing_type_mask.toBriefDesc();

              // The x axis is the axis we need to check the innermost
              // range of. Get the range values.

              step_csp->msp_integrationScope->range(
                pappso::Enums::Axis::x, range_start, range_end);

              qDebug() << "20250904 -Gotten range values:" << range_start << "-"
                       << range_end;
            }
          else if(step_csp->m_ySrcType.bitMatches(src_processing_type_mask))
            {
              // qDebug() << "Step's m_ySrcType.bitMatches"
              //          << src_processing_type_mask.toBriefDesc();

              // The y axis is the axis we need to check the innermost
              // range of. Get the range values.

              step_csp->msp_integrationScope->range(
                pappso::Enums::Axis::y, range_start, range_end);

              // qDebug() << "20250904 -Gotten range values:" << range_start <<
              // "-"
              //          << range_end;
            }
          else
            qFatal() << "Programming error.";

          // And now use the gotten range values of the currently iterated
          // step to establish if that dimension is inner or not.

          if(is_first_iterated_item)
            {
              // qDebug()
              //   << "This was the first item we iterated into.";

              // Special situation where we are just setting values
              // because this is the first step that we iterate into and
              // thus we have no other values to perform comparisons with,
              // whe just seed the system.
              inner_range_start = range_start;
              inner_range_end   = range_end;

              // qDebug() << "First iterated step, with inner range:"
              //          << inner_range_start << "-" << inner_range_end;

              // qDebug() << "Setting step as innermost step.";
              // innermost_step_csp = step_csp;

              is_first_iterated_item = false;
            }
          else
            {
              // qDebug() << "Not first iterated step.";

              // This is not the first step we iterate into,
              // so there are already range values, that we
              // need to compare with those of this last step so as
              // to update the innermost range values if necessary.

              // This variable will report if the currently iterated-into
              // step has range values that make them innermost with
              // respect to the previous got ones.
              bool was_at_least_one_range_value_inner = false;

              if(range_start > inner_range_start)
                {
                  inner_range_start = range_start;

                  was_at_least_one_range_value_inner = true;

                  // qDebug() << "20250904 -New inner range start:"
                  //          << inner_range_start;
                }

              if(range_end < inner_range_end)
                {
                  inner_range_end = range_end;

                  was_at_least_one_range_value_inner = true;

                  // qDebug() << "20250904 -New inner range end:"
                  //          << inner_range_end;
                }

              if(was_at_least_one_range_value_inner)
                {
                  // qDebug() << "20250904 -Setting step as innermost step.";
                  // innermost_step_csp = step_csp;
                }
            }

          // Done, go to next step.
          continue;
        }
    }

  // qDebug() << "20250904 -Finished iterating in the vector of steps.";

  // At this point we have iterated in all the steps. To know if at least
  // one step was found matching the src_processing_type_mask, we can test
  // the inner_range_(start|end) values against NaN.

  // We can't just check innermost_step_csp because the params might
  // tell not to save the step.

  if(qIsNaN(inner_range_start) || qIsNaN(inner_range_end))
    return false;

  // We know we can update the values in the variables passed as out
  // params.
  out_range.first  = inner_range_start;
  out_range.second = inner_range_end;

  // If the callers provides a vector of steps, then document the step.
  // Check for options, nonetheless.

  bool should_store_the_step_in_the_end = false;

  if(out_processing_steps_p != nullptr)
    {
      // qDebug() << "The caller wants the innermost step to be stored.";

      if(innermost_step_csp != nullptr)
        {
          // qDebug() << "One innermost step was indeed found.";

          if(!store_only_rhomboid)
            {
              // qDebug()
              //<< "The caller wants *all* 2D steps, not only the skewed
              // ones.";
              should_store_the_step_in_the_end = true;
            }
          else
            {
              if(innermost_step_csp->msp_integrationScope->isRhomboid())
                {
                  should_store_the_step_in_the_end = true;
                  // qDebug() << "The caller wants *only* the 2D skewed
                  // steps and "
                  //             "the step *is* rhomboid";
                }
              // else
              //   {
              //     qDebug() << "The caller wants *only* the 2D rhomboid
              //     steps
              //     "
              //                 "and sthe step is *not* rhomboid";
              //   }
            }
        }
      // else
      // qDebug() << "Not a single step was found.";
    }
  // else
  // qDebug() << "The caller does *not* want the innermost step to be
  // stored.";

  // At this point, if we have to store the step, check that another
  // call by another processing type mask has not set it already
  // (typically ANY_RT and ANY_DT with steps DT_RT).

  if(should_store_the_step_in_the_end)
    {
      // qDebug() << "Really, the step should be stored.";

      // We only push back the range if it has not been found
      // already. Note that this function might be called multiple
      // times with different src_processing_type_mask values.

      bool was_step_found = false;

      for(auto &iter_step_csp : *out_processing_steps_p)
        {
          if(iter_step_csp->m_dateAndTime == innermost_step_csp->m_dateAndTime)
            {
              was_step_found = true;

              // qDebug() << "Did not store the step because it had been "
              //"stored already.";

              break;
            }
        }

      if(!was_step_found)
        {
          // qDebug() << "The step is actually skewed, so storing it:"
          //<< innermost_step_p->toString();

          out_processing_steps_p->push_back(innermost_step_csp);
        }
    }

  // Always return true because we had returned false already when we
  // discovered that the processing types did not match.

  return true;
}

bool
ProcessingFlow::innermostRange(
  const QString src_processing_type_mask,
  std::pair<double, double> &out_range,
  std::vector<ProcessingStepCstSPtr> *out_processing_steps_p,
  bool store_only_2d_steps,
  bool store_only_skewed) const
{
  return innermostRange(ProcessingType(src_processing_type_mask),
                        out_range,
                        out_processing_steps_p,
                        store_only_2d_steps,
                        store_only_skewed);
}

size_t
ProcessingFlow::greatestMsLevel() const
{
  // We want to know what is the greatest MS level in the default ms
  // frag spec and in all the ms frag specs of the various processing
  // specs.

  size_t greatest_ms_level = m_defaultMsFragmentationSpec.getMsLevel();

  for(const ProcessingStepCstSPtr &step_csp : m_steps)
    {
      if(step_csp->getMsLevel() > greatest_ms_level)
        {
          greatest_ms_level = step_csp->getMsLevel();
        }
    }

  // qDebug() << "The greatest MS level of the whole processing flow:"
  //<< greatest_ms_level;

  return greatest_ms_level;
}

ProcessingStepCstSPtr
ProcessingFlow::oldestStep() const
{
  // We rely on the QDateTime instance in the steps to check which of
  // the step was created last.

  if(!m_steps.size())
    return nullptr;

  if(m_steps.size() == 1)
    return m_steps.front();

  ProcessingStepCstSPtr oldest_step_csp = m_steps.front();

  // Seed the temporary date and time with the first step in the
  // vector.
  QDateTime temp_date_time = m_steps.front()->m_dateAndTime;

  for(std::vector<ProcessingStepCstSPtr>::const_iterator iterator =
        m_steps.begin() + 1;
      iterator != m_steps.end();
      ++iterator)
    {
      // Is the iterated step older than temp_date_time?
      if((*iterator)->m_dateAndTime < temp_date_time)
        {
          oldest_step_csp = *iterator;
          temp_date_time  = (*iterator)->m_dateAndTime;
        }
    }

  return oldest_step_csp;
}

ProcessingStepCstSPtr
ProcessingFlow::closestOlderStep(const QDateTime &date_time) const
{
  // qDebug() << "comparison date time:" << date_time
  //<< "number of steps:" << size();

  // We want a step that is older than date_time. But we also want
  // that the step be the most recent older step, that is, the closest
  // older step.
  if(!m_steps.size())
    return nullptr;

  if(m_steps.size() == 1)
    {
      if(m_steps.front()->m_dateAndTime < date_time)
        {
          // qDebug() << "Returning unique older:" <<
          // front()->m_dateAndTime;
          return m_steps.front();
        }
    }
  else
    return nullptr;


  // Seed the temporary date time with the oldest step's date and
  // time.

  ProcessingStepCstSPtr older_step_csp = nullptr;

  for(std::vector<ProcessingStepCstSPtr>::const_iterator iterator =
        m_steps.begin();
      iterator != m_steps.end();
      ++iterator)
    {
      // qDebug() << "Iterating in time:" <<
      // (*iterator)->m_dateAndTime;

      if((*iterator)->m_dateAndTime < date_time)
        {
          // qDebug() << "iter time is less than comparison date
          // time.";

          if(older_step_csp == nullptr)
            {
              // First iteration for which the date is less than
              // comparison time.
              older_step_csp = *iterator;

              continue;
            }

          // The currently iterated step is older than date_time,
          // fine, but is it more recent that the temporaray time ? If
          // so set it as the last older step.

          if((*iterator)->m_dateAndTime > older_step_csp->m_dateAndTime)
            {
              // qDebug() << "more than that: iter time is also more
              // recent than " "last itered time.";

              older_step_csp = *iterator;
            }
        }
    }

  return older_step_csp;
}

ProcessingStepCstSPtr
ProcessingFlow::mostRecentStep() const
{
  // We rely on the QDateTime instance in the steps to check which of
  // the step was created last.

  // Calling from on an empty container causes undefined behaviour.
  if(!m_steps.size())
    return nullptr;

  if(m_steps.size() == 1)
    return m_steps.front();

  ProcessingStepCstSPtr most_recent_step_csp = nullptr;

  QDateTime date_time;
  date_time.setDate(QDate(1900, 1, 1));

  for(const ProcessingStepCstSPtr &step_csp : m_steps)
    {
      // qDebug();

      if(step_csp->m_dateAndTime > date_time)
        most_recent_step_csp = step_csp;
    }

  return most_recent_step_csp;
}

std::vector<ProcessingStepCstSPtr>
ProcessingFlow::stepsMatchingSrcTypeX(const ProcessingType &processing_type,
                                      bool with_valid_fragmentation_spec) const
{
  std::vector<ProcessingStepCstSPtr> processing_steps;

  for(const ProcessingStepCstSPtr &step_csp : m_steps)
    {

      if(step_csp->m_xSrcType.bitMatches(processing_type))
        {
          if(with_valid_fragmentation_spec)
            {
              if(step_csp->getMsFragmentationSpec().isValid())
                processing_steps.push_back(step_csp);
            }
          else
            processing_steps.push_back(step_csp);
        }
    }

  return processing_steps;
}

std::vector<ProcessingStepCstSPtr>
ProcessingFlow::stepsMatchingSrcTypeX(const QString &processing_type,
                                      bool with_valid_fragmentation_spec) const
{
  return stepsMatchingSrcTypeX(ProcessingType(processing_type),
                               with_valid_fragmentation_spec);
}

std::vector<ProcessingStepCstSPtr>
ProcessingFlow::stepsMatchingDestType(const ProcessingType &processing_type,
                                      bool with_valid_fragmentation_spec) const
{
  std::vector<ProcessingStepCstSPtr> processing_steps;

  for(const ProcessingStepCstSPtr &step_csp : m_steps)
    {

      if(step_csp->m_destType.bitMatches(processing_type))
        {
          if(with_valid_fragmentation_spec)
            {
              if(step_csp->getMsFragmentationSpec().isValid())
                processing_steps.push_back(step_csp);
            }
          else
            processing_steps.push_back(step_csp);
        }
    }

  return processing_steps;
}

std::vector<ProcessingStepCstSPtr>
ProcessingFlow::stepsMatchingDestType(const QString &processing_type,
                                      bool with_valid_fragmentation_spec) const
{
  return stepsMatchingDestType(ProcessingType(processing_type),
                               with_valid_fragmentation_spec);
}

const pappso::MzIntegrationParams *
ProcessingFlow::mostRecentMzIntegrationParams() const
{

  // qDebug() << "*this processing flow:" << this->toString();

  ProcessingStepCstSPtr most_recent_step_csp = mostRecentStep();

  if(most_recent_step_csp != nullptr)
    {
      if(most_recent_step_csp->mpa_mzIntegrationParams != nullptr)
        {
          // qDebug()
          //<< "Returning immediately the most recent step's mz integ.
          // params.";

          return most_recent_step_csp->mpa_mzIntegrationParams;
        }
    }
  else
    {
      // qDebug() << "Not a single most recent step there. The flow
      // seems empty."; Apparently there is not a single step in the
      // processing flow.
      return nullptr;
    }

  // If the most recent step had no mz integration params set, we need
  // to iterate back in time.
  while(1)
    {
      // Get a step older than the most recent step.

      const ProcessingStepCstSPtr closest_older_step_csp =
        closestOlderStep(most_recent_step_csp->m_dateAndTime);

      if(closest_older_step_csp != nullptr)
        {
          // qDebug()
          //<< "iterating in infinite while loop with closest older
          // step
          // time:"
          //<< closest_older_step_p->m_dateAndTime;

          // Found an older step, but check if it has mz integration
          // params set to it.
          if(closest_older_step_csp->mpa_mzIntegrationParams != nullptr)
            {
              // qDebug() << "Found a step that is closest and that
              // has mz "
              //"integration params.";

              return closest_older_step_csp->mpa_mzIntegrationParams;
            }
          else
            {
              // qDebug() << "Continuing in the infinite while loop.";
              continue;
            }
        }
      else
        {
          // There are no more older steps.
          // qDebug()
          //<< "There are no more closest older steps. Returning
          // nullptr";

          return nullptr;
        }
    }

  return nullptr;
}

QString
ProcessingFlow::toString(int offset, const QString &spacer) const
{
  QString lead;

  for(int iter = 0; iter < offset; ++iter)
    lead += spacer;

  QString text = lead;
  // text += "Processing flow:\n";

  // text += lead;
  // text += spacer;
  text += "Sample name: ";
  if(mcsp_msRunDataSet == nullptr)
    text += "nullptr";
  else
    text += mcsp_msRunDataSet->getMsRunId()->getSampleName();
  text += "\n\n";

  // text += lead;
  // text += spacer;
  // text += "Default m/z integration params:\n";
  if(mp_defaultMzIntegrationParams != nullptr)
    {
      text += mp_defaultMzIntegrationParams->toString(offset, spacer);
      text += "\n";
    }

  // text += lead;
  // text += spacer;
  // text += "Default MS fragmentation spec:\n";
  text += m_defaultMsFragmentationSpec.toString(offset, spacer);
  text += "\n";

  // Only show the steps if there are any !
  if(m_steps.size())
    {
      // text += lead;
      // text += spacer;
      // text += "The steps:\n";

      for(const ProcessingStepCstSPtr &step_csp : m_steps)
        {
          text += QString("-> %1").arg(step_csp->toString(offset, spacer));
          // text += "\n";
        }
    }

  // Now handle the filter map. We'll only report the names of filters.

  if(m_filterMap.size())
    {
      text += "\nFilters:\n";

      for(std::map<QString, pappso::FilterNameInterfaceCstSPtr>::const_iterator
            iterator = m_filterMap.begin();
          iterator != m_filterMap.end();
          ++iterator)
        {
          text += iterator->first;
          text += "\n";
        }
    }

  return text;
}

void
ProcessingFlow::registerJsConstructor(QJSEngine *engine)
{
  if(!engine)
    {
      qWarning() << "Cannot register class: engine is null";
      return;
    }

  // Register the meta object as a constructor
  QJSValue jsMetaObject =
    engine->newQMetaObject(&ProcessingFlow::staticMetaObject);
  engine->globalObject().setProperty("ProcessingFlow", jsMetaObject);
}


} // namespace MineXpert
} // namespace MsXpS
