//! Device virtual file system

use alloc::sync::Arc;
use libk_util::OneTimeInit;
use yggdrasil_abi::{error::Error, io::FileMode};

use crate::{
    device::{block::BlockDevice, char::CharDevice},
    vfs::{
        impls::{fixed_path_symlink, MemoryDirectory},
        path::OwnedFilename,
        AccessToken, Filename, Metadata, Node, NodeFlags, NodeRef,
    },
};

static DEVFS_ROOT: OneTimeInit<NodeRef> = OneTimeInit::new();

/// Sets up the device filesystem
pub fn init() {
    let root = MemoryDirectory::empty(FileMode::new(0o555));
    DEVFS_ROOT.init(root);
}

/// Returns the root of the devfs.
///
/// # Panics
///
/// Will panic if the devfs hasn't yet been initialized.
pub fn root() -> &'static NodeRef {
    DEVFS_ROOT.get()
}

pub fn redirect<S: AsRef<str>>(name: S, destination: &str) -> Result<(), Error> {
    let name = name.as_ref();
    log::info!("Redirect {name} -> {destination}");

    let root = DEVFS_ROOT.get();
    let filename = OwnedFilename::new(name)?;
    root.remove_file(filename.as_ref(), unsafe { AccessToken::authorized() })
        .ok();
    root.add_child(filename, fixed_path_symlink(destination))
}

pub fn remove_node<S: AsRef<str>>(name: S) -> Result<(), Error> {
    let name = name.as_ref();
    let root = DEVFS_ROOT.get();
    let filename = Filename::new(name)?;
    root.remove_file(filename, unsafe { AccessToken::authorized() })
}

/// Adds a character device with a custom name
pub fn add_named_char_device<S: AsRef<str>>(
    dev: Arc<dyn CharDevice>,
    name: S,
    mode: FileMode,
) -> Result<(), Error> {
    let name = name.as_ref();
    log::info!("Add char device: {}", name);

    let node = Node::char(
        dev,
        NodeFlags::IN_MEMORY_PROPS,
        Metadata::now_root(mode, 0),
        None,
    );

    let filename = OwnedFilename::new(name)?;
    DEVFS_ROOT.get().add_child(filename, node)
}

/// Adds a block device with a custom name
pub fn add_named_block_device<S: AsRef<str>>(
    dev: Arc<dyn BlockDevice>,
    name: S,
    mode: FileMode,
) -> Result<NodeRef, Error> {
    let name = name.as_ref();
    log::info!("Add block device: {}", name);

    let node = Node::block(
        dev,
        NodeFlags::IN_MEMORY_PROPS,
        Metadata::now_root(mode, 0),
        None,
    );

    let filename = OwnedFilename::new(name)?;
    DEVFS_ROOT.get().add_child(filename, node.clone())?;

    Ok(node)
}
