//! Pseudo-devices
use core::task::{Context, Poll};

use abi::{error::Error, io::FileMode};
use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use device_api::device::Device;
use libk::{
    device::char::CharDevice,
    fs::devfs,
    random,
    vfs::{impls::read_fn_node, FileReadiness, OwnedFilename},
};

struct Null;
struct Zero;

impl FileReadiness for Null {
    fn poll_read(&self, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        Poll::Ready(Ok(()))
    }
}

#[async_trait]
impl CharDevice for Null {
    async fn read(&self, _data: &mut [u8]) -> Result<usize, Error> {
        Ok(0)
    }

    async fn write(&self, data: &[u8]) -> Result<usize, Error> {
        Ok(data.len())
    }

    fn read_nonblocking(&self, _data: &mut [u8]) -> Result<usize, Error> {
        Ok(0)
    }

    fn write_nonblocking(&self, data: &[u8]) -> Result<usize, Error> {
        Ok(data.len())
    }
}

impl Device for Null {
    fn display_name(&self) -> &str {
        "/dev/null"
    }
}

impl FileReadiness for Zero {
    fn poll_read(&self, _cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
        Poll::Ready(Ok(()))
    }
}

#[async_trait]
impl CharDevice for Zero {
    async fn read(&self, data: &mut [u8]) -> Result<usize, Error> {
        data.fill(0);
        Ok(data.len())
    }

    async fn write(&self, _data: &[u8]) -> Result<usize, Error> {
        Ok(0)
    }

    fn read_nonblocking(&self, data: &mut [u8]) -> Result<usize, Error> {
        data.fill(0);
        Ok(data.len())
    }

    fn write_nonblocking(&self, _data: &[u8]) -> Result<usize, Error> {
        Ok(0)
    }
}

impl Device for Zero {
    fn display_name(&self) -> &str {
        "/dev/zero"
    }
}

/// Adds "pseudo"-devices to the filesystem (i.e. /dev/random)
pub fn add_pseudo_devices() -> Result<(), Error> {
    let urandom = read_fn_node(move |_, buf| {
        random::read_unsecure(buf);
        Ok(buf.len())
    });

    let root = devfs::root();
    root.add_child(OwnedFilename::new("urandom").unwrap(), urandom)?;

    devfs::add_named_char_device(Arc::new(Null), "null", FileMode::new(0o666))?;
    devfs::add_named_char_device(Arc::new(Zero), "zero", FileMode::new(0o666))?;

    Ok(())
}
