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


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


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


/////////////////////// Local includes
#include "BaseMsRunDataSetTreeNodeVisitor.hpp"
#include "ProcessingStep.hpp"
#include <cmath>

namespace MsXpS
{
namespace MineXpert
{

BaseMsRunDataSetTreeNodeVisitor::BaseMsRunDataSetTreeNodeVisitor(
  QObject *parent)
  : libXpertMassCore::MonitoredTaskInterface(parent),
    mp_processingFlow(new ProcessingFlow(dynamic_cast<QObject *>(this)))
{
}

BaseMsRunDataSetTreeNodeVisitor::BaseMsRunDataSetTreeNodeVisitor(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const ProcessingFlow &processing_flow,
  QObject *parent)
  : libXpertMassCore::MonitoredTaskInterface(parent),
    mcsp_msRunDataSet(ms_run_data_set_csp),
    mp_processingFlow(processing_flow.clone(dynamic_cast<QObject *>(this)))
{
  // qDebug() << "Going to call setInnermostRanges().";
  setInnermostRanges();
}

BaseMsRunDataSetTreeNodeVisitor::~BaseMsRunDataSetTreeNodeVisitor()
{
  // qDebug();
}

void
BaseMsRunDataSetTreeNodeVisitor::setMsRunDataSet(
  MsRunDataSetCstSPtr ms_run_data_set_csp)
{
  mcsp_msRunDataSet = ms_run_data_set_csp;
}

void
BaseMsRunDataSetTreeNodeVisitor::setProcessingFlow(
  const ProcessingFlow &processing_flow)
{
  mp_processingFlow = processing_flow.clone(dynamic_cast<QObject *>(this));

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

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

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

ProcessingFlow *
BaseMsRunDataSetTreeNodeVisitor::getProcessingFlowPtr()
{
  return mp_processingFlow;
}

pappso::QualifiedMassSpectrumCstSPtr
BaseMsRunDataSetTreeNodeVisitor::checkQualifiedMassSpectrum(
  const pappso::MsRunDataSetTreeNode &node)
{
  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
    node.getQualifiedMassSpectrum();

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

  if(qualified_mass_spectrum_csp->getMassSpectrumSPtr() == nullptr)
    {
      // qDebug() << "Need to access the mass data right from the file.";

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

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

      // Node 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).";

  return qualified_mass_spectrum_csp;
}

bool
BaseMsRunDataSetTreeNodeVisitor::checkMsLevel(
  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";

  // qDebug().noquote() << "Checking MS level for qualified mass spectrum:"
  //<< qualified_mass_spectrum_csp->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.

  std::size_t greatest_ms_level = mp_processingFlow->greatestMsLevel();

  // qDebug().noquote() << "Greatest ms level in the processing flow:"
  //<< greatest_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 (returning false):"
          //<< 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;
}

void
BaseMsRunDataSetTreeNodeVisitor::setInnermostRanges()
{
  // qDebug();

  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.

  // 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, if any one of the source types matches the mask, then the
  // correct selection polygon axis range is used.

  // No that when asking that relevant 2D steps be stored in the vector
  // m_innermostSteps2D, we specify two options:
  //
  // 1. That 2D steps be stored *only*.  2. That only 2D steps that have a
  // skewed selection polygon 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 skewed
  // selection polygons are a challenge for the analysis of the data. The
  // checkXxRange() functions do monitor the m_innermostSteps2D vector for
  // skewed selection polygon rectangles.

  m_dtRange.first  = std::numeric_limits<double>::quiet_NaN();
  m_dtRange.second = std::numeric_limits<double>::quiet_NaN();

  // true: only 2D steps
  // true: only skewed 2D steps
  if(mp_processingFlow->innermostRange("ANY_DT", range_pair))
    {
      // 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  = std::numeric_limits<double>::quiet_NaN();
  m_mzRange.second = std::numeric_limits<double>::quiet_NaN();

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

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

  m_rtRange.first  = std::numeric_limits<double>::quiet_NaN();
  m_rtRange.second = std::numeric_limits<double>::quiet_NaN();

  // true: only 2D steps
  // true: only skewed 2D steps
  if(mp_processingFlow->innermostRange("ANY_RT", range_pair))
    {
      // 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 range-specifying step.");
    }
}

bool
BaseMsRunDataSetTreeNodeVisitor::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;
    }

  return true;
}

bool
BaseMsRunDataSetTreeNodeVisitor::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;
    }

  return true;
}

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

  // qDebug() << "The visitor has got a cancelOperationSignal, setting "
  //"m_isOperationCancelled to true.";
}

void
BaseMsRunDataSetTreeNodeVisitor::setNodesToProcessCount(
  std::size_t nodes_to_process)
{
  // qDebug() << "emitting increment nodes to process:" << nodes_to_process;

  // Since this visitor might be running in a thread, along with other threads,
  // we do not want to set the max progress bar value to only the number of
  // nodes that are going to be processed in the current thread. So we tell the
  // user of the visitor that we are only telling how many nodes are to be
  // processed in this thread. The user will increment the total count to
  // nodes_to_process. In a typical setting, the immediate user of this signal
  // is the MassDataIntegrator.
  emit taskIncrementMaxValueSignal(nodes_to_process);
}

bool
BaseMsRunDataSetTreeNodeVisitor::shouldStop() const
{
  // qDebug() << "returning" << m_isOperationCancelled;

  return m_isOperationCancelled;
}

void
BaseMsRunDataSetTreeNodeVisitor::silenceFeedback(bool silence_feedback)
{
  m_isProgressFeedbackSilenced = silence_feedback;
}

bool
BaseMsRunDataSetTreeNodeVisitor::shouldSilenceFeedback()
{
  return m_isProgressFeedbackSilenced;
}

} // namespace MineXpert

} // namespace MsXpS
