#pragma once

#include <cstdint>
#include <limits>

#include <psemek/random/device.hpp>

namespace psemek::random
{

	// based on www.pcg-random.org
	struct generator
	{
		generator() = default;
		generator(generator const &) = default;

		generator(std::uint64_t state, std::uint64_t inc)
			: state_{state}
			, inc_{inc}
		{}

		generator(device & rd)
			: state_{gen64(rd)}
			, inc_{gen64(rd)}
		{}

		generator(device && rd)
			: state_{gen64(rd)}
			, inc_{gen64(rd)}
		{}

		generator & operator = (generator const &) = default;

		using result_type = std::uint32_t;

		result_type operator()()
		{
			std::uint64_t oldstate = state_;
			state_ = oldstate * 6364136223846793005ULL + (inc_ | 1);
			std::uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
			std::uint32_t rot = oldstate >> 59u;
			return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
		}

		static constexpr result_type min()
		{
			return 0;
		}

		static constexpr result_type max()
		{
			return std::numeric_limits<result_type>::max();
		}

		template <typename OStream>
		friend void write(OStream & stream, generator const & gen)
		{
			write(stream, gen.state_);
			write(stream, gen.inc_);
		}

		template <typename IStream>
		friend void read(IStream & stream, generator & gen)
		{
			read(stream, gen.state_);
			read(stream, gen.inc_);
		}

	private:
		std::uint64_t state_ = 0;
		std::uint64_t inc_ = 0;

		template <typename RD>
		static std::uint64_t gen64(RD & rd)
		{
			return (static_cast<std::uint64_t>(rd()) << 32) | rd();
		}
	};

}
