/* 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 "DataPlottableTree.hpp"
#include "DataPlottableNode.hpp"
#include "BasePlotWnd.hpp"


namespace MsXpS
{
namespace MineXpert
{


DataPlottableTree::DataPlottableTree()
{
}

DataPlottableTree::~DataPlottableTree()
{
  for(auto &&node : m_rootNodes)
    {
      // Each node is responsible for freeing its children nodes!

      delete node;
    }

  m_rootNodes.clear();
}

const BasePlotCompositeWidget *
DataPlottableTree::getPlotWidget(QCPAbstractPlottable *plottable_p)
{
  DataPlottableNode *found_node_p = findNode(plottable_p);

  if(found_node_p != nullptr)
    return found_node_p->getPlotWidget();

  return nullptr;
}

DataPlottableNode *
DataPlottableTree::addPlottable(MsRunDataSetCstSPtr ms_run_data_set_csp,
                                QCPAbstractPlottable *plottable_p,
                                QCPAbstractPlottable *parent_plottable_p,
                                BasePlotCompositeWidget *plot_widget_p)
{
  if(plottable_p == nullptr)
    qFatal() << "The pointer cannot be nullptr.";

  // If parent_p is not nullptr, then, we need to find the node that handles it.
  // That node will be the host for the new node we created for plottable_p.
  // Otherwise, the created node will be a root node.

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  // Set the variable location right now.
  DataPlottableNode *new_node_p    = nullptr;
  DataPlottableNode *parent_node_p = nullptr;

  if(parent_plottable_p == nullptr)
    {
      new_node_p = new DataPlottableNode(
        ms_run_data_set_csp, plottable_p, nullptr, plot_widget_p);

      m_rootNodes.push_back(new_node_p);
    }
  else
    {
      // Find the proper node in the tree !

      parent_node_p = findNode(parent_plottable_p);

      if(parent_node_p == nullptr)
        qFatal() << "Cannot be that the plottable has no matching parent node.";

      new_node_p = new DataPlottableNode(
        ms_run_data_set_csp, plottable_p, parent_node_p, plot_widget_p);

      parent_node_p->m_children.push_back(new_node_p);
    }

  // qDebug().noquote() << "Added new plottable:" << plottable_p
  //<< "in new node:" << new_node_p
  //<< "with parent plottable:" << parent_plottable_p
  //<< "sitting in parent node:" << parent_node_p
  //<< "for ms run data set:" << ms_run_data_set_csp.get();

  // qDebug() << "Data plot plottable tree has size:" << size();
  // qDebug() << "Data plot plottable tree has depth:" << depth();

  // Let's see the usage count of the MsRunDataSetCstSPtr.
  // qDebug() << "Usage count:" << ms_run_data_set_csp.use_count();

  return new_node_p;
}

const std::vector<DataPlottableNode *> &
DataPlottableTree::getRootNodes() const
{
  return m_rootNodes;
}

DataPlottableNode *
DataPlottableTree::findNode(QCPAbstractPlottable *plot_plottable_p)
{
  // qDebug() << "Starting the find of node for plottable:" << plot_plottable_p
  //<< "Iterating in the root nodes of the tree.";

  for(auto &root_node_p : m_rootNodes)
    {
      // qDebug() << "Iterating in root node:" << root_node_p->toString(0,
      // true);

      DataPlottableNode *found_node_p = root_node_p->findNode(plot_plottable_p);

      if(found_node_p != nullptr)
        {
          // qDebug() << "The node that was found having the right plottable
          // is:"
          //<< found_node_p->toString(0, true)
          //<< "thus returning that node.";

          return found_node_p;
        }
      // else
      // qDebug() << "That node did not return any node for searching
      // plottable:"
      //<< plot_plottable_p;
    }

  // qDebug() << "Returning nullptr.";

  return nullptr;
}

std::vector<DataPlottableNode *>
DataPlottableTree::findNodes(MsRunDataSetCstSPtr ms_run_data_set_csp,
                             bool recursively)
{
  // We are asked a list of nodes (only root nodes if recursively is set to
  // false) that match ms_run_data_set_csp.

  std::vector<DataPlottableNode *> found_nodes;

  for(auto &&root_node : m_rootNodes)
    {
      // First look into the root nodes of this tree.
      if(root_node->mcsp_msRunDataSet == ms_run_data_set_csp)
        found_nodes.push_back(root_node);

      // Next search in the root nodes' children and recursively...
      if(recursively)
        root_node->findNodes(ms_run_data_set_csp, found_nodes);
    }

  // qDebug() << "Returning a list of nodes for ms run data set:"
  //<< ms_run_data_set_csp.get() << "of size:" << found_nodes.size();

  return found_nodes;
}

std::vector<DataPlottableNode *>::iterator
DataPlottableTree::findNode(DataPlottableNode *searched_node_p)
{
  // We are in the tree, that is in the entity that stores all the root nodes of
  // the tree. We are looking for a node searched_node_p.

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

  // First search if any of the root nodes is the searched_node_p.

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

  if(iter != m_rootNodes.end())
    return iter;

  // At this point we need to go deeper in the tree and look inside the children
  // of the root nodes.

  for(auto &&root_node_p : m_rootNodes)
    {
      // The following call searched for node_p in the children of the
      // root_node_p, so the returned iter is to be compared to the
      // root_node_p->children.end() iterator to see if the searched node was
      // found.

      std::vector<DataPlottableNode *>::iterator iter =
        root_node_p->findNodeIterator(searched_node_p);

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

  // Evidently, the node was not found, return the end of the root nodes.
  return m_rootNodes.end();
}

std::vector<DataPlottableNode *>
DataPlottableTree::findNodes(
  BasePlotCompositeWidget *base_plot_composite_widget_p, bool recursively)
{
  if(base_plot_composite_widget_p == nullptr)
    qFatal() << "Cannot be that the pointer is nullptr.";

  // qDebug() << "Finding nodes that match plot widget:"
  //<< base_plot_composite_widget_p;

  std::vector<DataPlottableNode *> found_nodes;

  for(auto &&root_node_p : m_rootNodes)
    {
      // qDebug() << "Iterated plot widget:" << root_node_p->mp_plotWidget;

      // First look into the root nodes of this tree.
      if(root_node_p->mp_plotWidget == base_plot_composite_widget_p)
        found_nodes.push_back(root_node_p);

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

  // qDebug() << "Returning a list of nodes for the composite plot widget:"
  //<< base_plot_composite_widget_p << "of size:" << found_nodes.size();

  return found_nodes;
}

bool
DataPlottableTree::isRootNode(DataPlottableNode *node_p)
{

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

  if(iter != m_rootNodes.end())
    return true;

  return false;
}

bool
DataPlottableTree::isRootPlottable(QCPAbstractPlottable *plot_plottable_p)
{
  for(auto &node : m_rootNodes)
    {
      // qDebug() << "In one node of the root nodes.";

      if(node->mp_plottable == plot_plottable_p)
        return true;
    }

  return false;
}

std::size_t
DataPlottableTree::depth() const
{
  // We want to know what is the depth of the tree.

  if(!m_rootNodes.size())
    return 0;

  // qDebug() << "There are" << m_rootNodes.size() << "root nodes";

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

  for(auto &node : m_rootNodes)
    {
      // When we enter the depth() function below, we'll be at depth value 1,
      // which is true, since we will be going into a root child of the tree.
      tmp_depth = node->depth(1);

      // qDebug() << "Returned depth:" << tmp_depth;

      if(tmp_depth > greatest_depth)
        greatest_depth = tmp_depth;
    }

  return greatest_depth;
}

std::size_t
DataPlottableTree::size(bool look_deep) const
{
  // qDebug() << "The tree has" << m_rootNodes.size() << "root nodes";

  if(!look_deep)
    return m_rootNodes.size();

  // qDebug() << "Seeding cumulative_node_count with the  root nodes count:"
  //<< m_rootNodes.size();

  std::size_t cumulative_node_count = m_rootNodes.size();

  // qDebug() << "Now iterating in the root nodes.";

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

      // qDebug() << "After iterating in one more root node, the cumulative node
      // " "count is:"
      //<< cumulative_node_count;
    }

  // qDebug() << "Returning the cumulative node count:" <<
  // cumulative_node_count;

  return cumulative_node_count;
}

bool
DataPlottableTree::reparent(DataPlottableNode *node_p,
                            DataPlottableNode *new_parent_node_p)
{

  // Can be nullptr if the node is a root node in the tree.

  if(node_p->mp_parent != nullptr && new_parent_node_p != nullptr)
    return node_p->reparent(new_parent_node_p);

  // qDebug() << "Reparenting node:" << node_p->toString(0, true);
  // if(node_p->mp_parent == nullptr)
  // qDebug() << "Initial parent node: nullptr";
  // else
  // qDebug() << "Initial parent node:" << node_p->mp_parent->toString(0,
  // true);

  // To reparent a node, it is necessary to remove it from its parent's vector
  // of nodes (either root or m_children) and then to push_back a modified
  // version of it to the new_parent_node_p's vector of nodes (either root or
  // m_children). The operation is thus a move of a node with a modified
  // mp_parent value. Because we need to push_back a node to the node vector
  // of the new_parent_p, if that new_parent_node_p is nullptr, then that
  // means that we are reparenting node_p to the root nodes of the tree.

  // First remove the node from its parent: two cases:
  // 1. if its parent is nullptr, then the node is a root node and we need to
  // remove it from m_rootNodes vector of nodes.
  // 2. if its parent is non-nullptr, then we need to remove it from it's
  // parent's m_children vector of nodes.

  if(node_p->mp_parent == nullptr)
    {
      // The node to reparent is a root node. Remove that node from
      // m_rootNodes.

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

      if(iter == m_rootNodes.end())
        {
          qFatal() << "Cannot be that root node not be found in m_rootNodes.";
          // Needed for cppcheck to see that erase will be called if
          // iterator is not end().
          return false;
        }

      // At this point, simply remove the node from the m_rootNodes vector
      // using the iterator.

      m_rootNodes.erase(iter);

      // Sanity check:
      iter = std::find_if(m_rootNodes.begin(),
                          m_rootNodes.end(),
                          [node_p](DataPlottableNode *iter_node) {
                            return iter_node == node_p;
                          });

      if(iter != m_rootNodes.end())
        {
          qFatal(
            "Cannot be that root node still be in the root nodes after its "
            "removal from there.");
        }
    }
  else
    {
      // The node to reparent is not a root node, so remove that node from its
      // parent's m_children vector of nodes.

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

      if(iter == node_p->mp_parent->m_children.end())
        {
          qFatal(
            "Cannot be that the node be not found amongst its parent's "
            "children.");
        }

      // At this point, remove the node from its parent's vector of children.

      node_p->mp_parent->m_children.erase(iter);

      // Sanity check:
      iter = std::find_if(node_p->mp_parent->m_children.begin(),
                          node_p->mp_parent->m_children.end(),
                          [node_p](DataPlottableNode *iter_node) {
                            return iter_node == node_p;
                          });

      if(iter != node_p->mp_parent->m_children.end())
        qFatal(
          "Cannot be that node still in parent children after its removal "
          "from "
          "there.");
    }

  // Now that the node was removed from its parent, we can modify its parent
  // using the parent provided as parameter to this functino call.

  node_p->setParent(new_parent_node_p);

  // Finally push back the reparented node to the list of children of its
  // parent or to the m_rootNodes vector of nodes if the parent is nullptr.

  // qDebug() << "Reparented node:" << node_p->toString(0, true);

  if(new_parent_node_p == nullptr)
    {
      m_rootNodes.push_back(node_p);
      // qDebug() << "New parent node: nullptr";
    }
  else
    {
      new_parent_node_p->m_children.push_back(node_p);
      // qDebug() << "New parent node:" << new_parent_node_p->toString(0, true);
    }

  return true;
}

bool
DataPlottableTree::removeNodeAndPlottable(DataPlottableNode *node_p,
                                          bool recursively)
{
  // If we are destroying plottables because these plottables are contained
  // inside a plot widget, then there are provisions not to reparent children of
  // this node if they sit in the same widget being destructed.

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

  // What is the plot widget in which resides the plottable being destroyed ?
  BasePlotCompositeWidget *plot_composite_widget_p = node_p->mp_plotWidget;

  // First off ask that the map item relating the plottable and the processing
  // flow be erased.

  plot_composite_widget_p->removePlottableProcessingFlowMapItem(
    node_p->mp_plottable);

  // We will need the parent of the node_p multiple times below.
  DataPlottableNode *parent_node_p = node_p->mp_parent;

  if(recursively)
    {

      // qDebug() << "Recursive removal of the plottable node:"
      //<< node_p->toString(0, true)
      //<< "calling the removal function for all the children ("
      //<< node_p->m_children.size() << ") of the node first.";

      // Let's start by removing the children of node_p

      while(node_p->m_children.size())
        {
          std::vector<DataPlottableNode *>::iterator iter =
            node_p->m_children.end() - 1;

          removeNodeAndPlottable(*iter, recursively);
        }
    }
  else
    {

      // Since we do not recursively delete a node (and its plottable), the
      // children of that node and plottable need to be reparented to the
      // parent of that node_p that is being removed. This way, we do never
      // create orphans.
      //
      // There is one situation where we do not reparent the
      // children : if the children of the plottable being destructed is
      // actually in the same plot widget being destroyed.

      // qDebug().noquote() << "Need to reparent " << node_p->m_children.size()
      //<< "node_p's child to the parent of that node.";

      // qDebug().noquote() << "Before reparenting the children, node is:"
      //<< node_p->toString(0, true);

      // qDebug().noquote() << "and the parent node is:"
      //<< (parent_node_p == nullptr
      //? "nullptr"
      //: parent_node_p->toString(0, true));

      for(std::size_t iter = 0; iter < node_p->m_children.size(); ++iter)
        {
          DataPlottableNode *iter_node_p = node_p->m_children.at(iter);

          // qDebug().noquote()
          //<< "In while loop, children count:" << node_p->m_children.size();

          reparent(iter_node_p, parent_node_p);

          // Since we have removed the node from the children vector, we
          // need to decrement the iter variable !
          --iter;

          // qDebug().noquote() << "Size of the node's children after "
          //"reparenting one node:"
          //<< node_p->m_children.size();
        }

      // qDebug().noquote()
      //<< "After reparenting all (or not) the children, node has become:"
      //<< node_p->toString(0, true);

      // qDebug() << "and the parent node has remained (nullptr) or become:"
      //<< (parent_node_p == nullptr ? "nullptr"
      //: parent_node_p->toString(0, true));
    }
  // End of
  // if(!recursively)

  // Now that we have handled (or not) all the children of node_p, remove
  // node_p itself. This node_p might be in the depth of this tree, or it
  // might be one of the root nodes.

  // Before destroying node_p, we need to handle the plottable itself, as
  // its pointer is in the node_p.

  // qDebug() << "Removing plottable:" << node_p->mp_plottable
  //<< "from the plot widget:" << node_p->mp_plotWidget;

  const_cast<pappso::BasePlotWidget *>(plot_composite_widget_p->getPlotWidget())
    ->removePlottable(node_p->mp_plottable);

  node_p->mp_plottable = nullptr;

  // qDebug() << "Done removing plottable.";

  // Replot the plot widget so that the trace plottable disappears.
  const_cast<pappso::BasePlotWidget *>(plot_composite_widget_p->getPlotWidget())
    ->replot();

  // Now destroy the node. To do that we need to know the parent of the node
  // to destroy.

  // qDebug() << "We now finally can actually destroy the node.";

  if(parent_node_p != nullptr)
    {
      // The parent of the node is not nullptr, that means that node_p is
      // not a root node of the tree. So we need to find it amongst the
      // children of the node_p's parent's node.

      // qDebug().noquote()
      //<< "The parent of the node is not nullptr, thus search for the "
      //"node in the parent node's m_children. Parent node:"
      //<< parent_node_p->toString(0, true);

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

      if(iter == parent_node_p->m_children.end())
        qFatal() << "Programming error.";

      // Ok, we found node_p amongst the children of node_p's parent.
      // Remove it from the parent's list of children. It is going to be
      // deleted.

      // qDebug().noquote() << "The iterator that was returned points to node :
      // "
      //<< (*iter)->toString(0, true)
      //<< "that should be the node were removing.";

      // Sanity check

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

      if((*iter)->mp_parent != parent_node_p)
        qFatal() << "Programming error.";

      // With the sanity checks, we can safely delete node_p and remove its
      // pointer from the vector.
      delete node_p;
      parent_node_p->m_children.erase(iter);

      // qDebug().noquote() << "After erasing node from parent node's "
      //"children, parent has become:"
      //<< parent_node_p->toString(0, true);
    }
  else
    {
      // The node is a root node since its parent node is nullptr.

      // qDebug() << "The parent of the node to remove is nullptr, that is, the
      // " "node to remove is a root node.Searching the node to remove " "in the
      // root nodes.";

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

      if(iter == m_rootNodes.end())
        qFatal(
          "Not possible that the node having a parent node = nullptr is "
          "not found in the root nodes. Programming error.");

      // Ok, we found node_p amongst the root nodes of this tree.
      // Remove the node from the root nodes list. It is going to be
      // deleted.

      // qDebug() << "Found iterator to node:" << node_p << "in root nodes:";

      for(auto &&iter_node_p : m_rootNodes)
        iter_node_p->toString(0, true);

      // Sanity check

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

      if((*iter)->mp_parent != parent_node_p)
        qFatal() << "Programming error.";

      // qDebug().noquote() << "Before erasing node, the root nodes have
      // become:";
      // for(auto &&iter_node_p : m_rootNodes)
      // iter_node_p->toString(0, true);

      // With the sanity checks, we can safely delete node_p and remove its
      // pointer from the vector.
      delete node_p;
      m_rootNodes.erase(iter);

      // qDebug().noquote() << "After erasing node, the root nodes have
      // become:";
      // for(auto &&iter_node_p : m_rootNodes)
      // iter_node_p->toString(0, true);
    }

  return true;
}


} // namespace MineXpert

} // namespace MsXpS
