#ifndef TREE_NODE_HPP
#define TREE_NODE_HPP

#include <cstdlib>
#include <iterator>
#include <vector>

#include "Body.hpp"
#include "Box.hpp"

// utilities for reverse iteration
template <typename T>
struct reversion_wrapper { T& iterable; };

template <typename T>
auto begin(reversion_wrapper<T> w) { using std::rbegin;	return rbegin(w.iterable); }

template <typename T>
auto end(reversion_wrapper<T> w) { using std::rend; return rend(w.iterable); }

template <typename T>
reversion_wrapper<T> reverse(T&& iterable) { return{ iterable }; }

namespace nbody {
	class Tree;
	//class for storing node information
	class Node {
		friend class Tree;
		friend class NodeIterator;
		friend class BarnesHutTree;
	protected:
		std::vector<Body> bodies;
		Box bb;
		Node* prev{ this };
		Node* next{ this };
		Node* nextSibling{ nullptr };
		Node* prevSibling{ nullptr };
		Node* afterSubtree{ nullptr };
		bool leaf{ true };
		Tree* tree{ nullptr };
		Body representative;
	public:
		Node(Tree* _tree);
		Node(const Box& bb_, std::vector<Body> bodies_, Tree* tree_);
		Node(std::vector<Body> bodies_, Tree* tree_);
		~Node() = default;
		bool isSplitable() const;
		void extendBBforBodies();
		void extendBBtoCube();
		Box getBB() const;
		std::vector<Body> getBodies() const;
		void insertBefore(Node* node);
		void insertAfter(Node* node);
		void unlink();
		void update();
		double getL() const;
		bool isCorrect() const;
		void print(std::size_t parallelId) const;
		bool sufficientForBody(const Body& body) const;
		bool sufficientForBox(const Box& box) const;
		void extractLocalBodiesTo(std::vector<Body>& result);

		// iterator classes for traversing of pointered node data structures
		template<typename Derived>
		class BaseNodeIterator {
		protected:
			Node* node_{ nullptr };
			BaseNodeIterator(Node * node) :node_(node) {};
		public:
			typedef std::ptrdiff_t difference_type;
			typedef Node value_type;
			typedef Node& reference;
			typedef Node* pointer;
			typedef std::bidirectional_iterator_tag iterator_category;

			const BaseNodeIterator& operator++() {
				return static_cast<Derived*>(this)->operator++();
			}
			BaseNodeIterator operator++(int) {
				auto result = *this; ++(*this); return result;
			}
			BaseNodeIterator& operator--() { 
				return static_cast<Derived*>(this)->operator--(); 
			}
			BaseNodeIterator operator--(int) {
				auto result = *this; --(*this); return result;
			}

			reference operator*() const { return *node_; }
			pointer operator->() const { return node_; }
			friend bool operator==(const BaseNodeIterator& lhs, const BaseNodeIterator& rhs) { return lhs.node_ == rhs.node_; }
			friend bool operator!=(const BaseNodeIterator& lhs, const BaseNodeIterator& rhs) { return !(lhs == rhs); }
		};
		struct NodeIterator : BaseNodeIterator<NodeIterator>{
			NodeIterator(Node * node) : BaseNodeIterator(node) {};
			const BaseNodeIterator& operator++() { node_ = node_->next; return *this; }
			BaseNodeIterator& operator--() { node_ = node_->prev; return *this; }

		};
		struct SiblingNodeIterator : BaseNodeIterator<SiblingNodeIterator>{
			SiblingNodeIterator(Node * node) : BaseNodeIterator(node) {};
			const BaseNodeIterator& operator++() { node_ = node_->nextSibling; return *this; }
			BaseNodeIterator& operator--() { node_ = node_->prevSibling; return *this; }
		};
	};
} // namespace nbody

#endif
