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


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


/////////////////////// Local includes
#include "DataPlottableNode.hpp"
#include "MsRunDataSet.hpp"

namespace MsXpS
{
namespace MineXpert
{


static_assert(
  std::is_same_v<MsRunDataSetCstSPtr, std::shared_ptr<const MsRunDataSet>>,
  "MsRunDataSetCstSPtr type mismatch");

static_assert(std::is_base_of_v<QWidget, BasePlotCompositeWidget>,
              "BasePlotCompositeWidget must inherit from QWidget");

DataPlottableNode::DataPlottableNode()
{
}

DataPlottableNode::DataPlottableNode(MsRunDataSetCstSPtr ms_run_data_set_csp,
                                     QCPAbstractPlottable *plottable_p,
                                     DataPlottableNode *parent_node_p,
                                     BasePlotCompositeWidget *plot_widget_p)
  : mp_parent(parent_node_p),
    mcsp_msRunDataSet(ms_run_data_set_csp),
    mp_plotWidget(plot_widget_p),
    mp_plottable(plottable_p)
{
}

DataPlottableNode::DataPlottableNode(const DataPlottableNode &other)
  : mp_parent(other.mp_parent),
    mcsp_msRunDataSet(other.mcsp_msRunDataSet),
    mp_plotWidget(other.mp_plotWidget),
    mp_plottable(other.mp_plottable),
    m_plotColor(other.m_plotColor)
{

  for(auto child_node_p : other.m_children)
    m_children.push_back(new DataPlottableNode(*child_node_p));
}

DataPlottableNode::~DataPlottableNode()
{

  // qDebug().noquote()
  //<< "Destroying node:" << toString()
  //<< "that should have no children, as they all should have been reparented.";

  // for(auto child_node_p : m_children)
  // delete child_node_p;

  // m_children.clear();
}

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


  mp_parent = other.mp_parent;

  for(auto &&child_node_p : m_children)
    delete child_node_p;

  m_children.clear();

  for(auto &&child_node_p : other.m_children)
    m_children.push_back(new DataPlottableNode(*child_node_p));

  mcsp_msRunDataSet = other.mcsp_msRunDataSet;

  mp_plotWidget = other.mp_plotWidget;
  mp_plottable  = other.mp_plottable;

  m_plotColor = other.m_plotColor;

