// GPL 3+
// Filippo Rusconi

/////////////////////// StdLib includes
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>


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


/////////////////////// pwiz includes


/////////////////////// Local includes
#include "pappsomspp/core/exception/exceptionnotpossible.h"
#include "msrundatasettreenode.h"
#include "pappsomspp/core/utils.h"


namespace pappso
{


MsRunDataSetTreeNode::MsRunDataSetTreeNode()
{
}


MsRunDataSetTreeNode::MsRunDataSetTreeNode(QualifiedMassSpectrumCstSPtr mass_spectrum_csp,
                                           MsRunDataSetTreeNode *parent_p)
  : mcsp_massSpectrum(mass_spectrum_csp), mp_parent(parent_p)
{
}


MsRunDataSetTreeNode::MsRunDataSetTreeNode(const MsRunDataSetTreeNode &other)
  : mcsp_massSpectrum(other.mcsp_massSpectrum), mp_parent(other.mp_parent)
{
  for(auto &&node : other.m_children)
    m_children.push_back(new MsRunDataSetTreeNode(*node));
}


MsRunDataSetTreeNode::~MsRunDataSetTreeNode()
{
  for(auto &&node : m_children)
    delete node;

  m_children.clear();
}


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

  mcsp_massSpectrum = other.mcsp_massSpectrum;
  mp_parent         = other.mp_parent;

  for(auto &&node : other.m_children)
    m_children.push_back(new MsRunDataSetTreeNode(*node));

  return *this;
}

void
MsRunDataSetTreeNode::setQualifiedMassSpectrum(
  QualifiedMassSpectrumCstSPtr qualified_mass_spectrum_csp)
{
  mcsp_massSpectrum = qualified_mass_spectrum_csp;
}


QualifiedMassSpectrumCstSPtr
MsRunDataSetTreeNode::getQualifiedMassSpectrum() const
{
  return mcsp_massSpectrum;
}


void
MsRunDataSetTreeNode::setParent(MsRunDataSetTreeNode *parent)
{
  mp_parent = parent;
}


MsRunDataSetTreeNode *
MsRunDataSetTreeNode::getParent() const
{
  // qDebug();

  return mp_parent;
}


bool
MsRunDataSetTreeNode::hasParent() const
{
  // qDebug();

  if(mp_parent != nullptr)
    return true;
  else
    return false;
}


void
MsRunDataSetTreeNode::size(std::size_t &cumulative_node_count) const
{

  // First account for this node.
  ++cumulative_node_count;

  // Then ask for each child to recursively account for themselves and their
  // children.

  for(auto &&node : m_children)
    {
      node->size(cumulative_node_count);
    }
}


MsRunDataSetTreeNode *
MsRunDataSetTreeNode::findNode(std::size_t spectrum_index)
{
  // qDebug();

  // Finding a node that contains a qualified mass spectrum that has been
  // acquired at spectrum_index, requires checking if that node is not *this and
  // if not if it is not one of the children. By essence, this work is
  // recursive.

  if(mcsp_massSpectrum->getMassSpectrumId().getSpectrumIndex() == spectrum_index)
    {
      // qDebug() << "The mass spectrum's node is this node.";

      return this;
    }

  // qDebug() << "Need to go searching in the children.";

  for(auto &node : m_children)
    {
      MsRunDataSetTreeNode *iterNode = node->findNode(spectrum_index);

      if(iterNode != nullptr)
        {
          // qDebug() << "Found the mass spectrum's node.";

          return iterNode;
        }
    }

  return nullptr;
}


MsRunDataSetTreeNode *
MsRunDataSetTreeNode::findNode(QualifiedMassSpectrumCstSPtr mass_spectrum_csp)
{
  // qDebug();

  // Finding a node that contains a qualified mass spectrum requires checking if
  // that node is not *this and if not if it is not one of the children. By
  // essence, this work is recursive.

  if(mass_spectrum_csp == mcsp_massSpectrum)
    {
      // qDebug() << "The mass spectrum's node is this node.";

      return this;
    }

  // qDebug() << "Need to go searching in the children.";

  for(auto &node : m_children)
    {
      MsRunDataSetTreeNode *iterNode = node->findNode(mass_spectrum_csp);

      if(iterNode != nullptr)
        {
          // qDebug() << "Found the mass spectrum's node.";

          return iterNode;
        }
    }

  return nullptr;
}


void
MsRunDataSetTreeNode::flattenedView(std::vector<MsRunDataSetTreeNode *> &nodes,
                                    bool with_descendants)
{

  // Do store this.

  nodes.push_back(this);

  // And now the descendants.

  if(with_descendants)
    {
      for(auto &&node : m_children)
        {
          node->flattenedView(nodes, with_descendants);
        }
    }
  else
    {
    }
}


void
MsRunDataSetTreeNode::flattenedViewChildrenOnly(std::vector<MsRunDataSetTreeNode *> &nodes,
                                                bool with_descendants)
{

  // Do not store this, only this->m_children !

  for(auto &&node : m_children)
    node->flattenedView(nodes, with_descendants);
}


void
MsRunDataSetTreeNode::flattenedViewMsLevelNodes(std::size_t ms_level,
                                                std::size_t depth,
                                                std::vector<MsRunDataSetTreeNode *> &nodes,
                                                bool with_descendants)
{

  if(ms_level == (depth + 1))
    {

      // There we are. The ms_level that is asked matches the current depth of
      // the node we are in.

      flattenedView(nodes, with_descendants);
    }
  else if(ms_level > (depth + 1))
    {
      // We still do not have to store the nodes, because what we are
      // searching is down the tree...

      for(auto &&node : m_children)
        {
          node->flattenedViewMsLevelNodes(ms_level, depth + 1, nodes, with_descendants);
        }
    }
}


