using System;
using System.Runtime.CompilerServices;
namespace MoonTools.ECS;
/// 
/// This class implements the well equidistributed long-period linear pseudorandom number generator.
/// Code taken from Chris Lomont: http://lomont.org/papers/2008/Lomont_PRNG_2008.pdf
/// 
public class Random
{
	public const int STATE_BYTE_COUNT = 68; // 16 state ints + 1 index int
	uint[] State = new uint[16];
	uint Index = 0;
	uint Seed;
	/// 
	/// Initializes the RNG with an arbitrary seed.
	/// 
	public Random()
	{
		Init((uint) Environment.TickCount);
	}
	/// 
	/// Initializes the RNG with a given seed.
	/// 
	public void Init(uint seed)
	{
		Seed = seed;
		uint s = seed;
		for (int i = 0; i < 16; i++)
		{
			s = (((s * 214013 + 2531011) >> 16) & 0x7fffffff) | 0;
			State[i] = ~ ~s; //i ;
		}
		Index = 0;
	}
	/// 
	/// Returns the seed that was used to initialize the RNG.
	/// 
	public uint GetSeed()
	{
		return Seed;
	}
	/// 
	/// Returns the entire state of the RNG as a string.
	/// 
	public string PrintState()
	{
		var s = "";
		for (var i = 0; i < 16; i += 1)
		{
			s += State[i];
		}
		s += Index;
		return s;
	}
	/// 
	/// Saves the entire state of the RNG to a Span.
	/// 
	/// Must be a span of at least STATE_BYTE_COUNT bytes.
	/// Thrown if the byte span is too short.
	public unsafe void SaveState(Span bytes)
	{
#if DEBUG
		if (bytes.Length < STATE_BYTE_COUNT)
		{
			throw new ArgumentException("Byte span too short!");
		}
#endif
		fixed (byte* ptr = bytes)
		{
			var offset = 0;
			for (var i = 0; i < 16; i += 1)
			{
				Unsafe.Write(ptr + offset, State[i]);
				offset += 4;
			}
			Unsafe.Write(ptr + offset, Index);
		}
	}
	/// 
	/// Loads the entire state of the RNG from a Span.
	/// 
	/// Must be a span of at least STATE_BYTE_COUNT bytes.
	/// Thrown if the byte span is too short.
	public unsafe void LoadState(Span bytes)
	{
#if DEBUG
		if (bytes.Length < STATE_BYTE_COUNT)
		{
			throw new ArgumentException("Byte span too short!");
		}
#endif
		fixed (byte* ptr = bytes)
		{
			var offset = 0;
			for (var i = 0; i < 16; i += 1)
			{
				State[i] = Unsafe.Read(ptr + offset);
				offset += 4;
			}
			Index = Unsafe.Read(ptr + offset);
		}
	}
	private uint NextInternal()
	{
		uint a, b, c, d;
		a = State[Index];
		c = State[(Index+13)&15];
		b = a^c^(a<<16)^(c<<15);
		c = State[(Index+9)&15];
		c ^= (c>>11);
		a = State[Index] = b^c;
		d = (uint) (a ^((a<<5)&0xDA442D24UL));
		Index = (Index + 15)&15;
		a = State[Index];
		State[Index] = a^b^d^(a<<2)^(b<<18)^(c<<28);
		return State[Index];
	}
	/// 
	/// Returns a non-negative signed integer.
	/// 
	public int Next()
	{
		return (int) (NextInternal() >>> 1); // unsigned bitshift right to get rid of signed bit
	}
	/// 
	/// Returns a non-negative signed integer less than max.
	/// 
	public int Next(int max)
	{
		return (int) (((double) Next()) * max / int.MaxValue);
	}
	/// 
	/// Returns a signed integer greater than or equal to min and less than max.
	/// 
	public int Next(int min, int max)
	{
		var diff = max - min;
		var next = Next(diff);
		return min + next;
	}
	/// 
	/// Returns a non-negative signed 64 bit integer.
	/// 
	public long NextInt64()
	{
		long next = NextInternal();
		next <<= 32;
		next |= NextInternal();
		next >>>= 1;
		return next;
	}
	/// 
	/// Returns a non-negative signed 64 bit integer less than max.
	/// 
	public long NextInt64(long max)
	{
		var next = NextInt64();
		return (long) (((double) next) * max / long.MaxValue);
	}
	/// 
	/// Returns a non-negative signed 64 bit integer greater than or equal to min and less than max.
	/// 
	public long NextInt64(long min, long max)
	{
		var diff = max - min;
		var next = NextInt64(diff);
		return min + next;
	}
	/// 
	/// Returns a single-precision floating point value between 0 and 1.
	/// 
	public float NextSingle()
	{
		var n = NextInternal();
		return ((float) n) / uint.MaxValue;
	}
	/// 
	/// Returns a double-precision floating point value between 0 and 1.
	/// 
	public double NextDouble()
	{
		var n = NextInternal();
		return ((double) n) / uint.MaxValue;
	}
}