#ifndef TREE_HPP
#define TREE_HPP

#include <cstdlib>
#include <string>
#include <vector>
#include <memory>

#include <boost/range/iterator_range.hpp>

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

namespace nbody {
	class Simulation;

	//superclass for Barnes-Hut tree
	class Tree {
		friend class Node;
		std::vector<std::unique_ptr<Node>> nodesMemory;

	protected:
		Node* nodes;
		std::size_t maxLeafBodies{ 0 };
		std::size_t parallelId{ 0 };
		Simulation* simulation{ nullptr };
	public:
		Tree(std::size_t parallelId_);
		virtual void clean();
		// constructs a Node, inserts it in the tree's memory management container, returns a stable pointer
		template<typename... Args>
		Node* createNode(Args&&... args) {
			auto ptr = std::make_unique<Node>(std::forward<Args>(args)...);
			auto raw_ptr = ptr.get();
			nodesMemory.push_back(std::move(ptr));
			return raw_ptr;
		}
		virtual void build(std::vector<Body> bodies) = 0;
		virtual void build(std::vector<Body> bodies, Box domain) = 0;
		virtual std::size_t numberOfChildren() const = 0;
		virtual std::size_t numberOfNodes() const;
		virtual bool isCorrect() const;
		virtual void accumulateForceOnto(Body& body);
		virtual void computeForces();
		virtual std::vector<Body> extractLocalBodies();
		virtual std::vector<Body> copyRefinements(Box domain) const;
		virtual void rebuild(Box domain);
		virtual void rebuild(Box domain, const std::vector<Body>& bodies);
		virtual void rebuild();
		virtual Box getRootBB() const;
		virtual void print(std::size_t parallelId) const;
		virtual Box advance();
		virtual Box getLocalBB() const;
	protected:
		// represents a traversable view over nodes->next
		auto getNodesView() {
			return boost::make_iterator_range(Node::NodeIterator{ nodes->next }, Node::NodeIterator{ nodes });
		}
		auto getNodesView() const {
			return boost::make_iterator_range(Node::NodeIterator{ nodes->next }, Node::NodeIterator{ nodes });
		}

		// represents a traversable view over nodes->nextSibling
		auto getSiblingNodesView(Node* start) {
			return boost::make_iterator_range(Node::SiblingNodeIterator{ start->next }, Node::SiblingNodeIterator{ nullptr });
		}
		auto getSiblingNodesView(Node* start) const {
			return boost::make_iterator_range(Node::SiblingNodeIterator{ start->next }, Node::SiblingNodeIterator{ nullptr });
		}

		template<typename Func>
		void foreach_localParticle(Func func) {
			for (auto& n : getNodesView()) {
				if (n.leaf) {
					for (auto& b : n.bodies) {
						if (!b.refinement) {
							func(b);
						}
					}
				}
			}
		}

		template<typename Func>
		void foreach_localParticle(Func func) const {
			for (auto& n : getNodesView()) {
				if (n.leaf) {
					for (auto& b : n.bodies) {
						if (!b.refinement) {
							func(b);
						}
					}
				}
			}
		}
	};
} // namespace nbody

#endif
