use libk_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::{error::Error, io::SeekFrom};

use crate::{
    device::{block::BlockDeviceFile, char::CharDeviceFile},
    vfs::{CommonImpl, NodeRef},
};

pub struct BlockFile {
    pub(super) device: BlockDeviceFile,
    pub(super) node: NodeRef,
    pub(super) position: IrqSafeSpinlock<u64>,
    pub(super) read: bool,
    pub(super) write: bool,
}

#[derive(Clone)]
pub struct CharFile {
    pub(super) device: CharDeviceFile,
    pub(super) node: NodeRef,
    pub(super) read: bool,
    pub(super) write: bool,
}

impl BlockFile {
    pub fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
        block!(self.read_async(buf).await)?
    }

    async fn read_async(&self, buf: &mut [u8]) -> Result<usize, Error> {
        let mut position = self.position.lock();
        let count = self.device.read(*position, buf).await?;
        *position += count as u64;
        Ok(count)
    }

    async fn write_async(&self, buf: &[u8]) -> Result<usize, Error> {
        let mut position = self.position.lock();
        let count = self.device.0.write(*position, buf).await?;
        *position += count as u64;
        Ok(count)
    }

    pub fn write(&self, buf: &[u8]) -> Result<usize, Error> {
        block!(self.write_async(buf).await)?
    }

    pub fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
        let mut position = self.position.lock();

        let newpos = match from {
            SeekFrom::Current(off) => {
                let newpos = i64::try_from(*position).unwrap() + off;
                if newpos < 0 {
                    return Err(Error::InvalidArgument);
                }
                newpos as u64
            }
            SeekFrom::Start(pos) => pos,
            SeekFrom::End(off) => {
                let size = i64::try_from(CommonImpl::size(&self.device, &self.node)?).unwrap();
                let newpos = size + off;

                if newpos < 0 {
                    return Err(Error::InvalidArgument);
                }

                newpos as u64
            }
        };

        *position = newpos;
        Ok(newpos)
    }
}

impl CharFile {
    pub fn read(&self, buf: &mut [u8], non_blocking: bool) -> Result<usize, Error> {
        if !self.read {
            return Err(Error::InvalidOperation);
        }

        if non_blocking {
            self.device.0.read_nonblocking(buf)
        } else {
            block!(self.device.0.read(buf).await)?
        }
    }

    pub fn write(&self, buf: &[u8], non_blocking: bool) -> Result<usize, Error> {
        if !self.write {
            return Err(Error::InvalidOperation);
        }

        if non_blocking {
            self.device.0.write_nonblocking(buf)
        } else {
            block!(self.device.0.write(buf).await)?
        }
    }

    pub fn is_terminal(&self) -> bool {
        self.device.0.is_terminal()
    }
}
