Documentation
use super::*;
use bytes::{Buf, Bytes};

pub(super) type Result<T> = std::result::Result<T, DecodeError>;

macro_rules! incomplete_if_less_than {
    ($buf:ident, $len:expr) => {
        if $buf.remaining() < $len {
            return Err(DecodeError::InComplete);
        }
    };
}

pub(super) fn eat_stream_id<B: Buf>(buf: &mut B) -> Result<u32> {
    incomplete_if_less_than!(buf, 4);

    let stream_id = buf.get_u32();
    Ok(stream_id & MAX_U31)
}

pub(super) fn eat_flags<B: Buf>(buf: &mut B) -> Result<(FrameType, Flags)> {
    incomplete_if_less_than!(buf, 2);

    let flags = buf.get_u16();
    let ft = flags >> 10;
    let frame_type = match FrameType::from_bits(flags) {
        Some(frame_type) => frame_type,
        None => return Err(DecodeError::UnrecognizedFrameType(ft)),
    };
    let flags = Flags::from_bits_truncate(flags);

    Ok((frame_type, flags))
}

pub(super) fn eat_version<B: Buf>(buf: &mut B) -> Result<Version> {
    incomplete_if_less_than!(buf, 4);

    let major = buf.get_u16();
    let minor = buf.get_u16();
    Ok(Version::new(major, minor))
}

pub(super) fn eat_u8<B: Buf>(buf: &mut B) -> Result<u8> {
    incomplete_if_less_than!(buf, 2);

    Ok(buf.get_u8())
}

pub(super) fn eat_u16<B: Buf>(buf: &mut B) -> Result<u16> {
    incomplete_if_less_than!(buf, 2);

    Ok(buf.get_u16())
}

pub(super) fn eat_u24<B: Buf>(buf: &mut B) -> Result<U24> {
    let high = eat_u8(buf)?;
    let low = eat_u16(buf)?;
    Ok(U24::new(high, low))
}

pub(super) fn eat_u31<B: Buf>(buf: &mut B) -> Result<u32> {
    incomplete_if_less_than!(buf, 4);

    Ok(buf.get_u32() & MAX_U31)
}

pub(super) fn eat_u32<B: Buf>(buf: &mut B) -> Result<u32> {
    incomplete_if_less_than!(buf, 4);

    Ok(buf.get_u32())
}

pub(super) fn eat_u63<B: Buf>(buf: &mut B) -> Result<u64> {
    incomplete_if_less_than!(buf, 8);

    Ok(buf.get_u64() & MAX_U63)
}

pub(super) fn eat_bytes<B: Buf>(buf: &mut B, len: usize) -> Result<Bytes> {
    incomplete_if_less_than!(buf, len);

    Ok(buf.copy_to_bytes(len))
}

pub(super) fn eat_payload<B: Buf>(
    buf: &mut B,
    can_have_data: bool,
) -> Result<Payload> {
    let metadata_len =
        if can_have_data { eat_u24(buf)?.into_usize() } else { 0 };
    let metadata = if metadata_len > 0 {
        Some(eat_bytes(buf, metadata_len)?)
    } else {
        None
    };
    let data = match buf.remaining() {
        0 => None,
        len => Some(eat_bytes(buf, len)?),
    };
    Ok(Payload::new(metadata, data))
}

pub(super) fn eat_resume_token<B: Buf>(
    buf: &mut B,
    flags: Flags,
) -> Result<Option<Bytes>> {
    let resume_token = if flags.contains(Flags::RESUME) {
        let token_len = eat_u16(buf)?;
        Some(eat_bytes(buf, token_len as usize)?)
    } else {
        None
    };
    Ok(resume_token)
}

#[cfg(test)]
mod tests {
    use super::*;
    use bytes::{BufMut, BytesMut};

    #[test]
    fn test_eat_flags() {
        let mut invalid_flags = BytesMut::new();
        invalid_flags.put_u16(
            (0x2F << 10)
                | Flags::METADATA.bits()
                | Flags::IGNORE.bits()
                | Flags::RESUME.bits(),
        );

        let mut valid_flags = BytesMut::new();
        valid_flags.put_u16(
            FrameType::SETUP.bits()
                | Flags::METADATA.bits()
                | Flags::IGNORE.bits()
                | Flags::RESUME.bits()
                | 0b01
                | 0b10,
        );

        assert_eq!(
            eat_flags(&mut invalid_flags),
            Err(DecodeError::UnrecognizedFrameType(0x2F))
        );
        assert_eq!(
            eat_flags(&mut valid_flags),
            Ok((
                FrameType::SETUP,
                Flags::METADATA | Flags::IGNORE | Flags::RESUME
            ))
        );
    }
}