#include <algorithm>
#include <cmath>
#include <numeric>
#include <iostream>
#include <limits>

#include "Node.hpp"
#include "Tree.hpp"

namespace nbody {
	Node::Node(Tree* tree_):tree(tree_) {}

	Node::Node(Box bb_, std::vector<Body> bodies_, Tree* tree_): bodies(std::move(bodies_)), bb(bb_), tree(tree_) {}

	Node::Node(std::vector<Body> bodies_, Tree* tree_) : bodies(std::move(bodies_)), bb(bodies), tree(tree_) {}

	Box Node::getBB() const {
		return bb;
	}

	//check if node needs to be splitted during tree build
	bool Node::isSplitable() const {
		bool result = true;

		if (bodies.size() <= tree->maxLeafBodies) {
			result = false;
		}
		//this is to prevent errors with collocated particles
		if (bb.volume() <= std::numeric_limits<float>::epsilon()) {
			result = false;
		}
		return result;
	}

	void Node::extendBBforBodies() {
		bb.extendForBodies(bodies);
	}

	void Node::extendBBtoCube() {
		bb.extendToCube();
	}

	std::vector<Body> Node::getBodies() const {
		return bodies;
	}

	void Node::insertBefore(Node* node) {
		node->next = this;
		node->prev = this->prev;
		this->prev->next = node;
		this->prev = node;
	}

	void Node::insertAfter(Node* node) {
		this->next->insertBefore(node);
	}

	void Node::unlink() {
		this->next->prev = this->prev;
		this->prev->next = this->next;
		this->next = this;
		this->prev = this;
	}

	bool Node::isCorrect() const {
		if (afterSubtree == nullptr) {
			std::cerr << "after subtree null\n";
			return false;
		}
		if (!bb.isCorrectBox()) {
			std::cerr << "bb wrong\n";
			return false;
		}
		if (bb.min > bb.max) {
			std::cerr << "bb  min " << bb.min << " max " << bb.max << '\n';
			return false;
		}
		if (std::any_of(std::begin(bodies), std::end(bodies), [&](const Body& b) {return !isContained(b, bb); })) {
			std::cerr << "bb out of bounds\n";
			return false;
		}
		if (!leaf) {
			Node* current = next;
			std::size_t children = 0;

			while (current != nullptr && current != afterSubtree) {
				current = current->afterSubtree;
				children++;
			}
			if (current == nullptr) {
				std::cerr << "afterSubtree null\n";
				return false;
			}
			if (children != tree->numberOfChildren()) {
				std::cerr << "wrong number of children " << children << '\n';
				return false;
			}
			current = next;
			for (std::size_t i = 0; i < tree->numberOfChildren(); i++) {
				current = current->afterSubtree;
			}
			if (current != afterSubtree) {
				std::cerr << "last sibling afterSubtree inconsistent\n";
				return false;
			}
		}
		if (!leaf && !bodies.empty()) {
			std::cerr << "non-empty inner node\n";
			return false;
		}
		if (leaf && nextSibling != nullptr && next != nextSibling) {
			std::cerr << "wrong next sibling\n";
			return false;
		}
		return true;
	}

	//update overall node information
	void Node::update() {
		auto siblingNodesView = tree->getSiblingNodesView(this);
		auto mass = [&] {
			return leaf
				? std::accumulate(std::begin(bodies), std::end(bodies), 0.0, [](double m, const Body& b) { return m + b.mass; })
				: std::accumulate(std::begin(siblingNodesView), std::end(siblingNodesView), 0.0, [](double m, const Node& n) { return m + n.representative.mass; }); }();
		auto position = [&] {
			return leaf
				? std::accumulate(std::begin(bodies), std::end(bodies), Vec3{}, [](Vec3 p, const Body& b) { return p + (b.position * b.mass); })
				: Vec3{ 0.0 }; }();
		representative.position = position / mass;
		representative.mass = mass;
	}

	//get criterion to check if node is sufficient for force evaluation
	double Node::getL() const {
		return bb.maxSidelength();
	}

	void Node::print(std::size_t parallelId) const {
		bb.printBB(parallelId);
		for (const auto& body : bodies) {
			std::cout << "  ";
			body.print(parallelId);
		}
	}

	//check if node is sufficient for force evaluation
	bool Node::sufficientForBody(const Body& body) const {
		const auto distance2 = SquaredDistance(representative.position, body.position);
		return distance2 > std::pow(getL(), 2.0);
	}

	//check if node is sufficient for force evaluation for all bodies in box
	bool Node::sufficientForBox(Box box) const {
		return bb.distanceToBox(box) > getL();
	}

	//get local bodies
	void Node::extractLocalBodiesTo(std::vector<Body>& result) {
		std::copy_if(std::begin(bodies), std::end(bodies), std::back_inserter(result), [](const Body& b) {return !b.refinement; });
		bodies.clear();
	}
} // namespace nbody
