// 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 split_mix_64_state SplitMix64State;
struct split_mix_64_state {
  u64 seed;
};

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

XOR256State wapp_prng_xorshift_init_state(void) {
  wapp_persist b8 seeded = false;
  if (!seeded) {
    seeded = true;
    seed_os_generator();
  }

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

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

u64 wapp_prng_xorshift_256(XOR256State *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 wapp_prng_xorshift_256ss(XOR256State *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 wapp_prng_xorshift_256p(XOR256State *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;
}

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

wapp_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(WAPP_PLATFORM_C) && WAPP_PLATFORM_C_VERSION >= WAPP_PLATFORM_C11_VERSION
#ifdef WAPP_PLATFORM_POSIX
wapp_intern void seed_os_generator(void) {
    struct timespec ts = {0};
    int result = clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
    wapp_runtime_assert(result == 0, "Invalid seed value");

    srand48(ts.tv_nsec);
}

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

    srand(ts.tv_nsec);
}

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

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

    srand(result);
}

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

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