use crate::base64::config::Base64DecodeConfig;
use crate::base64::error::Base64Error;

#[inline(always)]
pub(crate) fn decode_full_group_into(
    config: &Base64DecodeConfig,
    dst: &mut [u8],
    src: &[u8],
    src_offset: usize,
) -> Result<usize, Base64Error> {
    debug_assert_eq!(src.len(), 4);

    let b0 = src[0];
    let b1 = src[1];
    let b2 = src[2];
    let b3 = src[3];

    if config.padding == Some(b0)
        || config.padding == Some(b1)
        || config.padding == Some(b2)
        || config.padding == Some(b3)
    {
        return Err(Base64Error::InvalidPadding);
    }

    if (b0 | b1 | b2 | b3) & 0x80 != 0 {
        let pos = if b0 & 0x80 != 0 {
            0
        } else if b1 & 0x80 != 0 {
            1
        } else if b2 & 0x80 != 0 {
            2
        } else {
            3
        };
        return Err(Base64Error::InvalidCharacter(src[pos], src_offset + pos));
    }

    let decode_table = config.decode_table;
    let i0 = decode_table[b0 as usize];
    let i1 = decode_table[b1 as usize];
    let i2 = decode_table[b2 as usize];
    let i3 = decode_table[b3 as usize];

    if i0 < 0 || i1 < 0 || i2 < 0 || i3 < 0 {
        let pos = if i0 < 0 {
            0
        } else if i1 < 0 {
            1
        } else if i2 < 0 {
            2
        } else {
            3
        };
        return Err(Base64Error::InvalidCharacter(src[pos], src_offset + pos));
    }

    let triple = ((i0 as u32) << 18) | ((i1 as u32) << 12) | ((i2 as u32) << 6) | (i3 as u32);

    dst[0] = (triple >> 16) as u8;
    dst[1] = (triple >> 8) as u8;
    dst[2] = triple as u8;

    Ok(3)
}
