/**
 * SimplexNoise.js
 * A dependency-free, seeded 2D Simplex Noise implementation.
 * Based on the standard Stefan Gustavson algorithm.
 * @class
 */
import { SeededRandom } from "./SeededRandom.js";

export class SimplexNoise {
  /**
   * @param {number | string | SeededRandom} seedOrRng - Seed value or SeededRandom instance
   */
  constructor(seedOrRng) {
    // Allow passing a seed OR an existing RNG instance
    const rng =
      seedOrRng instanceof SeededRandom
        ? seedOrRng
        : new SeededRandom(seedOrRng);

    // 1. Build Permutation Table
    /** @type {Uint8Array} */
    this.p = new Uint8Array(256);
    for (let i = 0; i < 256; i++) {
      this.p[i] = i;
    }

    // Shuffle using our Seeded RNG (Fisher-Yates)
    for (let i = 255; i > 0; i--) {
      const n = Math.floor(rng.next() * (i + 1));
      const q = this.p[i];
      this.p[i] = this.p[n];
      this.p[n] = q;
    }

    // Duplicate for overflow handling
    /** @type {Uint8Array} */
    this.perm = new Uint8Array(512);
    /** @type {Uint8Array} */
    this.permMod12 = new Uint8Array(512);
    for (let i = 0; i < 512; i++) {
      this.perm[i] = this.p[i & 255];
      this.permMod12[i] = this.perm[i] % 12;
    }

    // Gradient vectors
    /** @type {number[]} */
    this.grad3 = [
      1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0, 1, 0, 1, -1, 0, 1, 1, 0, -1, -1,
      0, -1, 0, 1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1,
    ];
  }

  /**
   * Samples 2D Noise at coordinates x, y.
   * Returns a value roughly between -1.0 and 1.0.
   * @param {number} xin - X coordinate
   * @param {number} yin - Y coordinate
   * @returns {number} - Noise value
   */
  noise2D(xin, yin) {
    let n0, n1, n2; // Noise contributions from the three corners
    // Skew the input space to determine which simplex cell we're in
    const F2 = 0.5 * (Math.sqrt(3.0) - 1.0);
    const s = (xin + yin) * F2; // Hairy factor for 2D
    const i = Math.floor(xin + s);
    const j = Math.floor(yin + s);
    const G2 = (3.0 - Math.sqrt(3.0)) / 6.0;
    const t = (i + j) * G2;
    const X0 = i - t; // Unskew the cell origin back to (x,y) space
    const Y0 = j - t;
    const x0 = xin - X0; // The x,y distances from the cell origin
    const y0 = yin - Y0;

    // Determine which simplex we are in.
    let i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
    if (x0 > y0) {
      i1 = 1;
      j1 = 0;
    } // lower triangle, XY order: (0,0)->(1,0)->(1,1)
    else {
      i1 = 0;
      j1 = 1;
    } // upper triangle, YX order: (0,0)->(0,1)->(1,1)

    // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
    // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
    // c = (3-sqrt(3))/6
    const x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
    const y1 = y0 - j1 + G2;
    const x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords
    const y2 = y0 - 1.0 + 2.0 * G2;

    // Work out the hashed gradient indices of the three simplex corners
    const ii = i & 255;
    const jj = j & 255;
    const gi0 = this.permMod12[ii + this.perm[jj]];
    const gi1 = this.permMod12[ii + i1 + this.perm[jj + j1]];
    const gi2 = this.permMod12[ii + 1 + this.perm[jj + 1]];

    // Calculate the contribution from the three corners
    let t0 = 0.5 - x0 * x0 - y0 * y0;
    if (t0 < 0) n0 = 0.0;
    else {
      t0 *= t0;
      n0 = t0 * t0 * this.dot(this.grad3, gi0, x0, y0);
    }

    let t1 = 0.5 - x1 * x1 - y1 * y1;
    if (t1 < 0) n1 = 0.0;
    else {
      t1 *= t1;
      n1 = t1 * t1 * this.dot(this.grad3, gi1, x1, y1);
    }

    let t2 = 0.5 - x2 * x2 - y2 * y2;
    if (t2 < 0) n2 = 0.0;
    else {
      t2 *= t2;
      n2 = t2 * t2 * this.dot(this.grad3, gi2, x2, y2);
    }

    // Add contributions from each corner to get the final noise value.
    // The result is scaled to return values in the interval [-1,1].
    return 70.0 * (n0 + n1 + n2);
  }

  /**
   * Dot product helper.
   * @param {number[]} g - Gradient array
   * @param {number} gi - Gradient index
   * @param {number} x - X component
   * @param {number} y - Y component
   * @returns {number} - Dot product
   * @private
   */
  dot(g, gi, x, y) {
    return g[gi * 3] * x + g[gi * 3 + 1] * y;
  }
}
