import crypto from "crypto";

/**
 * NOTE(bowei): we use a hash function that is NOT md5 -
 * Either https://github.com/sublee/squirrel3-python/blob/master/squirrel3.py or https://github.com/svaarala/duktape/blob/master/misc/splitmix64.c works fine and is much faster
 * Reference: https://www.youtube.com/watch?v=e4b--cyXEsM or https://www.youtube.com/watch?v=LWFzPP8ZbdU
 * TODO(bowei): port bigint to wasm for faster 64bit operations
 */

// NOTE(bowei): untested
export function splitmix64(seed: bigint, i: bigint) {
    let z: bigint = seed + i * BigInt("0x9e3779b97f4a7c15");
    z = ( z ^ ( z >> BigInt(30) ) ) * BigInt("0xBF58476D1CE4E5B9");
    z = ( z ^ ( z >> BigInt(27) ) ) * BigInt(0x94D049BB133111EB);
    return z ^ ( z >> BigInt(31) );
}

export const INTMAX32 = 2 ** 32;
export function squirrel3(i: number) {
  let n = (i + INTMAX32) % INTMAX32;
    n = Math.imul(n, 0xb5297a4d);
    n ^= n >>> 8;
    n += 0x68e31da4;
    n ^= n << 8;
    n = Math.imul(n, 0x1b56c4e9);
    n ^= n >>> 8;
    return (n + INTMAX32) % INTMAX32;
}
export const PRIME32 = 0x3233f2cd; // not used ; useful for hashing integers; a 32 bit prime

/**
 * Md5 is 16 bytes, or max int of 256 ** 16 = 2 ** 128
 */
export class HashState {
  private seed!: Buffer;

  /**
   * HashState().step("foo") is equivalent to HashState("foo")
   */
  constructor(seed?: string) {
    const buffer = crypto
      .createHash("md5")
      .update((seed || "").toString())
      .digest();
    this.seed = buffer;
  }

  public peekRandom(): number {
    const buffer = crypto.createHash("md5").update(this.seed).digest();
    return Number(this.bufferToBigInt(buffer) % BigInt(2 ** 32)) % 2 ** 32;
  }

  // increment the seed linearly by 1
  public step(numSteps: number = 1) {
    this.seed = this.bigIntToBuffer(this.bufferToBigInt(this.seed) + BigInt(1));
  }

  public stepSeed(seed: string) {
    const buffer = crypto.createHash("md5").update(seed.toString()).digest();
    this.seed = this.bigIntToBuffer(
      this.bufferToBigInt(this.seed) + this.bufferToBigInt(buffer)
    );
  }

  private bigIntToBuffer(b: bigint): Buffer {
    let buf = Buffer.alloc(16);
    let val = b;
    for (let i = 0; i < 16; i++) {
      buf[i] = Number(val % BigInt(256));
      val = val / BigInt(256);
    }
    return buf;
  }

  private bufferToBigInt(b: Buffer): bigint {
    let val = BigInt(0);
    for (let i = 0; i < 16; i++) {
      val = val * BigInt(256) + BigInt(b[i]);
    }
    return val;
  }

  public random(): number {
    this.step();
    return this.peekRandom();
  }
}