use core::{
    any::Any,
    fmt,
    mem::MaybeUninit,
    task::{Context, Poll},
};

use alloc::{
    boxed::Box,
    collections::{btree_map::Entry, BTreeMap},
    sync::Arc,
};
use async_trait::async_trait;
use device::{BlockFile, CharFile};
use libk_mm::{
    address::PhysicalAddress, table::MapAttributes, OnDemandPage, PageProvider, VirtualPage,
};
use libk_util::{
    io::{ReadAt, WriteAt},
    sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
};
use yggdrasil_abi::{
    error::Error,
    io::{
        options::{self, FileOptionVariant},
        DirectoryEntry, FileMode, OpenOptions, RawFd, SeekFrom, TerminalOptions, TerminalSize,
        TimerOptions,
    },
    net::{MessageHeader, MessageHeaderMut},
    option::{OptionValue, RequestValue},
    process::{ProcessGroupId, ProcessWait, WaitFlags},
};

use crate::{
    device::{block::BlockDeviceFile, char::CharDeviceFile},
    task::process::Process,
    vfs::{
        node::NodeRef,
        traits::{Read, Seek, Write},
        FdPoll, FileReadiness, Node, SharedMemory, TimerFile,
    },
};

use self::{directory::DirectoryFile, pipe::PipeEnd, regular::RegularFile};

use super::{
    pid::PidFile,
    pty::{self, PseudoTerminalMaster, PseudoTerminalSlave},
    socket::SocketWrapper,
    Metadata,
};

mod device;
mod directory;
mod pipe;
mod regular;

/// Per-file optional instance data created when a regular file is opened
pub type InstanceData = Arc<dyn Any + Send + Sync>;

/// Describes the starting position of the directory
pub enum DirectoryOpenPosition {
    /// Contents should be fetched from the directory impl with given offset
    FromPhysical(u64),
    /// Contents should be fetched from the tree cache
    FromCache,
}

/// Wrapper type for a [File] shared reference
pub type FileRef = Arc<File>;

#[derive(Clone)]
struct FileOptions {
    non_blocking: bool,
}

pub struct File {
    options: IrqSafeRwLock<FileOptions>,
    inner: FileInner,
}

enum FileInner {
    Directory(DirectoryFile),
    Regular(RegularFile),
    Block(BlockFile),
    Char(CharFile),
    Socket(SocketWrapper),
    AnonymousPipe(PipeEnd),
    Poll(Arc<FdPoll>),
    Timer(TimerFile),
    SharedMemory(Arc<SharedMemory>),
    PtySlave(TerminalHalfWrapper<PseudoTerminalSlave>),
    PtyMaster(TerminalHalfWrapper<PseudoTerminalMaster>),
    Pid(PidFile),
}

#[async_trait]
pub trait TerminalHalf {
    async fn read(&self, buf: &mut [u8]) -> Result<usize, Error>;
    fn read_nonblocking(&self, buf: &mut [u8]) -> Result<usize, Error>;
    fn write(&self, buf: &[u8]) -> Result<usize, Error>;
    fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>>;
    fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error>;
}

#[derive(Clone)]
pub struct TerminalHalfWrapper<T: TerminalHalf> {
    half: Arc<T>,
    node: NodeRef,
}

/// Contains a per-process fd -> FileRef map
pub struct FileSet {
    map: BTreeMap<RawFd, FileRef>,
}

impl File {
    fn from_inner(inner: FileInner) -> Arc<Self> {
        Arc::new(Self {
            options: IrqSafeRwLock::new(FileOptions {
                non_blocking: false,
            }),
            inner,
        })
    }

    /// Constructs a pipe pair, returning its `(read, write)` ends
    pub fn new_pipe_pair(capacity: usize) -> (Arc<Self>, Arc<Self>) {
        let (read, write) = PipeEnd::new_pair(capacity);
        let read = FileInner::AnonymousPipe(read);
        let write = FileInner::AnonymousPipe(write);
        (Self::from_inner(read), Self::from_inner(write))
    }

    /// Constructs a new poll channel file
    pub fn new_poll_channel() -> Arc<Self> {
        Self::from_inner(FileInner::Poll(Arc::new(FdPoll::new())))
    }

