use alloc::sync::Arc;
use core::any::Any;

use libk::vfs::{CommonImpl, InstanceData, Metadata, Node, NodeFlags, NodeRef, RegularImpl};
use libk_util::sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock};
use yggdrasil_abi::{error::Error, io::OpenOptions};

use crate::{block::BlockAllocator, bvec::BVec, MemoryFilesystem};

pub(crate) struct FileNode<A: BlockAllocator> {
    pub(crate) data: IrqSafeSpinlock<BVec<'static, A>>,
    pub(crate) metadata: IrqSafeRwLock<Metadata>,
}

impl<A: BlockAllocator> FileNode<A> {
    pub fn new(fs: Arc<MemoryFilesystem<A>>, metadata: Metadata) -> NodeRef {
        Node::regular(
            Self {
                data: IrqSafeSpinlock::new(BVec::new()),
                metadata: IrqSafeRwLock::new(metadata),
            },
            NodeFlags::empty(),
            None,
            Some(fs),
        )
    }
}

impl<A: BlockAllocator> CommonImpl for FileNode<A> {
    fn as_any(&self) -> &dyn Any {
        self
    }

    fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
        Ok(self.data.lock().size() as u64)
    }

    fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
        Ok(*self.metadata.read())
    }

    fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> {
        *self.metadata.write() = *metadata;
        Ok(())
    }
}

impl<A: BlockAllocator> RegularImpl for FileNode<A> {
    fn open(
        &self,
        _node: &NodeRef,
        opts: OpenOptions,
    ) -> Result<(u64, Option<InstanceData>), Error> {
        // TODO provide APPEND by vfs driver instead
        if opts.contains(OpenOptions::APPEND) {
            Ok((self.data.lock().size() as u64, None))
        } else {
            Ok((0, None))
        }
    }

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

    fn write(
        &self,
        _node: &NodeRef,
        _instance: Option<&InstanceData>,
        pos: u64,
        buf: &[u8],
    ) -> Result<usize, Error> {
        self.metadata.write().set_mtime_now();
        self.data.lock().write(pos, buf)
    }

    fn truncate(&self, _node: &NodeRef, new_size: u64) -> Result<(), Error> {
        self.metadata.write().set_mtime_now();
        self.data.lock().truncate(new_size)
    }

    fn close(&self, _node: &NodeRef, _instance: Option<&InstanceData>) -> Result<(), Error> {
        Ok(())
    }
}
