#pragma once

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

#include <array>

namespace psemek::sir
{

	namespace detail
	{

		using std::begin;
		using std::end;
		using std::size;
		using std::data;

		template <typename Container>
		constexpr bool is_container_helper = !is_struct_v<Container> && requires (std::remove_cv_t<Container> c)
		{
			begin(c);
			end(c);
			size(c);
			requires requires (typename Container::value_type const & x) { c.insert(end(c), x); };
		};

		template <typename Container>
		constexpr bool is_contiguous_container_helper = is_container_helper<Container> && requires (Container & c)
		{
			data(c);
		};

		template <typename Container>
		constexpr bool is_associative_container_helper = is_container_helper<Container> && requires
		{
			typename Container::key_type;
			typename Container::mapped_type;
		};

	}

	using size_type = std::uint32_t;

	template <typename Container>
	struct is_container
		: std::bool_constant<detail::is_container_helper<Container>>
	{};

	template <typename Container>
	constexpr bool is_container_v = is_container<Container>::value;

	template <typename Container>
	struct is_contiguous_container
		: std::bool_constant<detail::is_contiguous_container_helper<Container>>
	{};

	template <typename Container>
	constexpr bool is_contiguous_container_v = is_contiguous_container<Container>::value;

	template <typename Container>
	struct is_associative_container
		: std::bool_constant<detail::is_associative_container_helper<Container>>
	{};

	template <typename Container>
	constexpr bool is_associative_container_v = is_associative_container<Container>::value;

	template <typename Container>
	struct has_static_size
		: std::false_type
	{};

	template <typename Container>
	constexpr bool has_static_size_v = has_static_size<Container>::value;

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

	template <typename T, std::size_t N>
	struct has_static_size<std::array<T, N>>
		: std::true_type
	{};

	template <typename Stream, typename Container>
	requires is_ostream_v<Stream>
	void write_size(Stream & s, Container const & c)
	{
		write(s, static_cast<size_type>(std::size(c)));
	}

	template <typename Stream, typename Container>
	requires is_istream_v<Stream>
	void read_size(Stream & s, Container & c)
	{
		size_type size;
		read(s, size);
		c.resize(size);
	}

	template <typename Stream, typename T>
	requires (is_ostream_v<Stream> && is_trivial_v<T>)
	void write_contiguous(Stream & s, T const * begin, std::size_t size)
	{
		write_padding<T>(s);
		s.write(reinterpret_cast<char const *>(begin), sizeof(T) * size);
	}

	template <typename Stream, typename T>
	requires (is_istream_v<Stream> && is_trivial_v<T>)
	void read_contiguous(Stream & s, T * begin, std::size_t size)
	{
		read_padding<T>(s);
		s.read(reinterpret_cast<char *>(begin), sizeof(T) * size);
	}

	template <typename Stream, typename Container>
	requires is_ostream_v<Stream>
	void write_container(Stream & s, Container const & c)
	{
		if constexpr (!has_static_size_v<Container>)
			write_size(s, c);

		using std::begin;
		using std::end;
		using std::data;
		using std::size;

		if constexpr (is_contiguous_container_v<Container> && is_trivial_v<std::decay_t<decltype(*begin(c))>>)
			write_contiguous(s, data(c), size(c));
		else
			write_sequence(s, begin(c), end(c));
	}

	template <typename Stream, typename Container>
	requires is_istream_v<Stream>
	void read_container(Stream & s, Container & c)
	{
		using std::begin;
		using std::end;
		using std::data;
		using std::size;

		using value_type = std::decay_t<decltype(*begin(c))>;

		if constexpr (has_static_size_v<Container>)
			read_sequence(s, begin(c), end(c));
		else if constexpr (is_contiguous_container_v<Container> && is_trivial_v<value_type>)
		{
			read_size(s, c);
			read_contiguous(s, data(c), size(c));
		}
		else if constexpr (is_associative_container_v<Container>)
		{
			size_type size;
			read(s, size);
			while (size --> 0)
			{
				typename Container::key_type key;
				typename Container::mapped_type value;
				read(s, key);
				read(s, value);
				c.insert(end(c), {std::move(key), std::move(value)});
			}
		}
		else
		{
			size_type size;
			read(s, size);
			while (size --> 0)
			{
				value_type x;
				read(s, x);
				c.insert(end(c), std::move(x));
			}
		}
	}

	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> && is_container_v<T>)
	void write(Stream & s, T const & x)
	{
		write_container(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> && is_container_v<T>)
	void read(Stream & s, T & x)
	{
		read_container(s, x);
	}

}
