use core::ptr;

use super::super::config::Base32EncodeConfig;
use super::super::error::Base32Error;

#[inline(always)]
pub(crate) fn encode_full_group_into(
    config: &Base32EncodeConfig,
    dst: &mut [u8],
    src: &[u8],
) -> Result<usize, Base32Error> {
    debug_assert_eq!(src.len() % 5, 0, "src length must be a multiple of 5");

    let alphabet_ptr = config.alphabet.as_ptr();
    let mut dst_offset = 0usize;

    for chunk in src.chunks_exact(5) {
        if dst_offset + 8 > dst.len() {
            return Err(Base32Error::DestinationBufferTooSmall {
                needed: src.len() / 5 * 8,
                provided: dst.len(),
            });
        }

        let b0 = chunk[0] as u32;
        let b1 = chunk[1] as u32;
        let b2 = chunk[2] as u32;
        let b3 = chunk[3] as u32;
        let b4 = chunk[4] as u32;

        let c0 = ((b0 >> 3) & 0x1F) as usize;
        let c1 = (((b0 << 2) | (b1 >> 6)) & 0x1F) as usize;
        let c2 = ((b1 >> 1) & 0x1F) as usize;
        let c3 = (((b1 << 4) | (b2 >> 4)) & 0x1F) as usize;
        let c4 = (((b2 << 1) | (b3 >> 7)) & 0x1F) as usize;
        let c5 = ((b3 >> 2) & 0x1F) as usize;
        let c6 = (((b3 << 3) | (b4 >> 5)) & 0x1F) as usize;
        let c7 = (b4 & 0x1F) as usize;

        let ptr = dst.as_mut_ptr();

        unsafe {
            ptr.add(dst_offset)
                .write(ptr::read_unaligned(alphabet_ptr.add(c0)));
            ptr.add(dst_offset + 1)
                .write(ptr::read_unaligned(alphabet_ptr.add(c1)));
            ptr.add(dst_offset + 2)
                .write(ptr::read_unaligned(alphabet_ptr.add(c2)));
            ptr.add(dst_offset + 3)
                .write(ptr::read_unaligned(alphabet_ptr.add(c3)));
            ptr.add(dst_offset + 4)
                .write(ptr::read_unaligned(alphabet_ptr.add(c4)));
            ptr.add(dst_offset + 5)
                .write(ptr::read_unaligned(alphabet_ptr.add(c5)));
            ptr.add(dst_offset + 6)
                .write(ptr::read_unaligned(alphabet_ptr.add(c6)));
            ptr.add(dst_offset + 7)
                .write(ptr::read_unaligned(alphabet_ptr.add(c7)));
        }

        dst_offset += 8;
    }

    Ok(dst_offset)
}
