//! Various helper node implementations for convenience
use core::{fmt, marker::PhantomData, str::FromStr};

use alloc::{
    string::{String, ToString},
    sync::Arc,
    vec::Vec,
};

use libk_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::{
    error::Error,
    io::{FileMode, FileType, OpenOptions},
};

use crate::vfs::{path::OwnedFilename, DirectoryOpenPosition, InstanceData};

use super::{
    traits::HardlinkImpl, CommonImpl, DirectoryImpl, Metadata, Node, NodeFlags, NodeRef,
    RegularImpl, SymlinkImpl,
};

trait SliceRead {
    fn read_slice(&self, offset: usize, buf: &mut [u8]) -> usize;
}

trait SliceWrite {
    fn write_slice(&mut self, offset: usize, buf: &[u8]) -> usize;
}

trait AsInstanceData {
    fn as_instance_data(&self) -> Vec<u8>;
}

/// Closure interface for reading a single value
pub trait ValueReadFn<T> = Fn() -> Result<T, Error> + Send + Sync;
/// Closure interface for writing a single value
pub trait ValueWriteFn<T> = Fn(T) -> Result<(), Error> + Send + Sync;
/// Closure interface for reading bytes
pub trait ReadFn = Fn(u64, &mut [u8]) -> Result<usize, Error> + Send + Sync;
/// Closure interface for writing bytes
pub trait WriteFn = Fn(u64, &[u8]) -> Result<usize, Error> + Send + Sync;
/// Closure interface for reading links
pub trait ReadLinkFn = Fn() -> Result<NodeRef, Error> + Send + Sync;

impl<T: AsRef<[u8]>> SliceRead for T {
    fn read_slice(&self, pos: usize, buf: &mut [u8]) -> usize {
        let value = self.as_ref();
        if pos >= value.len() {
            return 0;
        }
        let count = core::cmp::min(value.len() - pos, buf.len());
        buf[..count].copy_from_slice(&value[pos..pos + count]);
        count
    }
}

impl SliceWrite for Vec<u8> {
    fn write_slice(&mut self, offset: usize, buf: &[u8]) -> usize {
        if offset + buf.len() > self.len() {
            self.resize(offset + buf.len(), 0);
        }
        self[offset..offset + buf.len()].copy_from_slice(buf);
        buf.len()
    }
}

impl<T: ToString> AsInstanceData for T {
    fn as_instance_data(&self) -> Vec<u8> {
        self.to_string().as_bytes().to_vec()
    }
}

enum FnNodeData {
    Read(Vec<u8>),
    Write(IrqSafeSpinlock<Vec<u8>>),
}

/// Allows read-only access to a value. The value is converted to a string representation when it's
/// read.
pub struct ReadOnlyFnValueNode<T: ToString, R: ValueReadFn<T>> {
    read: R,
    _pd: PhantomData<T>,
}

/// Allows read-write access to a value (but not both at the same time). The value is converted
/// to/from a string representation when it's read/written.
pub struct FnValueNode<T: ToString + FromStr, R: ValueReadFn<T>, W: ValueWriteFn<T>> {
    read: R,
    write: W,
    _pd: PhantomData<T>,
}

/// Allows read-only access to a "functional file"
pub struct ReadOnlyFnNode<R: ReadFn> {
    read: R,
}

/// In-memory directory using tree cache
pub struct MemoryDirectory;
/// In-memory symlink with a fixed path
pub struct FixedPathSymlink {
    target: String,
}

/// In-memory hard link with a fixed target
pub struct FixedHardlink {
    target: NodeRef,
}
/// In-memory hard link with a function-based target
pub struct FnHardlink<R: Fn() -> Result<NodeRef, Error>> {
    read: R,
}

impl<T, R> ReadOnlyFnValueNode<T, R>
where
    T: ToString + Send + Sync + 'static,
    R: ValueReadFn<T> + 'static,
{
    /// Creates a new [ReadOnlyFnValueNode] with given read function
    pub const fn new(read: R) -> Self {
        Self {
            read,
            _pd: PhantomData,
        }
    }

    pub fn new_node(read: R) -> NodeRef {
        Node::regular(
            Self::new(read),
            NodeFlags::IN_MEMORY_PROPS,
            Some(Metadata::now_root(FileMode::new(0o444), 0)),
            None,
        )
    }
}

