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


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


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


namespace msxps
{
namespace minexpert
{


TicChromTreeNodeCombinerVisitor::TicChromTreeNodeCombinerVisitor(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const ProcessingFlow &processing_flow)
  : BaseMsRunDataSetTreeNodeVisitor(ms_run_data_set_csp, processing_flow)
{
  //  qDebug() << "Processing flow has" << m_processingFlow.size() << "steps";
}


TicChromTreeNodeCombinerVisitor::TicChromTreeNodeCombinerVisitor(
  const TicChromTreeNodeCombinerVisitor &other)
  : BaseMsRunDataSetTreeNodeVisitor(other),
    m_ticChromMapTrace(other.m_ticChromMapTrace)
{
}


TicChromTreeNodeCombinerVisitor &
TicChromTreeNodeCombinerVisitor::operator=(
  const TicChromTreeNodeCombinerVisitor &other)
{
  if(this == &other)
    return *this;

  BaseMsRunDataSetTreeNodeVisitor::operator=(other);

  mcsp_msRunDataSet  = other.mcsp_msRunDataSet;
  m_processingFlow   = other.m_processingFlow;
  m_ticChromMapTrace = other.m_ticChromMapTrace;

  return *this;
}


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


// void
// TicChromTreeNodeCombinerVisitor::nodesToProcess(std::size_t nodes_to_process)
//{
//// Do nothing here because this nodes_to_process count is relative to the
//// thread in which this visitor is operating and thus does not correspond to
//// the total number of nodes that need processing. That total count of nodes
//// to be processed is set in the main thread once for all.
//}


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


const pappso::MapTrace &
TicChromTreeNodeCombinerVisitor::getTicChromMapTrace() const
{
  return m_ticChromMapTrace;
}


bool
TicChromTreeNodeCombinerVisitor::visit(const pappso::MsRunDataSetTreeNode &node)
{

  // We visit a node, such that we can compute the TIC chromatogram.

  // qDebug() << "Visiting node:" << node.toString();

  if(!m_isProgressFeedbackSilenced)
    {
      // Whatever the status of this node (to be or not processed) let the user
      // know that we have gone through one more.

      // The "%c" format refers to the current value in the task monitor widget
      // that will craft the new text according to the current value after
      // having incremented it in the call below. This is necessary because this
      // visitor might be in a thread and the  we need to account for all the
      // thread when giving feedback to the user.

      emit incrementProgressBarCurrentValueAndSetStatusTextSignal(
        1, "Processed %c nodes");
    }

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

  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
    checkQualifiedMassSpectrum(node);

  // Immediately check if the qualified mass spectrum is of a MS level that
  // matches the greatest MS level found in the member processing flow instance.

  if(!checkMsLevel(node))
    {
      // qDebug() << "The checkMsLevel failed. Returning false, mass spectrum
      // not " "accounted for.";
      return false;
    }

  // At this point we need to go deep in the processing flow's steps to check
  // if the node matches the whole set of the steps' specs.

  // qDebug() << "The processing flow has" << m_processingFlow.size() <<
  // "steps.";

  for(auto &&step : m_processingFlow)
    {
      if(checkProcessingStep(*step, node))
        {
          // qDebug() << "The node:" << &node << "matches the iterated step.";
        }
      else
        {
          return false;
        }
    }

  // qDebug().noquote() << "The qualified mass spectrum:"
  //<< qualified_mass_spectrum_csp->toString()
  //<< "rt in minutes:" << qualified_mass_spectrum_csp->getRtInMinutes()
  //<< "dt in milliseconds:"
  //<< qualified_mass_spectrum_csp->getDtInMilliSeconds();

  // qDebug() << "The current qualified mass spectrum has size:"
  //<< qualified_mass_spectrum_csp->size();

  double sumY = 0;

  if(m_limitMzRangeStart != std::numeric_limits<double>::max() &&
     m_limitMzRangeEnd != std::numeric_limits<double>::max())
    {
      sumY = qualified_mass_spectrum_csp->getMassSpectrumSPtr()->sumY(
        m_limitMzRangeStart, m_limitMzRangeEnd);
    }
  else
    {
      sumY = qualified_mass_spectrum_csp->getMassSpectrumSPtr()->sumY();
    }

  if(!sumY)
    {
      // qDebug() << "sumY is zero, returning true.";
      return true;
    }

  double rt = qualified_mass_spectrum_csp->getRtInMinutes();

  using Pair     = std::pair<double, double>;
  using Map      = std::map<double, double>;
  using Iterator = Map::iterator;

  std::pair<Iterator, bool> res = m_ticChromMapTrace.insert(Pair(rt, sumY));

  if(!res.second)
    {
      // One other same rt value was seen already (like in ion mobility mass
      // spectrometry, for example). Only increment the y value.

      res.first->second += sumY;
    }

  // qDebug() << "This visitor:" << this
  //<< "has size:" << m_ticChromMapTrace.size();

  return true;
}


bool
TicChromTreeNodeCombinerVisitor::checkProcessingStep(
  const ProcessingStep &step, const pappso::MsRunDataSetTreeNode &node)
{
  //  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, node))
        {
          //          qDebug() << "The node matches the iterated spec.";
        }
      else
        {
          //          qDebug() << "The node does not match the iterated
          //          spec.";

          return false;
        }
    }