    /// Creates a buffer of shared memory and associates a [File] with it
    pub fn new_shared_memory(size: usize) -> Result<Arc<Self>, Error> {
        let shm = SharedMemory::new(size)?;
        Ok(Self::from_inner(FileInner::SharedMemory(Arc::new(shm))))
    }

    /// Creates a pair of PTY master/slave
    pub fn new_pseudo_terminal(
        config: TerminalOptions,
        size: TerminalSize,
    ) -> Result<(Arc<Self>, Arc<Self>), Error> {
        let (master, slave) = pty::create(config, size)?;
        let master = Arc::new(master);
        let slave = Arc::new(slave);
        let (master_node, slave_node) = Node::pseudo_terminal_nodes(
            &master,
            &slave,
            Metadata::now_root(FileMode::new(0o644), 0),
        );

        let master = Self::from_inner(FileInner::PtyMaster(TerminalHalfWrapper {
            half: master,
            node: master_node,
        }));
        let slave = Self::from_inner(FileInner::PtySlave(TerminalHalfWrapper {
            half: slave,
            node: slave_node,
        }));
        Ok((master, slave))
    }

    pub fn open_pty_master(pty: Arc<PseudoTerminalMaster>, node: Arc<Node>) -> Arc<Self> {
        Self::from_inner(FileInner::PtyMaster(TerminalHalfWrapper {
            half: pty,
            node,
        }))
    }

    pub fn open_pty_slave(pty: Arc<PseudoTerminalSlave>, node: Arc<Node>) -> Arc<Self> {
        Self::from_inner(FileInner::PtySlave(TerminalHalfWrapper { half: pty, node }))
    }

    /// Creates a new [TimerFile]-backed File
    pub fn new_timer(options: TimerOptions) -> FileRef {
        let repeat = options.contains(TimerOptions::REPEAT);
        let timer = TimerFile::new(repeat);
        Self::from_inner(FileInner::Timer(timer))
    }

    /// Creates a new [PidFile]-backed file
    pub fn new_pid(
        parent: Arc<Process>,
        wait: &ProcessWait,
        flags: WaitFlags,
    ) -> Result<FileRef, Error> {
        PidFile::new(parent, wait, flags)
            .map(FileInner::Pid)
            .map(Self::from_inner)
    }

    /// Constructs a [File] from a [PacketSocket], [ConnectionSocket] or a [ListenerSocket].
    pub fn from_socket<S: Into<SocketWrapper>>(socket: S) -> Arc<Self> {
        Self::from_inner(FileInner::Socket(socket.into()))
    }

    pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
        let position = IrqSafeSpinlock::new(position.into());
        Self::from_inner(FileInner::Directory(DirectoryFile { node, position }))
    }

    pub(crate) fn regular(
        node: NodeRef,
        position: u64,
        instance_data: Option<InstanceData>,
        opts: OpenOptions,
    ) -> Arc<Self> {
        let read = opts.contains(OpenOptions::READ);
        let write = opts.contains(OpenOptions::WRITE);

        Self::from_inner(FileInner::Regular(RegularFile {
            node,
            read,
            write,
            instance_data,
            position: IrqSafeSpinlock::new(position),
        }))
    }

    pub(crate) fn block(
        device: BlockDeviceFile,
        node: NodeRef,
        opts: OpenOptions,
    ) -> Result<Arc<Self>, Error> {
        let read = opts.contains(OpenOptions::READ);
        let write = opts.contains(OpenOptions::WRITE);

        if read && !device.is_readable() {
            return Err(Error::InvalidOperation);
        }
        if write && !device.is_writeable() {
            return Err(Error::ReadOnly);
        }

        Ok(Self::from_inner(FileInner::Block(BlockFile {
            device,
            node,
            position: IrqSafeSpinlock::new(0),
            read,
            write,
        })))
    }

    pub(crate) fn char(
        device: CharDeviceFile,
        node: NodeRef,
        opts: OpenOptions,
    ) -> Result<Arc<Self>, Error> {
        let read = opts.contains(OpenOptions::READ);
        let write = opts.contains(OpenOptions::WRITE);

        if read && !device.is_readable() {
            return Err(Error::InvalidOperation);
        }
        if write && !device.is_writeable() {
            return Err(Error::ReadOnly);
        }

        Ok(Self::from_inner(FileInner::Char(CharFile {
            device,
            node,
            read,
            write,
        })))
    }

    pub fn is_non_blocking(&self) -> bool {
        self.options.read().non_blocking
    }

    /// Clones an open file for sending it to another process
    pub fn send(self: &Arc<Self>) -> Result<Arc<Self>, Error> {
        let options = self.options.read().clone();
        Ok(Arc::new(Self {
            inner: self.inner.send()?,
            options: IrqSafeRwLock::new(options),
        }))
    }

    pub fn set_option(&self, option: u32, buffer: &[u8]) -> Result<(), Error> {
        let option = FileOptionVariant::try_from(option)?;
        let mut options = self.options.write();
        match option {
            FileOptionVariant::NonBlocking => {
                options.non_blocking = options::NonBlocking::load(buffer)?;
                Ok(())
            }
        }
    }

    pub fn get_option(&self, option: u32, buffer: &mut [u8]) -> Result<usize, Error> {
        let option = FileOptionVariant::try_from(option)?;
        let options = self.options.read();
        match option {
            FileOptionVariant::NonBlocking => {
                options::NonBlocking::store(&options.non_blocking, buffer)
            }
        }
    }

    /// Reads entries from the directory
    pub fn read_dir(&self, entries: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize, Error> {
        match &self.inner {
            FileInner::Directory(dir) => dir.read_entries(entries),
            _ => Err(Error::NotADirectory),
        }
    }

    /// Returns the underlying [Node] the file contains
    pub fn node(&self) -> Option<&NodeRef> {
        match &self.inner {
            FileInner::Directory(file) => Some(&file.node),
            FileInner::Regular(file) => Some(&file.node),
            FileInner::Block(file) => Some(&file.node),
            FileInner::Char(file) => Some(&file.node),
            FileInner::PtyMaster(half) => Some(&half.node),
            FileInner::PtySlave(half) => Some(&half.node),
            _ => None,
        }
    }

    /// Polls a file for "read-readiness"
    pub fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        match &self.inner {
            FileInner::Char(f) => f.device.poll_read(cx),
            FileInner::Poll(ch) => ch.poll_read(cx),
            FileInner::PtyMaster(half) => half.half.poll_read(cx),
            FileInner::PtySlave(half) => half.half.poll_read(cx),
            FileInner::Socket(socket) => socket.poll_read(cx),
            FileInner::Timer(timer) => timer.poll_read(cx),
            FileInner::Pid(pid) => pid.poll_read(cx),
            FileInner::AnonymousPipe(pipe) => pipe.poll_read(cx),
            // Polling not implemented, return ready immediately (XXX ?)
            _ => Poll::Ready(Err(Error::NotImplemented)),
        }
    }

    /// Performs a device-specific request
    pub fn device_request(
        &self,
        option: u32,
        buffer: &mut [u8],
        len: usize,
    ) -> Result<usize, Error> {
        match &self.inner {
            FileInner::Char(f) => f.device.device_request(option, buffer, len),
            FileInner::Block(f) => f.device.device_request(option, buffer, len),
            FileInner::PtySlave(half) => half.half.device_request(option, buffer, len),
            FileInner::PtyMaster(half) => half.half.device_request(option, buffer, len),
            _ => {
                log::warn!("device_request {option:#x}: not a device");
                Err(Error::InvalidOperation)
            }
        }
    }

    /// Resizes this file to match the requested `size`
    pub fn truncate(&self, size: u64) -> Result<(), Error> {
        match &self.inner {
            FileInner::Regular(file) => file.truncate(size),
            _ => {
                log::error!("TODO: truncate operation on unsupported file type");
                Err(Error::InvalidOperation)
            }
        }
    }

    /// Interprets the file as a poll channel
    pub fn as_poll_channel(&self) -> Result<&FdPoll, Error> {
        if let FileInner::Poll(poll) = &self.inner {
            Ok(poll)
        } else {
            Err(Error::InvalidOperation)
        }
    }

    /// Interprets the file as a socket
    pub fn as_socket(&self) -> Result<&SocketWrapper, Error> {
        match &self.inner {
            FileInner::Socket(socket) => Ok(socket),
            _ => Err(Error::InvalidOperation),
        }
    }

    pub fn is_terminal(&self) -> bool {
        match &self.inner {
            FileInner::Char(dev) => dev.is_terminal(),
            FileInner::PtySlave(_) | FileInner::PtyMaster(_) => true,
            _ => false,
        }
    }

    pub fn set_terminal_group(&self, group_id: ProcessGroupId) -> Result<(), Error> {
        use yggdrasil_abi::io::device as abi_device;

        let mut buffer = [0; 8];
        let len = abi_device::SetTerminalGroup::store_request(&group_id, &mut buffer)?;
        self.device_request(
            abi_device::SetTerminalGroup::VARIANT.into(),
            &mut buffer,
            len,
        )?;

        Ok(())
    }

    pub fn as_page_provider(&self) -> Result<&dyn PageProvider, Error> {
        match &self.inner {
            FileInner::Regular(f) => Ok(f),
            FileInner::Block(f) => Ok(f.device.0.as_ref()),
            FileInner::SharedMemory(f) => Ok(f.as_ref()),
            _ => todo!(),
        }
    }
}

