use alloc::sync::Arc;
use yggdrasil_abi::{error::Error, io::SeekFrom};

/// Positional read interface for VFS objects
pub trait ReadAt {
    /// Reads data at offset `pos` without changing the file position
    fn read_at(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error>;
}

/// Positional write interface for VFS objects
pub trait WriteAt {
    /// Writes data at offset `pos` without changing the file position
    fn write_at(&self, pos: u64, buf: &[u8]) -> Result<usize, Error>;
}

/// Immutable read interface for VFS objects
pub trait Read {
    /// Reads bytes into the given buffer
    fn read(&self, buf: &mut [u8]) -> Result<usize, Error>;

    /// Reads exactly `buf.len()` bytes into the given buffer, fails if such amount is not
    /// available
    fn read_exact(&self, buf: &mut [u8]) -> Result<(), Error> {
        match self.read(buf) {
            Ok(count) if count == buf.len() => Ok(()),
            Ok(_) => Err(Error::InvalidFile),
            Err(err) => Err(err),
        }
    }
}

/// Immutable write interface for VFS objects
pub trait Write {
    /// Writes bytes from the given buffer
    fn write(&self, buf: &[u8]) -> Result<usize, Error>;
}

/// Immutable file positioning interface for VFS objects
pub trait Seek {
    /// Changes position inside the file to a requested one. Fails if the file does not support
    /// seeking.
    fn seek(&self, from: SeekFrom) -> Result<u64, Error>;

    /// Returns the position within the file, determined as an offset in bytes from the beginning
    /// of the file. Fails if the file does not support seeking or the "offset" is not defined for
    /// such type of nodes.
    fn tell(&self) -> Result<u64, Error> {
        self.seek(SeekFrom::Current(0))
    }
}

impl<F: Read> Read for Arc<F> {
    #[inline]
    fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
        self.as_ref().read(buf)
    }

    #[inline]
    fn read_exact(&self, buf: &mut [u8]) -> Result<(), Error> {
        self.as_ref().read_exact(buf)
    }
}

impl<F: ReadAt> ReadAt for Arc<F> {
    #[inline]
    fn read_at(&self, pos: u64, buf: &mut [u8]) -> Result<usize, Error> {
        self.as_ref().read_at(pos, buf)
    }
}

impl<F: Seek> Seek for Arc<F> {
    #[inline]
    fn seek(&self, from: SeekFrom) -> Result<u64, Error> {
        self.as_ref().seek(from)
    }

    #[inline]
    fn tell(&self) -> Result<u64, Error> {
        self.as_ref().tell()
    }
}

impl<F: WriteAt> WriteAt for Arc<F> {
    #[inline]
    fn write_at(&self, pos: u64, buf: &[u8]) -> Result<usize, Error> {
        self.as_ref().write_at(pos, buf)
    }
}
