use crate::Encode;

pub struct Encoder<'buf> {
    buffer: &'buf mut [u8],
    position: usize,
}

impl<'buf> Encoder<'buf> {
    #[inline]
    pub fn new(buffer: &'buf mut [u8]) -> Self {
        Self {
            buffer,
            position: 0,
        }
    }

    #[inline]
    pub fn advance(&mut self, offset: usize) {
        self.position += offset
    }

    #[inline]
    pub fn position(&self) -> usize {
        self.position
    }

    #[inline]
    pub fn buffer_mut(&mut self) -> &mut [u8] {
        &mut self.buffer[self.position..]
    }
}

impl<'buf> Encode for Encoder<'buf> {
    #[inline]
    fn encode_sint32(&mut self, n: i32) {
        let (data, size) = varint_simd::encode_zigzag(n);
        self.buffer_mut()[0..size as usize].clone_from_slice(&data[0..size as usize]);
        self.advance(size as usize);
    }

    #[inline]
    fn encode_sint64(&mut self, n: i64) {
        let (data, size) = varint_simd::encode_zigzag(n);
        self.buffer_mut()[0..size as usize].clone_from_slice(&data[0..size as usize]);
        self.advance(size as usize);
    }

    #[inline]
    fn encode_uint32(&mut self, n: u32) {
        let (data, size) = varint_simd::encode(n);
        self.buffer_mut()[0..size as usize].clone_from_slice(&data[0..size as usize]);
        self.advance(size as usize);
    }

    #[inline]
    fn encode_uint64(&mut self, n: u64) {
        let (data, size) = varint_simd::encode(n);
        self.buffer_mut()[0..size as usize].clone_from_slice(&data[0..size as usize]);
        self.advance(size as usize);
    }

    #[inline]
    fn encode_sfixed32(&mut self, n: i32) {
        self.buffer_mut()[0..std::mem::size_of::<i32>()].clone_from_slice(&n.to_le_bytes());
        self.advance(std::mem::size_of::<i32>());
    }

    #[inline]
    fn encode_sfixed64(&mut self, n: i64) {
        self.buffer_mut()[0..std::mem::size_of::<i64>()].clone_from_slice(&n.to_le_bytes());
        self.advance(std::mem::size_of::<i64>());
    }

    #[inline]
    fn encode_fixed32(&mut self, n: u32) {
        self.buffer_mut()[0..std::mem::size_of::<u32>()].clone_from_slice(&n.to_le_bytes());
        self.advance(std::mem::size_of::<u32>());
    }

    #[inline]
    fn encode_fixed64(&mut self, n: u64) {
        self.buffer_mut()[0..std::mem::size_of::<u64>()].clone_from_slice(&n.to_le_bytes());
        self.advance(std::mem::size_of::<u64>());
    }

    #[inline]
    fn encode_float(&mut self, n: f32) {
        self.buffer_mut()[0..std::mem::size_of::<f32>()].clone_from_slice(&n.to_le_bytes());
        self.advance(std::mem::size_of::<f32>());
    }

    #[inline]
    fn encode_double(&mut self, n: f64) {
        self.buffer_mut()[0..std::mem::size_of::<f64>()].clone_from_slice(&n.to_le_bytes());
        self.advance(std::mem::size_of::<f64>());
    }

    #[inline]
    fn encode_bytes(&mut self, b: &[u8]) {
        let len = b.len();
        #[allow(clippy::cast_possible_truncation)]
        self.encode_int32(len as i32);
        self.buffer_mut()[0..len].clone_from_slice(b);
        self.advance(len);
    }
}

#[derive(Debug, Default)]
pub(crate) struct SizeHint {
    size: usize,
}

impl SizeHint {
    #[inline]
    pub fn size(&self) -> usize {
        self.size
    }
}

impl Encode for SizeHint {
    #[inline]
    fn encode_sint32(&mut self, n: i32) {
        let (_, size) = varint_simd::encode_zigzag(n);
        self.size += size as usize;
    }

    #[inline]
    fn encode_sint64(&mut self, n: i64) {
        let (_, size) = varint_simd::encode_zigzag(n);
        self.size += size as usize;
    }

    #[inline]
    fn encode_uint32(&mut self, n: u32) {
        let (_, size) = varint_simd::encode(n);
        self.size += size as usize;
    }

    #[inline]
    fn encode_uint64(&mut self, n: u64) {
        let (_, size) = varint_simd::encode(n);
        self.size += size as usize;
    }

    #[inline]
    fn encode_sfixed32(&mut self, _n: i32) {
        self.size += std::mem::size_of::<i32>();
    }

    #[inline]
    fn encode_sfixed64(&mut self, _n: i64) {
        self.size += std::mem::size_of::<i64>();
    }

    #[inline]
    fn encode_fixed32(&mut self, _n: u32) {
        self.size += std::mem::size_of::<u32>();
    }

    #[inline]
    fn encode_fixed64(&mut self, _n: u64) {
        self.size += std::mem::size_of::<u64>();
    }

    #[inline]
    fn encode_float(&mut self, _n: f32) {
        self.size += std::mem::size_of::<f32>();
    }

    #[inline]
    fn encode_double(&mut self, _n: f64) {
        self.size += std::mem::size_of::<f64>();
    }

    #[inline]
    fn encode_bytes(&mut self, b: &[u8]) {
        #[allow(clippy::cast_possible_truncation)]
        self.encode_int32(b.len() as i32);
        self.size += b.len();
    }
}