impl PageProvider for File {
    fn ondemand_fetch(&self, opaque: u64) -> Result<OnDemandPage, Error> {
        self.as_page_provider()?.ondemand_fetch(opaque)
    }

    fn get_page(&self, offset: u64) -> Result<VirtualPage, Error> {
        self.as_page_provider()?.get_page(offset)
    }

    fn release_page(&self, offset: u64, phys: PhysicalAddress, dirty: bool) -> Result<(), Error> {
        self.as_page_provider()?.release_page(offset, phys, dirty)
    }

    fn clone_page(
        &self,
        offset: u64,
        src_phys: PhysicalAddress,
        src_attrs: MapAttributes,
    ) -> Result<PhysicalAddress, Error> {
        self.as_page_provider()?
            .clone_page(offset, src_phys, src_attrs)
    }
}

impl Read for File {
    fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
        let non_blocking = self.options.read().non_blocking;
        match &self.inner {
            FileInner::Regular(file) => file.read(buf),
            FileInner::Block(file) => file.read(buf),
            FileInner::Char(file) => file.read(buf, non_blocking),
            FileInner::AnonymousPipe(pipe) => pipe.read(buf, non_blocking),
            FileInner::PtySlave(half) => half.read(buf, non_blocking),
            FileInner::PtyMaster(half) => half.read(buf, non_blocking),
            FileInner::Timer(timer) => timer.read(buf, non_blocking),
            FileInner::Pid(pid) => pid.read(buf, non_blocking),
            // TODO allow reads from sockets
            FileInner::Socket(socket) => {
                let mut header = MessageHeaderMut {
                    source: None,
                    ancillary: None,
                    ancillary_len: 0,
                    payload: buf,
                };
                socket.receive_message(&mut header, non_blocking)
            }
            FileInner::Directory(_) | FileInner::Poll(_) | FileInner::SharedMemory(_) => {
                Err(Error::InvalidOperation)
            }
        }
    }
}

impl Write for File {
    fn write(&self, buf: &[u8]) -> Result<usize, Error> {
        let non_blocking = self.options.read().non_blocking;
        match &self.inner {
            FileInner::Regular(file) => file.write(buf),
            FileInner::Block(file) => file.write(buf),
            FileInner::Char(file) => file.write(buf, non_blocking),
            FileInner::AnonymousPipe(pipe) => pipe.write(buf),
            FileInner::PtySlave(half) => half.write(buf),
            FileInner::PtyMaster(half) => half.write(buf),
            FileInner::Timer(timer) => timer.write(buf),
            FileInner::Socket(socket) => {
                let header = MessageHeader {
                    destination: None,
                    payload: buf,
                    ancillary: &[],
                };
                socket.send_message(&header, non_blocking)
            }
            FileInner::Pid(_)
            | FileInner::Poll(_)
            | FileInner::SharedMemory(_)
            | FileInner::Directory(_) => Err(Error::InvalidOperation),
        }
    }
}

impl ReadAt for File {
    fn read_at(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
        // let non_blocking = self.options.read().non_blocking;
        match &self.inner {
            FileInner::Regular(file) => file.read_at(pos, buf),
            _ => todo!(),
        }
    }
}

impl WriteAt for File {
    fn write_at(&self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
        // let non_blocking = self.options.read().non_blocking;
        match &self.inner {
            FileInner::Regular(file) => file.write_at(pos, buf),
            _ => todo!(),
        }
    }
}

