use std::time::Duration;

use crate::error::{Error, Result};

pub const MAX_STANDARD_ID: u16 = 0x07FF;
pub const MAX_EXTENDED_ID: u32 = 0x1FFF_FFFF;
pub const MAX_CAN_DATA_LEN: usize = 8;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CanId {
    Standard(u16),
    Extended(u32),
}

impl CanId {
    pub fn standard(id: u16) -> Result<Self> {
        if id > MAX_STANDARD_ID {
            return Err(Error::InvalidCanId(id.into()));
        }
        Ok(Self::Standard(id))
    }

    pub fn extended(id: u32) -> Result<Self> {
        if id > MAX_EXTENDED_ID {
            return Err(Error::InvalidCanId(id));
        }
        Ok(Self::Extended(id))
    }

    pub const fn raw(self) -> u32 {
        match self {
            Self::Standard(id) => id as u32,
            Self::Extended(id) => id,
        }
    }

    pub const fn is_extended(self) -> bool {
        matches!(self, Self::Extended(_))
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CanFrame {
    id: CanId,
    data: [u8; MAX_CAN_DATA_LEN],
    len: u8,
}

impl CanFrame {
    pub fn new(id: CanId, data: &[u8]) -> Result<Self> {
        if data.len() > MAX_CAN_DATA_LEN {
            return Err(Error::PayloadTooLong { len: data.len() });
        }

        let mut padded = [0_u8; MAX_CAN_DATA_LEN];
        padded[..data.len()].copy_from_slice(data);

        Ok(Self {
            id,
            data: padded,
            len: data.len() as u8,
        })
    }

    pub fn new_padded(id: CanId, data: &[u8]) -> Result<Self> {
        let mut frame = Self::new(id, data)?;
        frame.len = MAX_CAN_DATA_LEN as u8;
        Ok(frame)
    }

    pub const fn id(&self) -> CanId {
        self.id
    }

    pub fn data(&self) -> &[u8] {
        &self.data[..self.len as usize]
    }

    pub const fn padded_data(&self) -> [u8; MAX_CAN_DATA_LEN] {
        self.data
    }

    pub const fn len(&self) -> usize {
        self.len as usize
    }

    pub const fn is_empty(&self) -> bool {
        self.len == 0
    }
}

pub trait CanBus {
    fn send(&mut self, frame: &CanFrame) -> Result<()>;
    fn receive(&mut self, timeout: Duration) -> Result<Option<CanFrame>>;
}
