/*
	This file is part of NSEssentials.

	Use of this source code is granted via a BSD-style license, which can be found
	in License.txt in the repository root.

	@author Nico Schertler
*/

#pragma once

#include <Eigen/Dense>

namespace nse {
	namespace math
	{

		//Represents an n-dimensional axis-aligned bounding box. The lower limits are inclusive,
		//whereas the upper limits are exclusive.
		template <typename T, int DIM>
		struct BoundingBox
		{
			Eigen::Matrix<T, DIM, 1> min, max;

			//Initializes the bounding box with an empty area.
			BoundingBox()
			{
				reset();
			}

			BoundingBox(const Eigen::Matrix<T, DIM, 1>& min, const Eigen::Matrix<T, DIM, 1>& max)
				: min(min), max(max)
			{ }

			//Resets the bounding box to an empty area.
			void reset()
			{
				min.setConstant(std::numeric_limits<float>::max());
				max.setConstant(std::numeric_limits<float>::lowest());
			}

			//Computes the intersection of two axis-aligned bounding boxes and returns if there is an intersection.
			static bool intersect(const BoundingBox<T, DIM>& bb1, const BoundingBox<T, DIM>& bb2, BoundingBox<T, DIM>& out)
			{
				for (int i = 0; i < DIM; ++i)
				{
					out.min[i] = std::max(bb1.min[i], bb2.min[i]);
					if (out.min[i] > bb1.max[i] || out.min[i] > bb2.max[i])
						return false;

					out.max[i] = std::min(bb1.max[i], bb2.max[i]);
					if (out.max[i] < bb1.min[i] || out.max[i] < bb2.min[i])
						return false;
				}
				return true;
			}

			//Computes the union of two axis-aligned bounding boxes.
			static void unite(const BoundingBox<T, DIM>& bb1, const BoundingBox<T, DIM>& bb2, BoundingBox<T, DIM>& out)
			{
				for (int i = 0; i < DIM; ++i)
				{
					out.min[i] = std::min(bb1.min[i], bb2.min[i]);
					out.max[i] = std::max(bb1.max[i], bb2.max[i]);
				}
			}

			//expands the bounding box to contain every point in V
			void expand(const Eigen::Matrix<T, DIM, -1>& V)
			{
				for (int v = 0; v < V.cols(); ++v)
				{
					for (int i = 0; i < DIM; ++i)
					{
						if (V(i, v) < min(i))
							min(i) = V(i, v);
						if (V(i, v) >= max(i))
							max(i) = nextafter(V(i, v), std::numeric_limits<T>::max());
					}
				}
			}

			//Returns if the bounding box contains the given point.
			bool containsPoint(const Eigen::Matrix<T, DIM, 1>& v) const
			{
				for (int i = 0; i < DIM; ++i)
				{
					if (v(i) < min(i) || v(i) >= max(i))
						return false;
				}
				return true;
			}

			//Returns the box's diagonal
			Eigen::Matrix<T, DIM, 1> diagonal() const { return max - min; }

			//Returns the box's center
			Eigen::Matrix<T, DIM, 1> center() const { return 0.5f * (min + max); }

			//Transforms the bounding box with a given affine transform and returns the axis-aligned bounding box
			//of the transformed box.
			BoundingBox<T, DIM> transform(const Eigen::Transform<T, DIM, Eigen::Affine>& t) const
			{
				//http://dev.theomader.com/transform-bounding-boxes/

				Eigen::Matrix<T, DIM, 1> tMin = t.translation();
				Eigen::Matrix<T, DIM, 1> tMax = t.translation();

				for (int i = 0; i < DIM; ++i)
				{
					Eigen::Matrix<T, DIM, 1> a = t.linear().col(i) * min(i);
					Eigen::Matrix<T, DIM, 1> b = t.linear().col(i) * max(i);

					tMin += a.cwiseMin(b);
					tMax += a.cwiseMax(b);
				}

				return BoundingBox<T, DIM>(tMin, tMax);
			}
		};
	}
}