#include <iostream>
#include <cmath>
#include <cfloat>
#include "Box.hpp"

namespace nbody {
	using namespace std;

	Box::Box() {
		for (int i = 0; i < 3; i++) {
			this->min[i] = FLT_MAX;
			this->max[i] = -FLT_MAX;
		}
	}

	Box::~Box() {
	}

	//min/max getter/setters
	double Box::getMin(int index) {
		return this->min[index];
	}

	double Box::getMax(int index) {
		return this->max[index];
	}

	void Box::setMin(int index, double value) {
		this->min[index] = value;
	}

	void Box::setMax(int index, double value) {
		this->max[index] = value;
	}

	void Box::extendForBodies(vector<Body> bodies) {
		if (bodies.empty()) return;
		for (vector<Body>::iterator it = bodies.begin(); it != bodies.end(); it++) {
			for (int i = 0; i < 3; i++) {
				if (it->position[i] < this->min[i]) {
					this->min[i] = it->position[i];
				}
				if (it->position[i] > this->max[i]) {
					this->max[i] = it->position[i];
				}
			}
		}
	}

	void Box::extendToCube() {
		int longestSide = -1;
		double sidelength = 0.0;

		for (int i = 0; i < 3; i++) {
			if (this->max[i] - this->min[i] >= sidelength) {
				longestSide = i;
				sidelength = this->max[i] - this->min[i];
			}
		}
		if (longestSide == -1) {
			return;
		}
		for (int i = 0; i < 3; i++) {
			if (i != longestSide) {
				double extend = (sidelength - (this->max[i] - this->min[i])) / 2.0;

				this->min[i] -= extend;
				this->max[i] += extend;
			}
		}
	}

	vector<Body> Box::copyBodies(vector<Body> bodies) {
		vector<Body> result;

		for (vector<Body>::iterator it = bodies.begin(); it != bodies.end(); it++) {
			if (it->position[0] >= this->min[0] && it->position[0] <= this->max[0] &&
					it->position[1] >= this->min[1] && it->position[1] <= this->max[1] &&
					it->position[2] >= this->min[2] && it->position[2] <= this->max[2]) {
				result.push_back(*it);
			}
		}
		return result;
	}

	vector<Body> Box::extractBodies(vector<Body>& bodies) {
		vector<Body> result;
		vector<Body>::iterator it = bodies.begin();

		while (it != bodies.end()) {
			if (it->position[0] >= this->min[0] && it->position[0] <= this->max[0] &&
					it->position[1] >= this->min[1] && it->position[1] <= this->max[1] &&
					it->position[2] >= this->min[2] && it->position[2] <= this->max[2]) {
				result.push_back(*it);
				it = bodies.erase(it);
			} else {
				it++;
			}
		}
		return result;
	}

	bool Box::isContained(Body body) {
		for (int i = 0; i < 3; i++) {
			if (body.position[i] < this->min[i] || body.position[i] > this->max[i]) {
				return false;
			}
		}
		return true;
	}

	//TODO: check semantics
	bool Box::isContained(Box box) {
		for (int i = 0; i < 3; i++) {
			if (box.min[i] < this->min[i] || box.max[i] > this->max[i]) {
				return false;
			}
		}
		return true;
	}

	double Box::volume() {
		double result = 1.0;

		for (int i = 0; i < 3; i++) {
			result *= this->max[i] - this->min[i];
		}
		return result;
	}

	bool Box::overlapsSphere(double* sphereCenter, double sphereRadius) {
		double dmin = 0.0;

		for (int i = 0; i < 3; i++) {
			if (sphereCenter[i] < this->min[i]) {
				double dist = sphereCenter[i] - this->min[i];

				dmin += dist * dist;
			} else if (sphereCenter[i] > this->max[i]) {
				double dist = sphereCenter[i] - this->max[i];

				dmin += dist * dist;
			}
		}
		return dmin <= sphereRadius * sphereRadius;
	}

