use traits::*;

mod impls;
pub use impls::*;

mod traits;

mod sealed;

/// This trait provides four methods,
/// however, for any given choice of both `Self` and `T`,
/// at most one of these will be available.
pub trait NumericCast: sealed::Sealed {
    /// Cast this number to another numeric type,
    /// preserving it's numeric value exactly.
    fn cast<T>(self) -> T
    where
        Self: Sized + PerfectCast<T>, 
    {
        PerfectCast::cast(self)
    }

    /// Cast this number to another numeric type
    /// with a smaller range, wrapping any out-of-range
    /// values into the new range.
    fn cast_wrapping<T>(self) -> T
    where
        Self: Sized + WrappingCast<T>, 
    {
        WrappingCast::cast(self)
    }

    /// Cast this number to another numeric type,
    /// rounding any unrepresentable values towards zero
    fn cast_round_toward_zero<T>(self) -> T
    where
        Self: Sized + RoundTowardZeroCast<T>, 
    {
        RoundTowardZeroCast::cast(self)
    }

    /// Cast this number to another numeric type.
    /// Unrepresentable values will be rounded
    /// to the nearest value in the new type,
    /// with ties broken by picking whichever result
    /// is even (binary representation ends in 0).
    fn cast_round_ties_even<T>(self) -> T
    where
        Self: Sized + RoundTiesEvenCast<T>, 
    {
        RoundTiesEvenCast::cast(self)
    }
}

macro_rules! numeric_casts {
    ($($T: ident)*) => {$(
        impl NumericCast for $T {}
    )*}
}
numeric_casts! {
    u8 u16 u32 u64 u128 usize
    i8 i16 i32 i64 i128 isize
    f32 f64
}

pub trait BoolToBit: sealed::Sealed {
    fn to_bit<T>(self) -> T
    where
        Self: Sized + ToBit<T>,
    {
        self.to()
    }
}
impl BoolToBit for bool {}

pub trait CharToCodepoint: sealed::Sealed {
    fn to_codepoint<T>(self) -> T
    where
        Self: Sized + ToCodepoint<T>,
    {
        self.to()
    }

    fn to_codepoint_wrapping<T>(self) -> T
    where
        Self: Sized + ToCodepointWrapping<T>,
    {
        self.to()
    }
}
impl CharToCodepoint for char {}

pub trait CodepointToChar: sealed::Sealed {
    fn to_char(self) -> char;
}
impl CodepointToChar for u8 {
    fn to_char(self) -> char {
        crate::u8::char::codepoint_to_char(self)
    }
}

pub trait PointerToAddress {
    fn to_addr(self) -> usize;
}
impl<T> PointerToAddress for *const T {
    fn to_addr(self) -> usize {
        ptr_to_addr(self)
    }
}
impl<T> PointerToAddress for *mut T {
    fn to_addr(self) -> usize {
        ptr_to_addr(self)
    }
}

pub trait AddressToPointer: sealed::Sealed {
    fn to_ptr<T>(self) -> *const T;
    fn to_ptr_mut<T>(self) -> *mut T;
}
impl AddressToPointer for usize {
    fn to_ptr<T>(self) -> *const T {
        addr_to_ptr(self)
    }

    fn to_ptr_mut<T>(self) -> *mut T {
        addr_to_ptr_mut(self)
    }
}

pub trait ArrayStartPointer {
    type Item;
    fn start_ptr(self) -> *const Self::Item;
}
impl<T, const N: usize> ArrayStartPointer for *const [T; N] {
    type Item = T;

    fn start_ptr(self) -> *const Self::Item {
        array_start_ptr(self)
    }
}
impl<T, const N: usize> ArrayStartPointer for *mut [T; N] {
    type Item = T;

    fn start_ptr(self) -> *const Self::Item {
        array_start_ptr(self)
    }
}

pub trait ArrayStartPointerMut: ArrayStartPointer {
    fn start_ptr(self) -> *mut Self::Item;
}
impl<T, const N: usize> ArrayStartPointerMut for *mut [T; N] {
    fn start_ptr(self) -> *mut Self::Item {
        array_start_ptr_mut(self)
    }
}

