#include <cfloat>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include "Node.hpp"

namespace nbody {
	using namespace std;


	Node::Node(Tree* tree) {
		for (int i = 0; i < 3; i++) {
			this->bb.setMin(i, 1.0);
			this->bb.setMax(i, -1.0);
		}
		this->afterSubtree = NULL;
		this->prev = this;
		this->next = this;
		this->leaf = true;
		this->tree = tree;
		this->prevSibling = NULL;
		this->nextSibling = NULL;
		this->parent = NULL;
		pthread_rwlock_init(&this->lock, NULL);
	}

	Node::~Node() {
		pthread_rwlock_destroy(&this->lock);
	}

	Box Node::getBB() {
		return this->bb;
	}

	void Node::setBB(Box bb) {
		this->bb = bb;
	}

	bool Node::isSplitable() {
		bool result = true;

		if (this->bodies.size() < this->tree->maxLeafBodies + 1) {
			result = false;
		}
		if (this->bb.volume() <= FLT_EPSILON) {
			result = false;
		}
		return result;
	}

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

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

	vector<Body> Node::getBodies() {
		return this->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() {
		if (this->afterSubtree == NULL) {
			cout << "after subtree null" << endl;
			return false;
		}
		if (!this->bb.isCorrect()) {
			cout << "bb wrong" << endl;
			return false;
		}
		for (int i = 0; i < 3; i++) {
			if (this->bb.getMin(i) > this->bb.getMax(i)) {
				cout << "bb " << i << " min " << this->bb.getMin(i) << " " << this->bb.getMax(i) << endl;
				return false;
			}
		}
		for (vector<Body>::iterator it = this->bodies.begin(); it != this->bodies.end(); it++) {
			if (!this->bb.isContained(*it)) {
				cout << "bb out of bounds" << endl;
				return false;
			}
		}
		if (!this->leaf) {
			Node* current = this->next;
			int children = 0;

			while (current != NULL && current != this->afterSubtree) {
				current = current->afterSubtree;
				children++;
			}
			if (current == NULL) {
				cout << "afterSubtree null" << endl;
				return false;
			}
			if (children != this->tree->numberOfChildren()) {
				cout << "wrong number of children " << children << endl;
				return false;
			}
			current = this->next;
			for (int i = 0; i < this->tree->numberOfChildren(); i++) {
				current = current->afterSubtree;
			}
			if (current != this->afterSubtree) {
				cout << "last sibling afterSubtree inconsistent" << endl;
				return false;
			}
		}
		if (!this->leaf && this->bodies.size() > 0) {
			cout << "non-empty inner node" << endl;
			return false;
		}
		return true;
	}

	void Node::update() {
		double position[3] = {0.0, 0.0, 0.0};
		double mass = 0.0;

		if (this->leaf) {
			for (vector<Body>::iterator it = this->bodies.begin(); it != this->bodies.end(); it++) {
				mass += it->mass;
				for (int i = 0; i < 3; i++) {
					position[i] += it->position[i] * it->mass;
				}
			}
		} else {
			for (Node* node = this->next; node != this->tree->nodes && node != NULL; node = node->afterSubtree) {
				mass += node->representative.mass;
			}
		}
		for (int i = 0; i < 3; i++) {
			this->representative.position[i] = position[i] / mass;
		}
		this->representative.mass = mass;
	}

	double Node::getL() {
		return this->bb.maxSidelength();
	}

	void Node::print() {
		this->bb.print();
		for (vector<Body>::iterator it = this->bodies.begin(); it != this->bodies.end(); it++) {
			cout << "  ";
			it->print();
		}
	}

	bool Node::sufficientForBody(Body body) {
		double distance = 0.0;

		for (int i = 0; i < 3; i++) {
			distance += (this->representative.position[i] - body.position[i]) * (this->representative.position[i] - body.position[i]);
		}
		return sqrt(distance) > this->getL();
	}

	bool Node::sufficientForBox(Box box) {
		return this->bb.distanceToBox(box) > this->getL();
	}

	void Node::setBodies(vector<Body> bodies) {
		this->bodies = bodies;
	}
}
