<*
 Randoms:
 General usage -
 1. Create a Random (see std/math/random for various alternatives), or pick DefaultRandom
 2. Define it : `DefaultRandom my_random;`
 3. Seed it: `random::seed(&my_random, some_seed);` or : `rand::seed_entropy(&my_random);`
 4. Use it with the functions in random: `float random_float = random::next_float(&my_random);`

 For just a simple integer between 0 and n (not including n), you can use `rand(n)`.
*>
module std::math::random;

<*
 @require is_random(random)
*>
macro void seed(random, seed)
{
	random.set_seed(@as_char_view(seed));
}

<*
 Seed the random with some best effort entropy.

 @require is_random(random)
*>
macro void seed_entropy(random)
{
	random.set_seed(&&entropy());
}

<*
 Get the next value between 0 and range (not including range).

 @require is_random(random)
 @require range > 0
*>
macro int next(random, uint range)
{
	if (range == 1) return 0;
	uint mask = ~0U;
	range--;
	mask >>= range.clz();
	uint x @noinit;
	do
	{
		x = random.next_int() & mask;
	}
	while (x > range);
	return x;
}

<*
 Get a random in the range [min, max], both included.

 @require is_random(random)
 @require max >= min
*>
macro int next_in_range(random, int min, int max)
{
	return next(random, max - min + 1) + min;
}

alias DefaultRandom = Sfc64Random;

tlocal Sfc64Random default_random @private;
tlocal bool default_random_initialized @private = false;

<*
Seed the default random function.
*>
fn void srand(ulong seed) @builtin
{
	default_random.set_seed(@as_char_view(seed));
	default_random_initialized = true;
}

<*
Get a default random value between 0 and range (not including range)
*>
fn int rand(int range) @builtin
{
	init_default_random();
	return next(&default_random, range);
}

<*
Get a random in the range, both included.
 @require max >= min
*>
fn int rand_in_range(int min, int max) @builtin
{
	init_default_random();
	return next_in_range(&default_random, min, max);
}

fn double rnd() @builtin
{
	init_default_random();
	ulong val = default_random.next_long() & (1UL << 53 - 1);
	return val * 0x1.0p-53;
}

<*
 Get 'true' or 'false'

 @require is_random(random)
*>
macro bool next_bool(random)
{
	return (bool)(random.next_byte() & 1);
}

<*
 Get a float between 0 and 1.0, not including 1.0.

 @require is_random(random)
*>
macro float next_float(random)
{
	uint val = random.next_int() & (1U << 24 - 1);
	return val * 0x1.0p-24f;
}

<*
 Get a double between 0 and 1.0, not including 1.0.

 @require is_random(random)
*>
macro double next_double(random)
{
	ulong val = random.next_long() & (1UL << 53 - 1);
	return val * 0x1.0p-53;
}

// True if the value is a Random.
macro bool is_random(random) => $defined(Random r = random);

macro uint128 @long_to_int128(#function) => (uint128)#function << 64 + #function;
macro ulong @int_to_long(#function) => (ulong)#function << 32 + #function;
macro uint @short_to_int(#function) => (uint)#function << 16 + #function;
macro ushort @char_to_short(#function) => (ushort)#function << 8 + #function;

macro @random_value_to_bytes(#function, char[] bytes)
{
	var $byte_size = $sizeof(#function());
	usz len = bytes.len;
	// Same size or smaller? Then just copy.
	while (len > 0)
	{
		var value = #function();
		if (len <= $byte_size)
		{
			bytes[..] = ((char*)&value)[:len];
			return;
		}
		bytes[:$byte_size] = ((char*)&value)[:$byte_size];
		len -= $byte_size;
		bytes = bytes[$byte_size..];
	}
	unreachable();
}

// This is the interface to implement for Random implementations, usually
// it is not invoked directly.
interface Random
{
	fn void set_seed(char[] input);
	fn char next_byte();
	fn ushort next_short();
	fn uint next_int();
	fn ulong next_long();
	fn uint128 next_int128();
	fn void next_bytes(char[] buffer);
}


macro init_default_random() @private
{
	if (!default_random_initialized)
	{
		seed_entropy(&default_random);
		default_random_initialized = true;
	}
}
