package rand

import "base:intrinsics"
import "base:runtime"

when ODIN_ARCH == .amd64 || ODIN_ARCH == .i386 {
	// LLVM thinks that using SIMD for read_u64 is good,
	// when it causes a ~3x performance regression.  As
	// far as I can tell, this behavior is limited to
	// Intel.
	@(private = "file")
	TARGET_FEATURES :: "-sse,-avx,-avx2"
} else {
	@(private = "file")
	TARGET_FEATURES :: ""
}

/*
The state for a xoshiro256** pseudorandom generator.
*/
Xoshiro256_Random_State :: struct {
	s: [4]u64,
}

@(enable_target_feature = TARGET_FEATURES)
xoshiro256_random_generator_proc :: proc(data: rawptr, mode: runtime.Random_Generator_Mode, p: []byte) {
	@(require_results, enable_target_feature = TARGET_FEATURES)
	read_u64 :: proc "contextless" (r: ^Xoshiro256_Random_State) -> u64 {
		// xoshiro256** output function and state transition

		result := rotate_left64(r.s[1] * 5, 7) * 9
		t := r.s[1] << 17

		r.s[2] = r.s[2] ~ r.s[0]
		r.s[3] = r.s[3] ~ r.s[1]
		r.s[1] = r.s[1] ~ r.s[2]
		r.s[0] = r.s[0] ~ r.s[3]
		r.s[2] = r.s[2] ~ t
		r.s[3] = rotate_left64(r.s[3], 45)

		return result

		rotate_left64 :: #force_inline proc "contextless" (x: u64, k: int) -> u64 {
			n :: 64
			s := uint(k) & (n-1)
			return x << s | x >> (n-s)
		}
	}

	@(thread_local)
	global_rand_seed: Xoshiro256_Random_State

	init :: proc "contextless" (r: ^Xoshiro256_Random_State, seed: u64) {
		// splitmix64 to expand a 64-bit seed into 256 bits of state
		sm64_next :: proc "contextless" (s: ^u64) -> u64 {
			s^ += 0x9E3779B97F4A7C15
			z := s^
			z = (z ~ (z >> 30)) * 0xBF58476D1CE4E5B9
			z = (z ~ (z >> 27)) * 0x94D049BB133111EB
			return z ~ (z >> 31)
		}

		local_seed := seed
		r.s[0] = sm64_next(&local_seed)
		r.s[1] = sm64_next(&local_seed)
		r.s[2] = sm64_next(&local_seed)
		r.s[3] = sm64_next(&local_seed)
		// Extremely unlikely all zero; ensure non-zero state
		if (r.s[0] | r.s[1] | r.s[2] | r.s[3]) == 0 {
			// force a minimal non-zero tweak
			r.s[0] = 1
		}
	}

	r: ^Xoshiro256_Random_State = ---
	if data == nil {
		r = &global_rand_seed
	} else {
		r = cast(^Xoshiro256_Random_State)data
	}

	switch mode {
	case .Read:
		if (r.s[0] | r.s[1] | r.s[2] | r.s[3]) == 0 {
			init(r, u64(intrinsics.read_cycle_counter()))
		}

		switch len(p) {
		case size_of(u64):
			// Fast path for a 64-bit destination.
			intrinsics.unaligned_store((^u64)(raw_data(p)), read_u64(r))
		case:
			// All other cases.
			n := len(p) / size_of(u64)
			buff := ([^]u64)(raw_data(p))[:n]
			for &e in buff {
				intrinsics.unaligned_store(&e, read_u64(r))
			}
			// Handle remaining bytes
			rem := len(p) % size_of(u64)
			if rem > 0 {
				val := read_u64(r)
				tail := p[len(p) - rem:]
				for &b in tail {
					b = byte(val)
					val >>= 8
				}
			}
		}

	case .Reset:
		seed: u64 = 0
		runtime.mem_copy_non_overlapping(&seed, raw_data(p), min(size_of(seed), len(p)))
		init(r, seed)

	case .Query_Info:
		if len(p) != size_of(Generator_Query_Info) {
			return
		}
		info := (^Generator_Query_Info)(raw_data(p))
		info^ += {.Uniform, .Resettable}
	}
}

/*
Returns an instance of the xoshiro256** pseudorandom generator.  If no
initial state is provided, the PRNG will be lazily initialized with the
system timestamp counter on first-use.

WARNING: This random number generator is NOT cryptographically secure.

Inputs:
- state: Optional initial PRNG state.

Returns:
- A `Generator` instance.
*/
@(require_results)
xoshiro256_random_generator :: proc "contextless" (state: ^Xoshiro256_Random_State = nil) -> Generator {
	return {
		procedure = xoshiro256_random_generator_proc,
		data = state,
	}
}
