// vim:fileencoding=utf-8:foldmethod=marker

#include "xorshift.h"
#include "../../common/aliases/aliases.h"
#include "../../common/assert/assert.h"
#include "../../common/platform/platform.h"
#include <stdlib.h>
#include <time.h>

typedef struct SplitMix64State SplitMix64State;
struct SplitMix64State {
	u64 seed;
};

wp_intern u64 rol64(u64 x, u64 bits);
wp_intern u64 split_mix_64(SplitMix64State *state);
wp_intern void seed_os_generator(void);
wp_intern u64 generate_random_number(void);

WpXor256State wpPrngXorshiftInit(void) {
	wp_persist b8 seeded = false;
	if (!seeded) {
		seeded = true;
		seed_os_generator();
	}

	SplitMix64State sm64 = {.seed = generate_random_number()};

	return (WpXor256State){
		.x = split_mix_64(&sm64),
		.y = split_mix_64(&sm64),
		.z = split_mix_64(&sm64),
		.w = split_mix_64(&sm64),
	};
}

u64 wpPrngXorshift256(WpXor256State *state) {
	u64 t = state->x ^ (state->x << 11);

	state->x = state->y;
	state->y = state->z;
	state->z = state->w;
	state->w = (state->w ^ (state->w >> 19)) ^ (t ^ (t >> 8));

	return state->w;
}

u64 wpPrngXorshift256ss(WpXor256State *state) {
	const u64 result = rol64(state->z * 5, 7) * 9;
	const u64 t = state->z << 17;

	state->y ^= state->w;
	state->x ^= state->z;
	state->z ^= state->y;
	state->w ^= state->x;

	state->y ^= t;
	state->x = rol64(state->x, 45);

	return result;
}

u64 wpPrngXorshift256p(WpXor256State *state) {
	const u64 result = state->w + state->x;
	const u64 t = state->z << 17;

	state->y ^= state->w;
	state->x ^= state->z;
	state->z ^= state->y;
	state->w ^= state->x;

	state->y ^= t;
	state->x  = rol64(state->x, 45);

	return result;
}

wp_intern u64 rol64(u64 x, u64 bits) {
	return (x << bits) | (x >> (64 - bits));
}

wp_intern u64 split_mix_64(SplitMix64State *state) {
	state->seed += 0x9E3779B97f4A7C15;

	u64 result = state->seed;
	result = (result ^ (result >> 30)) * 0xBF58476D1CE4E5B9;
	result = (result ^ (result >> 27)) * 0x94D049BB133111EB;

	return result ^ (result >> 31);
}

#if defined(WP_PLATFORM_C) && WP_PLATFORM_C_VERSION >= WP_PLATFORM_C11_VERSION
#ifdef WP_PLATFORM_POSIX
wp_intern void seed_os_generator(void) {
	struct timespec ts = {0};
	int result = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
	wpRuntimeAssert(result == 0, "Invalid seed value");

	srand48(ts.tv_nsec);
}

wp_intern u64 generate_random_number(void) {
	return lrand48();
}
#else
wp_intern void seed_os_generator(void) {
	struct timespec ts = {0};
	int result = timespec_get(&ts, TIME_UTC);
	wpRuntimeAssert(result != 0, "Invalid seed value");

	srand(ts.tv_nsec);
}

wp_intern u64 generate_random_number(void) {
	i32 n1 = rand();
	i32 n2 = rand();

	return (((u64)n1) << 32 | (u64)n2);
}
#endif // !WP_PLATFORM_POSIX
#else
wp_intern void seed_os_generator(void) {
	time_t result = time(NULL);
	wpRuntimeAssert(result != (time_t)(-1), "Invalid seed value");

	srand(result);
}

wp_intern u64 generate_random_number(void) {
	i32 n1 = rand();
	i32 n2 = rand();

	return (((u64)n1) << 32 | (u64)n2);
}
#endif // !WP_PLATFORM_C