impl Seek for File {
    fn tell(&self) -> Result<u64, Error> {
        match &self.inner {
            FileInner::Regular(file) => Ok(*file.position.lock()),
            FileInner::Block(file) => Ok(*file.position.lock()),
            FileInner::Directory(_) => Err(Error::IsADirectory),
            _ => Ok(0),
        }
    }

    fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
        if from == SeekFrom::Current(0) {
            return self.tell();
        }

        match &self.inner {
            FileInner::Regular(file) => file.seek(from),
            FileInner::Block(file) => file.seek(from),
            FileInner::Directory(_) => Err(Error::IsADirectory),
            _ => Err(Error::InvalidOperation),
        }
    }
}

impl fmt::Debug for File {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.inner {
            FileInner::Regular(file) => f
                .debug_struct("RegularFile")
                .field("position", &*file.position.lock())
                .field("read", &file.read)
                .field("write", &file.write)
                .finish_non_exhaustive(),
            FileInner::Block(file) => f
                .debug_struct("BlockFile")
                .field("position", &*file.position.lock())
                .field("read", &file.read)
                .field("write", &file.write)
                .finish_non_exhaustive(),
            FileInner::Char(file) => f
                .debug_struct("CharFile")
                .field("read", &file.read)
                .field("write", &file.write)
                .finish_non_exhaustive(),
            FileInner::Directory(_) => f.debug_struct("DirectoryFile").finish_non_exhaustive(),
            FileInner::AnonymousPipe(_) => f.debug_struct("AnonymousPipe").finish_non_exhaustive(),
            FileInner::Poll(_) => f.debug_struct("Poll").finish_non_exhaustive(),
            FileInner::SharedMemory(_) => f.debug_struct("SharedMemory").finish_non_exhaustive(),
            FileInner::PtySlave(_) => f.debug_struct("PtySlave").finish_non_exhaustive(),
            FileInner::PtyMaster(_) => f.debug_struct("PtyMaster").finish_non_exhaustive(),
            FileInner::Socket(socket) => fmt::Debug::fmt(socket, f),
            FileInner::Pid(pid) => fmt::Debug::fmt(pid, f),
            FileInner::Timer(_) => f.debug_struct("Timer").finish_non_exhaustive(),
        }
    }
}

impl FileInner {
    pub fn send(&self) -> Result<Self, Error> {
        match self {
            Self::Char(char) => Ok(Self::Char(char.clone())),
            Self::Regular(file) => Ok(Self::Regular(file.clone())),
            Self::SharedMemory(shm) => Ok(Self::SharedMemory(shm.clone())),
            Self::Pid(pid) => Ok(Self::Pid(pid.clone())),
            Self::PtySlave(half) => Ok(Self::PtySlave(half.clone())),
            Self::PtyMaster(half) => Ok(Self::PtyMaster(half.clone())),
            _ => todo!(),
        }
    }
}

impl<T: TerminalHalf> TerminalHalfWrapper<T> {
    pub fn read(&self, buf: &mut [u8], non_blocking: bool) -> Result<usize, Error> {
        if non_blocking {
            self.half.read_nonblocking(buf)
        } else {
            block!(self.half.read(buf).await)?
        }
    }

    pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
        self.half.write(buf)
    }
}

impl FileSet {
    const MAX_FILES: usize = 256;

    /// Creates an empty [FileSet]
    pub fn new() -> Self {
        Self {
            map: BTreeMap::new(),
        }
    }

    /// Returns the [FileRef] associated with given `fd` or an error if it does not exist
    pub fn file(&self, fd: RawFd) -> Result<&FileRef, Error> {
        self.map.get(&fd).ok_or(Error::InvalidFile)
    }

    /// Associates a `file` with `fd`, returning an error if requested `fd` is already taken
    pub fn set_file(&mut self, fd: RawFd, file: FileRef) -> Result<(), Error> {
        if self.map.contains_key(&fd) {
            return Err(Error::AlreadyExists);
        }
        self.map.insert(fd, file);
        Ok(())
    }

    /// Associates a `file` with any available [RawFd] and returns it
    pub fn place_file(&mut self, file: FileRef, skip_stdio: bool) -> Result<RawFd, Error> {
        let start = if skip_stdio { 3 } else { 0 };
        for idx in start..Self::MAX_FILES as u32 {
            let fd = RawFd::from(idx);

            if let Entry::Vacant(e) = self.map.entry(fd) {
                e.insert(file);
                return Ok(fd);
            }
        }

        // TODO OutOfFiles
        Err(Error::OutOfMemory)
    }

