#pragma once

#include <cgv/type/standard_types.h>
#include <cgv/math/vec.h>
#include <cgv/math/fvec.h>

namespace cgv {
	namespace media {

/**
 * An axis aligned box, defined by to points: min and max
 */
template<typename T, cgv::type::uint32_type N>
class axis_aligned_box
{
public:
	/// internally fixed sized points and vectors are used
	typedef cgv::math::fvec<T,N> fpnt_type;
	typedef cgv::math::fvec<T,N> fvec_type;
	/// the interface allows also to work with variable sized points
	typedef cgv::math::vec<T> pnt_type;
	typedef cgv::math::vec<T> vec_type;
protected:
	fpnt_type minp;
	fpnt_type maxp;
public:
	/// standard constructor does not initialize
	axis_aligned_box() { invalidate(); }
	/// type conversion copy constructor
	template <typename S>
	axis_aligned_box(const axis_aligned_box<S, N>& B) : 
		minp(T(B.get_min_pnt()(0)), T(B.get_min_pnt()(1)), T(B.get_min_pnt()(2))), 
		maxp(T(B.get_max_pnt()(0)), T(B.get_max_pnt()(1)), T(B.get_max_pnt()(2))) {}
	/// construct from min point and max point
	axis_aligned_box(const fpnt_type& _minp, const fpnt_type& _maxp) : minp(_minp), maxp(_maxp) {}
	/// construct from min point and max point
	axis_aligned_box(const pnt_type& _minp, const pnt_type& _maxp)
	{
		invalidate();
		unsigned i;
		for (i=0; i<_minp.size(); ++i) {
			if (i == N)
				break;
			minp(i) = _minp(i);
		}
		for (i=0; i<_maxp.size(); ++i) {
			if (i == N)
				break;
			maxp(i) = _maxp(i);
		}
	}
	/// set to invalid min and max points
	void invalidate() {
		for (unsigned int c = 0; c<N; ++c) {
			minp(c) = 1;
			maxp(c) = 0;
		}
	}
	/// return a const reference to corner 0
	const fpnt_type& get_min_pnt() const { return minp; }
	/// return a const reference to corner 7
	const fpnt_type& get_max_pnt() const { return maxp; }
	/// return a reference to corner 0
	fpnt_type& ref_min_pnt() { return minp; }
	/// return a reference to corner 7
	fpnt_type& ref_max_pnt() { return maxp; }
	/// return the i-th corner where the lower N bits of i are used to select between min (bit = 0) and max (bit = 1) coordinate
	fpnt_type get_corner(int i) const
	{
		fpnt_type c = minp;
		int bit = 1;
		for (unsigned int dim=0; dim < N; ++dim, bit *= 2)
			if (i & bit)
				c(dim) = maxp(dim);
		return c;
	}
	/// return a vector with the extents in the different dimensions
	fvec_type get_extent() const { return maxp-minp; }
	/// return the center of the box
	fpnt_type get_center() const { return minp+(T)0.5*get_extent(); }
	/// check if aab is valid
	bool is_valid() const { return minp[0] <= maxp[0]; }
	/// check whether a point is inside the aabb
	bool inside(const fpnt_type& p) const {
		for (unsigned int c = 0; c<N; ++c) {
			if (p(c) < minp(c)) return false;
			if (p(c) >= maxp(c)) return false;
		}
		return true;
	}
	/// extent box to include given point
	void add_point(const fpnt_type& p) {
		if (is_valid()) {
			for (unsigned int c = 0; c<N; ++c) {
				if (p(c) > maxp(c)) maxp(c) = p(c);
				if (p(c) < minp(c)) minp(c) = p(c);
			}
		}
		else
			minp = maxp = p;
	}
	/// extent box to include given point
	void add_point(const pnt_type& p) {
		if (is_valid()) {
			for (unsigned int c = 0; p.size(); ++c) {
				if (c == N)
					break;
				if (p(c) > maxp(c)) maxp(c) = p(c);
				if (p(c) < minp(c)) minp(c) = p(c);
			}
		}
		else
			*this = axis_aligned_box<T,N>(p,p);
	}
	/// extent box to include given axis alinged box
	void add_axis_aligned_box(const axis_aligned_box<T,N>& aab) {
		if (!aab.is_valid())
			return;
		add_point(aab.minp);
		add_point(aab.maxp);
	}
	/// scale the complete box with respect to the world coordinate origin
	void scale(const T& f)
	{
		for (unsigned int c = 0; c<N; ++c) {
			maxp(c) *= f;
			minp(c) *= f;
		}
	}
	/// return the index of the coordinte with maximum extend
	unsigned get_max_extent_coord_index() const
	{
		fvec_type e = get_extent();
		unsigned j = 0;
		for (unsigned i=1; i<N; ++i)
			if (e(i) > e(j))
				j = i;
		return j;
	}
};

	}
}