  return *this;
}

QCPAbstractPlottable *
DataPlottableNode::getPlottable() const
{
  return mp_plottable;
}

BasePlotCompositeWidget *
DataPlottableNode::getPlotWidget() const
{
  return mp_plotWidget;
}

void
DataPlottableNode::setParent(DataPlottableNode *new_parent_node_p)
{
  // Can be nullptr if the node is a root node in the tree.
  mp_parent = new_parent_node_p;
}

DataPlottableNode *
DataPlottableNode::getParent() const
{
  return mp_parent;
}

bool
DataPlottableNode::reparent(DataPlottableNode *new_parent_node_p)
{
  // The new parent node cannot be nullptr, because that would mean that we
  // cannot move this node to the parent (the root nodes of the tree), since
  // from here we do not have access to the root nodes of the tree.

  if(new_parent_node_p == nullptr)
    qFatal() << "Programming error. The pointer cannot be nullptr.";

  // The initial parent cannot be nullptr, because that would mean that we would
  // not be able to remove this node from the root nodes that sit in the tree
  // (see comment above).

  if(mp_parent == nullptr)
    qFatal() << "Programming error.";

  // qDebug().noquote() << "Reparenting node:" << toString(0, true)
  //<< "from initial parent" << mp_parent->toString(0, true)
  //<< "to new parent" << new_parent_node_p->toString(0, true);

  // Search for *this node in its current parent node's children.

  std::vector<DataPlottableNode *>::iterator iter =
    std::find_if(mp_parent->m_children.begin(),
                 mp_parent->m_children.end(),
                 [this](DataPlottableNode *iter_node) {
                   return iter_node == this;
                 });

  if(iter == mp_parent->m_children.end())
    qFatal()
      << "Cannot be that a node is not found in its parent node's children.";

  // The node was found, so now we can erase it from the parent node's children.

  // Sanity check

  if(*iter != this)
    qFatal() << "Programming error.";

  mp_parent->m_children.erase(iter);

  // At this point, we can set the new parent to *this node.

  setParent(new_parent_node_p);

  // And of course add *this node to the new parent node's children.

  new_parent_node_p->m_children.push_back(this);

  return true;
}

bool
DataPlottableNode::hasParent() const
{
  if(mp_parent != nullptr)
    return true;
  else
    return false;
}

const std::vector<DataPlottableNode *> &
DataPlottableNode::getChildren() const
{
  return m_children;
}

DataPlottableNode *
DataPlottableNode::findNode(QCPAbstractPlottable *plottable_p)
{
  // qDebug().noquote() << "In node:" << this
  //<< ", finding node for plottable:" << plottable_p;

  // Finding a node that contains a plot widget 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(mp_plottable == plottable_p)
    {
      // qDebug() << "The right node is *this:" << this << "which we return";
      return this;
    }

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

  for(auto &node : m_children)
    {
      DataPlottableNode *found_node = node->findNode(plottable_p);

      if(found_node != nullptr)
        {
          // qDebug().noquote()
          //<< "In node:" << this
          //<< ", found the right node:" << found_node->toString(0, true)
          //<< "which we return";

          return found_node;
        }
    }

  // qDebug().noquote()
  //<< "In node" << toString(0, true)
  //<< ", returning nullptr because we did not find the node for plottable:"
  //<< plottable_p;

  return nullptr;
}

void
DataPlottableNode::findNodes(MsRunDataSetCstSPtr ms_run_data_set_csp,
                             std::vector<DataPlottableNode *> &found_nodes)
{
  // First look if any of the children is the current node.

  for(std::size_t iter = 0; iter < m_children.size(); ++iter)
    {
      if(m_children.at(iter)->mcsp_msRunDataSet == ms_run_data_set_csp)
        found_nodes.push_back(m_children.at(iter));
    }

  // Now do that recursively for all the childrens' children.

  for(std::size_t iter = 0; iter < m_children.size(); ++iter)
    {
      m_children.at(iter)->findNodes(ms_run_data_set_csp, found_nodes);
    }
}

void
DataPlottableNode::findNodes(
  BasePlotCompositeWidget *base_plot_composite_widget_p,
  std::vector<DataPlottableNode *> &found_nodes,
  bool recursively)
{
  // qDebug() << "Finding nodes matching plot widget:"
  //<< base_plot_composite_widget_p;

  for(auto &&node_p : m_children)
    {
      // qDebug() << "Iterating in a node that has plot widget:"
      //<< node_p->mp_plotWidget;

      if(node_p->mp_plotWidget == base_plot_composite_widget_p)
        found_nodes.push_back(node_p);

      if(recursively)
        node_p->findNodes(
          base_plot_composite_widget_p, found_nodes, recursively);
    }

  return;
}

void
DataPlottableNode::flattenedView(std::vector<DataPlottableNode *> &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);
        }
    }
}

void
DataPlottableNode::flattenedViewLevelNodes(
  std::size_t level,
  std::size_t depth,
  std::vector<DataPlottableNode *> &nodes,
  bool with_descendants)
{

  // std::cout << "level: " << level << " - depth: " << depth << std::endl;

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

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

      // std::cout << "level: " << level
      //<< " matches depth + 1: " << depth + 1
      //<< " Asking a flattened view of this node.";

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

      // std::cout << "Still need to delve into depth: " << depth + 1
      //<< std::endl;

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

void
DataPlottableNode::setPlotColor(const QColor &color)
{
  m_plotColor = color;
}

std::size_t
DataPlottableNode::depth(std::size_t depth) const
{
  // If there are no children in this node, that is the end of the this
  // branch. 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 tmp_depth      = 0;
  std::size_t greatest_depth = 0;

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

      tmp_depth = node->depth(depth + 1);

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

      if(tmp_depth > greatest_depth)
        greatest_depth = tmp_depth;
    }

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

  return greatest_depth;
}

