#pragma once
#include <utility>
#include <iterator>
namespace zstd
{
	namespace detail
	{
		template<typename T, typename = void>
		struct is_contiguous_container : std::false_type {};

		template<typename T>
		struct is_contiguous_container<T, std::void_t<decltype(
			std::data(std::declval<T&>()),
			std::size(std::declval<T&>())
			)>> : std::true_type {};
		template<typename T> constexpr auto is_contiguous_container_v = is_contiguous_container<T>::value;
	}

	template <typename T>
	struct span
	{
		static constexpr auto npos = static_cast<size_t>(-1);
		using element_type = T;
		using value_type = std::remove_cv_t<T>;
		using size_t = 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 = T*;
		using const_iterator = const T*;
		using reverse_iterator = std::reverse_iterator<iterator>;
		using const_reverse_iterator = std::reverse_iterator<const_iterator>;

		T* _begin = nullptr;
		T* _end = nullptr;

		constexpr span() noexcept = default;
		constexpr span(T* begin, T* end) noexcept;
		template <typename Container, typename = std::enable_if_t<detail::is_contiguous_container_v<Container>
		&& std::is_convertible_v<decltype(std::data(std::declval<Container&>())), T*>
			&& !std::is_same_v<std::decay_t<Container>, span>>>
			constexpr span(Container&& c) noexcept;
		constexpr span(const span&) noexcept = default;

		// element access
		constexpr T& operator[](size_t index) const noexcept;
		constexpr T& front() const noexcept;
		constexpr T& back() const noexcept;
		constexpr T* data() const noexcept;

		// iterators
		constexpr T* begin() const noexcept;
		constexpr T* end() const noexcept;

		// subviews
		constexpr span<T> subspan(size_t offset, size_t count = npos) const noexcept;

		// capacity
		constexpr size_t size() const noexcept;
		constexpr size_t size_bytes() const noexcept;
		constexpr bool empty() const noexcept;
	};
	//User defined deduction guide (C++17 onwards)
	template<typename Container>
	span(Container&&) -> span < std::remove_reference_t<decltype(*std::declval<Container>().data())>>;

	template<typename T>
	constexpr span<T>::span(T* begin, T* end) noexcept
		: _begin{ begin }, _end{ end }
	{}

	template<typename T>
	template<typename Container, typename>
	constexpr span<T>::span(Container&& c) noexcept
		: _begin{ std::data(c) }, _end{ std::data(c) + std::size(c) }
	{
	}

	template<typename T>
	constexpr T* span<T>::begin() const noexcept
	{
		return _begin;
	}

	template<typename T>
	constexpr T* span<T>::end() const noexcept
	{
		return _end;
	}
	template<typename T>
	inline constexpr span<T> span<T>::subspan(size_t offset, size_t count) const noexcept
	{
		return span<T>{_begin + offset, count != npos ? _begin + offset + count : _end};
	}
	template<typename T>
	inline constexpr T* span<T>::data() const noexcept
	{
		return _begin;
	}
	template<typename T>
	constexpr size_t span<T>::size() const noexcept
	{
		return _end - _begin;
	}

	template<typename T>
	inline constexpr size_t span<T>::size_bytes() const noexcept
	{
		return size() * sizeof(T);
	}

	template<typename T>
	inline constexpr bool span<T>::empty() const noexcept
	{
		return _begin == _end;
	}

	template<typename T>
	constexpr T& span<T>::operator[](size_t index) const noexcept
	{
		return _begin[index];
	}
	template<typename T>
	inline constexpr T& span<T>::front() const noexcept
	{
		return *_begin;
	}
	template<typename T>
	inline constexpr T& span<T>::back() const noexcept
	{
		return _end[-1];
	}
}
