#pragma once

#include <psemek/sir/trivial.hpp>

#include <utility>

namespace psemek::sir
{

	namespace detail
	{

		template <typename T>
		constexpr bool is_struct_helper = requires (T x)
		{
			std::get<0>(x);
			std::tuple_size_v<T>;
		};

		template <typename T, std::size_t ... Is>
		void write_struct_impl(ostream & s, T const & x, std::index_sequence<Is...>)
		{
			(write(s, std::get<Is>(x)), ...);
		}

		template <typename T, std::size_t ... Is>
		void read_struct_impl(istream & s, T & x, std::index_sequence<Is...>)
		{
			(read(s, std::get<Is>(x)), ...);
		}

	}

	template <typename T>
	struct is_struct
		: std::bool_constant<detail::is_struct_helper<T>>
	{};

	template <typename T>
	constexpr bool is_struct_v = is_struct<T>::value;

	template <typename T, std::size_t N>
	struct is_struct<T[N]>
		: std::true_type
	{};

	template <typename Stream, typename T>
	requires is_ostream_v<Stream>
	void write_struct(Stream & s, T const & x)
	{
		detail::write_struct_impl(s, x, std::make_index_sequence<std::tuple_size_v<T>>{});
	}

	template <typename Stream, typename T>
	requires is_istream_v<Stream>
	void read_struct(Stream & s, T & x)
	{
		detail::read_struct_impl(s, x, std::make_index_sequence<std::tuple_size_v<T>>{});
	}

	template <typename Stream, typename T, std::size_t N>
	requires is_ostream_v<Stream>
	void write_struct(ostream & s, T const (&x)[N])
	{
		write_sequence(s, x, x + N);
	}

	template <typename Stream, typename T, std::size_t N>
	requires is_istream_v<Stream>
	void read_struct(Stream & s, T (&x)[N])
	{
		read_sequence(s, x, x + N);
	}

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

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

}