impl<T, R> CommonImpl for ReadOnlyFnValueNode<T, R>
where
    T: ToString + Send + Sync,
    R: ValueReadFn<T>,
{
    fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
        Ok(0)
    }
}

impl<T, R> RegularImpl for ReadOnlyFnValueNode<T, R>
where
    T: ToString + Send + Sync,
    R: ValueReadFn<T>,
{
    fn open(
        &self,
        _node: &NodeRef,
        opts: OpenOptions,
    ) -> Result<(u64, Option<InstanceData>), Error> {
        if opts.contains(OpenOptions::WRITE) {
            return Err(Error::ReadOnly);
        }
        let t = (self.read)()?;
        Ok((0, Some(Arc::new(t.as_instance_data()))))
    }

    fn read(
        &self,
        _node: &NodeRef,
        instance: Option<&InstanceData>,
        pos: u64,
        buf: &mut [u8],
    ) -> Result<usize, Error> {
        let value = instance.unwrap().downcast_ref::<Vec<u8>>().unwrap();
        Ok(value.read_slice(pos as usize, buf))
    }

    fn write(
        &self,
        _node: &NodeRef,
        _instance: Option<&InstanceData>,
        _pos: u64,
        _buf: &[u8],
    ) -> Result<usize, Error> {
        Err(Error::ReadOnly)
    }

    fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> {
        Err(Error::ReadOnly)
    }
}

// Read-write FnNode

impl FnNodeData {
    fn write() -> Self {
        Self::Write(IrqSafeSpinlock::new(Vec::new()))
    }

    fn read<T: AsInstanceData>(value: T) -> Self {
        Self::Read(value.as_instance_data())
    }

    fn as_read(&self) -> Result<&Vec<u8>, Error> {
        match self {
            Self::Read(r) => Ok(r),
            Self::Write(_) => Err(Error::InvalidOperation),
        }
    }

    fn as_write(&self) -> Result<&IrqSafeSpinlock<Vec<u8>>, Error> {
        match self {
            Self::Write(w) => Ok(w),
            Self::Read(_) => Err(Error::InvalidOperation),
        }
    }
}

impl<T, R, W> FnValueNode<T, R, W>
where
    T: ToString + FromStr + Send + Sync + 'static,
    R: ValueReadFn<T> + 'static,
    W: ValueWriteFn<T> + 'static,
{
    /// Creates a new [FnValueNode] with given read and write functions
    pub fn new(read: R, write: W) -> Self {
        Self {
            read,
            write,
            _pd: PhantomData,
        }
    }

    pub fn new_node(read: R, write: W) -> NodeRef {
        Node::regular(
            Self::new(read, write),
            NodeFlags::IN_MEMORY_PROPS,
            Some(Metadata::now_root(FileMode::new(0o644), 0)),
            None,
        )
    }
}

impl<T, R, W> CommonImpl for FnValueNode<T, R, W>
where
    T: ToString + FromStr + Send + Sync,
    R: ValueReadFn<T>,
    W: ValueWriteFn<T>,
{
    fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
        Ok(0)
    }
}

