use deku::{
    bitvec::{BitSlice, BitVec, Msb0},
    prelude::*,
};

#[derive(DekuRead, DekuWrite, Debug, PartialEq)]
#[deku(endian = "endian", ctx = "_endian: deku::ctx::Endian")]

pub struct LatLng {
    #[deku(
        reader = "Self::read_lat_lon(deku::rest)",
        writer = "Self::write_lat_lon(deku::output, self.longitude)"
    )]
    pub longitude: f64,

    #[deku(
        reader = "Self::read_lat_lon(deku::rest)",
        writer = "Self::write_lat_lon(deku::output, self.latitude)"
    )]
    pub latitude: f64,
}

const LAT_LONG_FACTOR: f64 = 10_000_000.0;

impl LatLng {
    fn read_lat_lon(rest: &BitSlice<u8, Msb0>) -> Result<(&BitSlice<u8, Msb0>, f64), DekuError> {
        let (rest, value) = i32::read(rest, ())?;
        Ok((rest, f64::from(value) / LAT_LONG_FACTOR))
    }

    #[allow(clippy::cast_possible_truncation)]
    fn write_lat_lon(output: &mut BitVec<u8, Msb0>, field: f64) -> Result<(), DekuError> {
        let value = (field * LAT_LONG_FACTOR) as i32;
        value.write(output, ())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use deku::bitvec::BitView;

    #[test]
    fn test_read_lat_lon() -> Result<(), DekuError> {
        let (_, val) = LatLng::read_lat_lon(BitSlice::from_slice(&[0x00, 0x2E, 0xB6, 0x94]))?;
        assert!((-180.0 - val).abs() < f64::EPSILON);

        let (_, val) = LatLng::read_lat_lon(BitSlice::from_slice(&[0x00, 0xD2, 0x49, 0x6B]))?;
        assert!((180.0 - val).abs() < f64::EPSILON);

        let (_, val) = LatLng::read_lat_lon(BitSlice::from_slice(&[0x00, 0x00, 0x0, 0x00]))?;
        assert!((0.0 - val).abs() < f64::EPSILON);

        Ok(())
    }

    #[test]
    fn test_write_lat_lon() -> Result<(), DekuError> {
        let mut output = BitVec::with_capacity(32);
        LatLng::write_lat_lon(&mut output, -180.0)?;
        assert_eq!(output, [0x00u8, 0x2E, 0xB6, 0x94].view_bits::<Msb0>());

        let mut output = BitVec::with_capacity(32);
        LatLng::write_lat_lon(&mut output, 180.0)?;
        assert_eq!(output, [0x00u8, 0xD2, 0x49, 0x6B].view_bits::<Msb0>());

        let mut output = BitVec::with_capacity(32);
        LatLng::write_lat_lon(&mut output, 0.0)?;
        assert_eq!(output, [0x00u8, 0x00, 0x0, 0x00].view_bits::<Msb0>());

        Ok(())
    }

    #[test]
    fn test_deku_read() -> Result<(), DekuError> {
        let slice = BitSlice::from_slice(&[0x00, 0x2E, 0xB6, 0x94, 0x80, 0x07, 0x56, 0xCD]);
        let (rest, ll) = LatLng::read(slice, deku::ctx::Endian::Little)?;

        assert_eq!(rest.len(), 0);
        assert!((-180.0 - ll.longitude).abs() < f64::EPSILON);
        assert!((-85.0 - ll.latitude).abs() < f64::EPSILON);

        Ok(())
    }

    #[test]
    fn test_deku_write() -> Result<(), DekuError> {
        let mut output = BitVec::with_capacity(64);
        LatLng {
            longitude: -180.0,
            latitude: -85.0,
        }
        .write(&mut output, deku::ctx::Endian::Little)?;

        assert_eq!(
            output,
            [0x00u8, 0x2E, 0xB6, 0x94, 0x80, 0x07, 0x56, 0xCD].view_bits::<Msb0>()
        );

        Ok(())
    }
}
