use super::*;
use crate::{DirEntry, FileSystem as _, FileType, FsError, Metadata, OpenOptions, ReadDir, Result};
use futures::future::BoxFuture;
use slab::Slab;
use std::collections::VecDeque;
use std::convert::identity;
use std::ffi::OsString;
use std::fmt;
use std::path::{Component, Path, PathBuf};
use std::sync::{Arc, RwLock};
#[derive(Clone, Default)]
pub struct FileSystem {
    pub(super) inner: Arc<RwLock<FileSystemInner>>,
}
impl FileSystem {
    pub fn set_memory_limiter(&self, limiter: crate::limiter::DynFsMemoryLimiter) {
        self.inner.write().unwrap().limiter = Some(limiter);
    }
    pub fn new_open_options_ext(&self) -> &FileSystem {
        self
    }
    pub fn canonicalize_unchecked(&self, path: &Path) -> Result<PathBuf> {
        let lock = self.inner.read().map_err(|_| FsError::Lock)?;
        lock.canonicalize_without_inode(path)
    }
    pub fn mount_directory_entries(
        &self,
        target_path: &Path,
        other: &Arc<dyn crate::FileSystem + Send + Sync>,
        mut source_path: &Path,
    ) -> Result<()> {
        let fs_lock = self.inner.read().map_err(|_| FsError::Lock)?;
        if cfg!(windows) {
            let mut components = source_path.components();
            if let Some(Component::Prefix(_)) = components.next() {
                source_path = components.as_path();
            }
        }
        let (_target_path, root_inode) = match fs_lock.canonicalize(target_path) {
            Ok((p, InodeResolution::Found(inode))) => (p, inode),
            Ok((_p, InodeResolution::Redirect(..))) => {
                return Err(FsError::AlreadyExists);
            }
            Err(_) => {
                return self.mount(target_path.to_path_buf(), other, source_path.to_path_buf());
            }
        };
        let _root_node = match fs_lock.storage.get(root_inode).unwrap() {
            Node::Directory(dir) => dir,
            _ => {
                return Err(FsError::AlreadyExists);
            }
        };
        let source_path = fs_lock.canonicalize_without_inode(source_path)?;
        std::mem::drop(fs_lock);
        let source = other.read_dir(&source_path)?;
        for entry in source.data {
            let meta = entry.metadata?;
            let entry_target_path = target_path.join(entry.path.file_name().unwrap());
            if meta.is_file() {
                self.insert_arc_file_at(entry_target_path, other.clone(), entry.path)?;
            } else if meta.is_dir() {
                self.insert_arc_directory_at(entry_target_path, other.clone(), entry.path)?;
            }
        }
        Ok(())
    }
    pub fn union(&self, other: &Arc<dyn crate::FileSystem + Send + Sync>) {
        let mut remaining = VecDeque::new();
        remaining.push_back(PathBuf::from("/"));
        while let Some(next) = remaining.pop_back() {
            if next
                .file_name()
                .map(|n| n.to_string_lossy().starts_with(".wh."))
                .unwrap_or(false)
            {
                let rm = next.to_string_lossy();
                let rm = &rm[".wh.".len()..];
                let rm = PathBuf::from(rm);
                let _ = crate::FileSystem::remove_dir(self, rm.as_path());
                let _ = crate::FileSystem::remove_file(self, rm.as_path());
                continue;
            }
            let _ = crate::FileSystem::create_dir(self, next.as_path());
            let dir = match other.read_dir(next.as_path()) {
                Ok(dir) => dir,
                Err(_) => {
                    continue;
                }
            };
            for sub_dir_res in dir {
                let sub_dir = match sub_dir_res {
                    Ok(sub_dir) => sub_dir,
                    Err(_) => {
                        continue;
                    }
                };
                match sub_dir.file_type() {
                    Ok(t) if t.is_dir() => {
                        remaining.push_back(sub_dir.path());
                    }
                    Ok(t) if t.is_file() => {
                        if sub_dir.file_name().to_string_lossy().starts_with(".wh.") {
                            let rm = next.to_string_lossy();
                            let rm = &rm[".wh.".len()..];
                            let rm = PathBuf::from(rm);
                            let _ = crate::FileSystem::remove_dir(self, rm.as_path());
                            let _ = crate::FileSystem::remove_file(self, rm.as_path());
                            continue;
                        }
                        let _ = self
                            .new_open_options_ext()
                            .insert_arc_file(sub_dir.path(), other.clone());
                    }
                    _ => {}
                }
            }
        }
    }
    pub fn mount(
        &self,
        target_path: PathBuf,
        other: &Arc<dyn crate::FileSystem + Send + Sync>,
        source_path: PathBuf,
    ) -> Result<()> {
        if crate::FileSystem::read_dir(self, target_path.as_path()).is_ok() {
            return Err(FsError::AlreadyExists);
        }
        let (inode_of_parent, name_of_directory) = {
            let guard = self.inner.read().map_err(|_| FsError::Lock)?;
            let path = guard.canonicalize_without_inode(target_path.as_path())?;
            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
            let name_of_directory = path
                .file_name()
                .ok_or(FsError::InvalidInput)?
                .to_os_string();
            let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
                InodeResolution::Found(a) => a,
                InodeResolution::Redirect(..) => {
                    return Err(FsError::AlreadyExists);
                }
            };
            (inode_of_parent, name_of_directory)
        };
        {
            let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
            let inode_of_directory = fs.storage.vacant_entry().key();
            let real_inode_of_directory = fs.storage.insert(Node::ArcDirectory(ArcDirectoryNode {
                inode: inode_of_directory,
                name: name_of_directory,
                fs: other.clone(),
                path: source_path,
                metadata: {
                    let time = time();
                    Metadata {
                        ft: FileType {
                            dir: true,
                            ..Default::default()
                        },
                        accessed: time,
                        created: time,
                        modified: time,
                        len: 0,
                    }
                },
            }));
            assert_eq!(
                inode_of_directory, real_inode_of_directory,
                "new directory inode should have been correctly calculated",
            );
            fs.add_child_to_node(inode_of_parent, inode_of_directory)?;
        }
        Ok(())
    }
}
impl crate::FileSystem for FileSystem {
    fn read_dir(&self, path: &Path) -> Result<ReadDir> {
        let guard = self.inner.read().map_err(|_| FsError::Lock)?;
        let (path, inode_of_directory) = guard.canonicalize(path)?;
        let inode_of_directory = match inode_of_directory {
            InodeResolution::Found(a) => a,
            InodeResolution::Redirect(fs, path) => {
                return fs.read_dir(path.as_path());
            }
        };
        let inode = guard.storage.get(inode_of_directory);
        let children = match inode {
            Some(Node::Directory(DirectoryNode { children, .. })) => children
                .iter()
                .filter_map(|inode| guard.storage.get(*inode))
                .map(|node| DirEntry {
                    path: {
                        let mut entry_path = path.to_path_buf();
                        entry_path.push(node.name());
                        entry_path
                    },
                    metadata: Ok(node.metadata().clone()),
                })
                .collect(),
            Some(Node::ArcDirectory(ArcDirectoryNode { fs, path, .. })) => {
                return fs.read_dir(path.as_path());
            }
            _ => return Err(FsError::InvalidInput),
        };
        Ok(ReadDir::new(children))
    }
    fn create_dir(&self, path: &Path) -> Result<()> {
        if self.read_dir(path).is_ok() {
            return Err(FsError::AlreadyExists);
        }
        let (inode_of_parent, name_of_directory) = {
            let guard = self.inner.read().map_err(|_| FsError::Lock)?;
            let path = guard.canonicalize_without_inode(path)?;
            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
            let name_of_directory = path
                .file_name()
                .ok_or(FsError::InvalidInput)?
                .to_os_string();
            let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
                InodeResolution::Found(a) => a,
                InodeResolution::Redirect(fs, mut path) => {
                    drop(guard);
                    path.push(name_of_directory);
                    return fs.create_dir(path.as_path());
                }
            };
            (inode_of_parent, name_of_directory)
        };
        if self.read_dir(path).is_ok() {
            return Err(FsError::AlreadyExists);
        }
        {
            let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
            let inode_of_directory = fs.storage.vacant_entry().key();
            let real_inode_of_directory = fs.storage.insert(Node::Directory(DirectoryNode {
                inode: inode_of_directory,
                name: name_of_directory,
                children: Vec::new(),
                metadata: {
                    let time = time();
                    Metadata {
                        ft: FileType {
                            dir: true,
                            ..Default::default()
                        },
                        accessed: time,
                        created: time,
                        modified: time,
                        len: 0,
                    }
                },
            }));
            assert_eq!(
                inode_of_directory, real_inode_of_directory,
                "new directory inode should have been correctly calculated",
            );
            fs.add_child_to_node(inode_of_parent, inode_of_directory)?;
        }
        Ok(())
    }
    fn remove_dir(&self, path: &Path) -> Result<()> {
        let (inode_of_parent, position, inode_of_directory) = {
            let guard = self.inner.read().map_err(|_| FsError::Lock)?;
            let (path, _) = guard.canonicalize(path)?;
            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
            let name_of_directory = path
                .file_name()
                .ok_or(FsError::InvalidInput)?
                .to_os_string();
            let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
                InodeResolution::Found(a) => a,
                InodeResolution::Redirect(fs, mut parent_path) => {
                    drop(guard);
                    parent_path.push(name_of_directory);
                    return fs.remove_dir(parent_path.as_path());
                }
            };
            let (position, inode_of_directory) = guard
                .as_parent_get_position_and_inode_of_directory(
                    inode_of_parent,
                    &name_of_directory,
                    DirectoryMustBeEmpty::Yes,
                )?;
            (inode_of_parent, position, inode_of_directory)
        };
        let inode_of_directory = match inode_of_directory {
            InodeResolution::Found(a) => a,
            InodeResolution::Redirect(fs, path) => {
                return fs.remove_dir(path.as_path());
            }
        };
        {
            let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
            fs.storage.remove(inode_of_directory);
            fs.remove_child_from_node(inode_of_parent, position)?;
        }
        Ok(())
    }
    fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<()>> {
        Box::pin(async {
            let name_of_to;
            let (
                (position_of_from, inode, inode_of_from_parent),
                (inode_of_to_parent, name_of_to),
                inode_dest,
            ) = {
                let fs = self.inner.read().map_err(|_| FsError::Lock)?;
                let from = fs.canonicalize_without_inode(from)?;
                let to = fs.canonicalize_without_inode(to)?;
                let parent_of_from = from.parent().ok_or(FsError::BaseNotDirectory)?;
                let parent_of_to = to.parent().ok_or(FsError::BaseNotDirectory)?;
                let name_of_from = from
                    .file_name()
                    .ok_or(FsError::InvalidInput)?
                    .to_os_string();
                name_of_to = to.file_name().ok_or(FsError::InvalidInput)?.to_os_string();
                let inode_of_from_parent = match fs.inode_of_parent(parent_of_from)? {
                    InodeResolution::Found(a) => a,
                    InodeResolution::Redirect(..) => {
                        return Err(FsError::InvalidInput);
                    }
                };
                let inode_of_to_parent = match fs.inode_of_parent(parent_of_to)? {
                    InodeResolution::Found(a) => a,
                    InodeResolution::Redirect(..) => {
                        return Err(FsError::InvalidInput);
                    }
                };
                let maybe_position_and_inode_of_file =
                    fs.as_parent_get_position_and_inode_of_file(inode_of_to_parent, &name_of_to)?;
                let (position_of_from, inode) = fs
                    .as_parent_get_position_and_inode(inode_of_from_parent, &name_of_from)?
                    .ok_or(FsError::EntryNotFound)?;
                (
                    (position_of_from, inode, inode_of_from_parent),
                    (inode_of_to_parent, name_of_to),
                    maybe_position_and_inode_of_file,
                )
            };
            let inode = match inode {
                InodeResolution::Found(a) => a,
                InodeResolution::Redirect(..) => {
                    return Err(FsError::InvalidInput);
                }
            };
            {
                let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
                if let Some((position, inode_of_file)) = inode_dest {
                    match inode_of_file {
                        InodeResolution::Found(inode_of_file) => {
                            fs.storage.remove(inode_of_file);
                        }
                        InodeResolution::Redirect(..) => {
                            return Err(FsError::InvalidInput);
                        }
                    }
                    fs.remove_child_from_node(inode_of_to_parent, position)?;
                }
                fs.update_node_name(inode, name_of_to)?;
                if inode_of_from_parent != inode_of_to_parent {
                    fs.remove_child_from_node(inode_of_from_parent, position_of_from)?;
                    fs.add_child_to_node(inode_of_to_parent, inode)?;
                }
                else {
                    let mut inode = fs.storage.get_mut(inode_of_from_parent);
                    match inode.as_mut() {
                        Some(Node::Directory(node)) => node.metadata.modified = time(),
                        Some(Node::ArcDirectory(node)) => node.metadata.modified = time(),
                        _ => return Err(FsError::UnknownError),
                    }
                }
            }
            Ok(())
        })
    }
    fn metadata(&self, path: &Path) -> Result<Metadata> {
        let guard = self.inner.read().map_err(|_| FsError::Lock)?;
        match guard.inode_of(path)? {
            InodeResolution::Found(inode) => Ok(guard
                .storage
                .get(inode)
                .ok_or(FsError::UnknownError)?
                .metadata()
                .clone()),
            InodeResolution::Redirect(fs, path) => {
                drop(guard);
                fs.metadata(path.as_path())
            }
        }
    }
    fn remove_file(&self, path: &Path) -> Result<()> {
        let (inode_of_parent, position, inode_of_file) = {
            let guard = self.inner.read().map_err(|_| FsError::Lock)?;
            let path = guard.canonicalize_without_inode(path)?;
            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
            let name_of_file = path
                .file_name()
                .ok_or(FsError::InvalidInput)?
                .to_os_string();
            let inode_of_parent = match guard.inode_of_parent(parent_of_path)? {
                InodeResolution::Found(a) => a,
                InodeResolution::Redirect(fs, mut parent_path) => {
                    parent_path.push(name_of_file);
                    return fs.remove_file(parent_path.as_path());
                }
            };
            let maybe_position_and_inode_of_file =
                guard.as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?;
            match maybe_position_and_inode_of_file {
                Some((position, inode_of_file)) => (inode_of_parent, position, inode_of_file),
                None => return Err(FsError::EntryNotFound),
            }
        };
        let inode_of_file = match inode_of_file {
            InodeResolution::Found(a) => a,
            InodeResolution::Redirect(fs, path) => {
                return fs.remove_file(path.as_path());
            }
        };
        {
            let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
            fs.storage.remove(inode_of_file);
            fs.remove_child_from_node(inode_of_parent, position)?;
        }
        Ok(())
    }
    fn new_open_options(&self) -> OpenOptions {
        OpenOptions::new(self)
    }
}
impl fmt::Debug for FileSystem {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let fs: &FileSystemInner = &self.inner.read().unwrap();
        fs.fmt(formatter)
    }
}
pub(super) struct FileSystemInner {
    pub(super) storage: Slab<Node>,
    pub(super) limiter: Option<crate::limiter::DynFsMemoryLimiter>,
}
#[derive(Debug)]
pub(super) enum InodeResolution {
    Found(Inode),
    Redirect(Arc<dyn crate::FileSystem + Send + Sync + 'static>, PathBuf),
}
impl InodeResolution {
    #[allow(dead_code)]
    pub fn unwrap(&self) -> Inode {
        match self {
            Self::Found(a) => *a,
            Self::Redirect(..) => {
                panic!("failed to unwrap the inode as the resolution is a redirect");
            }
        }
    }
}
impl FileSystemInner {
    pub(super) fn inode_of(&self, path: &Path) -> Result<InodeResolution> {
        let mut node = self.storage.get(ROOT_INODE).unwrap();
        let mut components = path.components();
        match components.next() {
            Some(Component::RootDir) => {}
            _ => return Err(FsError::BaseNotDirectory),
        }
        while let Some(component) = components.next() {
            node = match node {
                Node::Directory(DirectoryNode { children, .. }) => children
                    .iter()
                    .filter_map(|inode| self.storage.get(*inode))
                    .find(|node| node.name() == component.as_os_str())
                    .ok_or(FsError::EntryNotFound)?,
                Node::ArcDirectory(ArcDirectoryNode {
                    fs, path: fs_path, ..
                }) => {
                    let mut path = fs_path.clone();
                    path.push(PathBuf::from(component.as_os_str()));
                    for component in components.by_ref() {
                        path.push(PathBuf::from(component.as_os_str()));
                    }
                    return Ok(InodeResolution::Redirect(fs.clone(), path));
                }
                _ => return Err(FsError::BaseNotDirectory),
            };
        }
        Ok(InodeResolution::Found(node.inode()))
    }
    pub(super) fn inode_of_parent(&self, parent_path: &Path) -> Result<InodeResolution> {
        match self.inode_of(parent_path)? {
            InodeResolution::Found(inode_of_parent) => {
                match self.storage.get(inode_of_parent) {
                    Some(Node::Directory(DirectoryNode { .. })) => {
                        Ok(InodeResolution::Found(inode_of_parent))
                    }
                    Some(Node::ArcDirectory(ArcDirectoryNode { fs, path, .. })) => {
                        Ok(InodeResolution::Redirect(fs.clone(), path.clone()))
                    }
                    _ => Err(FsError::BaseNotDirectory),
                }
            }
            InodeResolution::Redirect(fs, path) => Ok(InodeResolution::Redirect(fs, path)),
        }
    }
    pub(super) fn as_parent_get_position_and_inode_of_directory(
        &self,
        inode_of_parent: Inode,
        name_of_directory: &OsString,
        directory_must_be_empty: DirectoryMustBeEmpty,
    ) -> Result<(usize, InodeResolution)> {
        match self.storage.get(inode_of_parent) {
            Some(Node::Directory(DirectoryNode { children, .. })) => children
                .iter()
                .enumerate()
                .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
                .find_map(|(nth, node)| match node {
                    Node::Directory(DirectoryNode {
                        inode,
                        name,
                        children,
                        ..
                    }) if name.as_os_str() == name_of_directory => {
                        if directory_must_be_empty.no() || children.is_empty() {
                            Some(Ok((nth, InodeResolution::Found(*inode))))
                        } else {
                            Some(Err(FsError::DirectoryNotEmpty))
                        }
                    }
                    _ => None,
                })
                .ok_or(FsError::InvalidInput)
                .and_then(identity), Some(Node::ArcDirectory(ArcDirectoryNode {
                fs, path: fs_path, ..
            })) => {
                let mut path = fs_path.clone();
                path.push(name_of_directory);
                Ok((0, InodeResolution::Redirect(fs.clone(), path)))
            }
            _ => Err(FsError::BaseNotDirectory),
        }
    }
    pub(super) fn as_parent_get_position_and_inode_of_file(
        &self,
        inode_of_parent: Inode,
        name_of_file: &OsString,
    ) -> Result<Option<(usize, InodeResolution)>> {
        match self.storage.get(inode_of_parent) {
            Some(Node::Directory(DirectoryNode { children, .. })) => children
                .iter()
                .enumerate()
                .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
                .find_map(|(nth, node)| match node {
                    Node::File(FileNode { inode, name, .. })
                    | Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. })
                    | Node::CustomFile(CustomFileNode { inode, name, .. })
                    | Node::ArcFile(ArcFileNode { inode, name, .. })
                        if name.as_os_str() == name_of_file =>
                    {
                        Some(Some((nth, InodeResolution::Found(*inode))))
                    }
                    _ => None,
                })
                .or(Some(None))
                .ok_or(FsError::InvalidInput),
            Some(Node::ArcDirectory(ArcDirectoryNode {
                fs, path: fs_path, ..
            })) => {
                let mut path = fs_path.clone();
                path.push(name_of_file);
                Ok(Some((0, InodeResolution::Redirect(fs.clone(), path))))
            }
            _ => Err(FsError::BaseNotDirectory),
        }
    }
    fn as_parent_get_position_and_inode(
        &self,
        inode_of_parent: Inode,
        name_of: &OsString,
    ) -> Result<Option<(usize, InodeResolution)>> {
        match self.storage.get(inode_of_parent) {
            Some(Node::Directory(DirectoryNode { children, .. })) => children
                .iter()
                .enumerate()
                .filter_map(|(nth, inode)| self.storage.get(*inode).map(|node| (nth, node)))
                .find_map(|(nth, node)| match node {
                    Node::File(FileNode { inode, name, .. })
                    | Node::Directory(DirectoryNode { inode, name, .. })
                    | Node::ReadOnlyFile(ReadOnlyFileNode { inode, name, .. })
                    | Node::CustomFile(CustomFileNode { inode, name, .. })
                    | Node::ArcFile(ArcFileNode { inode, name, .. })
                        if name.as_os_str() == name_of =>
                    {
                        Some(Some((nth, InodeResolution::Found(*inode))))
                    }
                    _ => None,
                })
                .or(Some(None))
                .ok_or(FsError::InvalidInput),
            Some(Node::ArcDirectory(ArcDirectoryNode {
                fs, path: fs_path, ..
            })) => {
                let mut path = fs_path.clone();
                path.push(name_of);
                Ok(Some((0, InodeResolution::Redirect(fs.clone(), path))))
            }
            _ => Err(FsError::BaseNotDirectory),
        }
    }
    pub(super) fn update_node_name(&mut self, inode: Inode, new_name: OsString) -> Result<()> {
        let node = self.storage.get_mut(inode).ok_or(FsError::UnknownError)?;
        node.set_name(new_name);
        node.metadata_mut().modified = time();
        Ok(())
    }
    pub(super) fn add_child_to_node(&mut self, inode: Inode, new_child: Inode) -> Result<()> {
        match self.storage.get_mut(inode) {
            Some(Node::Directory(DirectoryNode {
                children,
                metadata: Metadata { modified, .. },
                ..
            })) => {
                children.push(new_child);
                *modified = time();
                Ok(())
            }
            _ => Err(FsError::UnknownError),
        }
    }
    pub(super) fn remove_child_from_node(&mut self, inode: Inode, position: usize) -> Result<()> {
        match self.storage.get_mut(inode) {
            Some(Node::Directory(DirectoryNode {
                children,
                metadata: Metadata { modified, .. },
                ..
            })) => {
                children.remove(position);
                *modified = time();
                Ok(())
            }
            _ => Err(FsError::UnknownError),
        }
    }
    pub(super) fn canonicalize(&self, path: &Path) -> Result<(PathBuf, InodeResolution)> {
        let new_path = self.canonicalize_without_inode(path)?;
        let inode = self.inode_of(&new_path)?;
        Ok((new_path, inode))
    }
    pub(super) fn canonicalize_without_inode(&self, path: &Path) -> Result<PathBuf> {
        let mut components = path.components();
        match components.next() {
            Some(Component::RootDir) => {}
            _ => return Err(FsError::InvalidInput),
        }
        let mut new_path = PathBuf::with_capacity(path.as_os_str().len());
        new_path.push("/");
        for component in components {
            match component {
                Component::RootDir => return Err(FsError::UnknownError),
                Component::CurDir => (),
                Component::ParentDir => {
                    if !new_path.pop() {
                        return Err(FsError::InvalidInput);
                    }
                }
                Component::Normal(name) => {
                    new_path.push(name);
                }
                Component::Prefix(_) => return Err(FsError::InvalidInput),
            }
        }
        Ok(new_path)
    }
}
impl fmt::Debug for FileSystemInner {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(
            formatter,
            "\n{inode:<8}    {ty:<4}    name",
            inode = "inode",
            ty = "type",
        )?;
        fn debug(
            nodes: Vec<&Node>,
            slf: &FileSystemInner,
            formatter: &mut fmt::Formatter<'_>,
            indentation: usize,
        ) -> fmt::Result {
            for node in nodes {
                writeln!(
                    formatter,
                    "{inode:<8}    {ty:<4}   {indentation_symbol:indentation_width$}{name}",
                    inode = node.inode(),
                    ty = match node {
                        Node::File { .. } => "file",
                        Node::ReadOnlyFile { .. } => "ro-file",
                        Node::ArcFile { .. } => "arc-file",
                        Node::CustomFile { .. } => "custom-file",
                        Node::Directory { .. } => "dir",
                        Node::ArcDirectory { .. } => "arc-dir",
                    },
                    name = node.name().to_string_lossy(),
                    indentation_symbol = " ",
                    indentation_width = indentation * 2 + 1,
                )?;
                if let Node::Directory(DirectoryNode { children, .. }) = node {
                    debug(
                        children
                            .iter()
                            .filter_map(|inode| slf.storage.get(*inode))
                            .collect(),
                        slf,
                        formatter,
                        indentation + 1,
                    )?;
                }
            }
            Ok(())
        }
        debug(
            vec![self.storage.get(ROOT_INODE).unwrap()],
            self,
            formatter,
            0,
        )
    }
}
impl Default for FileSystemInner {
    fn default() -> Self {
        let time = time();
        let mut slab = Slab::new();
        slab.insert(Node::Directory(DirectoryNode {
            inode: ROOT_INODE,
            name: OsString::from("/"),
            children: Vec::new(),
            metadata: Metadata {
                ft: FileType {
                    dir: true,
                    ..Default::default()
                },
                accessed: time,
                created: time,
                modified: time,
                len: 0,
            },
        }));
        Self {
            storage: slab,
            limiter: None,
        }
    }
}
#[allow(dead_code)] pub(super) enum DirectoryMustBeEmpty {
    Yes,
    No,
}
impl DirectoryMustBeEmpty {
    pub(super) fn yes(&self) -> bool {
        matches!(self, Self::Yes)
    }
    pub(super) fn no(&self) -> bool {
        !self.yes()
    }
}
#[cfg(test)]
mod test_filesystem {
    use std::{borrow::Cow, path::Path};
    use tokio::io::AsyncReadExt;
    use crate::{mem_fs::*, ops, DirEntry, FileSystem as FS, FileType, FsError};
    macro_rules! path {
        ($path:expr) => {
            std::path::Path::new($path)
        };
        (buf $path:expr) => {
            std::path::PathBuf::from($path)
        };
    }
    #[tokio::test]
    async fn test_new_filesystem() {
        let fs = FileSystem::default();
        let fs_inner = fs.inner.read().unwrap();
        assert_eq!(fs_inner.storage.len(), 1, "storage has a root");
        assert!(
            matches!(
                fs_inner.storage.get(ROOT_INODE),
                Some(Node::Directory(DirectoryNode {
                    inode: ROOT_INODE,
                    name,
                    children,
                    ..
                })) if name == "/" && children.is_empty(),
            ),
            "storage has a well-defined root",
        );
    }
    #[tokio::test]
    async fn test_create_dir() {
        let fs = FileSystem::default();
        assert_eq!(
            fs.create_dir(path!("/")),
            Err(FsError::AlreadyExists),
            "creating the root which already exists",
        );
        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating a directory",);
        {
            let fs_inner = fs.inner.read().unwrap();
            assert_eq!(
                fs_inner.storage.len(),
                2,
                "storage contains the new directory"
            );
            assert!(
                matches!(
                    fs_inner.storage.get(ROOT_INODE),
                    Some(Node::Directory(DirectoryNode {
                        inode: ROOT_INODE,
                        name,
                        children,
                        ..
                    })) if name == "/" && children == &[1]
                ),
                "the root is updated and well-defined",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(1),
                    Some(Node::Directory(DirectoryNode {
                        inode: 1,
                        name,
                        children,
                        ..
                    })) if name == "foo" && children.is_empty(),
                ),
                "the new directory is well-defined",
            );
        }
        assert_eq!(
            fs.create_dir(path!("/foo/bar")),
            Ok(()),
            "creating a sub-directory",
        );
        {
            let fs_inner = fs.inner.read().unwrap();
            assert_eq!(
                fs_inner.storage.len(),
                3,
                "storage contains the new sub-directory",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(ROOT_INODE),
                    Some(Node::Directory(DirectoryNode {
                        inode: ROOT_INODE,
                        name,
                        children,
                        ..
                    })) if name == "/" && children == &[1]
                ),
                "the root is updated again and well-defined",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(1),
                    Some(Node::Directory(DirectoryNode {
                        inode: 1,
                        name,
                        children,
                        ..
                    })) if name == "foo" && children == &[2]
                ),
                "the new directory is updated and well-defined",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(2),
                    Some(Node::Directory(DirectoryNode {
                        inode: 2,
                        name,
                        children,
                        ..
                    })) if name == "bar" && children.is_empty()
                ),
                "the new directory is well-defined",
            );
        }
    }
    #[tokio::test]
    async fn test_remove_dir() {
        let fs = FileSystem::default();
        assert_eq!(
            fs.remove_dir(path!("/")),
            Err(FsError::BaseNotDirectory),
            "removing a directory that has no parent",
        );
        assert_eq!(
            fs.remove_dir(path!("/foo")),
            Err(FsError::EntryNotFound),
            "cannot remove a directory that doesn't exist",
        );
        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating a directory",);
        assert_eq!(
            fs.create_dir(path!("/foo/bar")),
            Ok(()),
            "creating a sub-directory",
        );
        {
            let fs_inner = fs.inner.read().unwrap();
            assert_eq!(
                fs_inner.storage.len(),
                3,
                "storage contains all the directories",
            );
        }
        assert_eq!(
            fs.remove_dir(path!("/foo")),
            Err(FsError::DirectoryNotEmpty),
            "removing a directory that has children",
        );
        assert_eq!(
            fs.remove_dir(path!("/foo/bar")),
            Ok(()),
            "removing a sub-directory",
        );
        assert_eq!(fs.remove_dir(path!("/foo")), Ok(()), "removing a directory",);
        {
            let fs_inner = fs.inner.read().unwrap();
            assert_eq!(
                fs_inner.storage.len(),
                1,
                "storage contains all the directories",
            );
        }
    }
    #[tokio::test]
    async fn test_rename() {
        let fs = FileSystem::default();
        assert_eq!(
            fs.rename(path!("/"), path!("/bar")).await,
            Err(FsError::BaseNotDirectory),
            "renaming a directory that has no parent",
        );
        assert_eq!(
            fs.rename(path!("/foo"), path!("/")).await,
            Err(FsError::BaseNotDirectory),
            "renaming to a directory that has no parent",
        );
        assert_eq!(fs.create_dir(path!("/foo")), Ok(()));
        assert_eq!(fs.create_dir(path!("/foo/qux")), Ok(()));
        assert_eq!(
            fs.rename(path!("/foo"), path!("/bar/baz")).await,
            Err(FsError::EntryNotFound),
            "renaming to a directory that has parent that doesn't exist",
        );
        assert_eq!(fs.create_dir(path!("/bar")), Ok(()));
        assert!(
            matches!(
                fs.new_open_options()
                    .write(true)
                    .create_new(true)
                    .open(path!("/bar/hello1.txt")),
                Ok(_),
            ),
            "creating a new file (`hello1.txt`)",
        );
        assert!(
            matches!(
                fs.new_open_options()
                    .write(true)
                    .create_new(true)
                    .open(path!("/bar/hello2.txt")),
                Ok(_),
            ),
            "creating a new file (`hello2.txt`)",
        );
        {
            let fs_inner = fs.inner.read().unwrap();
            assert_eq!(fs_inner.storage.len(), 6, "storage has all files");
            assert!(
                matches!(
                    fs_inner.storage.get(ROOT_INODE),
                    Some(Node::Directory(DirectoryNode {
                        inode: ROOT_INODE,
                        name,
                        children,
                        ..
                    })) if name == "/" && children == &[1, 3]
                ),
                "`/` contains `foo` and `bar`",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(1),
                    Some(Node::Directory(DirectoryNode {
                        inode: 1,
                        name,
                        children,
                        ..
                    })) if name == "foo" && children == &[2]
                ),
                "`foo` contains `qux`",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(2),
                    Some(Node::Directory(DirectoryNode {
                        inode: 2,
                        name,
                        children,
                        ..
                    })) if name == "qux" && children.is_empty()
                ),
                "`qux` is empty",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(3),
                    Some(Node::Directory(DirectoryNode {
                        inode: 3,
                        name,
                        children,
                        ..
                    })) if name == "bar" && children == &[4, 5]
                ),
                "`bar` is contains `hello.txt`",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(4),
                    Some(Node::File(FileNode {
                        inode: 4,
                        name,
                        ..
                    })) if name == "hello1.txt"
                ),
                "`hello1.txt` exists",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(5),
                    Some(Node::File(FileNode {
                        inode: 5,
                        name,
                        ..
                    })) if name == "hello2.txt"
                ),
                "`hello2.txt` exists",
            );
        }
        assert_eq!(
            fs.rename(path!("/bar/hello2.txt"), path!("/foo/world2.txt"))
                .await,
            Ok(()),
            "renaming (and moving) a file",
        );
        assert_eq!(
            fs.rename(path!("/foo"), path!("/bar/baz")).await,
            Ok(()),
            "renaming a directory",
        );
        assert_eq!(
            fs.rename(path!("/bar/hello1.txt"), path!("/bar/world1.txt"))
                .await,
            Ok(()),
            "renaming a file (in the same directory)",
        );
        {
            let fs_inner = fs.inner.read().unwrap();
            dbg!(&fs_inner);
            assert_eq!(
                fs_inner.storage.len(),
                6,
                "storage has still all directories"
            );
            assert!(
                matches!(
                    fs_inner.storage.get(ROOT_INODE),
                    Some(Node::Directory(DirectoryNode {
                        inode: ROOT_INODE,
                        name,
                        children,
                        ..
                    })) if name == "/" && children == &[3]
                ),
                "`/` contains `bar`",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(1),
                    Some(Node::Directory(DirectoryNode {
                        inode: 1,
                        name,
                        children,
                        ..
                    })) if name == "baz" && children == &[2, 5]
                ),
                "`foo` has been renamed to `baz` and contains `qux` and `world2.txt`",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(2),
                    Some(Node::Directory(DirectoryNode {
                        inode: 2,
                        name,
                        children,
                        ..
                    })) if name == "qux" && children.is_empty()
                ),
                "`qux` is empty",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(3),
                    Some(Node::Directory(DirectoryNode {
                        inode: 3,
                        name,
                        children,
                        ..
                    })) if name == "bar" && children == &[4, 1]
                ),
                "`bar` contains `bar` (ex `foo`)  and `world1.txt` (ex `hello1`)",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(4),
                    Some(Node::File(FileNode {
                        inode: 4,
                        name,
                        ..
                    })) if name == "world1.txt"
                ),
                "`hello1.txt` has been renamed to `world1.txt`",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(5),
                    Some(Node::File(FileNode {
                        inode: 5,
                        name,
                        ..
                    })) if name == "world2.txt"
                ),
                "`hello2.txt` has been renamed to `world2.txt`",
            );
        }
    }
    #[tokio::test]
    async fn test_metadata() {
        use std::thread::sleep;
        use std::time::Duration;
        let fs = FileSystem::default();
        let root_metadata = fs.metadata(path!("/"));
        assert!(matches!(
            root_metadata,
            Ok(Metadata {
                ft: FileType { dir: true, .. },
                accessed,
                created,
                modified,
                len: 0
            }) if accessed == created && created == modified && modified > 0
        ));
        assert_eq!(fs.create_dir(path!("/foo")), Ok(()));
        let foo_metadata = fs.metadata(path!("/foo"));
        assert!(foo_metadata.is_ok());
        let foo_metadata = foo_metadata.unwrap();
        assert!(matches!(
            foo_metadata,
            Metadata {
                ft: FileType { dir: true, .. },
                accessed,
                created,
                modified,
                len: 0
            } if accessed == created && created == modified && modified > 0
        ));
        sleep(Duration::from_secs(3));
        assert_eq!(fs.rename(path!("/foo"), path!("/bar")).await, Ok(()));
        assert!(
            matches!(
                fs.metadata(path!("/bar")),
                Ok(Metadata {
                    ft: FileType { dir: true, .. },
                    accessed,
                    created,
                    modified,
                    len: 0
                }) if
                    accessed == foo_metadata.accessed &&
                    created == foo_metadata.created &&
                    modified > foo_metadata.modified
            ),
            "the modified time is updated when file is renamed",
        );
        assert!(
            matches!(
                fs.metadata(path!("/")),
                Ok(Metadata {
                    ft: FileType { dir: true, .. },
                    accessed,
                    created,
                    modified,
                    len: 0
                }) if
                    accessed == foo_metadata.accessed &&
                    created == foo_metadata.created &&
                    modified > foo_metadata.modified
            ),
            "the modified time of the parent is updated when file is renamed",
        );
    }
    #[tokio::test]
    async fn test_remove_file() {
        let fs = FileSystem::default();
        assert!(
            matches!(
                fs.new_open_options()
                    .write(true)
                    .create_new(true)
                    .open(path!("/foo.txt")),
                Ok(_)
            ),
            "creating a new file",
        );
        {
            let fs_inner = fs.inner.read().unwrap();
            assert_eq!(fs_inner.storage.len(), 2, "storage has all files");
            assert!(
                matches!(
                    fs_inner.storage.get(ROOT_INODE),
                    Some(Node::Directory(DirectoryNode {
                        inode: ROOT_INODE,
                        name,
                        children,
                        ..
                    })) if name == "/" && children == &[1]
                ),
                "`/` contains `foo.txt`",
            );
            assert!(
                matches!(
                    fs_inner.storage.get(1),
                    Some(Node::File(FileNode {
                        inode: 1,
                        name,
                        ..
                    })) if name == "foo.txt"
                ),
                "`foo.txt` exists and is a file",
            );
        }
        assert_eq!(
            fs.remove_file(path!("/foo.txt")),
            Ok(()),
            "removing a file that exists",
        );
        {
            let fs_inner = fs.inner.read().unwrap();
            assert_eq!(fs_inner.storage.len(), 1, "storage no longer has the file");
            assert!(
                matches!(
                    fs_inner.storage.get(ROOT_INODE),
                    Some(Node::Directory(DirectoryNode {
                        inode: ROOT_INODE,
                        name,
                        children,
                        ..
                    })) if name == "/" && children.is_empty()
                ),
                "`/` is empty",
            );
        }
        assert_eq!(
            fs.remove_file(path!("/foo.txt")),
            Err(FsError::EntryNotFound),
            "removing a file that exists",
        );
    }
    #[tokio::test]
    async fn test_readdir() {
        let fs = FileSystem::default();
        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating `foo`");
        assert_eq!(fs.create_dir(path!("/foo/sub")), Ok(()), "creating `sub`");
        assert_eq!(fs.create_dir(path!("/bar")), Ok(()), "creating `bar`");
        assert_eq!(fs.create_dir(path!("/baz")), Ok(()), "creating `bar`");
        assert!(
            matches!(
                fs.new_open_options()
                    .write(true)
                    .create_new(true)
                    .open(path!("/a.txt")),
                Ok(_)
            ),
            "creating `a.txt`",
        );
        assert!(
            matches!(
                fs.new_open_options()
                    .write(true)
                    .create_new(true)
                    .open(path!("/b.txt")),
                Ok(_)
            ),
            "creating `b.txt`",
        );
        let readdir = fs.read_dir(path!("/"));
        assert!(readdir.is_ok(), "reading the directory `/`");
        let mut readdir = readdir.unwrap();
        assert!(
            matches!(
                readdir.next(),
                Some(Ok(DirEntry {
                    path,
                    metadata: Ok(Metadata { ft, .. }),
                }))
                    if path == path!(buf "/foo") && ft.is_dir()
            ),
            "checking entry #1",
        );
        assert!(
            matches!(
                readdir.next(),
                Some(Ok(DirEntry {
                    path,
                    metadata: Ok(Metadata { ft, .. }),
                }))
                    if path == path!(buf "/bar") && ft.is_dir()
            ),
            "checking entry #2",
        );
        assert!(
            matches!(
                readdir.next(),
                Some(Ok(DirEntry {
                    path,
                    metadata: Ok(Metadata { ft, .. }),
                }))
                    if path == path!(buf "/baz") && ft.is_dir()
            ),
            "checking entry #3",
        );
        assert!(
            matches!(
                readdir.next(),
                Some(Ok(DirEntry {
                    path,
                    metadata: Ok(Metadata { ft, .. }),
                }))
                    if path == path!(buf "/a.txt") && ft.is_file()
            ),
            "checking entry #4",
        );
        assert!(
            matches!(
                readdir.next(),
                Some(Ok(DirEntry {
                    path,
                    metadata: Ok(Metadata { ft, .. }),
                }))
                    if path == path!(buf "/b.txt") && ft.is_file()
            ),
            "checking entry #5",
        );
        assert!(matches!(readdir.next(), None), "no more entries");
    }
    #[tokio::test]
    async fn test_canonicalize() {
        let fs = FileSystem::default();
        assert_eq!(fs.create_dir(path!("/foo")), Ok(()), "creating `foo`");
        assert_eq!(fs.create_dir(path!("/foo/bar")), Ok(()), "creating `bar`");
        assert_eq!(
            fs.create_dir(path!("/foo/bar/baz")),
            Ok(()),
            "creating `baz`",
        );
        assert_eq!(
            fs.create_dir(path!("/foo/bar/baz/qux")),
            Ok(()),
            "creating `qux`",
        );
        assert!(
            matches!(
                fs.new_open_options()
                    .write(true)
                    .create_new(true)
                    .open(path!("/foo/bar/baz/qux/hello.txt")),
                Ok(_)
            ),
            "creating `hello.txt`",
        );
        let fs_inner = fs.inner.read().unwrap();
        assert_eq!(
            fs_inner
                .canonicalize(path!("/"))
                .map(|(a, b)| (a, b.unwrap())),
            Ok((path!(buf "/"), ROOT_INODE)),
            "canonicalizing `/`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!("foo"))
                .map(|(a, b)| (a, b.unwrap())),
            Err(FsError::InvalidInput),
            "canonicalizing `foo`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!("/././././foo/"))
                .map(|(a, b)| (a, b.unwrap())),
            Ok((path!(buf "/foo"), 1)),
            "canonicalizing `/././././foo/`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!("/foo/bar//"))
                .map(|(a, b)| (a, b.unwrap())),
            Ok((path!(buf "/foo/bar"), 2)),
            "canonicalizing `/foo/bar//`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!("/foo/bar/../bar"))
                .map(|(a, b)| (a, b.unwrap())),
            Ok((path!(buf "/foo/bar"), 2)),
            "canonicalizing `/foo/bar/../bar`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!("/foo/bar/../.."))
                .map(|(a, b)| (a, b.unwrap())),
            Ok((path!(buf "/"), ROOT_INODE)),
            "canonicalizing `/foo/bar/../..`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!("/foo/bar/../../.."))
                .map(|(a, b)| (a, b.unwrap())),
            Err(FsError::InvalidInput),
            "canonicalizing `/foo/bar/../../..`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!("C:/foo/"))
                .map(|(a, b)| (a, b.unwrap())),
            Err(FsError::InvalidInput),
            "canonicalizing `C:/foo/`",
        );
        assert_eq!(
            fs_inner
                .canonicalize(path!(
                    "/foo/./../foo/bar/../../foo/bar/./baz/./../baz/qux/../../baz/./qux/hello.txt"
                ))
                .map(|(a, b)| (a, b.unwrap())),
            Ok((path!(buf "/foo/bar/baz/qux/hello.txt"), 5)),
            "canonicalizing a crazily stupid path name",
        );
    }
    #[tokio::test]
    #[ignore = "Not yet supported. See https://github.com/wasmerio/wasmer/issues/3678"]
    async fn mount_to_overlapping_directories() {
        let top_level = FileSystem::default();
        ops::touch(&top_level, "/file.txt").unwrap();
        let nested = FileSystem::default();
        ops::touch(&nested, "/another-file.txt").unwrap();
        let top_level: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(top_level);
        let nested: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(nested);
        let fs = FileSystem::default();
        fs.mount("/top-level".into(), &top_level, "/".into())
            .unwrap();
        fs.mount("/top-level/nested".into(), &nested, "/".into())
            .unwrap();
        assert!(ops::is_dir(&fs, "/top-level"));
        assert!(ops::is_file(&fs, "/top-level/file.txt"));
        assert!(ops::is_dir(&fs, "/top-level/nested"));
        assert!(ops::is_file(&fs, "/top-level/nested/another-file.txt"));
    }
    #[tokio::test]
    async fn test_merge_flat() {
        let main = FileSystem::default();
        let other = FileSystem::default();
        crate::ops::create_dir_all(&other, "/a/x").unwrap();
        other
            .insert_ro_file(&Path::new("/a/x/a.txt"), Cow::Borrowed(b"a"))
            .unwrap();
        other
            .insert_ro_file(&Path::new("/a/x/b.txt"), Cow::Borrowed(b"b"))
            .unwrap();
        other
            .insert_ro_file(&Path::new("/a/x/c.txt"), Cow::Borrowed(b"c"))
            .unwrap();
        let out = other.read_dir(&Path::new("/")).unwrap();
        dbg!(&out);
        let other: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(other);
        main.mount_directory_entries(&Path::new("/"), &other, &Path::new("/a"))
            .unwrap();
        let mut buf = Vec::new();
        let mut f = main
            .new_open_options()
            .read(true)
            .open(&Path::new("/x/a.txt"))
            .unwrap();
        f.read_to_end(&mut buf).await.unwrap();
        assert_eq!(buf, b"a");
    }
}