#pragma once

#include <iostream>
#include <limits>
#include <type_traits>
#include <cmath>
#include <format>

namespace psemek::math
{

	namespace detail
	{

		template <typename T>
		concept has_min = requires ()
		{
			{ T::min() } -> std::same_as<T>;
		};

		template <typename T>
		concept has_max = requires ()
		{
			{ T::max() } -> std::same_as<T>;
		};

	}

	// Can be specialized in client code
	template <typename T>
	struct limits
	{
		static constexpr T min()
		{
			if constexpr (detail::has_min<T>)
			{
				return T::min();
			}
			else if constexpr (std::is_floating_point_v<T>)
			{
				return -std::numeric_limits<T>::infinity();
			}
			else if constexpr (std::is_integral_v<T>)
			{
				return std::numeric_limits<T>::min();
			}
			else
			{
				static_assert("unknown type");
			}
		}

		static constexpr T max()
		{
			if constexpr (detail::has_max<T>)
			{
				return T::max();
			}
			else if constexpr (std::is_floating_point_v<T>)
			{
				return std::numeric_limits<T>::infinity();
			}
			else if constexpr (std::is_integral_v<T>)
			{
				return std::numeric_limits<T>::max();
			}
			else
			{
				static_assert("unknown type");
			}
		}
	};

	template <typename T>
	struct interval_iterator
	{
		T value;

		T operator*() const { return value; }

		interval_iterator & operator++()
		{
			++value;
			return *this;
		}

		interval_iterator operator++(int)
		{
			auto copy = *this;
			++value;
			return copy;
		}

		friend bool operator == (interval_iterator const & i1, interval_iterator const & i2)
		{
			return i1.value == i2.value;
		}

		friend bool operator != (interval_iterator const & i1, interval_iterator const & i2)
		{
			return !(i1 == i2);
		}
	};

	template <typename T>
	struct interval
	{
		T min = limits<T>::max();
		T max = limits<T>::min();

		static interval singleton(T const & value)
		{
			return {value, value};
		}

		static interval full()
		{
			return {limits<T>::min(), limits<T>::max()};
		}

		bool empty() const
		{
			return min > max;
		}

		T length() const
		{
			return max - min;
		}

		T center() const
		{
			return min + (max - min) / T(2);
		}

		using iterator = interval_iterator<T>;
		using const_iterator = iterator;

		iterator begin() const { return {min}; }
		iterator end() const { return {max}; }

		interval & operator += (T const & delta);
		interval & operator -= (T const & delta);

		interval & operator &= (T const & a);
		interval & operator |= (T const & a);

		interval & operator &= (interval const & i);
		interval & operator |= (interval const & i);
	};

	template <typename T>
	interval(T, T) -> interval<T>;

	template <typename T1, typename T>
	interval<T1> cast(interval<T> const & i)
	{
		return {static_cast<T1>(i.min), static_cast<T1>(i.max)};
	}

	template <typename T>
	interval<T> operator - (interval<T> const & i)
	{
		return {-i.max, -i.min};
	}

	template <typename T>
	interval<T> operator + (interval<T> const & i, T const & delta)
	{
		return {i.min + delta, i.max + delta};
	}

	template <typename T>
	interval<T> operator + (T const & delta, interval<T> const & i)
	{
		return {delta + i.min, delta + i.max};
	}

	template <typename T>
	interval<T> operator - (interval<T> const & i, T const & delta)
	{
		return {i.min - delta, i.max - delta};
	}

	template <typename T>
	interval<T> operator - (T const & delta, interval<T> const & i)
	{
		return {delta - i.max, delta - i.min};
	}

	template <typename T>
	interval<T> operator & (interval<T> const & i, T const & a)
	{
		using std::min;
		using std::max;
		return {max(i.min, a), min(i.max, a)};
	}

	template <typename T>
	interval<T> operator & (T const & a, interval<T> const & i)
	{
		using std::min;
		using std::max;
		return {max(a, i.min), min(a, i.max)};
	}

	template <typename T>
	interval<T> operator | (interval<T> const & i, T const & a)
	{
		using std::min;
		using std::max;
		return {min(i.min, a), max(i.max, a)};
	}

	template <typename T>
	interval<T> operator | (T const & a, interval<T> const & i)
	{
		using std::min;
		using std::max;
		return {min(a, i.min), max(a, i.max)};
	}

	template <typename T>
	interval<T> operator & (interval<T> const & i1, interval<T> const & i2)
	{
		using std::min;
		using std::max;
		return {max(i1.min, i2.min), min(i1.max, i2.max)};
	}

	template <typename T>
	interval<T> operator | (interval<T> const & i1, interval<T> const & i2)
	{
		using std::min;
		using std::max;
		return {min(i1.min, i2.min), max(i1.max, i2.max)};
	}

	template <typename T>
	interval<T> & interval<T>::operator += (T const & delta)
	{
		return *this = *this + delta;
	}

	template <typename T>
	interval<T> & interval<T>::operator -= (T const & delta)
	{
		return *this = *this - delta;
	}

	template <typename T>
	interval<T> & interval<T>::operator &= (T const & a)
	{
		return *this = *this & a;
	}

	template <typename T>
	interval<T> & interval<T>::operator |= (T const & a)
	{
		return *this = *this | a;
	}

	template <typename T>
	interval<T> & interval<T>::operator &= (interval<T> const & i)
	{
		return *this = *this & i;
	}

	template <typename T>
	interval<T> & interval<T>::operator |= (interval<T> const & i)
	{
		return *this = *this | i;
	}

	template <typename T>
	bool operator == (interval<T> const & i1, interval<T> const & i2)
	{
		return i1.min == i2.min && i1.max == i2.max;
	}

	template <typename T>
	bool operator != (interval<T> const & i1, interval<T> const & i2)
	{
		return !(i1 == i2);
	}

	template <typename T>
	T clamp(T x, interval<T> const & i)
	{
		return std::max(i.min, std::min(i.max, x));
	}

	template <typename T>
	T lerp(interval<T> const & i, T t)
	{
		return i.min + i.length() * t;
	}

	template <typename T>
	T unlerp(interval<T> const & i, T t)
	{
		return (t - i.min) / i.length();
	}

	template <typename T>
	std::ostream & operator << (std::ostream & os, interval<T> const & i)
	{
		os << '[' << i.min << " .. " << i.max << ']';
		return os;
	}

	template <typename T>
	interval<T> expand(interval<T> const & i, T const & d)
	{
		return {i.min - d, i.max + d};
	}

	template <typename T>
	interval<T> shrink(interval<T> const & i, T const & d)
	{
		return expand(i, -d);
	}

	template <typename T>
	bool isfinite(interval<T> const & i)
	{
		return std::isfinite(i.min) && std::isfinite(i.max);
	}

	template <typename T>
	requires(std::is_integral_v<T>)
	interval<T> closed(interval<T> const & i)
	{
		return {i.min, i.max + T(1)};
	}

}

namespace std
{

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

		template <typename FormatContext>
		auto format(::psemek::math::interval<T> const & i, FormatContext & ctx) const
		{
			ctx.advance_to(std::format_to(ctx.out(), "["));
			ctx.advance_to(formatter<T, Char>::format(i.min, ctx));
			ctx.advance_to(std::format_to(ctx.out(), " .. "));
			ctx.advance_to(formatter<T, Char>::format(i.max, ctx));
			return std::format_to(ctx.out(), "]");
		}
	};

}
