//! Geometry helpers: Catmull-Rom smoothing + hand-rolled gradient noise.
//!
//! The sigil stores sparse *control* points; everything is rendered as a
//! Catmull-Rom spline so straight skeleton walks read as flowing curves. A
//! small fBm gradient-noise field domain-warps the sampled points so the whole
//! figure breathes and morphs instead of rigidly transforming.

use nannou::prelude::*;

/// xorshift64* — deterministic, no rng-crate global state. Shared by the
/// visual modules so live and offline renders are bit-identical per seed.
pub struct Rng(u64);
impl Rng {
    pub fn new(seed: u64) -> Self {
        Rng(seed | 1)
    }
    fn next_u64(&mut self) -> u64 {
        let mut x = self.0;
        x ^= x << 13;
        x ^= x >> 7;
        x ^= x << 17;
        self.0 = x;
        x.wrapping_mul(0x2545_F491_4F6C_DD1D)
    }
    pub fn unit(&mut self) -> f32 {
        (self.next_u64() >> 40) as f32 / (1u64 << 24) as f32
    }
    pub fn range(&mut self, a: f32, b: f32) -> f32 {
        a + (b - a) * self.unit()
    }
    pub fn idx(&mut self, n: usize) -> usize {
        (self.next_u64() as usize) % n.max(1)
    }
    pub fn chance(&mut self, p: f32) -> bool {
        self.unit() < p
    }
}

pub fn smoothstep(e0: f32, e1: f32, x: f32) -> f32 {
    let t = ((x - e0) / (e1 - e0)).clamp(0.0, 1.0);
    t * t * (3.0 - 2.0 * t)
}

/// Centripetal-ish Catmull-Rom: sample `seg` points between each control pair.
/// Endpoints are duplicated so the curve passes through the first/last point.
pub fn catmull_rom(ctrl: &[Vec2], seg: usize) -> Vec<Vec2> {
    if ctrl.len() < 3 {
        return ctrl.to_vec();
    }
    let n = ctrl.len();
    let pt = |i: i32| ctrl[i.clamp(0, n as i32 - 1) as usize];
    let mut out = Vec::with_capacity(n * seg);
    for i in 0..n - 1 {
        let p0 = pt(i as i32 - 1);
        let p1 = pt(i as i32);
        let p2 = pt(i as i32 + 1);
        let p3 = pt(i as i32 + 2);
        for s in 0..seg {
            let t = s as f32 / seg as f32;
            let t2 = t * t;
            let t3 = t2 * t;
            // Standard Catmull-Rom basis (tension 0.5).
            let a = p1 * 2.0;
            let b = (p2 - p0) * t;
            let c = (p0 * 2.0 - p1 * 5.0 + p2 * 4.0 - p3) * t2;
            let d = (-p0 + p1 * 3.0 - p2 * 3.0 + p3) * t3;
            out.push((a + b + c + d) * 0.5);
        }
    }
    out.push(ctrl[n - 1]);
    out
}

// --- gradient (Perlin-style) noise, 2D, hash-based, no external crate -------

fn hash2(mut x: u32) -> f32 {
    x ^= x >> 16;
    x = x.wrapping_mul(0x7feb_352d);
    x ^= x >> 15;
    x = x.wrapping_mul(0x846c_a68b);
    x ^= x >> 16;
    x as f32 / u32::MAX as f32
}

fn grad(ix: i32, iy: i32, seed: u32) -> Vec2 {
    let h = (ix as u32)
        .wrapping_mul(0x9E37_79B1)
        ^ (iy as u32).wrapping_mul(0x85EB_CA77)
        ^ seed.wrapping_mul(0xC2B2_AE3D);
    let a = hash2(h) * std::f32::consts::TAU;
    vec2(a.cos(), a.sin())
}

/// Perlin gradient noise in roughly -1..1.
pub fn noise2(p: Vec2, seed: u32) -> f32 {
    let xi = p.x.floor() as i32;
    let yi = p.y.floor() as i32;
    let fx = p.x - xi as f32;
    let fy = p.y - yi as f32;
    let u = fx * fx * (3.0 - 2.0 * fx);
    let v = fy * fy * (3.0 - 2.0 * fy);
    let n = |cx: i32, cy: i32| {
        let g = grad(cx, cy, seed);
        g.dot(vec2(p.x - cx as f32, p.y - cy as f32))
    };
    let x1 = n(xi, yi) * (1.0 - u) + n(xi + 1, yi) * u;
    let x2 = n(xi, yi + 1) * (1.0 - u) + n(xi + 1, yi + 1) * u;
    (x1 * (1.0 - v) + x2 * v) * 1.4
}

/// Fractal sum of [`noise2`] — 3 octaves, ~ -1..1.
pub fn fbm(p: Vec2, seed: u32) -> f32 {
    let mut a = 0.5;
    let mut f = 1.0;
    let mut sum = 0.0;
    for o in 0..3 {
        sum += a * noise2(p * f, seed.wrapping_add(o * 1013));
        f *= 2.03;
        a *= 0.5;
    }
    sum
}

/// Curl-ish 2D warp vector from the fBm field (divergence-free-ish flow).
pub fn flow(p: Vec2, t: f32, seed: u32) -> Vec2 {
    let e = 0.15;
    let q = p * 0.004 + vec2(t * 0.05, -t * 0.04);
    let n1 = fbm(q, seed);
    let n2 = fbm(q + vec2(e, 0.0), seed);
    let n3 = fbm(q + vec2(0.0, e), seed);
    vec2(n3 - n1, -(n2 - n1)) / e
}
