#pragma once

#include <psemek/math/point.hpp>
#include <psemek/util/hash.hpp>

#include <iostream>
#include <type_traits>
#include <algorithm>

namespace psemek::math
{

	template <typename Point, std::size_t K>
	struct simplex
	{
		using point_type = Point;

		point_type points[K + 1];

		point_type & operator[](std::size_t i)
		{
			return points[i];
		}

		point_type const & operator[](std::size_t i) const
		{
			return points[i];
		}
	};

	template <typename ... Args>
	simplex(Args ...) -> simplex<std::common_type_t<Args...>, sizeof...(Args) - 1>;

	template <typename Point, std::size_t K>
	auto begin(simplex<Point, K> & s)
	{
		return std::begin(s.points);
	}

	template <typename Point, std::size_t K>
	auto begin(simplex<Point, K> const & s)
	{
		return std::begin(s.points);
	}

	template <typename Point, std::size_t K>
	auto end(simplex<Point, K> & s)
	{
		return std::end(s.points);
	}

	template <typename Point, std::size_t K>
	auto end(simplex<Point, K> const & s)
	{
		return std::end(s.points);
	}

	template <typename Point>
	using segment = simplex<Point, 1>;

	template <typename Point>
	using triangle = simplex<Point, 2>;

	template <typename P, std::size_t K>
	bool operator == (simplex<P, K> const & s1, simplex<P, K> const & s2)
	{
		return std::equal(s1.points, s1.points + K + 1, s2.points);
	}

	template <typename P, std::size_t K>
	bool operator != (simplex<P, K> const & s1, simplex<P, K> const & s2)
	{
		return !(s1 == s2);
	}

	template <typename P, std::size_t K>
	bool operator < (simplex<P, K> const & s1, simplex<P, K> const & s2)
	{
		return std::lexicographical_compare(s1.points, s1.points + K + 1, s2.points, s2.points + K + 1);
	}

	template <typename P, std::size_t K>
	bool operator > (simplex<P, K> const & s1, simplex<P, K> const & s2)
	{
		return s2 < s1;
	}

	template <typename P, std::size_t K>
	bool operator <= (simplex<P, K> const & s1, simplex<P, K> const & s2)
	{
		return !(s2 < s1);
	}

	template <typename P, std::size_t K>
	bool operator >= (simplex<P, K> const & s1, simplex<P, K> const & s2)
	{
		return !(s1 < s2);
	}

	template <typename T, std::size_t N, std::size_t K>
	simplex<point<T, N>, K> operator + (simplex<point<T, N>, K> s, math::vector<T, N> const & v)
	{
		for (auto & p : s.points)
			p += v;
		return s;
	}

	template <typename T, std::size_t N, std::size_t K>
	simplex<point<T, N>, K> operator + (math::vector<T, N> const & v, simplex<point<T, N>, K> s)
	{
		for (auto & p : s.points)
			p += v;
		return s;
	}

	template <typename T, std::size_t N, std::size_t K>
	simplex<point<T, N>, K> operator - (simplex<point<T, N>, K> s, math::vector<T, N> const & v)
	{
		for (auto & p : s.points)
			p -= v;
		return s;
	}

	template <typename Point, std::size_t K>
	std::ostream & operator << (std::ostream & os, simplex<Point, K> const & s)
	{
		os << '(' << s[0];
		for (std::size_t i = 1; i <= K; ++i)
			os << ", " << s[i];
		os << ')';
		return os;
	}

}

namespace std
{

	template <typename Point, std::size_t K>
	struct hash<::psemek::math::simplex<Point, K>>
	{
		std::uint64_t operator()(::psemek::math::simplex<Point, K> const & simplex) const
		{
			std::uint64_t result = 0;
			std::hash<Point> h;
			for (auto const & p : simplex.points)
				::psemek::util::hash_combine(result, h(p));
			return result;
		}
	};

}
