use core::mem::MaybeUninit;

use alloc::boxed::Box;
use yggdrasil_rt::{
    io::{DirectoryEntry, RawFd},
    path::Path,
    sys as syscall,
};

use crate::{error::EResult, headers::errno::Errno};

use super::{AsRawFd, FromRawFd};

#[derive(Debug)]
pub struct DirReader {
    fd: Option<RawFd>,

    // Read buffer
    buffer: Box<[MaybeUninit<DirectoryEntry>]>,
    position: usize,
    len: usize,
}

impl DirReader {
    const BUFFER_SIZE: usize = 16;

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

    pub unsafe fn close(&mut self) -> EResult<()> {
        if let Some(fd) = self.fd.take() {
            unsafe { syscall::close(fd) }?;
            EResult::Ok(())
        } else {
            EResult::Err(Errno::EBADF)
        }
    }

    pub fn as_raw_fd(&self) -> RawFd {
        self.fd.unwrap()
    }

    fn fill_buf(&mut self) -> EResult<&[DirectoryEntry]> {
        let Some(fd) = self.fd else {
            return EResult::Err(Errno::EBADF);
        };

        if self.position == self.len {
            let count = unsafe { syscall::read_directory_entries(fd, &mut self.buffer) }?;

            self.position = 0;
            self.len = count;
        }

        EResult::Ok(unsafe { self.buffer[self.position..self.len].assume_init_ref() })
    }

    fn consume(&mut self, count: usize) {
        self.position = (self.position + count).min(self.len);
    }

    pub fn read_entry(&mut self) -> EResult<Option<DirectoryEntry>> {
        let buf = self.fill_buf()?;

        if let Some(&entry) = buf.first() {
            self.consume(1);
            EResult::Ok(Some(entry))
        } else {
            EResult::Ok(None)
        }
    }
}

impl AsRawFd for DirReader {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.unwrap()
    }
}

impl FromRawFd for DirReader {
    unsafe fn from_raw_fd(fd: RawFd) -> Self {
        Self {
            fd: Some(fd),

            buffer: Box::new_uninit_slice(Self::BUFFER_SIZE),
            position: 0,
            len: 0,
        }
    }
}

impl Drop for DirReader {
    fn drop(&mut self) {
        if self.fd.is_some() {
            unsafe {
                self.close().ok();
            }
        }
    }
}
