#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
    TooLarge,
    Underflow,
}

pub struct VarInt;

impl VarInt {
    pub const MAX: u64 = (1u64 << 62) - 1;

    pub fn size_for(value: u64) -> Result<usize, Error> {
        match value {
            0..=63 => Ok(1),
            64..=16_383 => Ok(2),
            16_384..=1_073_741_823 => Ok(4),
            v if v <= Self::MAX => Ok(8),
            _ => Err(Error::TooLarge),
        }
    }

    pub fn encode(value: u64, out: &mut Vec<u8>) -> Result<(), Error> {
        match value {
            0..=63 => out.push(value as u8),
            64..=16_383 => {
                let v = (value as u16) | 0x4000;
                out.extend_from_slice(&v.to_be_bytes());
            }
            16_384..=1_073_741_823 => {
                let v = (value as u32) | 0x8000_0000;
                out.extend_from_slice(&v.to_be_bytes());
            }
            v if v <= Self::MAX => {
                let w = v | 0xC000_0000_0000_0000;
                out.extend_from_slice(&w.to_be_bytes());
            }
            _ => return Err(Error::TooLarge),
        }
        Ok(())
    }

    pub fn decode(input: &[u8]) -> Result<(u64, usize), Error> {
        let first = *input.first().ok_or(Error::Underflow)?;
        let len = 1usize << (first >> 6);
        if input.len() < len {
            return Err(Error::Underflow);
        }
        let mut buf = [0u8; 8];
        buf[8 - len..].copy_from_slice(&input[..len]);
        let mut value = u64::from_be_bytes(buf);
        value &= (1u64 << (len * 8 - 2)) - 1;
        Ok((value, len))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn round_trips_boundaries() {
        for value in [0, 63, 64, 16_383, 16_384, 1_073_741_823, VarInt::MAX] {
            let mut out = Vec::new();
            VarInt::encode(value, &mut out).unwrap();
            assert_eq!(VarInt::decode(&out), Ok((value, out.len())));
        }
    }
}
