//! Interface to use paths in the filesystem

use alloc::borrow::ToOwned;
use alloc::string::String;

use derive_more::{Deref, DerefMut};

use super::node::{Interface, Ref};

/// Memory allocated non-empty path structure
#[derive(Debug, Deref, DerefMut, Clone, PartialEq, Eq)]
pub struct Path(String);

impl From<Ref<dyn Interface>> for Path {
    fn from(value: Ref<dyn Interface>) -> Self {
        let node = value.lock();
        if node.is_root() {
            Self::new_absolute()
        } else {
            let mut parent_path = Self::from(node.get_parent_dir() as Ref<dyn Interface>);
            parent_path.push(&node.name());
            parent_path
        }
    }
}

impl Path {
    /// Returns a new absolute path (the path of the filesystem's root)
    pub fn new_absolute() -> Self {
        Self("/".to_owned())
    }

    /// Returns a new relative path (the path of the "current" node)
    #[allow(unused)]
    pub fn new_relative() -> Self {
        Self(".".to_owned())
    }

    /// Returns whether `self` is relative or not
    #[allow(unused)]
    pub fn is_relative(&self) -> bool {
        // SAFETY: it is ensure in this structure that no empty path can be created
        unsafe { self.chars().next().unwrap_unchecked() == '.' }
    }

    /// Returns whether `self` is absolute or not
    #[allow(unused)]
    pub fn is_absolute(&self) -> bool {
        !self.is_relative()
    }

    /// Returns whether `self` represents the root or not
    ///
    /// Is `is_relative` if `true`, then this function will always return `false`.
    #[allow(unused)]
    pub fn is_root(&self) -> bool {
        self.is_absolute() && self.ends_with('/')
    }

    /// Extends `self` with a node represented as a [`String`]
    pub fn push(&mut self, node: &str) {
        if self.0 != "/" {
            self.extend_one('/');
        }
        self.extend(node.chars());
    }

    /// Truncates `self` to its parent dir
    ///
    /// Returns the name of the node truncated, and [`None`] if it does nothing
    #[allow(unused)]
    pub fn pop(&mut self) -> Option<String> {
        if self.0 == "." || self.0 == "/" {
            return None;
        };

        let binding = self.clone();
        let relative = self.is_relative();
        let mut ancestors = binding.split('/');

        // SAFETY: the path is non empty and not reduced to "/" or "."
        let node = unsafe { ancestors.next_back().unwrap_unchecked() }.to_owned();
        if relative {
            ancestors.next();
        };

        self.clone_from(&if relative { Self::new_relative() } else { Self::new_absolute() });
        ancestors.for_each(|ancestor| self.push(ancestor));

        Some(node)
    }
}

#[cfg(test)]
mod test {
    use super::Path;

    #[test_case]
    fn absolute() {
        let root = Path::new_absolute();
        assert!(root.is_absolute());
        assert!(root.is_root());
        assert_eq!(&root.0, "/");
    }

    #[test_case]
    fn relative() {
        let current = Path::new_relative();
        assert!(current.is_relative());
        assert!(!current.is_root());
        assert_eq!(&current.0, ".");
    }

    #[test_case]
    fn absolute_manipulation() {
        let mut path = Path::new_absolute();
        path.push("bin");
        path.push("ls");
        assert_eq!(&path.0, "/bin/ls");
        assert_eq!(&path.pop().unwrap(), "ls");
        assert_eq!(&path.0, "/bin");
    }

    #[test_case]
    fn relative_manipulation() {
        let mut path = Path::new_relative();
        path.push("foo");
        path.push("bar");
        assert_eq!(&path.0, "./foo/bar");
        assert_eq!(&path.pop().unwrap(), "bar");
        assert_eq!(&path.0, "./foo");
    }
}