use core::any::Any;

use alloc::sync::Arc;
use libk::{
    block,
    error::Error,
    vfs::{CommonImpl, InstanceData, Metadata, Node, NodeFlags, NodeRef, RegularImpl},
};
use yggdrasil_abi::io::OpenOptions;

use crate::{inode::InodeAccess, Ext2Fs};

pub struct RegularNode {
    fs: Arc<Ext2Fs>,
    pub(crate) inode: InodeAccess,
}

impl RegularNode {
    pub fn new(fs: Arc<Ext2Fs>, inode: InodeAccess) -> NodeRef {
        Node::regular(
            Self {
                fs: fs.clone(),
                inode,
            },
            NodeFlags::empty(),
            None,
            Some(fs),
        )
    }

    async fn resize(&self, new_size: u64) -> Result<(), Error> {
        self.inode
            .amap_mut(async |inode| {
                inode.resize(&self.fs, new_size).await?;
                Ok(())
            })
            .await
    }

    async fn read(&self, pos: u64, buffer: &mut [u8]) -> Result<usize, Error> {
        self.inode
            .amap(async |inode| self.fs.read_inode_data(inode, pos, buffer).await)
            .await
    }

    async fn write(&self, mut pos: u64, buffer: &[u8]) -> Result<usize, Error> {
        self.inode
            .amap_mut(async |inode| {
                let need_size = pos + buffer.len() as u64;
                inode.reserve(&self.fs, need_size).await?;

                let mut offset = 0;
                let mut remaining = buffer.len();

                while remaining != 0 {
                    let block_index = pos / self.fs.block_size as u64;
                    let block_offset = (pos % self.fs.block_size as u64) as usize;
                    let amount = remaining.min(self.fs.block_size - block_offset);

                    self.fs
                        .with_inode_block_mut(inode, block_index as u32, amount, |block| {
                            block[block_offset..block_offset + amount]
                                .copy_from_slice(&buffer[offset..offset + amount]);
                            Ok(())
                        })
                        .await?;

                    pos += amount as u64;
                    offset += amount;
                    remaining -= amount;
                }
                Ok(offset)
            })
            .await
    }
}

impl CommonImpl for RegularNode {
    fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
        block!(self.inode.metadata().await)?
    }

    fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> {
        block!(self.inode.update_metadata(metadata).await)?
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
        block!(self.inode.size().await)?
    }
}

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

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

    fn truncate(&self, _node: &NodeRef, new_size: u64) -> Result<(), Error> {
        if self.fs.force_readonly {
            return Err(Error::ReadOnly);
        }
        block!(self.resize(new_size).await)?
    }

    fn write(
        &self,
        _node: &NodeRef,
        _instance: Option<&InstanceData>,
        pos: u64,
        buf: &[u8],
    ) -> Result<usize, Error> {
        if self.fs.force_readonly {
            return Err(Error::ReadOnly);
        }
        block!(self.write(pos, buf).await)?
    }

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