//! xoshiro256++ — the registered deterministic generator (extension §6.3).
//!
//! All simulation randomness flows from this generator, seeded from the
//! contract and advanced only by simulation steps in canonical order.
//! Sink-local randomness MUST NOT touch state. The algorithm is
//! Blackman & Vigna's xoshiro256++ 1.0 (public domain), reproduced here
//! so the crate has zero RNG deps and the byte-for-byte behavior is
//! pinned by this source plus the conformance vectors.

#[derive(Clone, Debug)]
pub struct Xoshiro256pp {
    s: [u64; 4],
}

impl Xoshiro256pp {
    /// Seed from the contract's 32 bytes: four little-endian u64 words.
    /// The all-zero state is the one degenerate point of the xoshiro
    /// family; it is mapped to the first four outputs of splitmix64(0),
    /// per the author's recommended escape. This mapping is normative.
    pub fn from_seed(seed: [u8; 32]) -> Self {
        let mut s = [0u64; 4];
        for (i, w) in s.iter_mut().enumerate() {
            *w = u64::from_le_bytes(seed[i * 8..(i + 1) * 8].try_into().unwrap());
        }
        if s == [0, 0, 0, 0] {
            let mut sm = 0u64;
            for w in s.iter_mut() {
                *w = splitmix64(&mut sm);
            }
        }
        Self { s }
    }

    pub fn next_u64(&mut self) -> u64 {
        let result = self.s[0]
            .wrapping_add(self.s[3])
            .rotate_left(23)
            .wrapping_add(self.s[0]);
        let t = self.s[1] << 17;
        self.s[2] ^= self.s[0];
        self.s[3] ^= self.s[1];
        self.s[1] ^= self.s[2];
        self.s[0] ^= self.s[3];
        self.s[2] ^= t;
        self.s[3] = self.s[3].rotate_left(45);
        result
    }
}

fn splitmix64(state: &mut u64) -> u64 {
    *state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
    let mut z = *state;
    z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
    z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
    z ^ (z >> 31)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn deterministic_and_seed_sensitive() {
        let mut a = Xoshiro256pp::from_seed([7u8; 32]);
        let mut b = Xoshiro256pp::from_seed([7u8; 32]);
        let mut c = Xoshiro256pp::from_seed([8u8; 32]);
        let xs: Vec<u64> = (0..8).map(|_| a.next_u64()).collect();
        let ys: Vec<u64> = (0..8).map(|_| b.next_u64()).collect();
        let zs: Vec<u64> = (0..8).map(|_| c.next_u64()).collect();
        assert_eq!(xs, ys);
        assert_ne!(xs, zs);
    }

    #[test]
    fn zero_seed_escapes_degenerate_state() {
        let mut r = Xoshiro256pp::from_seed([0u8; 32]);
        assert_ne!(r.next_u64() | r.next_u64(), 0);
    }
}
