use core::marker::PhantomData;
use core::ops::Index;

#[repr(transparent)]
#[derive(Debug)]
pub struct Ptr<T: ?Sized> {
    ptr: *const T,
}

impl<T: ?Sized> Clone for Ptr<T> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<T: ?Sized> Copy for Ptr<T> {}

impl<T: ?Sized> PartialEq for Ptr<T> {
    fn eq(&self, other: &Self) -> bool {
        core::ptr::addr_eq(self.ptr, other.ptr)
    }
}

impl<T: ?Sized> Eq for Ptr<T> {}

unsafe impl<T: ?Sized + Send> Send for Ptr<T> {}
unsafe impl<T: ?Sized + Sync> Sync for Ptr<T> {}

impl<T> Ptr<T> {
    pub const fn null() -> Self {
        Self {
            ptr: core::ptr::null(),
        }
    }

    pub const fn is_null(self) -> bool {
        self.ptr.is_null()
    }

    pub fn from_raw(ptr: *const T) -> Self {
        Self { ptr }
    }

    pub fn as_raw(self) -> *const T {
        self.ptr
    }

    #[inline(always)]
    pub unsafe fn as_ref<'a>(self) -> &'a T {
        debug_assert!(!self.is_null(), "null Ptr dereference");
        unsafe { &*self.ptr }
    }

    #[inline(always)]
    pub fn get<'a>(self) -> Option<&'a T> {
        if self.is_null() {
            None
        } else {
            Some(unsafe { &*self.ptr })
        }
    }

    #[inline(always)]
    pub unsafe fn at<'a>(self, index: usize) -> &'a T {
        debug_assert!(!self.is_null(), "null Ptr array access");
        unsafe { &*self.ptr.add(index) }
    }

    /// Get element at index, returns None if ptr is null
    #[inline(always)]
    pub fn get_at<'a>(self, index: usize) -> Option<&'a T> {
        if self.is_null() {
            None
        } else {
            Some(unsafe { &*self.ptr.add(index) })
        }
    }

    /// Offset the pointer, returning a new Ptr
    #[inline(always)]
    pub unsafe fn add(self, count: usize) -> Self {
        Self {
            ptr: unsafe { self.ptr.add(count) },
        }
    }

    /// Convert to slice (requires knowing the length)
    #[inline(always)]
    pub unsafe fn as_slice<'a>(self, len: usize) -> &'a [T] {
        debug_assert!(!self.is_null() || len == 0, "null Ptr to non-empty slice");
        if len == 0 {
            &[]
        } else {
            unsafe { core::slice::from_raw_parts(self.ptr, len) }
        }
    }

    /// Convert to slice, returns None if null
    #[inline(always)]
    pub fn get_slice<'a>(self, len: usize) -> Option<&'a [T]> {
        if self.is_null() {
            if len == 0 { Some(&[]) } else { None }
        } else {
            Some(unsafe { core::slice::from_raw_parts(self.ptr, len) })
        }
    }
}

impl<T> core::ops::Deref for Ptr<T> {
    type Target = T;

    #[inline(always)]
    fn deref(&self) -> &T {
        debug_assert!(!self.is_null(), "Null Ptr dereference");
        unsafe { &*self.ptr }
    }
}

impl<T> Default for Ptr<T> {
    fn default() -> Self {
        Self::null()
    }
}

impl<T> From<&T> for Ptr<T> {
    fn from(r: &T) -> Self {
        Self { ptr: r as *const T }
    }
}

impl<T> From<&[T]> for Ptr<T> {
    fn from(slice: &[T]) -> Self {
        Self {
            ptr: slice.as_ptr(),
        }
    }
}

impl<T> From<*const T> for Ptr<T> {
    fn from(ptr: *const T) -> Self {
        Self { ptr }
    }
}

impl<T> From<Option<&T>> for Ptr<T> {
    fn from(opt: Option<&T>) -> Self {
        match opt {
            Some(r) => Ptr::from(r),
            None => Ptr::null(),
        }
    }
}
