#ifndef __octree_h
#define __octree_h

#include <iostream>

/**\brief An n-dimensional octree. Perhaps it should be named a tttntree (two-to-the n tree)?
 *
 * This templated class provides an n-dimensional binary tree, storing an
 * object whose type is specified by the first template parameter at each
 * node (whether or not that node is a leaf).
 * The second template parameter is an integer constant specifying the
 * dimension of the tree.
 * The final template parameter takes an allocator object just as the STL does.
 *
 * The tree itself stores the center and overall bounds of the root node;
 * the nodes themselves do not contain any information on the geometry.
 * If needed, it can be derived from an octree_path object referring to
 * the node or stored in the template class at each node for fast access.
 * This makes the class useful for storing abstract trees with minimal overhead
 * as well as trees embedded in a metric space.
 *
 * Access to entries in the tree is provided by octree_cursor and octree_iterator
 * objects, both of which inherit octree_path.
 * An octree_path stores a vector of integers describing a descent from the root
 * node to a particular child node of the octree.
 * Since each node of a \f$d\f$-dimensional tree has \f$2^d\f$ children,
 * the integers in the vector will all be in \f$\{0,\ldots,2^d-1\}\f$.
 *
 * The octree_cursor class provides a free-form way to visit nodes in the octree;
 * it does not behave like an iterator that guarantees each node will be visited once.
 * Instead, it provides a way to move up, down, and across the tree from any location.
 * This makes it useful for local queries that do not need to traverse the entire tree.
 *
 * The octree_iterator class simply traverses the tree in depth-first order
 * and can be configured to visit only leaf nodes or to include all nodes.
 */
template <typename T_, int d_ = 3, typename A_ = std::allocator<T_> >
class octree
{
public:
  typedef T_ value_type;
  typedef T_* pointer;
  typedef T_& reference;

  typedef const T_* const_pointer;
  typedef const T_& const_reference;

  typedef octree<T_, d_, A_> _self_type;
  typedef _self_type* _self_pointer;

  typedef octree_node<T_, d_, A_>* octree_node_pointer;
  typedef octree_node<T_, d_, A_>& octree_node_reference;
  typedef const octree_node<T_, d_, A_>* const_octree_node_pointer;
  typedef const octree_node<T_, d_, A_>& const_octree_node_reference;

  typedef typename std::vector<octree_node_pointer>::size_type size_type;

  typedef A_ allocator_type;

  // Ugly. But necessary according to young me. Old me says so.
  typedef octree_iterator<T_, T_&, T_*, _self_type, _self_pointer, d_> iterator;
  typedef octree_iterator<T_, const T_&, const T_*, _self_type, _self_pointer, d_> const_iterator;

  typedef octree_cursor<T_, T_&, T_*, _self_type, _self_pointer, d_> cursor;
  typedef octree_cursor<T_, const T_&, const T_*, _self_type, _self_pointer, d_> const_cursor;

  octree(const double* center, double size);
  octree(const double* center, double size, const value_type& root_node_value);
  virtual ~octree();

  /** \brief Iterator based access.
   *
   * Iterators come in const and non-const versions as well as versions
   * that visit all nodes or just leaf nodes. By default, only leaf nodes
   * are visited.
   *
   * You may add or remove children while iterating with some caveats.
   * When adding children, any iterator currently referencing the node
   * to which children were added will reference the first child node
   * when incremented. Iterators confined to leaf nodes may reference
   * non-leaf nodes when refinement occurs between increments/decrements.
   * When removing children from node \a A, any iterators pointing to
   * grandchildren of \a A will become invalid. Iterators pointing to
   * children of \a A may not be dereferenced but may be incremented or
   * decremented safely.
   */
  //@{
  iterator begin(bool only_leaves = true) { return iterator(_M_root, _M_root, only_leaves); }
  iterator end(bool only_leaves = true) { return iterator(_M_root, nullptr, only_leaves); }

  const_iterator begin(bool only_leaves = true) const
  {
    return const_iterator(_M_root, _M_root, only_leaves);
  }
  const_iterator end(bool only_leaves = true) const
  {
    return const_iterator(_M_root, 0, only_leaves);
  }
  //@}

  octree_node_pointer root() { return this->_M_root; }

  size_t size(bool only_leaves = false);

  /** \brief Geometric information
   * Binary trees of dimension 2 or higher are often used to partition geometric objects into
   * subsets. In order to do this, the tree must have some geometric center and size. These are set
   * by the constructor but may be queried at any time. They may not be modified as that would
   * require a re-partitioning of the objects (typically stored at nodes or leaf-nodes).
   */
  //@{
  const double* center() const { return this->_M_center; }
  double size() const { return this->_M_size; }
  //@}

protected:
  octree_node_pointer _M_root;
  double _M_center[d_];
  double _M_size;
};

#endif // __octree_h
