#pragma once

#include <psemek/math/interval.hpp>
#include <psemek/math/point.hpp>

#include <iostream>
#include <format>

namespace psemek::math
{

	template <typename T, std::size_t N>
	struct box
	{
		interval<T> axes[N];

		using point_type = point<T, N>;
		using vector_type = vector<T, N>;

		interval<T> & operator[](std::size_t i)
		{
			return axes[i];
		}

		interval<T> const & operator[](std::size_t i) const
		{
			return axes[i];
		}

		static box singleton(point_type const & p)
		{
			box b;
			for (std::size_t i = 0; i < N; ++i)
				b.axes[i] = interval<T>::singleton(p[i]);
			return b;
		}

		static box full()
		{
			box b;
			for (std::size_t i = 0; i < N; ++i)
				b.axes[i] = interval<T>::full();
			return b;
		}

		bool empty() const
		{
			for (auto const & i : axes)
				if (i.empty())
					return true;
			return false;
		}

		T volume() const
		{
			T result = T{1};
			for (auto const & i : axes)
				result *= i.length();
			return result;
		}

		math::vector<T, N> dimensions() const
		{
			math::vector<T, N> result;
			for (std::size_t i = 0; i < N; ++i)
				result[i] = axes[i].length();
			return result;
		}

		point_type center() const
		{
			point_type p;
			for (std::size_t i = 0; i < N; ++i)
				p[i] = axes[i].center();
			return p;
		}

		template <typename ... Ts>
		point_type corner(Ts const & ... ts) const
		{
			static_assert(sizeof...(Ts) == N);

			T w[N] = {static_cast<T>(ts)...};
			point_type res;
			for (std::size_t i = 0; i < N; ++i)
				res[i] = lerp(axes[i], w[i]);
			return res;
		}

		box & operator += (vector_type const & delta);
		box & operator -= (vector_type const & delta);

		box & operator &= (point_type const & p);
		box & operator |= (point_type const & p);

		box & operator &= (box const & b);
		box & operator |= (box const & b);
	};

