use alloc::{
    borrow::Cow,
    string::{String, ToString},
    sync::Arc,
    vec::Vec,
};
use core::{
    fmt,
    str::{self, FromStr},
};
use smallvec::smallvec;
use crate::{
    ast::{Ident, IdentError},
    ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryNamespace, Serializable,
    Span,
};
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum PathError {
    #[error("invalid library path: cannot be empty")]
    Empty,
    #[error("invalid library path component: cannot be empty")]
    EmptyComponent,
    #[error("invalid library path component: {0}")]
    InvalidComponent(#[from] crate::ast::IdentError),
    #[error("invalid library path: contains invalid utf8 byte sequences")]
    InvalidUtf8,
    #[error(transparent)]
    InvalidNamespace(#[from] crate::library::LibraryNamespaceError),
    #[error("cannot join a path with reserved name to other paths")]
    UnsupportedJoin,
}
pub enum LibraryPathComponent<'a> {
    Namespace(&'a LibraryNamespace),
    Normal(&'a Ident),
}
impl<'a> LibraryPathComponent<'a> {
    #[inline(always)]
    pub fn as_str(&self) -> &'a str {
        match self {
            Self::Namespace(ns) => ns.as_str(),
            Self::Normal(id) => id.as_str(),
        }
    }
    #[inline]
    pub fn to_ident(&self) -> Ident {
        match self {
            Self::Namespace(ns) => ns.to_ident(),
            Self::Normal(id) => Ident::clone(id),
        }
    }
}
impl<'a> Eq for LibraryPathComponent<'a> {}
impl<'a> PartialEq for LibraryPathComponent<'a> {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Namespace(a), Self::Namespace(b)) => a == b,
            (Self::Normal(a), Self::Normal(b)) => a == b,
            _ => false,
        }
    }
}
impl<'a> PartialEq<str> for LibraryPathComponent<'a> {
    fn eq(&self, other: &str) -> bool {
        self.as_ref().eq(other)
    }
}
impl<'a> AsRef<str> for LibraryPathComponent<'a> {
    fn as_ref(&self) -> &str {
        match self {
            Self::Namespace(ns) => ns.as_str(),
            Self::Normal(ident) => ident.as_str(),
        }
    }
}
impl<'a> fmt::Display for LibraryPathComponent<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(self.as_ref())
    }
}
impl From<LibraryPathComponent<'_>> for Ident {
    #[inline]
    fn from(component: LibraryPathComponent<'_>) -> Self {
        component.to_ident()
    }
}
type Components = smallvec::SmallVec<[Ident; 1]>;
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct LibraryPath {
    inner: Arc<LibraryPathInner>,
}
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct LibraryPathInner {
    ns: LibraryNamespace,
    components: Components,
}
impl LibraryPath {
    pub fn new(source: impl AsRef<str>) -> Result<Self, PathError> {
        let source = source.as_ref();
        if source.is_empty() {
            return Err(PathError::Empty);
        }
        let mut parts = source.split("::");
        let ns = parts
            .next()
            .ok_or(PathError::Empty)
            .and_then(|part| LibraryNamespace::new(part).map_err(PathError::InvalidNamespace))?;
        let mut components = Components::default();
        parts.map(Ident::new).try_for_each(|part| {
            part.map_err(PathError::InvalidComponent).map(|c| components.push(c))
        })?;
        Ok(Self::make(ns, components))
    }
    pub fn new_from_components<I>(ns: LibraryNamespace, components: I) -> Self
    where
        I: IntoIterator<Item = Ident>,
    {
        Self::make(ns, components.into_iter().collect())
    }
    #[inline]
    fn make(ns: LibraryNamespace, components: Components) -> Self {
        Self {
            inner: Arc::new(LibraryPathInner { ns, components }),
        }
    }
}
impl LibraryPath {
    #[allow(clippy::len_without_is_empty)]
    pub fn len(&self) -> usize {
        self.inner.components.iter().map(|c| c.len()).sum::<usize>()
            + self.inner.ns.as_str().len()
            + (self.inner.components.len() * 2)
    }
    pub fn byte_len(&self) -> usize {
        self.inner.components.iter().map(|c| c.as_bytes().len()).sum::<usize>()
            + self.inner.ns.as_str().as_bytes().len()
            + (self.inner.components.len() * 2)
    }
    pub fn path(&self) -> Cow<'_, str> {
        if self.inner.components.is_empty() {
            Cow::Borrowed(self.inner.ns.as_str())
        } else {
            Cow::Owned(self.to_string())
        }
    }
    pub fn namespace(&self) -> &LibraryNamespace {
        &self.inner.ns
    }
    pub fn last(&self) -> &str {
        self.last_component().as_str()
    }
    pub fn last_component(&self) -> LibraryPathComponent<'_> {
        self.inner
            .components
            .last()
            .map(LibraryPathComponent::Normal)
            .unwrap_or_else(|| LibraryPathComponent::Namespace(&self.inner.ns))
    }
    pub fn num_components(&self) -> usize {
        self.inner.components.len() + 1
    }
    pub fn components(&self) -> impl Iterator<Item = LibraryPathComponent> + '_ {
        core::iter::once(LibraryPathComponent::Namespace(&self.inner.ns))
            .chain(self.inner.components.iter().map(LibraryPathComponent::Normal))
    }
    pub fn is_kernel_path(&self) -> bool {
        matches!(self.inner.ns, LibraryNamespace::Kernel)
    }
    pub fn is_exec_path(&self) -> bool {
        matches!(self.inner.ns, LibraryNamespace::Exec)
    }
    pub fn is_anon_path(&self) -> bool {
        matches!(self.inner.ns, LibraryNamespace::Anon)
    }
    pub fn starts_with(&self, other: &LibraryPath) -> bool {
        let mut a = self.components();
        let mut b = other.components();
        loop {
            match (a.next(), b.next()) {
                (_, None) => break true,
                (None, _) => break false,
                (Some(a), Some(b)) => {
                    if a != b {
                        break false;
                    }
                },
            }
        }
    }
}
impl LibraryPath {
    pub fn set_namespace(&mut self, ns: LibraryNamespace) {
        let inner = Arc::make_mut(&mut self.inner);
        inner.ns = ns;
    }
    pub fn join(&self, other: &Self) -> Result<Self, PathError> {
        if other.inner.ns.is_reserved() {
            return Err(PathError::UnsupportedJoin);
        }
        let mut path = self.clone();
        {
            let inner = Arc::make_mut(&mut path.inner);
            inner.components.push(other.inner.ns.to_ident());
            inner.components.extend(other.inner.components.iter().cloned());
        }
        Ok(path)
    }
    pub fn push(&mut self, component: impl AsRef<str>) -> Result<(), PathError> {
        let component = component.as_ref().parse::<Ident>().map_err(PathError::InvalidComponent)?;
        self.push_ident(component);
        Ok(())
    }
    pub fn push_ident(&mut self, component: Ident) {
        let inner = Arc::make_mut(&mut self.inner);
        inner.components.push(component);
    }
    pub fn append<S>(&self, component: S) -> Result<Self, PathError>
    where
        S: AsRef<str>,
    {
        let mut path = self.clone();
        path.push(component)?;
        Ok(path)
    }
    pub fn append_ident(&self, component: Ident) -> Result<Self, PathError> {
        let mut path = self.clone();
        path.push_ident(component);
        Ok(path)
    }
    pub fn prepend<S>(&self, component: S) -> Result<Self, PathError>
    where
        S: AsRef<str>,
    {
        let ns = component
            .as_ref()
            .parse::<LibraryNamespace>()
            .map_err(PathError::InvalidNamespace)?;
        let component = self.inner.ns.to_ident();
        let mut components = smallvec![component];
        components.extend(self.inner.components.iter().cloned());
        Ok(Self::make(ns, components))
    }
    pub fn pop(&mut self) -> Option<Ident> {
        let inner = Arc::make_mut(&mut self.inner);
        inner.components.pop()
    }
    pub fn strip_last(&self) -> Option<Self> {
        match self.inner.components.len() {
            0 => None,
            1 => Some(Self::make(self.inner.ns.clone(), smallvec![])),
            _ => {
                let ns = self.inner.ns.clone();
                let mut components = self.inner.components.clone();
                components.pop();
                Some(Self::make(ns, components))
            },
        }
    }
    pub fn validate<S>(source: S) -> Result<usize, PathError>
    where
        S: AsRef<str>,
    {
        let source = source.as_ref();
        let mut count = 0;
        let mut components = source.split("::");
        let ns = components.next().ok_or(PathError::Empty)?;
        LibraryNamespace::validate(ns).map_err(PathError::InvalidNamespace)?;
        count += 1;
        for component in components {
            validate_component(component)?;
            count += 1;
        }
        Ok(count)
    }
    pub fn append_unchecked<S>(&self, component: S) -> Self
    where
        S: AsRef<str>,
    {
        let component = component.as_ref().to_string().into_boxed_str();
        let component = Ident::new_unchecked(Span::unknown(Arc::from(component)));
        let mut path = self.clone();
        path.push_ident(component);
        path
    }
}
impl<'a> TryFrom<Vec<LibraryPathComponent<'a>>> for LibraryPath {
    type Error = PathError;
    fn try_from(iter: Vec<LibraryPathComponent<'a>>) -> Result<Self, Self::Error> {
        let mut iter = iter.into_iter();
        let ns = match iter.next() {
            None => return Err(PathError::Empty),
            Some(LibraryPathComponent::Namespace(ns)) => ns.clone(),
            Some(LibraryPathComponent::Normal(ident)) => LibraryNamespace::try_from(ident.clone())?,
        };
        let mut components = Components::default();
        for component in iter {
            match component {
                LibraryPathComponent::Normal(ident) => components.push(ident.clone()),
                LibraryPathComponent::Namespace(LibraryNamespace::User(name)) => {
                    components.push(Ident::new_unchecked(Span::unknown(name.clone())));
                },
                LibraryPathComponent::Namespace(_) => return Err(PathError::UnsupportedJoin),
            }
        }
        Ok(Self::make(ns, components))
    }
}
impl From<LibraryNamespace> for LibraryPath {
    fn from(ns: LibraryNamespace) -> Self {
        Self::make(ns, smallvec![])
    }
}
impl From<LibraryPath> for String {
    fn from(path: LibraryPath) -> Self {
        path.to_string()
    }
}
impl TryFrom<String> for LibraryPath {
    type Error = PathError;
    #[inline]
    fn try_from(value: String) -> Result<Self, Self::Error> {
        Self::new(value)
    }
}
impl<'a> TryFrom<&'a str> for LibraryPath {
    type Error = PathError;
    #[inline]
    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
        Self::new(value)
    }
}
impl FromStr for LibraryPath {
    type Err = PathError;
    #[inline]
    fn from_str(value: &str) -> Result<Self, Self::Err> {
        Self::new(value)
    }
}
impl Serializable for LibraryPath {
    fn write_into<W: ByteWriter>(&self, target: &mut W) {
        let len = self.byte_len();
        target.write_u16(len as u16);
        target.write_bytes(self.inner.ns.as_str().as_bytes());
        for component in self.inner.components.iter() {
            target.write_bytes(b"::");
            target.write_bytes(component.as_str().as_bytes());
        }
    }
}
impl Deserializable for LibraryPath {
    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
        let len = source.read_u16()? as usize;
        let path = source.read_slice(len)?;
        let path =
            str::from_utf8(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
        Self::new(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
    }
}
impl fmt::Display for LibraryPath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.inner.ns)?;
        for component in self.inner.components.iter() {
            write!(f, "::{component}")?;
        }
        Ok(())
    }
}
fn validate_component(component: &str) -> Result<(), PathError> {
    if component.is_empty() {
        Err(PathError::EmptyComponent)
    } else if component.len() > LibraryNamespace::MAX_LENGTH {
        Err(PathError::InvalidComponent(IdentError::InvalidLength {
            max: LibraryNamespace::MAX_LENGTH,
        }))
    } else {
        Ident::validate(component).map_err(PathError::InvalidComponent)
    }
}
#[cfg(test)]
mod tests {
    use vm_core::assert_matches;
    use super::{super::LibraryNamespaceError, IdentError, LibraryPath, PathError};
    #[test]
    fn new_path() {
        let path = LibraryPath::new("foo").unwrap();
        assert_eq!(path.num_components(), 1);
        let path = LibraryPath::new("foo::bar").unwrap();
        assert_eq!(path.num_components(), 2);
        let path = LibraryPath::new("foo::bar::baz").unwrap();
        assert_eq!(path.num_components(), 3);
        let path = LibraryPath::new("#exec::bar::baz").unwrap();
        assert_eq!(path.num_components(), 3);
        let path = LibraryPath::new("#sys::bar::baz").unwrap();
        assert_eq!(path.num_components(), 3);
    }
    #[test]
    fn new_path_fail() {
        let path = LibraryPath::new("");
        assert_matches!(path, Err(PathError::Empty));
        let path = LibraryPath::new("::");
        assert_matches!(path, Err(PathError::InvalidNamespace(LibraryNamespaceError::Empty)));
        let path = LibraryPath::new("foo::");
        assert_matches!(path, Err(PathError::InvalidComponent(IdentError::Empty)));
        let path = LibraryPath::new("::foo");
        assert_matches!(path, Err(PathError::InvalidNamespace(LibraryNamespaceError::Empty)));
        let path = LibraryPath::new("foo::1bar");
        assert_matches!(path, Err(PathError::InvalidComponent(IdentError::InvalidStart)));
        let path = LibraryPath::new("foo::b@r");
        assert_matches!(path, Err(PathError::InvalidComponent(IdentError::InvalidChars)));
        let path = LibraryPath::new("#foo::bar");
        assert_matches!(
            path,
            Err(PathError::InvalidNamespace(LibraryNamespaceError::InvalidStart))
        );
    }
}