    /// Removes and closes a [FileRef] from the struct
    pub fn close_file(&mut self, fd: RawFd) -> Result<(), Error> {
        // Do nothing, file will be dropped and closed
        // TODO call File's close() and return its status
        let _ = self.take_file(fd)?;
        Ok(())
    }

    pub fn take_file(&mut self, fd: RawFd) -> Result<FileRef, Error> {
        self.map.remove(&fd).ok_or(Error::InvalidFile)
    }

    /// Removes all [FileRef]s from the struct which do not pass the `predicate` check
    pub fn retain<F: Fn(&RawFd, &mut FileRef) -> bool>(&mut self, predicate: F) {
        self.map.retain(predicate);
    }

    /// Returns an iterator over the file set
    pub fn iter(&self) -> impl Iterator<Item = (&RawFd, &FileRef)> {
        self.map.iter()
    }

    /// Closes all of the files
    pub fn close_all(&mut self) {
        self.map.clear();
    }
}

#[cfg(test)]
mod tests {
    use core::{
        mem::MaybeUninit,
        str::FromStr,
        task::{Context, Poll},
    };
    use std::sync::{Arc, Mutex};

    use async_trait::async_trait;
    use yggdrasil_abi::{
        error::Error,
        io::{DirectoryEntry, FileType, OpenOptions, SeekFrom},
        util::FixedString,
    };

    use crate::vfs::{
        file::DirectoryOpenPosition,
        impls::const_value_node,
        node::{AccessToken, CommonImpl, DirectoryImpl, Node, NodeFlags, NodeRef, RegularImpl},
        traits::{Read, Seek, Write},
        FileReadiness, Filename, InstanceData,
    };

    #[test]
    fn file_send_sync() {
        fn file_send<T: Send>(_f: &T) {}
        fn file_sync<T: Sync>(_f: &T) {}

        let node = const_value_node("1234");
        let file = node
            .open(OpenOptions::READ, AccessToken::test_authorized())
            .unwrap();

        file_send(&file);
        file_sync(&file);
    }

    #[test]
    fn physical_dir_read() {
        struct D {
            entries: Vec<(String, NodeRef)>,
        }
        struct F;

        impl CommonImpl for D {}
        impl DirectoryImpl for D {
            fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
                Ok(DirectoryOpenPosition::FromPhysical(0))
            }

            fn read_entries(
                &self,
                _node: &NodeRef,
                pos: u64,
                entries: &mut [MaybeUninit<DirectoryEntry>],
            ) -> Result<(usize, u64), Error> {
                let pos = pos as usize;
                if pos == self.entries.len() {
                    return Ok((0, pos as u64));
                }

                let count = core::cmp::min(entries.len(), self.entries.len() - pos);
                for i in 0..count {
                    let (name, node) = &self.entries[i];
                    let entry = DirectoryEntry {
                        name: FixedString::from_str(name)?,
                        ty: Some(node.ty()),
                    };

                    entries[i].write(entry);
                }

                Ok((count, (pos + count) as u64))
            }
        }

        impl CommonImpl for F {}
        impl RegularImpl for F {}

        let d = Node::directory(
            D {
                entries: Vec::from_iter([
                    (
                        "f1".to_owned(),
                        Node::regular(F, NodeFlags::empty(), None, None),
                    ),
                    (
                        "f2".to_owned(),
                        Node::regular(F, NodeFlags::empty(), None, None),
                    ),
                    (
                        "f3".to_owned(),
                        Node::regular(F, NodeFlags::empty(), None, None),
                    ),
                ]),
            },
            NodeFlags::empty(),
            None,
            None,
        );

        let f = d.open_directory(AccessToken::test_authorized()).unwrap();

        let mut entries = [MaybeUninit::uninit(); 16];
        let count = f.read_dir(&mut entries).unwrap();
        assert_eq!(count, 3);

        unsafe {
            assert_eq!(
                entries[..count].assume_init_ref(),
                &[
                    DirectoryEntry {
                        name: FixedString::from_str("f1").unwrap(),
                        ty: Some(FileType::File),
                    },
                    DirectoryEntry {
                        name: FixedString::from_str("f2").unwrap(),
                        ty: Some(FileType::File),
                    },
                    DirectoryEntry {
                        name: FixedString::from_str("f3").unwrap(),
                        ty: Some(FileType::File)
                    }
                ]
            );
        }

