#pragma once

#include <psemek/io/stream.hpp>
#include <psemek/util/any_set.hpp>

namespace psemek::sir
{

	namespace detail
	{

		template <typename T>
		constexpr bool is_istream_helper = requires (T & x, T const & cx, char * p, std::size_t n)
		{
			x.read(p, n);
			{ cx.offset() } -> std::same_as<std::size_t>;
		};

		template <typename T>
		constexpr bool is_ostream_helper = requires (T & x, T const & cx, char const * p, std::size_t n)
		{
			x.write(p, n);
			{ cx.offset() } -> std::same_as<std::size_t>;
		};

	}

	template <typename T>
	struct is_istream
		: std::bool_constant<detail::is_istream_helper<T>>
	{};

	template <typename T>
	constexpr bool is_istream_v = is_istream<T>::value;

	template <typename T>
	struct is_ostream
		: std::bool_constant<detail::is_ostream_helper<T>>
	{};

	template <typename T>
	constexpr bool is_ostream_v = is_ostream<T>::value;

	struct istream
	{
		istream(io::istream & s, std::size_t offset = 0)
			: s_(s)
			, offset_(offset)
		{}

		void read(char * p, std::size_t size)
		{
			s_.read_all(p, size);
			offset_ += size;
		}

		io::istream & stream() { return s_; }
		io::istream const & stream() const { return s_; }

		std::size_t offset() const { return offset_; }

		util::any_set & context() { return context_; }

	private:
		io::istream & s_;
		std::size_t offset_ = 0;
		util::any_set context_;
	};

	struct ostream
	{
		ostream(io::ostream & s, std::size_t offset = 0)
			: s_(s)
			, offset_(offset)
		{}

		void write(char const * p, std::size_t size)
		{
			s_.write_all(p, size);
			offset_ += size;
		}

		io::ostream & stream() { return s_; }
		io::ostream const & stream() const { return s_; }

		std::size_t offset() const { return offset_; }

		util::any_set & context() { return context_; }

	private:
		io::ostream & s_;
		std::size_t offset_ = 0;
		util::any_set context_;
	};

	struct null_istream
	{
		null_istream(std::size_t offset = 0)
			: offset_(offset)
		{}

		void read(char *, std::size_t size)
		{
			offset_ += size;
		}

		std::size_t offset() const { return offset_; }

	private:
		std::size_t offset_ = 0;
	};

	struct null_ostream
	{
		null_ostream(std::size_t offset = 0)
			: offset_(offset)
		{}

		void write(char const *, std::size_t size)
		{
			offset_ += size;
		}

		std::size_t offset() const { return offset_; }

	private:
		std::size_t offset_ = 0;
	};

	static_assert(is_istream_v<istream>);
	static_assert(is_ostream_v<ostream>);
	static_assert(is_istream_v<null_istream>);
	static_assert(is_ostream_v<null_ostream>);

}