impl<T, R, W> RegularImpl for FnValueNode<T, R, W>
where
    T: ToString + FromStr + Send + Sync,
    R: ValueReadFn<T>,
    W: ValueWriteFn<T>,
{
    fn open(
        &self,
        _node: &NodeRef,
        opts: OpenOptions,
    ) -> Result<(u64, Option<InstanceData>), Error> {
        if opts.contains(OpenOptions::READ | OpenOptions::WRITE) {
            Err(Error::InvalidOperation)
        } else if opts.contains(OpenOptions::WRITE) {
            Ok((0, Some(Arc::new(FnNodeData::write()))))
        } else if opts.contains(OpenOptions::READ) {
            let t = (self.read)()?;
            Ok((0, Some(Arc::new(FnNodeData::read(t)))))
        } else {
            Err(Error::InvalidOperation)
        }
    }

    fn close(&self, _node: &NodeRef, instance: Option<&InstanceData>) -> Result<(), Error> {
        if let Ok(write) = instance
            .unwrap()
            .downcast_ref::<FnNodeData>()
            .unwrap()
            .as_write()
        {
            let write = write.lock();
            // Flush write
            let str = core::str::from_utf8(write.as_ref())
                .map_err(|_| Error::InvalidArgument)?
                .trim();
            let t = T::from_str(str).map_err(|_| Error::InvalidArgument)?;

            (self.write)(t)?;
        }
        Ok(())
    }

    fn read(
        &self,
        _node: &NodeRef,
        instance: Option<&InstanceData>,
        pos: u64,
        buf: &mut [u8],
    ) -> Result<usize, Error> {
        let instance = instance.unwrap().downcast_ref::<FnNodeData>().unwrap();
        Ok(instance.as_read()?.read_slice(pos as usize, buf))
    }

    fn write(
        &self,
        _node: &NodeRef,
        instance: Option<&InstanceData>,
        pos: u64,
        buf: &[u8],
    ) -> Result<usize, Error> {
        let instance = instance.unwrap().downcast_ref::<FnNodeData>().unwrap();
        Ok(instance.as_write()?.lock().write_slice(pos as _, buf))
    }

    fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> {
        Ok(())
    }
}

// Byte read-only node

impl<R> ReadOnlyFnNode<R>
where
    R: ReadFn + 'static,
{
    /// Creates a new [ReadOnlyFnNode] with given read function
    pub fn new(read: R) -> Self {
        Self { read }
    }
}

impl<R> CommonImpl for ReadOnlyFnNode<R>
where
    R: ReadFn,
{
    fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
        Ok(0)
    }
}

impl<R> RegularImpl for ReadOnlyFnNode<R>
where
    R: ReadFn,
{
    fn open(
        &self,
        _node: &NodeRef,
        opts: OpenOptions,
    ) -> Result<(u64, Option<InstanceData>), Error> {
        if opts.contains(OpenOptions::WRITE) {
            return Err(Error::ReadOnly);
        }
        Ok((0, None))
    }

    fn read(
        &self,
        _node: &NodeRef,
        _instance: Option<&InstanceData>,
        pos: u64,
        buf: &mut [u8],
    ) -> Result<usize, Error> {
        (self.read)(pos, buf)
    }
}

// In-memory directory

impl MemoryDirectory {
    /// Creates a [MemoryDirectory] with no children
    pub fn empty(mode: FileMode) -> NodeRef {
        Node::directory(
            MemoryDirectory,
            NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
            Some(Metadata::now_root(mode, 0)),
            None,
        )
    }
}

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

// In-memory fixed path symlink

impl CommonImpl for FixedPathSymlink {}
impl SymlinkImpl for FixedPathSymlink {
    fn read_link(&self, buf: &mut [u8]) -> Result<usize, Error> {
        if buf.len() >= self.target.len() {
            buf[..self.target.len()].copy_from_slice(self.target.as_bytes());
            Ok(self.target.len())
        } else {
            log::warn!("FixedPathSymlink: BufferTooSmall");
            Err(Error::BufferTooSmall)
        }
    }

    fn read_to_string(&self) -> Result<String, Error> {
        Ok(self.target.clone())
    }
}

// In-memory fixed hard link

impl HardlinkImpl for FixedHardlink {
    fn target(&self, _node: &NodeRef) -> Result<NodeRef, Error> {
        Ok(self.target.clone())
    }
}

impl<R: Fn() -> Result<NodeRef, Error> + Sync + Send> HardlinkImpl for FnHardlink<R> {
    fn target(&self, _node: &NodeRef) -> Result<NodeRef, Error> {
        (self.read)()
    }
}

/// Creates a read-only value node with given `value`
pub fn const_value_node<T>(value: T) -> NodeRef
where
    T: ToString + Clone + Send + Sync + 'static,
{
    ReadOnlyFnValueNode::new_node(move || Ok(value.clone()))
}

pub fn read_write_node<T, R, W>(read: R, write: W) -> NodeRef
where
    T: ToString + FromStr + Send + Sync + 'static,
    R: ValueReadFn<T> + 'static,
    W: ValueWriteFn<T> + 'static,
{
    FnValueNode::new_node(read, write)
}