        let count = f.read_dir(&mut entries).unwrap();
        assert_eq!(count, 0);
    }

    #[test]
    fn cache_dir_read() {
        struct D;

        impl CommonImpl for D {}
        impl DirectoryImpl for D {
            fn open(&self, _node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
                Ok(DirectoryOpenPosition::FromCache)
            }
        }

        let d = Node::directory(D, NodeFlags::empty(), None, None);
        let child = Node::directory(D, NodeFlags::empty(), None, None);

        d.add_child(Filename::new("child1").unwrap(), child)
            .unwrap();

        let f = d.open_directory(AccessToken::test_authorized()).unwrap();

        let mut entries = [MaybeUninit::uninit(); 16];
        let count = f.read_dir(&mut entries).unwrap();
        assert_eq!(count, 3);
        unsafe {
            assert_eq!(
                entries[..count].assume_init_ref(),
                &[
                    DirectoryEntry {
                        name: FixedString::from_str(".").unwrap(),
                        ty: Some(FileType::Directory)
                    },
                    DirectoryEntry {
                        name: FixedString::from_str("..").unwrap(),
                        ty: Some(FileType::Directory)
                    },
                    DirectoryEntry {
                        name: FixedString::from_str("child1").unwrap(),
                        ty: Some(FileType::Directory)
                    }
                ]
            );
        }

        let count = f.read_dir(&mut entries).unwrap();
        assert_eq!(count, 0);
    }

    #[test]
    fn file_read_write() {
        struct F {
            data: Arc<Mutex<Vec<u8>>>,
        }

        impl CommonImpl for F {
            fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
                Ok(self.data.lock().unwrap().len() as _)
            }
        }
        impl RegularImpl for F {
            fn open(
                &self,
                _node: &NodeRef,
                _opts: OpenOptions,
            ) -> Result<(u64, Option<InstanceData>), Error> {
                Ok((0, None))
            }

            fn read(
                &self,
                _node: &NodeRef,
                _instance: Option<&InstanceData>,
                pos: u64,
                buf: &mut [u8],
            ) -> Result<usize, Error> {
                let pos = pos as usize;
                let data = self.data.lock().unwrap();
                if pos >= data.len() {
                    return Ok(0);
                }
                let count = core::cmp::min(data.len() - pos, buf.len());
                buf[..count].copy_from_slice(&data[pos..pos + count]);
                Ok(count)
            }

            fn write(
                &self,
                _node: &NodeRef,
                _instance: Option<&InstanceData>,
                pos: u64,
                buf: &[u8],
            ) -> Result<usize, Error> {
                let pos = pos as usize;
                let mut data = self.data.lock().unwrap();
                data.resize(pos + buf.len(), 0);
                data[pos..pos + buf.len()].copy_from_slice(buf);
                Ok(buf.len())
            }
        }

        let data = Arc::new(Mutex::new(vec![]));
        let node = Node::regular(F { data: data.clone() }, NodeFlags::empty(), None, None);
        let file = node
            .open(
                OpenOptions::READ | OpenOptions::WRITE,
                AccessToken::test_authorized(),
            )
            .unwrap();
        let mut buf = [0; 512];

        assert_eq!(&*data.lock().unwrap(), b"");
        assert_eq!(file.tell().unwrap(), 0);

        assert_eq!(file.write(b"Hello").unwrap(), 5);
        assert_eq!(file.tell().unwrap(), 5);
        assert_eq!(&*data.lock().unwrap(), b"Hello");

        assert_eq!(file.seek(SeekFrom::End(-2)).unwrap(), 3);
        assert_eq!(file.tell().unwrap(), 3);

        assert_eq!(file.write(b"123456").unwrap(), 6);
        assert_eq!(file.tell().unwrap(), 9);
        assert_eq!(&*data.lock().unwrap(), b"Hel123456");

        assert_eq!(file.seek(SeekFrom::Start(2)).unwrap(), 2);
        assert_eq!(file.read(&mut buf).unwrap(), 7);
        assert_eq!(file.tell().unwrap(), 9);
        assert_eq!(&buf[..7], b"l123456");
    }
}