	template <typename T1, typename T, std::size_t N>
	box<T1, N> cast(box<T, N> const & b)
	{
		box<T1, N> result;
		for (std::size_t i = 0; i < N; ++i)
			result[i] = cast<T1>(b[i]);
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator + (box<T, N> const & b, vector<T, N> const & delta)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = b[i] + delta[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator + (vector<T, N> const & delta, box<T, N> const & b)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = delta[i] + b[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator - (box<T, N> const & b, vector<T, N> const & delta)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = b[i] - delta[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator & (box<T, N> const & b, point<T, N> const & p)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = b[i] & p[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator & (point<T, N> const & p, box<T, N> const & b)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = p[i] & b[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator | (box<T, N> const & b, point<T, N> const & p)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = b[i] | p[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator | (point<T, N> const & p, box<T, N> const & b)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = p[i] | b[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator & (box<T, N> const & b1, box<T, N> const & b2)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = b1[i] & b2[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> operator | (box<T, N> const & b1, box<T, N> const & b2)
	{
		box<T, N> result;
		for (std::size_t i = 0; i < N; ++i)
		{
			result[i] = b1[i] | b2[i];
		}
		return result;
	}

	template <typename T, std::size_t N>
	box<T, N> & box<T, N>::operator += (vector<T, N> const & delta)
	{
		return *this = *this + delta;
	}

	template <typename T, std::size_t N>
	box<T, N> & box<T, N>::operator -= (vector<T, N> const & delta)
	{
		return *this = *this - delta;
	}

	template <typename T, std::size_t N>
	box<T, N> & box<T, N>::operator &= (point<T, N> const & p)
	{
		return *this = *this & p;
	}

	template <typename T, std::size_t N>
	box<T, N> & box<T, N>::operator |= (point<T, N> const & p)
	{
		return *this = *this | p;
	}

	template <typename T, std::size_t N>
	box<T, N> & box<T, N>::operator &= (box<T, N> const & b)
	{
		return *this = *this & b;
	}

	template <typename T, std::size_t N>
	box<T, N> & box<T, N>::operator |= (box<T, N> const & b)
	{
		return *this = *this | b;
	}

	template <typename T, std::size_t N>
	bool operator == (box<T, N> const & b1, box<T, N> const & b2)
	{
		for (std::size_t i = 0; i < N; ++i)
			if (b1[i] != b2[i])
				return false;
		return true;
	}

	template <typename T, std::size_t N>
	bool operator != (box<T, N> const & b1, box<T, N> const & b2)
	{
		return !(b1 == b2);
	}

	template <typename T, std::size_t N>
	std::ostream & operator << (std::ostream & os, box<T, N> const & b)
	{
		for (std::size_t i = 0; i < N; ++i)
		{
			if (i != 0) os << 'x';
			os << b[i];
		}
		return os;
	}

	template <typename T, std::size_t N>
	point<T, N> clamp(point<T, N> p, box<T, N> const & b)
	{
		for (std::size_t i = 0; i < N; ++i)
			p[i] = clamp(p[i], b[i]);
		return p;
	}

	template <typename T, std::size_t N>
	box<T, N> expand(box<T, N> b, T const & d)
	{
		for (std::size_t i = 0; i < N; ++i)
		{
			b[i] = expand(b[i], d);
		}
		return b;
	}

	template <typename T, std::size_t N>
	box<T, N> expand(box<T, N> b, vector<T, N> const & d)
	{
		for (std::size_t i = 0; i < N; ++i)
		{
			b[i] = expand(b[i], d[i]);
		}
		return b;
	}

	template <typename T, std::size_t N>
	box<T, N> shrink(box<T, N> b, T const & d)
	{
		return expand(b, -d);
	}

	template <typename T, std::size_t N>
	box<T, N> shrink(box<T, N> b, vector<T, N> const & d)
	{
		return expand(b, -d);
	}

	template <typename T, std::size_t N>
	box<T, N> span(point<T, N> const & p1, point<T, N> const & p2)
	{
		box<T, N> b;
		for (std::size_t i = 0; i < N; ++i)
		{
			b.axes[i] = {p1[i], p2[i]};
			if (b.axes[i].min > b.axes[i].max)
				std::swap(b.axes[i].min, b.axes[i].max);
		}
		return b;
	}

	template <typename T, std::size_t N>
	point<T, N> lerp(box<T, N> const & b, vector<T, N> const & v)
	{
		point<T, N> p;
		for (std::size_t i = 0; i < N; ++i)
			p[i] = lerp(b[i], v[i]);
		return p;
	}

	template <typename T, std::size_t N>
	vector<T, N> unlerp(box<T, N> const & b, point<T, N> const & p)
	{
		vector<T, N> v;
		for (std::size_t i = 0; i < N; ++i)
			v[i] = unlerp(b[i], p[i]);
		return v;
	}

	template <typename T, std::size_t N>
	bool isfinite(box<T, N> const & b)
	{
		for (std::size_t i = 0; i < N; ++i)
			if (!isfinite(b[i]))
				return false;
		return true;
	}

}

namespace std
{

	template <typename T, std::size_t N, typename Char>
	struct formatter<::psemek::math::box<T, N>, Char>
		: formatter<::psemek::math::interval<T>, Char>
	{
		using formatter<::psemek::math::interval<T>, Char>::parse;

		template <typename FormatContext>
		auto format(::psemek::math::box<T, N> const & b, FormatContext & ctx) const
		{
			for (std::size_t i = 0; i < b.dimension(); ++i)
			{
				if (i > 0) ctx.advance_to(std::format_to(ctx.out(), "x"));
				ctx.advance_to(formatter<::psemek::math::interval<T>, Char>::format(b[i], ctx));
			}
			return ctx.out();
		}
	};

}
