#pragma once

#include <psemek/math/vector.hpp>
#include <psemek/math/point.hpp>
#include <psemek/math/box.hpp>
#include <psemek/math/matrix.hpp>
#include <psemek/math/homogeneous.hpp>

namespace psemek::math
{

	template <typename T, std::size_t D>
	struct orthographic
	{
		using vector_type = math::vector<T, D>;
		using point_type = math::point<T, D>;
		using homogeneous_matrix_type = math::matrix<T, D + 1, D + 1>;
		using box_type = math::box<T, D>;

		orthographic();
		orthographic(box_type r);

		box_type box() const;
		box_type box(box_type r);

		vector_type operator() (vector_type v) const;
		point_type operator() (point_type p) const;

		homogeneous_matrix_type homogeneous_matrix() const;

		orthographic inverse() const;

	private:
		box_type r_;
	};

	template <typename T, std::size_t D>
	orthographic(box<T, D>) -> orthographic<T, D>;

	template <typename T, std::size_t D>
	orthographic<T, D>::orthographic()
	{
		for (std::size_t i = 0; i < D; ++i)
		{
			r_[i] = {T(-1), T(1)};
		}
	}

	template <typename T, std::size_t D>
	orthographic<T, D>::orthographic(box_type r)
		: r_(r)
	{}

	template <typename T, std::size_t D>
	box<T, D> orthographic<T, D>::box() const
	{
		return r_;
	}

	template <typename T, std::size_t D>
	box<T, D> orthographic<T, D>::box(box_type r)
	{
		std::swap(r, r_);
		return r;
	}

	template <typename T, std::size_t D>
	vector<T, D> orthographic<T, D>::operator() (vector<T, D> v) const
	{
		return as_vector(homogeneous_matrix() * homogeneous(v));
	}

	template <typename T, std::size_t D>
	point<T, D> orthographic<T, D>::operator() (point<T, D> p) const
	{
		return as_point(homogeneous_matrix() * homogeneous(p));
	}

	template <typename T, std::size_t D>
	matrix<T, D + 1, D + 1> orthographic<T, D>::homogeneous_matrix() const
	{
		auto m = matrix<T, D + 1, D + 1>::zero();

		for (std::size_t d = 0; d < D; ++d)
			m[d][d] = T(2) / r_[d].length();

		for (std::size_t d = 0; d < D; ++d)
			m[d][D] = - (r_[d].max + r_[d].min) / r_[d].length();

		m[D][D] = T(1);

		return m;
	}

	template <typename T, std::size_t D>
	orthographic<T, D> orthographic<T, D>::inverse() const
	{
		auto inv = [](interval<T> const & i)
		{
			auto const s = i.min + i.max;
			auto const d = i.length();
			return interval<T>{ (- s - 2) / d, (- s + 2) / d };
		};

		box_type inv_box;
		for (std::size_t d = 0; d < D; ++d)
			inv_box[d] = inv(r_[d]);
		return {inv_box};
	}

}
