//! Random, as an input.
//!
//! The protocols draw random bytes for a few things: a ws client's
//! `Sec-WebSocket-Key` and its frame masks, a multipart boundary.  In C
//! they call `lws_get_random()`; here the caller hands in a [`Random`], so
//! a test or a replay chooses where the bytes come from, and the protocol
//! crates never reach for the platform.
//!
//! A draw is the unit: one call to [`Random::fill`] is one
//! `lws_get_random()` in C.  That matters for [`SeededRandom`], which, like
//! C, spends whole 64-bit words per draw, so the same draws in the same
//! order give the same bytes as C.

/// A source of random bytes, handed in by the IO side or a test.
///
/// What the protocols use it for, a ws key and masks, must be
/// unpredictable to the peer, so a real connection's source must be a
/// cryptographically secure one, such as the operating system's.
pub trait Random {
    /// Fills `buf` with random bytes: one draw.
    ///
    /// # Errors
    ///
    /// [`Unavailable`] when the source cannot give random bytes now.  The
    /// connection drawing them fails, as C fails a short
    /// `lws_get_random()`, rather than going on with predictable ones.
    fn fill(&mut self, buf: &mut [u8]) -> Result<(), Unavailable>;
}

/// The random source could not give random bytes.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Unavailable;

impl core::fmt::Display for Unavailable {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str("random source unavailable")
    }
}

impl core::error::Error for Unavailable {}

/// lws' seeded random: xoshiro256** seeded through C's splitmix64.
///
/// C lws puts this in place of the platform's random under fault injection
/// (`lws_fi_random_seed()`), so a run making the same draws in the same
/// order gets the same bytes, and its transcripts record them.  This is the
/// same stream, for replaying those transcripts.
///
/// **It is predictable by design.**  Never use it as the source for a real
/// connection: a peer that can predict a ws client's masks can use them
/// against intermediaries, which is what masking exists to prevent.
///
/// Each draw takes as many 64-bit words as cover it, each laid out
/// little-endian, and drops what is left of the last one, as C does.  So a
/// 4-byte draw spends a whole word:
///
/// ```
/// use npro_core::random::{Random, SeededRandom};
///
/// let mut r = SeededRandom::new(1);
/// let mut key = [0; 16];
/// let mut mask = [0; 4];
/// r.fill(&mut key)?;
/// r.fill(&mut mask)?;
///
/// // a ws client's key and its first frame's mask, as C's ws-client
/// // transcript records them
/// assert_eq!(&key[..4], &[0x3a, 0xfa, 0x26, 0xb5]);
/// assert_eq!(mask, [0x16, 0x73, 0x98, 0x76]);
/// # Ok::<(), npro_core::random::Unavailable>(())
/// ```
#[cfg(feature = "replay")]
#[derive(Clone, Debug)]
#[expect(
    missing_copy_implementations,
    reason = "a copy made by accident would draw the same random twice"
)]
pub struct SeededRandom {
    s: [u64; 4],
}

#[cfg(feature = "replay")]
impl SeededRandom {
    /// The stream C's `lws_fi_random_seed(cx, seed)` starts.
    #[must_use]
    pub fn new(mut seed: u64) -> Self {
        let mut s = [0; 4];
        for w in &mut s {
            *w = splitmix64(&mut seed);
        }
        Self { s }
    }

    /// The next 64-bit word of the stream (C's `lws_xos()`).
    pub const fn next_u64(&mut self) -> u64 {
        let [s0, s1, s2, s3] = &mut self.s;
        let result = s1.wrapping_mul(5).rotate_left(7).wrapping_mul(9);
        let t = s1.wrapping_shl(17);

        *s2 ^= *s0;
        *s3 ^= *s1;
        *s1 ^= *s2;
        *s0 ^= *s3;
        *s2 ^= t;
        *s3 = s3.rotate_left(45);

        result
    }
}

#[cfg(feature = "replay")]
impl Random for SeededRandom {
    fn fill(&mut self, buf: &mut [u8]) -> Result<(), Unavailable> {
        for chunk in buf.chunks_mut(8) {
            for (b, w) in chunk.iter_mut().zip(self.next_u64().to_le_bytes()) {
                *b = w;
            }
        }
        Ok(())
    }
}

/// C's splitmix64 (`lib/misc/prng.c`).
///
/// It mixes the state as it was *before* adding the increment, where the
/// reference splitmix64 mixes it after.  This is C's, so that the streams
/// match; the textbook one would seed a different stream.
#[cfg(feature = "replay")]
const fn splitmix64(s: &mut u64) -> u64 {
    let mut r = *s;
    *s = s.wrapping_add(0x9e37_79b9_7f4a_7c15);
    r = (r ^ (r >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
    r = (r ^ (r >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
    r ^ (r >> 31)
}

#[cfg(all(test, feature = "replay"))]
mod tests {
    use super::*;

    #[test]
    fn matches_c_api_test_random_prng() {
        // minimal-examples-lowlevel/api-tests/api-test-random-prng: the
        // first bytes lws_fi_random_seed(cx, 1234) gives
        let mut r = SeededRandom::new(1234);
        let mut b = [0; 16];
        r.fill(&mut b).unwrap();
        assert_eq!(
            b,
            [
                0x6b, 0x42, 0x89, 0x9e, 0xa3, 0x63, 0xa1, 0xa3, 0x24, 0x60, 0x07, 0xbb, 0xb7, 0x67,
                0x64, 0xdc
            ]
        );
        assert_eq!(r.next_u64(), 0xbdf5_c7a4_e6e0_a48b);
    }

    #[test]
    fn a_draw_spends_whole_words() {
        // seed 1: the ws-client transcript's key, then its first mask from
        // the low half of the third word
        let mut r = SeededRandom::new(1);
        let mut key = [0; 16];
        let mut mask = [0; 4];
        r.fill(&mut key).unwrap();
        r.fill(&mut mask).unwrap();
        assert_eq!(
            key,
            [
                0x3a, 0xfa, 0x26, 0xb5, 0x0a, 0x4a, 0x09, 0x65, 0x27, 0x65, 0x6e, 0xed, 0x31, 0x1e,
                0x86, 0xab
            ]
        );
        assert_eq!(mask, [0x16, 0x73, 0x98, 0x76]);

        // and a second mask starts on the next word, not the 4 bytes left
        let mut a = SeededRandom::new(1);
        let mut skip = [0; 24];
        a.fill(&mut skip).unwrap();
        let mut next = [0; 4];
        r.fill(&mut next).unwrap();
        let mut want = [0; 4];
        a.fill(&mut want).unwrap();
        assert_eq!(next, want);
    }

    #[test]
    fn reseeding_restarts_the_stream() {
        let mut a = SeededRandom::new(7);
        let first = a.next_u64();
        a.next_u64();
        assert_eq!(SeededRandom::new(7).next_u64(), first);
        assert_ne!(SeededRandom::new(8).next_u64(), first);
    }

    #[test]
    fn an_empty_draw_spends_nothing() {
        let mut a = SeededRandom::new(1);
        a.fill(&mut []).unwrap();
        assert_eq!(a.next_u64(), SeededRandom::new(1).next_u64());
    }
}