#pragma once

#include <psemek/sir/platform.hpp>
#include <psemek/sir/stream.hpp>

#include <type_traits>

namespace psemek_sir_adl_helper
{

	struct fake_istream
	{
		void read(char *, std::size_t){}

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

	struct fake_ostream
	{
		void write(char const *, std::size_t){}

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

	static_assert(::psemek::sir::is_istream_v<fake_istream>);
	static_assert(::psemek::sir::is_ostream_v<fake_ostream>);

	template <typename T>
	constexpr bool is_custom_helper = requires (T & x, fake_istream & is, fake_ostream & os)
	{
		write(os, x);
		read(is, x);
	};

}

namespace psemek::sir
{

	template <typename T>
	struct is_custom
		: std::bool_constant<::psemek_sir_adl_helper::is_custom_helper<T>>
	{};

	template <typename T>
	constexpr bool is_custom_v = is_custom<T>::value;

	template <typename T>
	struct is_empty
		: std::bool_constant<std::is_empty_v<T>>
	{};

	template <typename T>
	constexpr bool is_empty_v = is_empty<T>::value;

	template <typename T>
	struct is_trivial
		: std::bool_constant<std::is_trivially_default_constructible_v<T> && std::is_trivially_copyable_v<T> && std::is_standard_layout_v<T>>
	{};

	template <typename T>
	constexpr bool is_trivial_v = is_trivial<T>::value;

	template <typename Stream, typename T>
	requires (is_ostream_v<Stream> && !is_custom_v<T> && is_empty_v<T>)
	void write(Stream &, T const &)
	{}

	template <typename Stream, typename T>
	requires (is_istream_v<Stream> && !is_custom_v<T> && is_empty_v<T>)
	void read(Stream &, T &)
	{}

	template <typename T, typename Stream>
	requires is_ostream_v<Stream>
	void write_padding(Stream & s)
	{
		char const zeros[alignof(T)] = {};
		std::size_t const padding = (alignof(T) - s.offset()) % alignof(T);
		if (padding != 0)
			s.write(zeros, padding);
	}

	template <typename T, typename Stream>
	requires is_istream_v<Stream>
	void read_padding(Stream & s)
	{
		char zeros[alignof(T)] = {};
		std::size_t const padding = (alignof(T) - s.offset()) % alignof(T);
		if (padding != 0)
			s.read(zeros, padding);
	}

	template <typename Stream, typename T>
	requires (is_ostream_v<Stream>)
	void write_trivial(Stream & s, T const & x)
	{
		write_padding<T>(s);
		s.write(reinterpret_cast<char const *>(std::addressof(x)), sizeof(x));
	}

	template <typename Stream, typename T>
	requires (is_ostream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && is_trivial_v<T>)
	void write(Stream & s, T const & x)
	{
		write_trivial(s, x);
	}

	template <typename Stream, typename T>
	requires (is_istream_v<Stream>)
	void read_trivial(Stream & s, T & x)
	{
		read_padding<T>(s);
		s.read(reinterpret_cast<char *>(std::addressof(x)), sizeof(x));
	}

	template <typename Stream, typename T>
	requires (is_istream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && is_trivial_v<T>)
	void read(Stream & s, T & x)
	{
		read_trivial(s, x);
	}

	template <typename Iterator>
	void write_sequence(ostream & s, Iterator begin, Iterator end)
	{
		for (; begin != end; ++begin)
			write(s, *begin);
	}

	template <typename Iterator>
	void read_sequence(istream & s, Iterator begin, Iterator end)
	{
		for (; begin != end; ++begin)
			read(s, *begin);
	}

}