std::vector<MsRunDataSetTreeNode *>
MsRunDataSetTreeNode::productNodesByPrecursorMz(pappso_double precursor_mz,
                                                PrecisionPtr precision_ptr,
                                                std::vector<MsRunDataSetTreeNode *> &nodes)
{
  if(precision_ptr == nullptr)
    throw ExceptionNotPossible(
      QObject::tr("Fatal error at msrundatasettreenode.cpp "
                  "-- ERROR precision_ptr cannot be nullptr. "
                  "Program aborted."));

  // Check if this node matches the requirements.

  pappso_double mz = mcsp_massSpectrum->getPrecursorMz();

  if(mz != std::numeric_limits<double>::max())
    {

      // Calculate the mz range using the tolerance.

      pappso_double lower_mz = precursor_mz - (precision_ptr->delta(precursor_mz) / 2);
      pappso_double upper_mz = precursor_mz + (precision_ptr->delta(precursor_mz) / 2);

      if(mz >= lower_mz && mz <= upper_mz)
        {
          // We are iterating in a node that holds a mass spectrum that was
          // acquired by fragmenting an ion that matches the searched mz value.

          nodes.push_back(this);
        }
    }

  // Now handle in the same way, but recursively, all the children of this node.

  for(auto &&node : m_children)
    {
      node->productNodesByPrecursorMz(precursor_mz, precision_ptr, nodes);
    }

  return nodes;
}


std::vector<MsRunDataSetTreeNode *>
MsRunDataSetTreeNode::precursorIonNodesByPrecursorMz(pappso_double precursor_mz,
                                                     PrecisionPtr precision_ptr,
                                                     std::vector<MsRunDataSetTreeNode *> &nodes)
{
  if(precision_ptr == nullptr)
    throw ExceptionNotPossible(
      QObject::tr("Fatal error at msrundatasettreenode.cpp "
                  "-- ERROR precision_ptr cannot be nullptr. "
                  "Program aborted."));

  // Calculate the mz range using the tolerance.
  pappso_double lower_mz = precursor_mz - (precision_ptr->delta(precursor_mz) / 2);
  pappso_double upper_mz = precursor_mz + (precision_ptr->delta(precursor_mz) / 2);

  // Check if this node matches the requirements.

  pappso_double mz = mcsp_massSpectrum->getPrecursorMz();

  if(mz != std::numeric_limits<double>::max())
    {
      if(mz >= lower_mz && mz <= upper_mz)
        {
          // We are iterating in a node that hold a mass spectrum that was
          // acquired by fragmenting an ion matching the searched mz value. We
          // can extract the spectrum index of that precursor mass spectrum and
          // then get its corresponding node, that we'll store.

          std::size_t precursor_spectrum_index = mcsp_massSpectrum->getPrecursorSpectrumIndex();

          MsRunDataSetTreeNode *found_node = findNode(precursor_spectrum_index);

          if(precursor_spectrum_index !=
             found_node->mcsp_massSpectrum->getMassSpectrumId().getSpectrumIndex())
            throw ExceptionNotPossible(
              QObject::tr("Fatal error at msrundatasettreenode.cpp "
                          "-- ERROR precursor_spectrum_index bad value. "
                          "Program aborted."));

          nodes.push_back(found_node);
        }
    }

  // Now handle in the same way, but recursively, all the children of this node.

  for(auto &&node : m_children)
    {
      node->precursorIonNodesByPrecursorMz(precursor_mz, precision_ptr, nodes);
    }

  return nodes;
}


void
MsRunDataSetTreeNode::accept(MsRunDataSetTreeNodeVisitorInterface &visitor)
{
  // qDebug() << "now calling visitor.visit(*this);";

  visitor.visit(*this);

  // qDebug() << "and now calling node->accept(visitor) for each child node.";

  visitor.setNodesToProcessCount(m_children.size());

  for(auto &&node : m_children)
    node->accept(visitor);
}


std::size_t
MsRunDataSetTreeNode::depth(std::size_t depth) const
{

  // qDebug() << "Got depth:" << depth;

  // If there are no children in this node, that is the end of the tree.
  // Do not change anything an return.

  if(!m_children.size())
    {
      // qDebug() << "No children, returning" << depth;

      return depth;
    }

  // qDebug() << "There are" << m_children.size() << "children nodes";


  // At this point we know we can already increment depth by one because
  // we go down one level by iterating in the m_children vector of nodes.

  // qDebug() << "Children found, incrementing depth to" << depth + 1;

  std::size_t local_depth = depth + 1;

  std::size_t tmp_depth      = 0;
  std::size_t greatest_depth = 0;

  for(auto &node : m_children)
    {
      // qDebug() << "In the children for loop";

      tmp_depth = node->depth(local_depth);

      // qDebug() << "Got depth from iterated node:" << tmp_depth;

      if(tmp_depth > greatest_depth)
        greatest_depth = tmp_depth;
    }

  // qDebug() << "Returning:" << greatest_depth;

  return greatest_depth;
}


QString
MsRunDataSetTreeNode::toString(bool with_data) const
{
  QString text =
    QString("mcsp_massSpectrum: %1 ; to string: %2 ; children: %3\n")
      .arg(Utils::pointerToString(const_cast<QualifiedMassSpectrum *>(mcsp_massSpectrum.get())))
      .arg(mcsp_massSpectrum->toString())
      .arg(m_children.size());

  if(with_data)
    text += mcsp_massSpectrum->getMassSpectrumCstSPtr()->toString();

  return text;
}

} // namespace pappso
