#pragma once

#include <psemek/math/vector.hpp>
#include <psemek/math/point.hpp>
#include <psemek/math/sign.hpp>
#include <psemek/math/robust.hpp>

#ifdef PSEMEK_ROBUST_PREDICATES
#include <boost/multiprecision/gmp.hpp>
#endif

#include <limits>
#include <type_traits>

namespace psemek::math
{

	template <typename T, std::size_t N, typename ... Vectors>
	sign_t orientation(fast_predicate_tag, vector<T, N> const & v1, Vectors const & ... vs)
	{
		T const d = det(v1, vs...);

		if (d > T{})
			return sign_t::positive;
		else if (d < T{})
			return sign_t::negative;
		else
			return sign_t::zero;
	}

	template <typename T>
	std::enable_if_t<std::is_floating_point_v<T>, sign_t>
		orientation(robust_predicate_tag, point<T, 2> const & p0, point<T, 2> const & p1, point<T, 2> const & p2)
	{
#ifdef PSEMEK_ROBUST_PREDICATES
		constexpr T error = std::numeric_limits<T>::epsilon() * T(5) / T(2);

		T const d = (p1[0] - p0[0]) * (p2[1] - p0[1])
			- (p1[1] - p0[1]) * (p2[0] - p0[0]);

		T const t = std::abs((p1[0] - p0[0]) * (p2[1] - p0[1]))
				+ std::abs((p1[1] - p0[1]) * (p2[0] - p0[0]));

		if (d > t * error)
			return sign_t::positive;
		else if (d < - t * error)
			return sign_t::negative;
		else
		{
			using exact_type = boost::multiprecision::mpq_rational;

			return orientation(cast<exact_type>(p0), cast<exact_type>(p1), cast<exact_type>(p2));
		}
#else
	return orientation(fast_predicate_tag{}, p0, p1, p2);
#endif
	}

	template <typename T, std::size_t N, typename ... Points>
	sign_t orientation(fast_predicate_tag, point<T, N> const & p0, Points const & ... ps)
	{
		auto o = orientation((ps - p0)...);

		if constexpr ((N % 2) == 0)
		{
			return o;
		}
		else
		{
			return inverse(o);
		}
	}

	template <typename ... Args>
	sign_t orientation(Args const & ... args)
	{
		return orientation(default_robust_tag, args...);
	}

}
