use core::{
    mem::{size_of, MaybeUninit},
    ptr,
};
use std::alloc::{alloc_zeroed, handle_alloc_error, Layout};

/// Returns a zeroed value
///
/// Equivalent to:
/// ```ignore
/// MaybeUninit::zeroed().assume_init()
/// ```
pub const fn zero<T>() -> T {
    unsafe { MaybeUninit::zeroed().assume_init() }
}

/// Returns a value whose bits are all '1'
pub const fn ones<T>() -> T {
    unsafe {
        let mut x = MaybeUninit::uninit();
        ptr::write_bytes(x.as_mut_ptr(), 0xFF, 1);
        x.assume_init()
    }
}

/// Sets all the bytes to 0
pub const fn make_zero<T: ?Sized>(v: &mut T) {
    unsafe { ptr::write_bytes(v as *mut _ as *mut u8, 0, core::mem::size_of_val(v)) }
}

/// Allocate a zeroed value on the heap
pub fn zeroed_box<T>() -> Box<T> {
    unsafe {
        let layout = Layout::new::<T>();
        let ptr = alloc_zeroed(layout);
        if ptr.is_null() {
            handle_alloc_error(layout);
        }
        Box::from_raw(ptr.cast())
    }
}

/// Reads T from a pointer D with `offset` bytes
pub const fn read<D, T: Copy>(data: *const D, offset: usize) -> T {
    unsafe { *data.cast::<u8>().add(offset).cast() }
}

/// Writes T from a pointer D with `offset` bytes
pub const fn write<D, T: Copy>(data: *mut D, offset: usize, val: T) {
    unsafe { data.cast::<u8>().add(offset).cast::<T>().write(val) }
}

/// Trait that supports reading and writing primitive values for different endian types
pub trait MemValue: Sized + Copy {
    fn from_le_bytes(bytes: [u8; size_of::<Self>()]) -> Self;
    fn from_be_bytes(bytes: [u8; size_of::<Self>()]) -> Self;
    fn from_ne_bytes(bytes: [u8; size_of::<Self>()]) -> Self;

    fn to_le_bytes(self) -> [u8; size_of::<Self>()];
    fn to_be_bytes(self) -> [u8; size_of::<Self>()];
    fn to_ne_bytes(self) -> [u8; size_of::<Self>()];

    /// # Safety
    /// The given pointer must be [valid] for `Self` reads and point to a properly initialized value
    /// of `Self`.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn read_le(ptr: *const Self) -> Self;
    /// # Safety
    /// The given pointer must be aligned to a `Self` boundary, be [valid] for `Self` reads and
    /// point to a properly initialized value of `Self`.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn read_le_aligned(ptr: *const Self) -> Self;
    /// # Safety
    /// The given pointer must be [valid] for `Self` reads and point to a properly initialized value
    /// of `Self`.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn read_be(ptr: *const Self) -> Self;
    /// # Safety
    /// The given pointer must be aligned to a `Self` boundary, be [valid] for `Self` reads and
    /// point to a properly initialized value of `Self`.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn read_be_aligned(ptr: *const Self) -> Self;
    /// # Safety
    /// The given pointer must be [valid] for `Self` reads and point to a properly initialized value
    /// of `Self`.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn read_ne(ptr: *const Self) -> Self;
    /// # Safety
    /// The given pointer must be aligned to a `Self` boundary, be [valid] for `Self` reads and
    /// point to a properly initialized value of `Self`.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn read_ne_aligned(ptr: *const Self) -> Self;

    /// # Safety
    /// The given pointer must be [valid] for `Self` writes.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn write_le(self, ptr: *mut Self);
    /// # Safety
    /// The given pointer must be aligned to a `Self` boundary and be [valid] for `Self` writes.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn write_le_aligned(self, ptr: *mut Self);
    /// # Safety
    /// The given pointer must be [valid] for `Self` writes.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn write_be(self, ptr: *mut Self);
    /// # Safety
    /// The given pointer must be aligned to a `Self` boundary and be [valid] for `Self` writes.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn write_be_aligned(self, ptr: *mut Self);
    /// # Safety
    /// The given pointer must be [valid] for `Self` writes.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn write_ne(self, ptr: *mut Self);
    /// # Safety
    /// The given pointer must be aligned to a `Self` boundary and be [valid] for `Self` writes.
    ///
    /// [valid]: https://doc.rust-lang.org/stable/std/ptr/index.html#safety
    unsafe fn write_ne_aligned(self, ptr: *mut Self);
}