  return true;
}


bool
TicChromTreeNodeCombinerVisitor::checkProcessingSpec(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  const pappso::MsRunDataSetTreeNode &node)
{
  //  qDebug();

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

  const ProcessingSpec *spec_p = type_spec_pair.second;

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

  const MsFragmentationSpec fragmentation_spec =
    spec_p->getMsFragmentationSpec();

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

      // For each member of the MsFragmentationSpec structure, check if it is to
      // be used for the check.

      if(fragmentation_spec.getMsLevel())
        {
          // qDebug() << "Node:" << &node
          //<< "The fragmentation spec has ms levels.";

          // Logically, if the currently handled mass spectrum has a ms level
          // that is greater than the fragmentation spec ms level, then that
          // means that it should be visited: in the process of making data
          // mining, one goes from the lowest ms level to higher ms levels, so
          // we need to keep the mass spectrum. At some point down the
          // processing datetime, a process spec will limit the mass spectra
          // that are actually validated.

          if(qualified_mass_spectrum_csp->getMsLevel() >=
             fragmentation_spec.getMsLevel())
            {
              // qDebug() << "Node:" << &node
              //<< "The ms level matches, with fragspec level:"
              //<< fragmentation_spec.msLevelsToString()
              //<< "and the node spectrum level:"
              //<< qualified_mass_spectrum_csp->getMsLevel();
            }
          else
            {
              // qDebug() << "Node:" << &node
              //<< "The ms level does not match, with fragspec level:"
              //<< fragmentation_spec.msLevelsToString()
              //<< "and the node spectrum level:"
              //<< qualified_mass_spectrum_csp->getMsLevel();

              return false;
            }
        }

      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())
        {
          if(!fragmentation_spec.containsSpectrumPrecursorIndex(
               qualified_mass_spectrum_csp->getPrecursorSpectrumIndex()))
            {
              // qDebug() << "The precursor spectrum index does not match.";

              return false;
            }
        }
    }
  else
    {
      // Else, fragmentation is not a criterion for filtering data. So go on.
      // qDebug() << "The fragmentation spec is *not* valid.";
    }

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

  ProcessingType type = type_spec_pair.first;

  // Because we are working on the TIC chromatogram visitor, we need to match
  // the corresponding processing type that creates a TIC chromatogram right
  // from the data file, apart from other processes that create a TIC chrom from
  // other data settings, which in truth are called XIC chrom.

  if(type.bitMatches("RT_TO_ANY") || type.bitMatches("FILE_TO_RT"))
    {
      //      qDebug();
      if(!checkProcessingTypeByRt(type_spec_pair, node))
        return false;
    }
  else if(type.bitMatches("MZ_TO_ANY"))
    {
      //      qDebug();
      if(!checkProcessingTypeByMz(type_spec_pair, node))
        return false;
    }
  else if(type.bitMatches("DT_TO_ANY"))
    {
      //      qDebug();
      if(!checkProcessingTypeByDt(type_spec_pair, node))
        return false;
    }

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

  return true;
} // namespace minexpert


bool
TicChromTreeNodeCombinerVisitor::checkProcessingTypeByRt(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  const pappso::MsRunDataSetTreeNode &node)
{
  //  qDebug();

  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
    node.getQualifiedMassSpectrum();

  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
TicChromTreeNodeCombinerVisitor::checkProcessingTypeByMz(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  [[maybe_unused]] const pappso::MsRunDataSetTreeNode &node)
{
  //  qDebug();

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

  // Make a local copy of the type_spec_pair
  std::pair<ProcessingType, ProcessingSpec *> local_type_spec_pair(
    type_spec_pair);

  // Provide that copy for setting values in it to the function that gives the
  // innmermost m/z range of all ProcessingSpec instances contained in the
  // ProcessingFlow.

  if(m_processingFlow.innermostMzRange(local_type_spec_pair))
    {
      m_limitMzRangeStart = local_type_spec_pair.second->getStart();
      m_limitMzRangeEnd   = local_type_spec_pair.second->getEnd();
    }
  else
    {
      // At this point there is no mz range-based criterion.
      m_limitMzRangeStart = -1;
      m_limitMzRangeEnd   = -1;
    }

  // 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
TicChromTreeNodeCombinerVisitor::checkProcessingTypeByDt(
  const std::pair<ProcessingType, ProcessingSpec *> &type_spec_pair,
  const pappso::MsRunDataSetTreeNode &node)
{
  //  qDebug();

  pappso::QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp =
    node.getQualifiedMassSpectrum();

  const ProcessingSpec *spec_p = type_spec_pair.second;

  if(spec_p->hasValidRange())
    {
      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