	double Box::maxSidelength() {
		double max = 0.0;

		for (int i = 0; i < 3; i++) {
			if (this->max[i] - this->min[i] > max) {
				max = this->max[i] - this->min[i];
			}
		}
		return max;
	}

	double Box::distanceToPosition(double* position) {
		int inside = 0;
		double nextPosition[3] = {position[0], position[1], position[2]};

		for (int i = 0; i < 3; i++) {
			if (nextPosition[i] < this->min[i]) {
				nextPosition[i] = this->min[i];
			} else if (nextPosition[i] > this->max[i]) {
				nextPosition[i] = this->max[i];
			} else {
				inside++;
			}
		}
		if (inside == 3) {
			return 0.0;
		} else {
			double dist = 0.0;

			for (int i = 0; i < 3; i++) {
				dist += (nextPosition[i] - position[i]) * (nextPosition[i] - position[i]);
			}
			return sqrt(dist);
		}
	}

	double Box::distanceToBox(Box box) {
		double length = 0.0;

		for (int i = 0; i < 3; i++) {
			double elem;

			if (box.min[i] < this->min[i] && box.max[i] < this->min[i]) {
				elem = this->min[i] - box.max[i];
			} else if (box.min[i] > this->max[i] && box.max[i] > this->max[i]) {
				elem = box.min[i] - this->max[i];
			} else {
				elem = 0.0;
			}
			length += elem * elem;
		}
		return sqrt(length);
	}

	vector<Box> Box::octreeSplit() {
		vector<Box> result;

		for (unsigned int i = 0; i < 8; i++) {
			Box current = *this;

			for (unsigned int j = 0; j < 3; j++) {
				double middle = current.getMin(j) + (current.getMax(j) - current.getMin(j)) / 2.0;

				if (i & (1 << j)) {
					current.setMin(j, middle);
				} else {
					current.setMax(j, middle);
				}
			}
			result.push_back(current);
		}
		return result;
	}

	vector<Box> Box::splitLongestSide() {
		vector<Box> result;
		int longestIndex = -1;
		double longestSide = -1.0;

		for (int i = 0; i < 3; i++) {
			if (this->max[i] - this->min[i] > longestSide) {
				longestSide = this->max[i] - this->min[i];
				longestIndex = i;
			}
		}
		double middle = this->min[longestIndex] + (this->max[longestIndex] - this->min[longestIndex]) / 2.0;
		result.push_back(*this);
		result.back().max[longestIndex] = middle;
		result.push_back(*this);
		result.back().min[longestIndex] = middle;
		return result;
	}

	bool Box::isCorrect() {
		for (int i = 0; i < 3; i++) {
			if (this->max[i] < this->min[i]) {
				cout << "inverted bb" << endl;
				return false;
			}
		}
		return true;
	}

	void Box::print() {
		cout << " min ";
		for (int i = 0; i < 3; i++) {
			cout << this->min[i] << " ";
		}
		cout << " max ";
		for (int i = 0; i < 3; i++) {
			cout << this->max[i] << " ";
		}
		cout << endl;
	}

	bool Box::contained(double* position) {
		for (int i = 0; i < 3; i++) {
			if (position[i] < this->min[i] || position[i] > this->max[i]) {
				return false;
			}
		}
		return true;
	}

	void Box::extend(Box extender) {
		for (int i = 0; i < 3; i++) {
			if (this->min[i] > extender.min[i]) {
				this->min[i] = extender.min[i];
			}
			if (this->max[i] < extender.max[i]) {
				this->max[i] = extender.max[i];
			}
		}
	}

	void Box::extend(Body extender) {
		for (int i = 0; i < 3; i++) {
			if (this->min[i] > extender.position[i]) {
				this->min[i] = extender.position[i];
			}
			if (this->max[i] < extender.position[i]) {
				this->max[i] = extender.position[i];
			}
		}
	}
}
