//! Generic implementations.

use core::mem::MaybeUninit;

use crate::error::InvalidInput;
use crate::util::lut16;

#[allow(unsafe_code, reason = "XXX")]
/// ## Safety
///
/// We assume that:
///
/// 1. `src.len() <= dst.len()`.
pub(crate) const unsafe fn encode_generic_unchecked<const UPPER: bool>(
    mut src: &[u8],
    mut dst: &mut [[MaybeUninit<u8>; 2]],
) {
    debug_assert!(
        src.len() <= dst.len(),
        "implementation bug: `src` should not be longer than `dst`"
    );

    let digits = lut16::<UPPER>();

    while !src.is_empty() {
        let cur_src;
        // SAFETY: `src` is not empty.
        (cur_src, src) = src.split_first().unwrap_unchecked();

        let cur_dst;
        // SAFETY: it's guaranteed by the caller that `dst` has at least the same length
        // as `src`, then `dst` is not empty.
        (cur_dst, dst) = dst.split_first_mut().unwrap_unchecked();

        cur_dst[0].write(digits[(*cur_src >> 4) as usize]);
        cur_dst[1].write(digits[(*cur_src & 0x0F) as usize]);
    }
}

#[allow(unsafe_code, reason = "XXX")]
/// ## Safety
///
/// We assume that:
///
/// 1. `src.len() <= dst.len()`.
/// 2. When `VALIDATED`, the input contains only valid hexadecimal characters.
///
/// ## Errors
///
/// When `!VALIDATED` and the input contains invalid hexadecimal characters.
pub(crate) const unsafe fn decode_generic_unchecked<const VALIDATED: bool>(
    mut src: &[[u8; 2]],
    mut dst: &mut [MaybeUninit<u8>],
) -> Result<(), InvalidInput> {
    const NIL: u8 = u8::MAX;

    const LUT: [u8; u8::MAX as usize + 1] = {
        let mut lut = [NIL; _];

        let mut i = 0;

        while i <= u8::MAX as usize {
            #[allow(clippy::cast_possible_truncation, reason = "XXX")]
            match i as u8 {
                b'0'..=b'9' => {
                    lut[i] = i as u8 - b'0';
                }
                b'a'..=b'f' => {
                    lut[i] = i as u8 - b'a' + 10;
                }
                b'A'..=b'F' => {
                    lut[i] = i as u8 - b'A' + 10;
                }
                _ => {}
            };

            i += 1;
        }

        lut
    };

    debug_assert!(
        src.len() <= dst.len(),
        "implementation bug: `src` should not be longer than `dst`"
    );

    while !src.is_empty() {
        let cur_src;
        // SAFETY: `src` is not empty.
        (cur_src, src) = src.split_first().unwrap_unchecked();

        let cur_dst;
        // SAFETY: it's guaranteed by the caller that `dst` has at least the same length
        // as `src`, then `dst` is not empty.
        (cur_dst, dst) = dst.split_first_mut().unwrap_unchecked();

        let &[hi, lo] = cur_src;

        let hi = LUT[hi as usize];
        let lo = LUT[lo as usize];

        if !VALIDATED && (hi == NIL || lo == NIL) {
            return Err(InvalidInput);
        }

        cur_dst.write(hi << 4 | lo);
    }

    Ok(())
}
