use crate::{UdsError, ValidationError};
use ace_macros::FrameCodec;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, FrameCodec)]
#[frame(error = UdsError)]
pub struct ClearDiagnosticInformationRequest {
    pub group_of_dtc: DtcGroup,
    pub memory_selection: Option<u8>,
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, FrameCodec)]
#[frame(error = UdsError)]
pub struct ClearDiagnosticInformationResponse {}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DtcGroup {
    Reserved([u8; 3]),
    VehicleManufacturerSpecific([u8; 3]),
    FunctionalGroup(FunctionalGroup),
    All,
}

impl<'a> ace_core::codec::FrameRead<'a> for DtcGroup {
    type Error = UdsError;

    fn decode(buf: &mut &'a [u8]) -> Result<Self, Self::Error> {
        let bytes = ace_core::codec::take_n(buf, 3).map_err(|e| UdsError::from(e))?;
        let value = u32::from_be_bytes([0, bytes[0], bytes[1], bytes[2]]);
        let group = match value {
            0x000000..=0x0000FF => DtcGroup::Reserved([bytes[0], bytes[1], bytes[2]]),
            0x000100..=0x00FEFF => {
                DtcGroup::VehicleManufacturerSpecific([bytes[0], bytes[1], bytes[2]])
            }
            0x00FF00..=0x00FFFE => {
                let functional_group = FunctionalGroup::decode(&mut &[bytes[2]][..])?;
                DtcGroup::FunctionalGroup(functional_group)
            }
            0x00FFFF => DtcGroup::All,
            val => return Err(UdsError::Validation(ValidationError::InvalidDtcGroup(val))),
        };
        Ok(group)
    }
}

impl ace_core::codec::FrameWrite for DtcGroup {
    type Error = UdsError;

    fn encode<W: ace_core::codec::Writer>(&self, buf: &mut W) -> Result<(), Self::Error> {
        let bytes = match self {
            DtcGroup::Reserved(b) => *b,
            DtcGroup::VehicleManufacturerSpecific(b) => *b,
            DtcGroup::FunctionalGroup(fg) => {
                let mut tmp = [0u8; 1];
                fg.encode(&mut tmp.as_mut_slice())?;
                [0xFF, 0xFF, tmp[0]]
            }
            DtcGroup::All => [0xFF, 0xFF, 0xFF],
        };
        buf.write_bytes(&bytes).map_err(|e| UdsError::from(e))
    }
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, FrameCodec)]
#[frame(error = UdsError)]
pub enum FunctionalGroup {
    #[frame(id_pat = "0x00..=0x32 | 0x34..=0xCF | 0xE0..=0xFD | 0xFF")]
    IsoSaeReserved(u8),
    #[frame(id = 0x33)]
    EmissionsSystem,
    #[frame(id = 0xD0)]
    SafetySystem,
    #[frame(id_pat = "0xD1..=0xDF")]
    LegislativeSystem(u8),
    #[frame(id = 0xFE)]
    VOBDSystem,
}
