pub fn bin2hex<B>(bin: B) -> String
where
    B: AsRef<[u8]>,
{
    bin.as_ref()
        .iter()
        .map(|b| format!("{:02x}", b))
        .collect::<Vec<String>>()
        .join("")
}

pub fn hex2bin<S>(hex: S) -> Option<Vec<u8>>
where
    S: AsRef<str>,
{
    let hex = hex.as_ref();
    if hex.len() % 2 != 0 {
        return None;
    }

    let mut bytes = Vec::with_capacity(hex.len() / 2);
    for i in (0..hex.len()).step_by(2) {
        let byte_str = &hex[i..i + 2];
        match u8::from_str_radix(byte_str, 16) {
            Ok(byte) => bytes.push(byte),
            Err(_) => return None,
        }
    }
    Some(bytes)
}

pub fn fuzzy_bytes(mut data: Vec<u8>, modifier: u64) -> Vec<u8> {
    let binding = modifier.to_be_bytes();
    let modifier_bytes = binding.as_slice();

    for data_chunk in data.chunks_mut(64 / 8) {
        for (data_byte, modifier_byte) in data_chunk.iter_mut().zip(modifier_bytes.iter()) {
            *data_byte ^= *modifier_byte;
        }
    }

    data
}

#[cfg(test)]
mod test {
    use crate::rng;

    use super::*;

    #[test]
    fn test_fuzzy_bytes() {
        let data = vec![1, 2, 3, 4, 5];
        let modifier = rng::random_number();

        let fuzzy = fuzzy_bytes(data.clone(), modifier);
        let unfuzzy = fuzzy_bytes(fuzzy.clone(), modifier);

        assert_eq!(unfuzzy, data);
        assert_ne!(fuzzy, data);
        assert_ne!(fuzzy, unfuzzy);

        assert_eq!(fuzzy.len(), data.len());
    }
}

pub fn checksum(data: &[u8]) -> u8 {
    let mut checksum: u8 = 0;
    for byte in data {
        checksum = checksum.wrapping_add(*byte);
    }

    checksum
}
