use std::{fmt, str::FromStr, 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(_))
    }

    pub const fn kind_name(self) -> &'static str {
        match self {
            Self::Standard(_) => "standard",
            Self::Extended(_) => "extended",
        }
    }
}

impl fmt::Display for CanId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} 0x{:X}", self.kind_name(), self.raw())
    }
}

impl FromStr for CanId {
    type Err = String;

    fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
        let value = value.trim();
        let (kind, raw) = if let Some((kind, raw)) = value.split_once(':') {
            (kind.trim(), raw.trim())
        } else {
            let mut parts = value.split_whitespace();
            match (parts.next(), parts.next(), parts.next()) {
                (Some(kind), Some(raw), None) => (kind, raw),
                _ => {
                    return Err(
                        "CAN ID must include a kind, like 'standard 0x123' or 'extended:0x501'"
                            .to_owned(),
                    );
                }
            }
        };
        let raw = parse_can_id_number(raw)?;

        match kind.to_ascii_lowercase().as_str() {
            "standard" | "std" | "s" => {
                if raw > u32::from(MAX_STANDARD_ID) {
                    return Err(Error::InvalidCanId(raw).to_string());
                }
                CanId::standard(raw as u16).map_err(|err| err.to_string())
            }
            "extended" | "ext" | "e" => CanId::extended(raw).map_err(|err| err.to_string()),
            other => Err(format!("unknown CAN ID kind {other:?}")),
        }
    }
}

fn parse_can_id_number(value: &str) -> std::result::Result<u32, String> {
    if let Some(hex) = value
        .strip_prefix("0x")
        .or_else(|| value.strip_prefix("0X"))
    {
        u32::from_str_radix(hex, 16).map_err(|err| format!("invalid CAN ID {value:?}: {err}"))
    } else {
        value
            .parse::<u32>()
            .map_err(|err| format!("invalid CAN ID {value:?}: {err}"))
    }
}

#[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
    }
}

impl fmt::Display for CanFrame {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} len={} data=", self.id, self.len())?;
        for (index, byte) in self.data().iter().enumerate() {
            if index > 0 {
                f.write_str(" ")?;
            }
            write!(f, "{byte:02X}")?;
        }
        Ok(())
    }
}

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