const START_STATE: u16 = 0xE9CF;

pub(crate) fn whiten(data: &mut [u8]) {
    let mut state = START_STATE;

    for d in data.iter_mut() {
        let b;
        (b, state) = lfsr_byte(state);
        *d ^= b;
    }
}

// (byte, state)
fn lfsr_byte(mut state: u16) -> (u8, u16) {
    let mut out = 0;
    for i in (0..8).rev() {
        out |= u8::try_from(state & 1).unwrap() << i;
        state = lfsr(state);
    }

    (out, state)
}

// https://en.wikipedia.org/wiki/Linear-feedback_shift_register#Galois_LFSRs
fn lfsr(mut state: u16) -> u16 {
    let lsb = state & 1;
    state >>= 1;
    if lsb > 0 {
        state ^= 0xB400; // apply toggle mask
    }

    state
}

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

    #[test]
    fn basic() {
        let mut data = [0; 64];
        data[0..57]
            .clone_from_slice(&b"Hello world! The quick brown fox jumped over the lazy dog"[..]);
        let orig = data;

        whiten(&mut data);
        assert_ne!(orig, data);

        whiten(&mut data);
        assert_eq!(orig, data);
    }

    #[test]
    fn test_lfsr() {
        let start = 0xACE1;
        let end_expected = 0xE270;

        let state = lfsr(start);

        assert_eq!(end_expected, state);
    }

    #[test]
    fn test_lfsr_byte() {
        let start = 0xE9CF;
        let (out, state) = lfsr_byte(start);
        assert_eq!(0xF3, out);
        assert_eq!(0xE3B1, state);
    }

    #[test]
    fn test_doc_example() {
        let start = 0xE9CF;
        let expected_out = [
            0xF3, 0x8D, 0xD0, 0x6E, 0x1F, 0x65, 0x75, 0x75, 0xA5, 0xBA, 0xA9, 0xD0, 0x7A, 0x1D,
            0x1, 0x21,
        ];

        let mut actual_out = [0; 16];
        let mut state = start;
        for a in &mut actual_out {
            let (out, ns) = lfsr_byte(state);
            state = ns;
            *a = out;
        }

        assert_eq!(expected_out, actual_out);
    }

    #[test]
    fn test_doc_example_through_whitener() {
        let expected_out = [
            0xF3, 0x8D, 0xD0, 0x6E, 0x1F, 0x65, 0x75, 0x75, 0xA5, 0xBA, 0xA9, 0xD0, 0x7A, 0x1D,
            0x1, 0x21,
        ];

        let mut actual_out = [0; 16];
        whiten(&mut actual_out);

        assert_eq!(expected_out, actual_out);
    }
}