use super::super::config::Base16DecodeConfig;
use super::super::error::Base16Error;

#[cfg(all(
    any(target_arch = "x86", target_arch = "x86_64"),
    feature = "simd-avx2"
))]
use crate::cpu_features::is_available_feature_simd_avx2;
#[cfg(all(
    any(target_arch = "x86", target_arch = "x86_64"),
    feature = "simd-avx512"
))]
use crate::cpu_features::is_available_feature_simd_avx512;
#[cfg(all(
    any(target_arch = "x86", target_arch = "x86_64"),
    feature = "simd-ssse3"
))]
use crate::cpu_features::is_available_feature_simd_ssse3;

#[cfg(all(
    any(target_arch = "x86", target_arch = "x86_64"),
    feature = "simd-avx2"
))]
use super::simd::avx2::avx2_decode_into;
#[cfg(all(
    any(target_arch = "x86", target_arch = "x86_64"),
    feature = "simd-avx512"
))]
use super::simd::avx512::avx512_decode_into;
#[cfg(all(
    any(target_arch = "x86", target_arch = "x86_64"),
    feature = "simd-ssse3"
))]
use super::simd::ssse3::ssse3_decode_into;

#[inline]
pub fn decode_into(
    config: &Base16DecodeConfig,
    dst: &mut [u8],
    src: &[u8],
) -> Result<usize, Base16Error> {
    if src.is_empty() {
        return Ok(0);
    }

    if src.len() % 2 != 0 {
        return Err(Base16Error::InvalidLength(src.len()));
    }

    let output_len = src.len() / 2;

    if dst.len() < output_len {
        return Err(Base16Error::DestinationBufferTooSmall {
            needed: output_len,
            provided: dst.len(),
        });
    }

    let mut src_offset = 0usize;
    let mut dst_offset = 0usize;

    #[cfg(all(
        any(target_arch = "x86", target_arch = "x86_64"),
        feature = "simd-avx512"
    ))]
    if is_available_feature_simd_avx512() {
        let written =
            unsafe { avx512_decode_into(config, &mut dst[dst_offset..], &src[src_offset..]) };
        src_offset += written * 2;
        dst_offset += written;
    }

    #[cfg(all(
        any(target_arch = "x86", target_arch = "x86_64"),
        feature = "simd-avx2"
    ))]
    if is_available_feature_simd_avx2() {
        let written =
            unsafe { avx2_decode_into(config, &mut dst[dst_offset..], &src[src_offset..]) };
        src_offset += written * 2;
        dst_offset += written;
    }

    #[cfg(all(
        any(target_arch = "x86", target_arch = "x86_64"),
        feature = "simd-ssse3"
    ))]
    if is_available_feature_simd_ssse3() {
        let written =
            unsafe { ssse3_decode_into(config, &mut dst[dst_offset..], &src[src_offset..]) };
        src_offset += written * 2;
        dst_offset += written;
    }

    let decode_table = config.decode_table;

    unsafe {
        while src_offset < src.len() {
            let high_nibble = src[src_offset];
            let low_nibble = src[src_offset + 1];

            if high_nibble >= 128 {
                return Err(Base16Error::InvalidCharacter(high_nibble, src_offset));
            }
            if low_nibble >= 128 {
                return Err(Base16Error::InvalidCharacter(low_nibble, src_offset + 1));
            }

            let high_val = decode_table[high_nibble as usize];
            let low_val = decode_table[low_nibble as usize];

            if high_val < 0 {
                return Err(Base16Error::InvalidCharacter(high_nibble, src_offset));
            }
            if low_val < 0 {
                return Err(Base16Error::InvalidCharacter(low_nibble, src_offset + 1));
            }

            let ptr = dst.as_mut_ptr().add(dst_offset);
            ptr.write(((high_val as u8) << 4) | (low_val as u8));

            src_offset += 2;
            dst_offset += 1;
        }

        Ok(output_len)
    }
}
