use core::{cmp::min, fmt, ops::Add, str};
use typenum::*;
use crate::{ArrayLength, GenericArray};
#[inline(always)]
fn hex_encode_fallback<const UPPER: bool>(src: &[u8], dst: &mut [u8]) {
    if dst.len() < src.len() * 2 {
        unsafe { core::hint::unreachable_unchecked() };
    }
    let alphabet = match UPPER {
        true => b"0123456789ABCDEF",
        false => b"0123456789abcdef",
    };
    dst.chunks_exact_mut(2).zip(src).for_each(|(s, c)| {
        s[0] = alphabet[(c >> 4) as usize];
        s[1] = alphabet[(c & 0xF) as usize];
    });
}
#[inline]
fn hex_encode<const UPPER: bool>(src: &[u8], dst: &mut [u8]) {
    debug_assert!(dst.len() >= (src.len() * 2));
    #[cfg(any(miri, not(feature = "faster-hex")))]
    hex_encode_fallback::<UPPER>(src, dst);
    #[cfg(all(feature = "faster-hex", not(miri)))]
    match UPPER {
        true => unsafe { faster_hex::hex_encode_upper(src, dst).unwrap_unchecked() },
        false => unsafe { faster_hex::hex_encode(src, dst).unwrap_unchecked() },
    };
}
fn generic_hex<N: ArrayLength, const UPPER: bool>(
    arr: &GenericArray<u8, N>,
    f: &mut fmt::Formatter<'_>,
) -> fmt::Result
where
    N: Add<N>,
    Sum<N, N>: ArrayLength,
{
    let max_digits = N::USIZE * 2;
    let max_digits = match f.precision() {
        Some(precision) if precision < max_digits => precision,
        _ => max_digits,
    };
    let max_bytes = (max_digits >> 1) + (max_digits & 1);
    let input = {
        if max_bytes > N::USIZE {
            unsafe { core::hint::unreachable_unchecked() };
        }
        &arr[..max_bytes]
    };
    if N::USIZE <= 1024 {
        let mut buf = GenericArray::<u8, Sum<N, N>>::default();
        if N::USIZE < 16 {
            hex_encode_fallback::<UPPER>(arr, &mut buf);
        } else {
            hex_encode::<UPPER>(input, &mut buf);
        }
        f.write_str(unsafe { str::from_utf8_unchecked(buf.get_unchecked(..max_digits)) })?;
    } else {
        let mut buf = [0u8; 2048];
        let mut digits_left = max_digits;
        for chunk in input.chunks(1024) {
            hex_encode::<UPPER>(chunk, &mut buf);
            let n = min(chunk.len() * 2, digits_left);
            f.write_str(unsafe { str::from_utf8_unchecked(buf.get_unchecked(..n)) })?;
            digits_left -= n;
        }
    }
    Ok(())
}
impl<N: ArrayLength> fmt::LowerHex for GenericArray<u8, N>
where
    N: Add<N>,
    Sum<N, N>: ArrayLength,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        generic_hex::<_, false>(self, f)
    }
}
impl<N: ArrayLength> fmt::UpperHex for GenericArray<u8, N>
where
    N: Add<N>,
    Sum<N, N>: ArrayLength,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        generic_hex::<_, true>(self, f)
    }
}