use core::{
    ffi::{c_char, c_int, CStr},
    fmt,
    panic::Location,
    ptr::NonNull,
};

use yggdrasil_rt::io::RawFd;

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

pub trait Pointer {
    type Pointee;

    #[allow(clippy::wrong_self_convention)]
    fn is_null(self) -> bool;
}

impl<T> Pointer for *const T {
    type Pointee = T;

    fn is_null(self) -> bool {
        <*const T>::is_null(self)
    }
}

impl<T> Pointer for *mut T {
    type Pointee = T;

    fn is_null(self) -> bool {
        <*mut T>::is_null(self)
    }
}

pub struct NullTerminatedArrayIter<T: Pointer> {
    pointer: *const T,
}

impl<T: Pointer + Copy> NullTerminatedArrayIter<T> {
    pub fn new(pointer: *const T) -> Self {
        Self { pointer }
    }
}

impl<T: Pointer + Copy> Iterator for NullTerminatedArrayIter<T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.pointer.is_null() {
            return None;
        }
        let current = unsafe { *self.pointer };
        if current.is_null() {
            return None;
        }
        self.pointer = unsafe { self.pointer.add(1) };
        Some(current)
    }
}

pub trait PointerExt {
    unsafe fn ensure(self: *const Self) -> &'static Self;
    unsafe fn ensure_mut(self: *mut Self) -> &'static mut Self;
    unsafe fn ensure_slice(self: *const Self, len: usize) -> &'static [Self]
    where
        Self: Sized;
    unsafe fn ensure_slice_mut(self: *mut Self, len: usize) -> &'static mut [Self]
    where
        Self: Sized;
}

pub trait PointerStrExt {
    unsafe fn ensure_str(self: *const Self) -> &'static str;
    unsafe fn ensure_cstr(self: *const Self) -> &'static CStr;
}

pub struct CStringWriter {
    ptr: NonNull<c_char>,
    pos: usize,
    limit: usize,
}

impl<T> PointerExt for T {
    #[track_caller]
    unsafe fn ensure(self: *const Self) -> &'static Self {
        unsafe { self.as_ref().expect("ensure() failed: NULL pointer") }
    }

    #[track_caller]
    unsafe fn ensure_mut(self: *mut Self) -> &'static mut Self {
        let caller = Location::caller();
        match self.as_mut() {
            Some(ptr) => ptr,
            None => panic!("{caller:?}: ensure_mut() failed: NULL pointer"),
        }
    }

    #[track_caller]
    unsafe fn ensure_slice(self: *const Self, len: usize) -> &'static [Self]
    where
        Self: Sized,
    {
        if self.is_null() {
            panic!("ensure_slice() failed: NULL pointer");
        }
        unsafe { core::slice::from_raw_parts(self, len) }
    }

    #[track_caller]
    unsafe fn ensure_slice_mut(self: *mut Self, len: usize) -> &'static mut [Self]
    where
        Self: Sized,
    {
        if self.is_null() {
            panic!("ensure_slice_mut() failed: NULL pointer");
        }
        unsafe { core::slice::from_raw_parts_mut(self, len) }
    }
}

impl PointerStrExt for c_char {
    #[track_caller]
    unsafe fn ensure_str(self: *const Self) -> &'static str {
        self.ensure_cstr()
            .to_str()
            .expect("ensure_str() failed: not a valid string")
    }

    #[track_caller]
    unsafe fn ensure_cstr(self: *const Self) -> &'static CStr {
        if self.is_null() {
            panic!("ensure_str() failed: NULL pointer");
        }
        CStr::from_ptr(self)
    }
}

impl CStringWriter {
    pub unsafe fn from_ptr(ptr: *mut c_char, limit: usize) -> Option<Self> {
        let ptr = NonNull::new(ptr)?;
        Some(Self { ptr, limit, pos: 0 })
    }

    pub fn finish(self) -> Option<NonNull<c_char>> {
        if self.pos != self.limit {
            unsafe {
                self.ptr.add(self.pos).write(0);
            }
            Some(self.ptr)
        } else {
            None
        }
    }
}

impl fmt::Write for CStringWriter {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        if self.pos + s.len() < self.limit {
            let dst = unsafe {
                core::slice::from_raw_parts_mut(self.ptr.add(self.pos).cast().as_ptr(), s.len())
            };
            dst.copy_from_slice(s.as_bytes());
            self.pos += s.len();

            Ok(())
        } else {
            Err(fmt::Error)
        }
    }
}

pub fn at_fd(fd: c_int) -> EResult<Option<RawFd>> {
    match fd {
        AT_FDCWD => EResult::Ok(None),
        0.. => RawFd::e_try_from(fd).map(Some),
        _ => EResult::Err(Errno::EBADF),
    }
}

pub unsafe fn cstr_prefix<F: Fn(u8, u8) -> bool>(a: NonNull<u8>, b: &[u8], cmp: F) -> bool {
    for (i, &bch) in b.iter().enumerate() {
        let ach = a.add(i).read();
        if ach == 0 || !cmp(ach, bch) {
            return false;
        }
    }
    true
}

// pub unsafe fn cstr_matches(a: NonNull<u8>, b: &[u8]) -> bool {
//     cstr_prefix(a, b, |a, b| a == b)
// }

pub unsafe fn cstr_matches_insensitive(a: NonNull<u8>, b: &[u8]) -> bool {
    cstr_prefix(a, b, |a, b| u8::eq_ignore_ascii_case(&a, &b))
}
