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


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


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


namespace msxps
{
namespace minexpert
{


MassSpectrumTreeNodeCombinerVisitor::MassSpectrumTreeNodeCombinerVisitor(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const ProcessingFlow &processing_flow,
  pappso::MassSpectrumPlusCombinerSPtr &mass_spectrum_combiner_sp)
  : BaseMsRunDataSetTreeNodeVisitor(ms_run_data_set_csp, processing_flow),
    msp_combiner(mass_spectrum_combiner_sp)
{
  // qDebug().noquote() << "Creating mass spectrum visitor with:"
  //<< processing_flow.toString();
}


MassSpectrumTreeNodeCombinerVisitor::MassSpectrumTreeNodeCombinerVisitor(
  MsRunDataSetCstSPtr ms_run_data_set_csp,
  const ProcessingFlow &processing_flow,
  pappso::MassSpectrumPlusCombinerSPtr &mass_spectrum_combiner_sp,
  const pappso::MzIntegrationParams &mz_integration_params)
  : BaseMsRunDataSetTreeNodeVisitor(ms_run_data_set_csp, processing_flow),
    msp_combiner(mass_spectrum_combiner_sp),
    m_mzIntegrationParams(mz_integration_params)
{
}


MassSpectrumTreeNodeCombinerVisitor::MassSpectrumTreeNodeCombinerVisitor(
  const MassSpectrumTreeNodeCombinerVisitor &other)
  : BaseMsRunDataSetTreeNodeVisitor(other),
    msp_combiner(other.msp_combiner),
    m_mzIntegrationParams(other.m_mzIntegrationParams),
    m_mapTrace(other.m_mapTrace)
{
}


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

  BaseMsRunDataSetTreeNodeVisitor::operator=(other);

  m_mzIntegrationParams = other.m_mzIntegrationParams;
  m_mapTrace            = other.m_mapTrace;
  msp_combiner          = other.msp_combiner;

  return *this;
}


MassSpectrumTreeNodeCombinerVisitor::~MassSpectrumTreeNodeCombinerVisitor()
{
}


void
MassSpectrumTreeNodeCombinerVisitor::setCombiner(
  const pappso::MassSpectrumPlusCombinerSPtr &mass_spectrum_combiner_sp)
{
  msp_combiner = mass_spectrum_combiner_sp;
}


pappso::MassSpectrumPlusCombinerSPtr
MassSpectrumTreeNodeCombinerVisitor::getCombiner()
{
  return msp_combiner;
}


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


void
MassSpectrumTreeNodeCombinerVisitor::setMzIntegrationParams(
  const pappso::MzIntegrationParams &mz_integration_params)
{
  m_mzIntegrationParams = mz_integration_params;
}


const pappso::MzIntegrationParams &
MassSpectrumTreeNodeCombinerVisitor::getMzIntegrationParams()
{
  return m_mzIntegrationParams;
}


bool
MassSpectrumTreeNodeCombinerVisitor::visit(
  const pappso::MsRunDataSetTreeNode &node)
{
  // qDebug();

  // We visit a node, such that we can compute the mass spectrum.

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

  // 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();

  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 returned false";

      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() << "The node:" << &node << "matched all the steps, going on.";

  // At this point, we need to do the combination!

  // qDebug() << "Node:" << &node
  //<< "definitely accepted, undergoing combination.";

  // qDebug() << "Going to combine a mass spectrum this size:"
  //<< qualified_mass_spectrum_csp->size();

  msp_combiner->combine(m_mapTrace,
                        *(qualified_mass_spectrum_csp->getMassSpectrumSPtr()));

  // qDebug() << "At last, combined a new mass spectrum:"
  //<< qualified_mass_spectrum_csp->getMassSpectrumSPtr().get()
  //<< "with size:" << qualified_mass_spectrum_csp->size();

  // qDebug() << "After another combination, the map trace has size:"
  //<< m_mapTrace.size();

  return true;
}


bool
MassSpectrumTreeNodeCombinerVisitor::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:" << &node << "matches the iterated spec.";
        }
      else
        {
          // qDebug() << "The node:" << &node
          //<< "does not match the iterated spec. Returning false.";

          return false;
        }
    }

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

  return true;
}


bool
MassSpectrumTreeNodeCombinerVisitor::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.

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

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

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

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

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

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

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

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

      // This is the reason why we increment the
      // m_verifiedMsFragmentationLevels: we want the mass spectrum to be
      // taken into account.

      ++m_verifiedMsFragmentationLevels;
    }

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

  ProcessingType type = type_spec_pair.first;

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

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

          return false;
        }
    }
  else if(type.bitMatches("DT_TO_ANY"))
    {
      //      qDebug();
      if(!checkProcessingTypeByDt(type_spec_pair, node))
        {
          // qDebug() << "Node:" << &node << "Checking by Dt failed.";

          return false;
        }
    }

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

  return true;
}


bool
MassSpectrumTreeNodeCombinerVisitor::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
MassSpectrumTreeNodeCombinerVisitor::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?

  const ProcessingSpec *spec_p = type_spec_pair.second;

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

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

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

      if(m_processingFlow.innermostMzRange(local_type_spec_pair))
        {
          msp_combiner->setFilterResampleKeepXRange(
            pappso::FilterResampleKeepXRange(
              local_type_spec_pair.second->getStart(),
              local_type_spec_pair.second->getEnd()));
        }
      else
        qFatal(
          "Cannot be that there is no innermost m/z range if there is a "
          "valid "
          "m/z range in a spec.");
    }

  return true;
}


bool
MassSpectrumTreeNodeCombinerVisitor::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