void
DataPlottableNode::size(std::size_t &cumulative_node_count) const
{
  // qDebug() << "this node is for a widget that has" <<
  // mp_plotWidget->getPlotWidget()->plottableCount() << "plottables.";

  // qDebug() << "this node has" << m_children.size() << "children";

  // qDebug() << "before acccounting for the immediate children,
  // cumulative_node_count is:" << cumulative_node_count;
  cumulative_node_count += m_children.size();
  // qDebug() << "after acccounting for the immediate children,
  // cumulative_node_count is:" << cumulative_node_count;

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

      // qDebug() << "iterated in a children: new cumulative_node_count:" <<
      // cumulative_node_count;
    }

  // qDebug() << "done for this node with cumulative_node_count:" <<
  // cumulative_node_count;
}

QString
DataPlottableNode::toString(int offset, bool with_children) const
{
  QString lead = "\t";

  for(int iter = 0; iter < offset; ++iter)
    lead += "\t";

  QString text = lead;

  text += QString("Node: %1").arg(pappso::Utils::pointerToString(this));

  text += QString(" -- Plottable: %1")
            .arg(mp_plottable == nullptr
                   ? "nullptr"
                   : pappso::Utils::pointerToString(mp_plottable));

  text +=
    QString(" -- Parent: %1")
      .arg(mp_parent == nullptr ? "nullptr"
                                : pappso::Utils::pointerToString(mp_parent));

  text += QString(" -- Children count: %1").arg(m_children.size());

  if(with_children)
    {
      for(auto &&child : m_children)
        text += child->toString(offset + 1, with_children);
    }

  return text;
}

void
DataPlottableNode::toDebug(int offset, bool with_children) const
{
  QString lead = "\t";

  for(int iter = 0; iter < offset; ++iter)
    lead += "\t";

  QString text = lead;

  qDebug().noquote() << lead << "Node:" << this << "plottable:"
                     << pappso::Utils::pointerToString(mp_plottable)
                     << "parent:"
                     << (mp_parent == nullptr
                           ? "nullptr"
                           : pappso::Utils::pointerToString(mp_parent))
                     << "children count:" << m_children.size();

  if(with_children)
    {
      for(auto &&child : m_children)
        child->toDebug(offset + 1, with_children);
    }
}

std::vector<DataPlottableNode *>::iterator
DataPlottableNode::findNodeIterator(DataPlottableNode *searched_node_p)
{
  // We are searching for searched_node_p in this node. What we are looking into
  // the children of this node. If we do find searched_node_p amongst the
  // children of *this node, then we return the corresponding iterator.
  // Otherwise we step into each child node and perform a recursive findNode()
  // search on each of the children of each child node.

  if(searched_node_p == nullptr)
    qFatal() << "Pointer cannot be nullptr.";

  // qDebug().noquote() << "In node:" << toString(0, true)
  //<< ", finding node:" << searched_node_p->toString();

  // First look if any of the children is the searched node.

  std::vector<DataPlottableNode *>::iterator iter =
    std::find_if(m_children.begin(),
                 m_children.end(),
                 [searched_node_p](DataPlottableNode *iter_node) {
                   return iter_node == searched_node_p;
                 });

  if(iter != m_children.end())
    {
      // Great, *this node is actually the parent of the searched node.
      return iter;
    }

  // At this point we need to search the node into the children of the children
  // of this node.
  for(auto &&iter_node_p : m_children)
    {
      std::vector<DataPlottableNode *>::iterator iter =
        iter_node_p->findNodeIterator(searched_node_p);

      if(iter != iter_node_p->m_children.end())
        return iter;
    }

  // Well, we did not find the searched in the whole descendance of *this node.
  // Return the an an iterator to the end of the children vector that will be
  // easily tested against this->m_children.end() by the caller to know if the
  // node was found.

  return m_children.end();
}

} // namespace MineXpert

} // namespace MsXpS
