use std::io::{
    self,
    Write,
};

use bytes::Buf;

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Type {
    Varint = 0,
    Fixed64 = 1,
    LengthDelimited = 2,
    Fixed32 = 5,
}

impl Type {
    fn as_u32(self) -> u32 {
        match self {
            Type::Varint => 0,
            Type::Fixed64 => 1,
            Type::LengthDelimited => 2,
            Type::Fixed32 => 5,
        }
    }

    fn from_u32(val: u32) -> io::Result<Self> {
        match val {
            0 => Ok(Type::Varint),
            1 => Ok(Type::Fixed64),
            2 => Ok(Type::LengthDelimited),
            5 => Ok(Type::Fixed32),
            v => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Unexpected tag type: {}", v),
            )),
        }
    }
}

#[inline]
pub fn serialized_length(field_number: u32) -> usize {
    super::varint::serialized_length(u64::from(field_number << 3))
}

#[inline]
pub fn write<W: Write>(field_number: u32, typ: Type, w: &mut W) -> io::Result<()> {
    let encoded = (field_number << 3) | typ.as_u32();
    super::varint::write(u64::from(encoded), w)
}

#[inline]
pub fn read<B: Buf>(buf: &mut B) -> io::Result<Option<(u32, Type)>> {
    let encoded = match super::varint::read(buf)? {
        Some(v) => v,
        None => return Ok(None),
    };

    let field_number = (encoded >> 3) as u32;
    let typ = Type::from_u32((encoded & 0x7) as u32)?;
    Ok(Some((field_number, typ)))
}
