use core::{
    ffi::{c_char, c_int, c_long},
    mem::MaybeUninit,
    ptr::NonNull,
};

use alloc::boxed::Box;
use yggdrasil_rt::io::{DirectoryEntry, RawFd};

use crate::{
    error::{CIntZeroResult, CPtrResult, EResult, TryFromExt},
    io::{dir::DirReader, FromRawFd},
    util::{PointerExt, PointerStrExt},
};

use super::{errno::Errno, fcntl::AT_FDCWD, sys_types::ino_t};

#[derive(Debug)]
#[repr(C)]
pub struct DIR {
    reader: DirReader,
    buffer: MaybeUninit<dirent>,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct dirent {
    pub d_ino: ino_t,
    pub d_name: [c_char; 256],
}

pub type __scandir_filter_fn_t = extern "C" fn(*const dirent) -> c_int;
pub type __scandir_compar_fn_t = extern "C" fn(*mut *const dirent, *mut *const dirent) -> c_int;

impl DIR {
    unsafe fn close(mut dir_ptr: NonNull<Self>) -> EResult<()> {
        let dir = dir_ptr.as_mut();
        let result = dir.reader.close();
        drop(Box::from_raw(dir_ptr.as_ptr()));
        result
    }

    fn read_entry(&mut self) -> EResult<NonNull<dirent>> {
        match self.reader.read_entry() {
            EResult::Ok(Some(entry)) => {
                self.buffer.write(dirent::from(entry));
                EResult::Ok(unsafe { NonNull::new_unchecked(self.buffer.as_mut_ptr()) })
            }
            EResult::Ok(None) => EResult::Err(Errno::ESUCCESS),
            EResult::Err(err) => EResult::Err(err),
        }
    }
}

impl From<DirectoryEntry> for dirent {
    fn from(value: DirectoryEntry) -> Self {
        let mut d_name = [0; 256];
        let len = value.name.len();

        if len >= 255 {
            todo!("Name too long");
        }

        // SAFETY this is just to cast u8 to i8, so transmute should be safe
        d_name[..len].copy_from_slice(unsafe { core::mem::transmute(value.name.as_bytes()) });
        d_name[len] = 0;

        Self {
            // TODO
            d_ino: 0,
            d_name,
        }
    }
}

// Primary stuff

#[no_mangle]
unsafe extern "C" fn opendir(pathname: *const c_char) -> CPtrResult<DIR> {
    let pathname = pathname.ensure_str();

    let reader = DirReader::open_at(None, pathname)?;
    let dir = NonNull::from(Box::leak(Box::new(DIR {
        reader,
        buffer: MaybeUninit::uninit(),
    })));
    CPtrResult::success(dir)
}

#[no_mangle]
unsafe extern "C" fn fdopendir(fd: c_int) -> CPtrResult<DIR> {
    let fd = RawFd::e_try_from(fd)?;
    let reader = DirReader::from_raw_fd(fd);
    let dir = NonNull::from(Box::leak(Box::new(DIR {
        reader,
        buffer: MaybeUninit::uninit(),
    })));
    CPtrResult::success(dir)
}

#[no_mangle]
unsafe extern "C" fn closedir(dir: *mut DIR) -> CIntZeroResult {
    let dir = NonNull::new(dir).unwrap();
    DIR::close(dir)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn dirfd(dir: *mut DIR) -> c_int {
    let dir = dir.ensure_mut();
    dir.reader.as_raw_fd().into_raw().try_into().unwrap()
}

#[no_mangle]
unsafe extern "C" fn readdir(dir: *mut DIR) -> CPtrResult<dirent> {
    let dir = dir.as_mut().unwrap();
    let dirent = dir.read_entry()?;
    CPtrResult::success(dirent)
}

// Seeking

#[no_mangle]
unsafe extern "C" fn seekdir(_dir: *mut DIR, _offset: c_long) {
    todo!("<dirent.h>: seekdir()")
}

#[no_mangle]
unsafe extern "C" fn rewinddir(_dir: *mut DIR) {
    todo!("<dirent.h>: rewinddir()")
}

#[no_mangle]
unsafe extern "C" fn telldir(_dir: *mut DIR) -> c_long {
    todo!("<dirent.h>: telldir()")
}

// Directory scan and sorting

#[no_mangle]
unsafe extern "C" fn scandir(
    dirp: *const c_char,
    namelist: *mut *mut *mut dirent,
    filter: __scandir_filter_fn_t,
    compar: __scandir_compar_fn_t,
) -> c_int {
    scandirat(AT_FDCWD, dirp, namelist, filter, compar)
}

#[no_mangle]
unsafe extern "C" fn scandirat(
    _dirfd: c_int,
    _dirp: *const c_char,
    _namelist: *mut *mut *mut dirent,
    _filter: __scandir_filter_fn_t,
    _compar: __scandir_compar_fn_t,
) -> c_int {
    todo!("<dirent.h>: scandirat()")
}

#[no_mangle]
unsafe extern "C" fn alphasort(_a: *const *const dirent, _b: *const *const dirent) -> c_int {
    todo!("<dirent.h>: alphasort()")
}

#[no_mangle]
unsafe extern "C" fn versionsort(_a: *const *const dirent, _b: *const *const dirent) -> c_int {
    todo!("<dirent.h>: alphasort()")
}
