#pragma once

#include <psemek/util/exception.hpp>

#include <string_view>
#include <vector>

namespace psemek::util
{

	namespace detail
	{

		inline void unexpected_end()
		{
			throw exception("Unexpected binary stream end");
		}

		template <typename T>
		struct read_helper
		{
			static_assert(std::is_trivially_copyable_v<T>);

			static T read(std::string_view & data)
			{
				if (data.size() < sizeof(T))
					unexpected_end();

				T value;
				std::copy(data.data(), data.data() + sizeof(T), reinterpret_cast<char *>(std::addressof(value)));
				data.remove_prefix(sizeof(T));
				return value;
			}
		};

		template <typename T>
		struct read_helper<std::vector<T>>
		{
			static T read(std::string_view & data)
			{
				std::uint32_t size = read_helper<std::uint32_t>::read(data);

				std::vector<T> result;

				if constexpr (std::is_trivially_copyable_v<T>)
				{
					if (data.size() < sizeof(T) * size)
						unexpected_end();

					result.resize(size);
					std::copy(data.data(), data.data() + sizeof(T) * size, reinterpret_cast<char *>(result.data()));
				}
				else
				{
					for (std::uint32_t i = 0; i < size; ++i)
						result.push_back(read_helper<T>::read(data));
				}

				return result;
			}
		};

	}

	struct binary_istream
	{
		std::string_view data;

		bool eof() const
		{
			return data.empty();
		}

		template <typename T>
		T read()
		{
			return detail::read_helper<T>::read(data);
		}

		template <typename T>
		T const * read_ptr(std::size_t count)
		{
			std::size_t size = sizeof(T) * count;
			if (data.size() < size)
				detail::unexpected_end();

			auto p = data.data();
			data.remove_prefix(size);
			return reinterpret_cast<T const *>(p);
		}

		char const * read_raw(std::size_t count)
		{
			if (data.size() < count)
				detail::unexpected_end();

			auto p = data.data();
			data.remove_prefix(count);
			return p;
		}
	};

}
