use core::iter::Chain;

use le_stream::{FromLeStream, ToLeStream};
use log::{error, warn};

use crate::constants::U8_CAPACITY;
use crate::types::{Type, Uint8};

/// An octet string with a maximum size of [`OctStr::CAPACITY`] bytes.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct OctStr<const CAPACITY: usize = U8_CAPACITY>(heapless::Vec<u8, CAPACITY, u8>);

impl<const CAPACITY: usize> OctStr<CAPACITY> {
    /// The maximum size of an `OctStr` in bytes.
    pub const CAPACITY: usize = CAPACITY;

    /// Return the length in bytes.
    #[must_use]
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Determine whether the string is empty.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Attempt to truncate the string to a new size.
    ///
    /// # Errors
    ///
    /// Returns the original string if the new capacity is smaller than the current size.
    pub fn truncate<const NEW_CAPACITY: usize>(self) -> Result<OctStr<NEW_CAPACITY>, Self> {
        if self.len() <= NEW_CAPACITY {
            Ok(OctStr(self.0.into_iter().collect()))
        } else {
            Err(self)
        }
    }

    /// Attempt to widen a string to a new, larger capacity.
    #[must_use]
    pub fn widen<const NEW_CAPACITY: usize>(self) -> OctStr<NEW_CAPACITY> {
        const {
            assert!(CAPACITY <= NEW_CAPACITY);
        }
        OctStr(self.0.into_iter().collect())
    }
}

impl<const CAPACITY: usize> AsRef<[u8]> for OctStr<CAPACITY> {
    fn as_ref(&self) -> &[u8] {
        self.0.as_ref()
    }
}

impl<const CAPACITY: usize> AsMut<[u8]> for OctStr<CAPACITY> {
    fn as_mut(&mut self) -> &mut [u8] {
        self.0.as_mut()
    }
}

impl<const CAPACITY: usize> From<heapless::Vec<u8, CAPACITY, u8>> for OctStr<CAPACITY> {
    fn from(bytes: heapless::Vec<u8, CAPACITY, u8>) -> Self {
        Self(bytes)
    }
}

impl<const CAPACITY: usize> From<heapless::String<CAPACITY, u8>> for OctStr<CAPACITY> {
    fn from(string: heapless::String<CAPACITY, u8>) -> Self {
        Self::from(string.into_bytes())
    }
}

impl<const CAPACITY: usize> From<OctStr<CAPACITY>> for Type {
    fn from(value: OctStr<CAPACITY>) -> Self {
        Self::OctetString(value.widen())
    }
}

impl<const CAPACITY: usize> TryFrom<&[u8]> for OctStr<CAPACITY> {
    type Error = ();

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        if value.len() > Self::CAPACITY {
            return Err(());
        }

        Ok(Self::from(value.iter().copied().collect::<heapless::Vec<
            u8,
            CAPACITY,
            u8,
        >>()))
    }
}

impl<const CAPACITY: usize> TryFrom<Type> for OctStr<CAPACITY> {
    type Error = Type;

    fn try_from(typ: Type) -> Result<Self, Self::Error> {
        if let Type::OctetString(string) = typ {
            string.truncate().map_err(Type::OctetString)
        } else {
            Err(typ)
        }
    }
}

impl<const CAPACITY: usize> FromLeStream for OctStr<CAPACITY> {
    /// Read an `OctStr` from a little-endian byte stream.
    ///
    /// This will truncate the string if the read size exceeds its capacity.
    fn from_le_stream<T>(mut stream: T) -> Option<Self>
    where
        T: Iterator<Item = u8>,
    {
        let size: u8 = Option::<u8>::from(Uint8::from_le_stream(&mut stream)?).unwrap_or(0);

        if CAPACITY < size.into() {
            warn!("OctStr size {size} exceeds maximum capacity of {CAPACITY} bytes.");
        }

        let mut bytes = heapless::Vec::new();

        for _ in 0..size {
            bytes
                .push(u8::from_le_stream(&mut stream)?)
                .unwrap_or_else(|byte| error!("OctStr is full. Discarding byte: {byte:#04X}"));
        }

        Some(Self(bytes))
    }
}

impl<const CAPACITY: usize> ToLeStream for OctStr<CAPACITY> {
    type Iter = Chain<
        <Uint8 as ToLeStream>::Iter,
        <heapless::Vec<u8, CAPACITY, u8> as IntoIterator>::IntoIter,
    >;

    fn to_le_stream(self) -> Self::Iter {
        Uint8::try_from(u8::try_from(self.0.len()).expect("Length should fit into u8."))
            .expect("Length should be a valid Uint8.")
            .to_le_stream()
            .chain(self.0)
    }
}
