// Copyright (c) 2007-2020  INRIA (France).
// All rights reserved.
//
// This file is part of CGAL (www.cgal.org).
//
// $URL: https://github.com/CGAL/cgal/blob/v5.5.2/Orthtree/include/CGAL/Orthtree/Node.h $
// $Id: Node.h d1eca83 2022-11-07T17:34:54+00:00 Andreas Fabri
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
//
// Author(s)     : Jackson Campolattaro, Cédric Portaneri, Tong Zhao

#ifndef CGAL_ORTHTREE_NODE_H
#define CGAL_ORTHTREE_NODE_H

#include <CGAL/license/Orthtree.h>

#include <CGAL/Orthtree.h>

#include <boost/range/iterator_range.hpp>

#include <array>
#include <memory>
#include <bitset>
#include <cassert>
#include <iostream>

namespace CGAL {

/// \cond SKIP_IN_MANUAL
namespace Orthtrees
{

// Non-documented, or testing purpose only
struct Node_access
{
  template <typename Node, typename LC>
  static Node create_node (Node parent, LC local_coordinates)
  {
    return Node(parent, local_coordinates);
  }

  template <typename Node>
  static typename Node::Point_range& points(Node node) { return node.points(); }

  template <typename Node>
  static void split(Node node) { return node.split(); }
};

} // namespace Orthtrees
/// \endcond

/*!

  \brief represents a single node of the tree. Alternatively referred
  to as a cell, orthant, or sub-tree.

  A `Node` is a lightweight object and thus generally passed by
  copy. It is also a model of `ConstRange` with value type `Traits::Point_d`.

  \cgalModels `ConstRange`
 */
template<typename Traits, typename PointRange, typename PointMap>
class Orthtree<Traits, PointRange, PointMap>::Node
{

public:

  /// \name Types
  /// @{

  typedef Orthtree<Traits, PointRange, PointMap> Enclosing; ///< Orthtree type (enclosing class).
  typedef typename Enclosing::Dimension Dimension; ///< Dimension type.
  typedef typename Enclosing::Degree Degree; ///< Degree type.

  /*!
    \brief Self typedef for convenience.
   */
  typedef typename Orthtree<Traits, PointRange, PointMap>::Node Self;


  /// \cond SKIP_IN_MANUAL
  /*!
   * \brief Array for containing the child nodes of this node.
   */
  typedef std::array<Self, Degree::value> Children;
  /// \endcond

  /*!
    \brief Set of bits representing this node's relationship to its parent.

    Equivalent to an array of Booleans, where index[0] is whether `x`
    is greater, index[1] is whether `y` is greater, index[2] is whether
    `z` is greater, and so on for higher dimensions if needed.
    Used to represent a node's relationship to the center of its parent.
   */
  typedef std::bitset<Dimension::value> Local_coordinates;

  /*!
    \brief Coordinates representing this node's relationship
    with the rest of the tree.

    Each value `(x, y, z, ...)` of global coordinates is calculated by doubling
    the parent's global coordinates and adding the local coordinates.
   */
  typedef std::array<std::uint32_t, Dimension::value> Global_coordinates;


  typedef typename PointRange::const_iterator const_iterator; ///< constant iterator type.

  /*!
    \brief Adjacency directions.
   */
  typedef typename Traits::Adjacency Adjacency;

  /// @}

private:

  /// \cond SKIP_IN_MANUAL
  /*!
    \brief point range type.
   */
  typedef typename PointRange::iterator iterator; ///< constant iterator type.
  typedef boost::iterator_range<iterator> Point_range;
  /// \endcond

  // make Node trivially copiabled
  struct Data
  {
    Point_range points;
    Self parent;
    std::uint8_t depth;
    Global_coordinates global_coordinates;
    std::unique_ptr<Children> children;

    Data (Self parent)
      : parent (parent), depth (0) { }
  };

  Data* m_data;


  /// \cond SKIP_IN_MANUAL

  // Only the Orthtree class has access to the non-default
  // constructor, mutators, etc.
  friend Enclosing;

  // Hidden class to access methods for testing purposes
  friend Orthtrees::Node_access;