macro_rules! impl_mem_value {
	($($ty: ty),*) => {
		$(
			#[allow(unused_mut)]
			impl MemValue for $ty {
				#[inline]
				fn from_le_bytes(bytes: [u8; size_of::<Self>()]) -> Self {
					<$ty>::from_le_bytes(bytes)
				}

				#[inline]
				fn from_be_bytes(bytes: [u8; size_of::<Self>()]) -> Self {
					<$ty>::from_be_bytes(bytes)
				}

				#[inline]
				fn from_ne_bytes(bytes: [u8; size_of::<Self>()]) -> Self {
					<$ty>::from_ne_bytes(bytes)
				}

				#[inline]
				fn to_le_bytes(self) -> [u8; size_of::<Self>()] {
					<$ty>::to_le_bytes(self)
				}

				#[inline]
				fn to_be_bytes(self) -> [u8; size_of::<Self>()] {
					<$ty>::to_be_bytes(self)
				}

				#[inline]
				fn to_ne_bytes(self) -> [u8; size_of::<Self>()] {
					<$ty>::to_ne_bytes(self)
				}

				#[inline]
				unsafe fn read_le(ptr: *const Self) -> Self {
					let mut res = ptr.read_unaligned();
					#[cfg(not(target_endian = "little"))]
					{
						res = res.swap_bytes();
					}
					res
				}

				#[inline]
				unsafe fn read_le_aligned(ptr: *const Self) -> Self {
					let mut res = ptr.read();
					#[cfg(not(target_endian = "little"))]
					{
						res = res.swap_bytes();
					}
					res
				}

				#[inline]
				unsafe fn read_be(ptr: *const Self) -> Self {
					let mut res = ptr.read_unaligned();
					#[cfg(not(target_endian = "big"))]
					{
						res = res.swap_bytes();
					}
					res
				}

				#[inline]
				unsafe fn read_be_aligned(ptr: *const Self) -> Self {
					let mut res = ptr.read();
					#[cfg(not(target_endian = "big"))]
					{
						res = res.swap_bytes();
					}
					res
				}

				#[inline]
				unsafe fn read_ne(ptr: *const Self) -> Self {
					ptr.read_unaligned()
				}

				#[inline]
				unsafe fn read_ne_aligned(ptr: *const Self) -> Self {
					ptr.read()
				}

				#[inline]
				unsafe fn write_le(mut self, ptr: *mut Self) {
					#[cfg(not(target_endian = "little"))]
					{
						self = self.swap_bytes();
					}
					ptr.write_unaligned(self);
				}

				#[inline]
				unsafe fn write_le_aligned(mut self, ptr: *mut Self) {
					#[cfg(not(target_endian = "little"))]
					{
						self = self.swap_bytes();
					}
					ptr.write(self);
				}

				#[inline]
				unsafe fn write_be(mut self, ptr: *mut Self) {
					#[cfg(not(target_endian = "big"))]
					{
						self = self.swap_bytes();
					}
					ptr.write_unaligned(self);
				}

				#[inline]
				unsafe fn write_be_aligned(mut self, ptr: *mut Self) {
					#[cfg(not(target_endian = "big"))]
					{
						self = self.swap_bytes();
					}
					ptr.write(self);
				}

				#[inline]
				unsafe fn write_ne(self, ptr: *mut Self) {
					ptr.write_unaligned(self);
				}

				#[inline]
				unsafe fn write_ne_aligned(self, ptr: *mut Self) {
					ptr.write(self);
				}
			}
		)*
	};
}

impl_mem_value!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize);
