use core::num::NonZeroUsize;

use crate::{SyscallFatRegister, SyscallRegister, ThinWrapped};

pub fn make_error<E: SyscallRegister>(e: E) -> usize {
    (-(e.into_syscall_register() as isize)) as usize
}

pub fn get_error<E: SyscallRegister>(value: usize) -> E {
    E::from_syscall_register((-(value as isize)) as usize)
}

// Result<usize> is special: usize is not thinly-wrapped, but is represented in the ABI similar
// to Unix ssize_t-returning system calls
impl<E: SyscallRegister> SyscallRegister for Result<usize, E> {
    fn into_syscall_register(self) -> usize {
        match self {
            Ok(value) => value,
            Err(err) => make_error(err),
        }
    }

    fn from_syscall_register(value: usize) -> Self {
        if (value as isize) < 0 {
            Err(get_error(value))
        } else {
            Ok(value as _)
        }
    }
}

impl<T: Sized> SyscallFatRegister for Option<&[T]> {
    fn as_syscall_meta(&self) -> usize {
        match self {
            Some(value) => value.as_ptr().addr(),
            None => 0,
        }
    }

    fn as_syscall_data(&self) -> usize {
        match self {
            Some(value) => value.len(),
            None => 0,
        }
    }

    fn from_syscall_registers(meta: usize, data: usize) -> Self {
        if meta != 0 {
            Some(unsafe {
                core::slice::from_raw_parts(core::ptr::with_exposed_provenance(meta), data)
            })
        } else {
            None
        }
    }
}

impl<T: Sized> SyscallFatRegister for Option<&mut [T]> {
    fn as_syscall_meta(&self) -> usize {
        match self {
            Some(value) => value.as_ptr().addr(),
            None => 0,
        }
    }

    fn as_syscall_data(&self) -> usize {
        match self {
            Some(value) => value.len(),
            None => 0,
        }
    }

    fn from_syscall_registers(meta: usize, data: usize) -> Self {
        if meta != 0 {
            Some(unsafe {
                core::slice::from_raw_parts_mut(core::ptr::with_exposed_provenance_mut(meta), data)
            })
        } else {
            None
        }
    }
}

impl<T: ThinWrapped> SyscallRegister for Option<T> {
    fn into_syscall_register(self) -> usize {
        match self {
            None => usize::MAX,
            Some(value) => value.into_syscall_register(),
        }
    }

    fn from_syscall_register(value: usize) -> Self {
        match value {
            usize::MAX => None,
            _ => Some(T::from_syscall_register(value)),
        }
    }
}

impl<T: ThinWrapped, E: SyscallRegister> SyscallRegister for Result<T, E> {
    fn into_syscall_register(self) -> usize {
        match self {
            Ok(value) => value.into_syscall_register(),
            Err(err) => make_error(err),
        }
    }

    fn from_syscall_register(value: usize) -> Self {
        if (value as isize) < 0 {
            Err(get_error(value))
        } else {
            Ok(T::from_syscall_register(value))
        }
    }
}

impl SyscallRegister for Option<NonZeroUsize> {
    fn into_syscall_register(self) -> usize {
        match self {
            Some(value) => value.into(),
            None => 0,
        }
    }

    fn from_syscall_register(value: usize) -> Self {
        NonZeroUsize::new(value)
    }
}

impl<E: SyscallRegister> SyscallRegister for Result<NonZeroUsize, E> {
    fn into_syscall_register(self) -> usize {
        match self {
            Ok(value) => value.into(),
            Err(err) => make_error(err),
        }
    }

    fn from_syscall_register(value: usize) -> Self {
        if (value as isize) < 0 {
            Err(get_error(value))
        } else if value != 0 {
            Ok(unsafe { NonZeroUsize::new_unchecked(value) })
        } else {
            unreachable!()
        }
    }
}
