use core::ffi::c_int;

use yggdrasil_rt::{
    io::{FileMode, OpenOptions, RawFd, SeekFrom},
    path::Path,
    sys as syscall,
};

use crate::error::{EResult, TryFromExt};

use super::{AsRawFd, FromRawFd, IntoRawFd, Read, ReadAt, Seek, Write, WriteAt};

#[repr(transparent)]
pub struct RawFile(RawFd);

impl RawFile {
    pub fn open_at<P: AsRef<Path>>(
        at: Option<RawFd>,
        pathname: P,
        opts: OpenOptions,
        mode: FileMode,
    ) -> EResult<Self> {
        let fd = unsafe { syscall::open(at, pathname.as_ref().as_str(), opts, mode) }?;
        EResult::Ok(Self(fd))
    }

    pub fn open_directory_at<P: AsRef<Path>>(at: Option<RawFd>, pathname: P) -> EResult<Self> {
        let fd = unsafe { syscall::open_directory(at, pathname.as_ref().as_str()) }?;
        EResult::Ok(Self(fd))
    }

    pub unsafe fn close(&mut self) -> EResult<()> {
        unsafe { syscall::close(self.0) }?;
        EResult::Ok(())
    }

    pub fn truncate(&mut self, size: u64) -> EResult<()> {
        unsafe { syscall::truncate(self.0, size) }?;
        EResult::Ok(())
    }

    pub fn duplicate(&self, new: Option<RawFd>) -> EResult<Self> {
        let fd = unsafe { syscall::clone_fd(self.0, new) }?;
        EResult::Ok(Self(fd))
    }
}

impl AsRawFd for RawFile {
    fn as_raw_fd(&self) -> RawFd {
        self.0
    }
}

impl FromRawFd for RawFile {
    unsafe fn from_raw_fd(fd: RawFd) -> Self {
        Self(fd)
    }
}

impl IntoRawFd for RawFile {
    fn into_raw_fd(self) -> RawFd {
        self.0
    }
}

impl WriteAt for RawFile {
    fn write_at(&mut self, pos: u64, buffer: &[u8]) -> EResult<usize> {
        let count = unsafe { syscall::write_at(self.0, pos, buffer) }?;
        EResult::Ok(count)
    }
}

impl ReadAt for RawFile {
    fn read_at(&mut self, pos: u64, buffer: &mut [u8]) -> EResult<usize> {
        let count = unsafe { syscall::read_at(self.0, pos, buffer) }?;
        EResult::Ok(count)
    }
}

impl Write for RawFile {
    fn write(&mut self, data: &[u8]) -> EResult<usize> {
        let count = unsafe { syscall::write(self.0, data) }?;
        EResult::Ok(count)
    }

    fn flush(&mut self) -> EResult<()> {
        EResult::Ok(())
    }
}

impl Read for RawFile {
    fn read(&mut self, data: &mut [u8]) -> EResult<usize> {
        let count = unsafe { syscall::read(self.0, data) }?;
        EResult::Ok(count)
    }
}

impl Seek for RawFile {
    fn seek(&mut self, seek: SeekFrom) -> EResult<u64> {
        let mut pos = 0;
        unsafe { syscall::seek(self.0, seek, &mut pos) }?;
        EResult::Ok(pos)
    }
}

impl TryFromExt<c_int> for RawFile {
    fn e_try_from(value: c_int) -> EResult<Self> {
        let fd = RawFd::e_try_from(value)?;
        EResult::Ok(unsafe { Self::from_raw_fd(fd) })
    }
}