  /*!
   * \brief Access to the content held by this node
   * \return a reference to the collection of point indices
   */
  Point_range &points() { return m_data->points; }
  const Point_range &points() const { return m_data->points; }

  /// \name Construction
  /// @{

  /*!
    \brief creates a new node, optionally as the child of a parent

    If no parent is provided, the node created is assumed to be the
    root of a tree.  This means that `parent.is_null()` returns
    `true`, and the depth is zero.  If a parent is provided, the node
    becomes the child of that parent.  In that case, an index should
    be passed, telling this node its relationship to its parent.
    Depth and global coordinates are automatically determined in the
    constructor, and should generally be considered immutable after
    construction.

    \param parent the node containing this one
    \param index this node's relationship to its parent
  */
  explicit Node(Self parent, Local_coordinates local_coordinates)
    : m_data (new Data(parent)) {

    if (!parent.is_null()) {

      m_data->depth = parent.m_data->depth + 1;

      for (int i = 0; i < Dimension::value; i++)
        m_data->global_coordinates[i] = (2 * parent.m_data->global_coordinates[i]) + local_coordinates[i];

    }
    else
      for (int i = 0; i < Dimension::value; i++)
        m_data->global_coordinates[i] = 0;
  }

  void free() { delete m_data; }

  Node deep_copy(Self parent = Node()) const
  {
    if (is_null())
      return Node();

    Node out;
    out.m_data = new Data(parent);

    out.m_data->points = m_data->points;
    out.m_data->depth = m_data->depth;
    out.m_data->global_coordinates = m_data->global_coordinates;
    std::unique_ptr<Children> children;
    if (!is_leaf())
    {
      out.m_data->children = std::make_unique<Children>();
      for (int index = 0; index < Degree::value; index++)
        (*out.m_data->children)[index] = (*this)[index].deep_copy(out);
    }
    return out;
  }

  /// @}

  /// \name Mutators
  /// @{

  /*!
    \brief splits a node into subnodes.

    Only leaf nodes should be split.
    When a node is split it is no longer a leaf node.
    A number of `Degree::value` children are constructed automatically, and their values are set.
    Contents of this node are _not_ propagated automatically.
    It is the responsibility of the caller to redistribute the points contained by a node after splitting
   */
  void split() {

    CGAL_precondition (is_leaf());

    m_data->children = std::make_unique<Children>();
    for (int index = 0; index < Degree::value; index++) {

      (*m_data->children)[index] = std::move(Self(*this, {Local_coordinates(index)}));

    }
  }

  /*!
   * \brief eliminates this node's children, making it a leaf node.
   *
   * When a node is un-split, its children are automatically deleted.
   * After un-splitting a node it will be considered a leaf node.
   */
  void unsplit() {

    m_data->children.reset();
  }

  /// @}

  /// \endcond


public:

  /// \cond SKIP_IN_MANUAL
  // Default creates null node
  Node() : m_data(nullptr) { }

  // Comparison operator
  bool operator< (const Node& other) const { return m_data < other.m_data; }
  /// \endcond

  /// \name Type & Location
  /// @{

  /*!
    \brief returns `true` if the node is null, `false` otherwise.
  */
  bool is_null() const { return (m_data == nullptr); }

  /*!
    \brief returns `true` if the node has no parent, `false` otherwise.
    \pre `!is_null()`
  */
  bool is_root() const
  {
    CGAL_precondition(!is_null());
    return m_data->parent.is_null();
  }

  /*!
    \brief returns `true` if the node has no children, `false` otherwise.
    \pre `!is_null()`
  */
  bool is_leaf() const
  {
    CGAL_precondition(!is_null());
    return (!m_data->children);
  }

  /*!
    \brief returns this node's depth.
    \pre `!is_null()`
  */
  std::uint8_t depth() const
  {
    CGAL_precondition (!is_null());
    return m_data->depth;
  }

  /*!
    \brief returns this node's local coordinates (in relation to its parent).
    \pre `!is_null()`
  */
  Local_coordinates local_coordinates() const {

    CGAL_precondition (!is_null());
    // TODO: There must be a better way of doing this!

    Local_coordinates result;

    for (std::size_t i = 0; i < Dimension::value; ++ i)
      result[i] = global_coordinates()[i] & 1;

    return result;
  }

