#include <psemek/test/test.hpp>

#include <psemek/sir/trivial.hpp>
#include <psemek/io/memory_stream.hpp>

#include <numeric>
#include <array>

using namespace psemek;

namespace
{

	template <typename T>
	void test_primitive_write(T value)
	{
		T result;
		io::memory_ostream s{reinterpret_cast<char *>(&result), reinterpret_cast<char *>(&result) + sizeof(result)};
		sir::ostream os{s};
		sir::write(os, value);

		expect_equal(result, value);
	}

	template <typename T>
	void test_primitive_read(T value)
	{
		T result;
		io::memory_istream s{reinterpret_cast<char *>(&value), reinterpret_cast<char *>(&value) + sizeof(value)};
		sir::istream is{s};
		sir::read(is, result);

		expect_equal(result, value);
	}

	struct trivial_type
	{
		char c;
		std::int64_t i;
		std::uint32_t u;
		float f;

		friend bool operator == (trivial_type const &, trivial_type const &) = default;

		friend std::ostream & operator << (std::ostream & os, trivial_type const &)
		{
			return os << "trivial_type{...}";
		}
	};

}

test_case(sir_primitive_write)
{
	test_primitive_write<char>('a');
	test_primitive_write<std::uint8_t>(42);
	test_primitive_write<std::uint16_t>(42042);
	test_primitive_write<std::uint32_t>(1000000000u);
	test_primitive_write<std::uint64_t>(1000000000ull);
	test_primitive_write<std::int8_t>(-42);
	test_primitive_write<std::int16_t>(-10042);
	test_primitive_write<std::int32_t>(-1000000000u);
	test_primitive_write<std::int64_t>(-1000000000ull);
	test_primitive_write<float>(3.14159265f);
	test_primitive_write<double>(2.718281828459045);
}

test_case(sir_primitive_read)
{
	test_primitive_read<char>('a');
	test_primitive_read<std::uint8_t>(42);
	test_primitive_read<std::uint16_t>(42042);
	test_primitive_read<std::uint32_t>(1000000000u);
	test_primitive_read<std::uint64_t>(1000000000ull);
	test_primitive_read<std::int8_t>(-42);
	test_primitive_read<std::int16_t>(-10042);
	test_primitive_read<std::int32_t>(-1000000000u);
	test_primitive_read<std::int64_t>(-1000000000ull);
	test_primitive_read<float>(3.14159265f);
	test_primitive_read<double>(2.718281828459045);
}

test_case(sir_padding_write)
{
	char buffer[256];
	io::memory_ostream s{buffer, buffer + std::size(buffer)};
	sir::ostream os{s};

	sir::write(os, char('a'));
	expect_equal(os.offset(), 1);
	sir::write(os, std::uint32_t(42));
	expect_equal(os.offset(), 8);
	sir::write(os, char('b'));
	expect_equal(os.offset(), 9);
	sir::write(os, std::int32_t(-42));
	expect_equal(os.offset(), 16);
}

test_case(sir_padding_read)
{
	char buffer[256] = {0};
	io::memory_istream s{buffer, buffer + std::size(buffer)};
	sir::istream is{s};

	char c;
	std::uint32_t ui;
	sir::read(is, c);
	expect_equal(is.offset(), 1);
	sir::read(is, ui);
	expect_equal(is.offset(), 8);
	sir::read(is, c);
	expect_equal(is.offset(), 9);
	sir::read(is, ui);
	expect_equal(is.offset(), 16);
}

test_case(sir_trivial)
{
	char buffer[256];

	trivial_type value{'a', 42, 12345, 3.1415f};

	{
		io::memory_ostream s{buffer, buffer + std::size(buffer)};
		sir::ostream os{s};
		sir::write(os, char('x'));
		sir::write(os, value);

		expect_equal(os.offset(), sizeof(trivial_type) + alignof(trivial_type));
	}

	{
		io::memory_istream s{buffer, buffer + std::size(buffer)};
		sir::istream is{s};

		char c;
		trivial_type read_value;
		sir::read(is, c);
		sir::read(is, read_value);

		expect_equal(c, 'x');
		expect_equal(is.offset(), sizeof(trivial_type) + alignof(trivial_type));
		expect_equal(read_value, value);
	}
}

test_case(sir_trivial__array)
{
	char buffer[256];

	{
		std::array<std::uint32_t, 7> array;
		std::iota(std::begin(array), std::end(array), 0);

		io::memory_ostream s{buffer, buffer + std::size(buffer)};
		sir::ostream os{s};
		sir::write(os, array);

		expect_equal(os.offset(), std::size(array) * sizeof(array[0]));
	}
}