/// Creates a read-write value node with given `value`
pub fn value_node<T>(value: T) -> NodeRef
where
    T: ToString + FromStr + Clone + Send + Sync + 'static,
{
    let rd_state = Arc::new(IrqSafeSpinlock::new(value));
    let wr_state = rd_state.clone();

    FnValueNode::new_node(
        move || Ok(rd_state.lock().clone()),
        move |t| {
            *wr_state.lock() = t;
            Ok(())
        },
    )
}

/// Creates a read-only node with given read function
pub fn read_fn_node<R: ReadFn + 'static>(read: R) -> NodeRef {
    Node::regular(
        ReadOnlyFnNode::new(read),
        NodeFlags::IN_MEMORY_PROPS,
        Some(Metadata::now_root(FileMode::new(0o444), 0)),
        None,
    )
}

/// Creates an in-memory directory from the iterator
pub fn mdir<S: TryInto<OwnedFilename>, I: IntoIterator<Item = (S, NodeRef)>>(it: I) -> NodeRef
where
    S::Error: fmt::Debug,
{
    let dir = Node::directory(
        MemoryDirectory,
        NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
        Some(Metadata::now_root(FileMode::new(0o555), 0)),
        None,
    );
    for (name, node) in it {
        dir.add_child(name.try_into().unwrap(), node).unwrap();
    }
    dir
}

pub fn fixed_path_symlink_ext(target: impl Into<String>, metadata: Metadata) -> NodeRef {
    Node::symlink(
        FixedPathSymlink {
            target: target.into(),
        },
        NodeFlags::IN_MEMORY_PROPS,
        Some(metadata),
        None,
    )
}

pub fn fixed_path_symlink(target: impl Into<String>) -> NodeRef {
    fixed_path_symlink_ext(target, Metadata::now_root(FileMode::new(0o555), 0))
}

pub fn fixed_hardlink(target: NodeRef) -> Result<NodeRef, Error> {
    if target.is_hard_link() || target.ty() != FileType::File {
        return Err(Error::InvalidOperation);
    }
    Ok(Node::hardlink(FixedHardlink { target }))
}

pub fn fn_hardlink<R: Fn() -> Result<NodeRef, Error> + Send + Sync + 'static>(read: R) -> NodeRef {
    Node::hardlink(FnHardlink { read })
}

#[cfg(test)]
mod tests {
    use yggdrasil_abi::io::OpenOptions;

    use crate::vfs::{
        node::{
            impls::{const_value_node, value_node},
            AccessToken,
        },
        traits::{Read, Seek, Write},
    };

    #[test]
    fn read_only_fn_node() {
        let node = const_value_node("abcdef");
        let file = node
            .open(OpenOptions::READ, AccessToken::test_authorized())
            .unwrap();
        let mut buf = [0; 512];

        assert_eq!(file.tell().unwrap(), 0);

        assert_eq!(file.read(&mut buf[..3]).unwrap(), 3);
        assert_eq!(&buf[..3], b"abc");

        assert_eq!(file.read(&mut buf).unwrap(), 3);
        assert_eq!(&buf[..3], b"def");
    }

    #[test]
    fn fn_node() {
        let node = value_node(1234);
        let mut buf = [0; 512];

        // Try read the value
        let file = node
            .open(OpenOptions::READ, AccessToken::test_authorized())
            .unwrap();
        assert_eq!(file.tell().unwrap(), 0);
        assert_eq!(file.read(&mut buf).unwrap(), 4);
        assert_eq!(&buf[..4], b"1234");

        // Try write the value
        let file = node
            .open(OpenOptions::WRITE, AccessToken::test_authorized())
            .unwrap();
        assert_eq!(file.tell().unwrap(), 0);
        assert_eq!(file.write(b"654321").unwrap(), 6);
        drop(file);

        // Try read the value again
        let file = node
            .open(OpenOptions::READ, AccessToken::test_authorized())
            .unwrap();
        assert_eq!(file.tell().unwrap(), 0);
        assert_eq!(file.read(&mut buf).unwrap(), 6);
        assert_eq!(&buf[..6], b"654321");
    }
}