  /*!
    \brief returns this node's global coordinates.
    \pre `!is_null()`
  */
  Global_coordinates global_coordinates() const
  {
    CGAL_precondition (!is_null());
    return m_data->global_coordinates;
  }


  /// \name Adjacency
  /// @{

  /*!
    \brief returns this node's parent.
    \pre `!is_null()`
  */
  Self parent() const
  {
    CGAL_precondition (!is_null());
    return m_data->parent;
  }

  /*!
    \brief returns the nth child of this node.

    \pre `!is_null()`
    \pre `!is_leaf()`
    \pre `0 <= index && index < Degree::value`

    The orthtree subdivides the space in 2 on each dimension
    available, so a child node can be accessed by selecting a Boolean
    value for each dimension. The `index` parameter is thus
    interpreted as a bitmap, where each bit matches a dimension
    (starting by the least significant bit for coordinate X).

    For example, in the case of an octree (dimension 3):

    - index 0 (000 in binary) is the children on the "minimum corner" (xmin, ymin, zmin)
    - index 1 (001 in binary) is on (xmax, ymin, zmin)
    - index 2 (010 in binary) is on (xmin, ymax, zmin)
    - index 6 (101 in binary) is on (xmax, ymin, zmax)

    Diagram of a quadtree:

    ```
    Children of the current node:

    Y axis
    A
    |  +-------------------+-------------------+
    |  | Coord:  Ymax Xmin | Coord:  Ymax Xmax |
    |  | Bitmap:    1    0 | Bitmap:    1    1 |
    |  |                   |                   |
    |  | -> index = 2      | -> index = 3      |
    |  |                   |                   |
    |  |                   |                   |
    |  |                   |                   |
    |  |                   |                   |
    |  +-------------------+-------------------+
    |  | Coord:  Ymin Xmin | Coord:  Ymin Xmax |
    |  | Bitmap:    0    0 | Bitmap:    0    1 |
    |  |                   |                   |
    |  | -> index = 0      | -> index = 1      |
    |  |                   |                   |
    |  |                   |                   |
    |  |                   |                   |
    |  |                   |                   |
    |  +-------------------+-------------------+
    |
    +--------------------------------------------> X axis
    0
    ```

    The operator can be chained. For example, `n[5][2][3]` returns the
    third child of the second child of the fifth child of a node `n`.
  */
  Self operator[](std::size_t index) const {

    CGAL_precondition (!is_null());
    CGAL_precondition (!is_leaf());
    CGAL_precondition (index < Degree::value);

    return (*m_data->children)[index];
  }

