/**
 * RNG.js — Seeded pseudo-random number generator (Mulberry32)
 */
class RNG {
  constructor(seed = 42) {
    this._s = (seed >>> 0) || 1;
    this._spare = null;
    this._hasSpare = false;
  }

  next() {
    let s = this._s;
    s = Math.imul(s ^ (s >>> 15), s | 1);
    s ^= s + Math.imul(s ^ (s >>> 7), s | 61);
    this._s = s;
    return ((s ^ (s >>> 14)) >>> 0) / 4_294_967_296;
  }

  uniform(lo = 0, hi = 1) { return lo + this.next() * (hi - lo); }
  int(lo, hi)              { return (lo + Math.floor(this.next() * (hi - lo))) | 0; }
  bool(p = 0.5)            { return this.next() < p; }

  normal(mean = 0, std = 1) {
    if (this._hasSpare) {
      this._hasSpare = false;
      return mean + std * this._spare;
    }
    const u1  = Math.max(1e-14, this.next());
    const u2  = this.next();
    const mag = Math.sqrt(-2 * Math.log(u1));
    const z0  = mag * Math.cos(2 * Math.PI * u2);
    this._spare    = mag * Math.sin(2 * Math.PI * u2);
    this._hasSpare = true;
    return mean + std * z0;
  }

  correlatedNormals(rho = 0) {
    const z1 = this.normal();
    const z2 = rho * z1 + Math.sqrt(1 - rho * rho) * this.normal();
    return [z1, z2];
  }

  logNormal(mu = 0, sigma = 1) { return Math.exp(this.normal(mu, sigma)); }

  exponential(mean = 1) {
    return -mean * Math.log(Math.max(1e-14, this.next()));
  }

  poisson(lambda = 5) {
    const L = Math.exp(-lambda);
    let k = 0, p = 1;
    do { k++; p *= this.next(); } while (p > L);
    return k - 1;
  }

  studentT(nu = 4) {
    const z = this.normal();
    let chi2 = 0;
    for (let i = 0; i < nu; i++) { const g = this.normal(); chi2 += g * g; }
    return z / Math.sqrt(chi2 / nu);
  }

  fattailNormal(mean = 0, std = 1, nu = 4, capSigmas = 5) {
    const t       = this.studentT(nu);
    const scaledT = nu > 2 ? t / Math.sqrt(nu / (nu - 2)) : t;
    const capped  = Math.max(-capSigmas, Math.min(capSigmas, scaledT));
    return mean + std * capped;
  }

  fork()       { return new RNG(this.int(1, 0xffffffff)); }
  shuffle(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
      const j = this.int(0, i + 1);
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
  }
  pick(arr) { return arr[this.int(0, arr.length)]; }
}
module.exports = RNG;