#pragma once

#include <iterator>

namespace psemek::util
{

	template <typename T>
	struct span
	{
		using value_type = std::remove_cv_t<T>;
		using size_type = std::size_t;
		using difference_type = std::ptrdiff_t;
		using pointer = T *;
		using const_pointer = const T *;
		using reference = T &;
		using const_reference = const T &;
		using iterator = pointer;
		using reverse_iterator = std::reverse_iterator<iterator>;

		T * p_begin = nullptr;
		T * p_end = nullptr;

		span() = default;
		span(span const &) = default;
		span(span && other)
			: p_begin{other.p_begin}
			, p_end{other.p_end}
		{
			other.reset();
		}

		span(T * begin, T * end)
			: p_begin{begin}
			, p_end{end}
		{}

		span(T * begin, std::size_t size)
			: p_begin{begin}
			, p_end{begin + size}
		{}

		template <std::size_t N>
		span(T (&a)[N])
			: p_begin{a}
			, p_end{a + N}
		{}

		template <std::size_t N>
		requires (!std::is_same_v<T, std::remove_cv_t<T>>)
		span(std::remove_cv_t<T> (&a)[N])
			: p_begin{a}
			, p_end{a + N}
		{}

		template <typename Container>
		span(Container && a)
			: p_begin{a.data()}
			, p_end{a.data() + a.size()}
		{}

		span & operator =(span const &) = default;
		span & operator =(span && other)
		{
			if (this == &other)
				return *this;

			p_begin = other.p_begin;
			p_end = other.p_end;
			other.reset();
			return *this;
		}

		T * data() const { return p_begin; }

		T * begin() const { return p_begin; }
		T * end() const { return p_end; }

		size_type size() const { return p_end - p_begin; }

		bool empty() const { return p_begin == p_end; }

		reverse_iterator rbegin() const { return reverse_iterator(p_end); }
		reverse_iterator rend() const { return reverse_iterator(p_begin); }

		T & front() const { return *p_begin; }
		T & back() const { return *(p_end - 1); }

		span subspan(std::size_t start, std::size_t end) const
		{
			return span{p_begin + start, p_begin + end};
		}

		span prefix(std::size_t size) const
		{
			return subspan(0, size);
		}

		span suffix(std::size_t size) const
		{
			return span{p_end - size, p_end};
		}

		span & consume(std::size_t count)
		{
			p_begin += count;
			return *this;
		}

		void reset()
		{
			p_begin = nullptr;
			p_end = nullptr;
		}

		T & operator[] (size_type i) const { return p_begin[i]; }
	};

	template <typename T>
	bool operator == (span<T> const & s1, span<T> const & s2)
	{
		return (s1.begin() == s2.begin()) && (s1.end() == s2.end());
	}

	template <typename T>
	bool operator != (span<T> const & s1, span<T> const & s2)
	{
		return !(s1 == s2);
	}

	template <typename T, typename H>
	span<T> cast(span<H> const & s)
	{
		return span<T>{reinterpret_cast<T *>(s.begin()), reinterpret_cast<T *>(s.end())};
	}

	template <typename T>
	span<T const> make_span(std::initializer_list<T> const & list)
	{
		return {list.begin(), list.end()};
	}

	template <typename Container, typename = decltype(std::declval<Container>().data())>
	auto make_span(Container & container)
	{
		return span{container.data(), container.data() + container.size()};
	}

	template <typename T>
	auto make_singleton_span(T & value)
	{
		return span{std::addressof(value), std::addressof(value) + 1};
	}

}