  /*!
    \brief finds the directly adjacent node in a specific direction

    \pre `!is_null()`
    \pre `direction.to_ulong < 2 * Dimension::value`

    Adjacent nodes are found according to several properties:
    - adjacent nodes may be larger than the seek node, but never smaller
    - a node has at most `2 * Dimension::value` different adjacent nodes (in 3D: left, right, up, down, front, back)
    - adjacent nodes are not required to be leaf nodes

    Here's a diagram demonstrating the concept for a Quadtree:

    ```
    +---------------+---------------+
    |               |               |
    |               |               |
    |               |               |
    |       A       |               |
    |               |               |
    |               |               |
    |               |               |
    +-------+-------+---+---+-------+
    |       |       |   |   |       |
    |   A   |  (S)  +---A---+       |
    |       |       |   |   |       |
    +---+---+-------+---+---+-------+
    |   |   |       |       |       |
    +---+---+   A   |       |       |
    |   |   |       |       |       |
    +---+---+-------+-------+-------+
    ```

    - (S) : Seek node
    - A  : Adjacent node

    Note how the top adjacent node is larger than the seek node.  The
    right adjacent node is the same size, even though it contains
    further subdivisions.

    This implementation returns the adjacent node if it's found.  If
    there is no adjacent node in that direction, it returns a null
    node.

    \param direction which way to find the adjacent node relative to
    this one. Each successive bit selects the direction for the
    corresponding dimension: for an Octree in 3D, 010 means: negative
    direction in X, position direction in Y, negative direction in Z.

    \return the adjacent node if it exists, a null node otherwise.
  */
  Self adjacent_node (Local_coordinates direction) const
  {
    CGAL_precondition(!is_null());

    // Direction:   LEFT  RIGHT  DOWN    UP  BACK FRONT
    // direction:    000    001   010   011   100   101

    // Nodes only have up to 2*dim different adjacent nodes (since cubes have 6 sides)
    CGAL_precondition(direction.to_ulong() < Dimension::value * 2);

    // The root node has no adjacent nodes!
    if (is_root())
      return Self();

    // The least significant bit indicates the sign (which side of the node)
    bool sign = direction[0];

    // The first two bits indicate the dimension/axis (x, y, z)
    uint8_t dimension = uint8_t((direction >> 1).to_ulong());

    // Create an offset so that the bit-significance lines up with the dimension (e.g. 1, 2, 4 --> 001, 010, 100)
    int8_t offset = (uint8_t) 1 << dimension;

    // Finally, apply the sign to the offset
    offset = (sign ? offset : -offset);

    // Check if this child has the opposite sign along the direction's axis
    if (local_coordinates()[dimension] != sign) {

      // This means the adjacent node is a direct sibling, the offset can be applied easily!
      return parent()[local_coordinates().to_ulong() + offset];
    }

    // Find the parent's neighbor in that direction if it exists
    Self adjacent_node_of_parent = parent().adjacent_node(direction);

    // If the parent has no neighbor, then this node doesn't have one
    if (adjacent_node_of_parent.is_null())
      return Node();

    // If the parent's adjacent node has no children, then it's this node's adjacent node
    if (adjacent_node_of_parent.is_leaf())
      return adjacent_node_of_parent;

    // Return the nearest node of the parent by subtracting the offset instead of adding
    return adjacent_node_of_parent[local_coordinates().to_ulong() - offset];

  }

  /*!
    \brief equivalent to `adjacent_node()`, with an adjacency direction
    rather than a bitset.
   */
  Self adjacent_node(Adjacency adjacency) const {
    return adjacent_node(std::bitset<Dimension::value>(static_cast<int>(adjacency)));
  }

  /// @}

  /// \name Point Range
  /// @{

  /*!
    \brief checks whether the node is empty of points or not.
   */
  bool empty() const {
    return m_data->points.empty();
  }

  /*!
    \brief returns the number of points of this node.
   */
  std::size_t size() const {
    return std::size_t(std::distance(m_data->points.begin(), m_data->points.end()));
  }

  /*!
    \brief returns the iterator at the start of the collection of
    points held by this node.
   */
  const_iterator begin() const { return m_data->points.begin(); }

  /*!
    \brief returns the iterator at the end of the collection of
    points held by this node.
   */
  const_iterator end() const { return m_data->points.end(); }

  /// @}

  /// \cond SKIP_IN_MANUAL
  /// \name Operators
  /// @{

  /*!
   * \brief compares the topology of this node to another node.
   *
   * \todo
   *
   * \param rhs node to compare with
   * \return whether the nodes have different topology.
   */
  bool operator==(const Self &rhs) const {
    return m_data == rhs.m_data;
  }

  static bool is_topology_equal (const Self& a, const Self& b)
  {
    CGAL_assertion (!a.is_null() && !b.is_null());

    // If one node is a leaf, and the other isn't, they're not the same
    if (a.is_leaf() != b.is_leaf())
      return false;

    // If both nodes are non-leaf nodes
    if (!a.is_leaf()) {

      // Check all the children
      for (int i = 0; i < Degree::value; ++i) {
        // If any child cell is different, they're not the same
        if (!is_topology_equal(a[i], b[i]))
          return false;
      }
    }

    // If both nodes are leaf nodes, they must be in the same location
    return (a.global_coordinates() == b.global_coordinates());
  }

  friend std::ostream& operator<< (std::ostream& os, const Self& node)
  {
    return internal::print_orthtree_node(os, node);
  }
  /// \endcond
};

}

#endif //CGAL_ORTHTREE_NODE_H
