//! Platform-independent byte-order helpers owned by `Utils`.

use crate::Error;

fn checked_range(pos: i32, width: usize, len: usize) -> Result<std::ops::Range<usize>, Error> {
    let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?;
    let end = start.checked_add(width).ok_or(Error::IndexOutOfRange)?;
    if end > len {
        return Err(Error::IndexOutOfRange);
    }
    Ok(start..end)
}

pub(crate) fn read_single_little_endian(src: &[u8], pos: i32) -> Result<f32, Error> {
    let range = checked_range(pos, 4, src.len())?;
    let bytes: [u8; 4] = src[range]
        .try_into()
        .expect("range has the requested width");
    Ok(f32::from_le_bytes(bytes))
}

pub(crate) fn write_single_little_endian(
    dest: &mut [u8],
    pos: i32,
    value: f32,
) -> Result<(), Error> {
    let range = checked_range(pos, 4, dest.len())?;
    dest[range].copy_from_slice(&value.to_le_bytes());
    Ok(())
}

pub(crate) fn read_double_little_endian(src: &[u8], pos: i32) -> Result<f64, Error> {
    let range = checked_range(pos, 8, src.len())?;
    let bytes: [u8; 8] = src[range]
        .try_into()
        .expect("range has the requested width");
    Ok(f64::from_le_bytes(bytes))
}

pub(crate) fn write_double_little_endian(
    dest: &mut [u8],
    pos: i32,
    value: f64,
) -> Result<(), Error> {
    let range = checked_range(pos, 8, dest.len())?;
    dest[range].copy_from_slice(&value.to_le_bytes());
    Ok(())
}

#[cfg(test)]
mod tests {
    #![allow(clippy::float_cmp)] // Exact bit-preserving round trips are the behavior under test.

    use super::*;

    #[test]
    fn little_endian_float_golden_vectors_round_trip_at_offsets() {
        let mut bytes = [0xaa; 14];
        write_single_little_endian(&mut bytes, 1, -12.5).unwrap();
        assert_eq!(&bytes[1..5], &(-12.5_f32).to_bits().to_le_bytes());
        assert_eq!(
            read_single_little_endian(&bytes, 1).unwrap().to_bits(),
            (-12.5_f32).to_bits()
        );

        write_double_little_endian(&mut bytes, 5, std::f64::consts::PI).unwrap();
        assert_eq!(&bytes[5..13], &std::f64::consts::PI.to_bits().to_le_bytes());
        assert_eq!(
            read_double_little_endian(&bytes, 5).unwrap().to_bits(),
            std::f64::consts::PI.to_bits()
        );
        assert_eq!(bytes[0], 0xaa);
        assert_eq!(bytes[13], 0xaa);
    }

    #[test]
    fn byte_order_helpers_reject_invalid_ranges() {
        assert_eq!(
            read_single_little_endian(&[0; 4], -1),
            Err(Error::IndexOutOfRange)
        );
        assert_eq!(
            read_double_little_endian(&[0; 7], 0),
            Err(Error::IndexOutOfRange)
        );
        assert_eq!(
            write_single_little_endian(&mut [0; 4], 1, 0.0),
            Err(Error::IndexOutOfRange)
        );
    }
}
