use alloc::string::String;
use bytes::BufMut;

use crate::{format::Format, Packable};

pub fn pack_bytes_slice_len<T: BufMut>(buf: &mut T, slice: &[u8]) -> usize {
    if slice.len() <= u8::MAX as usize {
        buf.put_slice(&[Format::BIN8, slice.len() as u8]);
        2
    } else if slice.len() <= u16::MAX as usize {
        let mut s = [0u8; 3];
        s[0] = Format::BIN16;
        s[1..].copy_from_slice(&(slice.len() as u16).to_be_bytes());
        buf.put_slice(&s);
        3
    } else if slice.len() <= u32::MAX as usize {
        let mut s = [0u8; 5];
        s[0] = Format::BIN32;
        s[1..].copy_from_slice(&(slice.len() as u32).to_be_bytes());
        buf.put_slice(&s);
        5
    } else {
        #[cfg(feature = "strict")]
        panic!("strict serialization enabled; the buffer is too large");
        #[allow(unreachable_code)]
        return 0;
    }
}

/// Packs a byte slice as msgpack binary, returning the amount of written bytes.
pub fn pack_bytes<T: BufMut>(buf: &mut T, slice: &[u8]) -> usize {
    let n = pack_bytes_slice_len(buf, slice);
    buf.put_slice(slice);
    n + slice.len()
}

/// Packs `Option<&[u8]>` as msgpack nil (None) or binary (Some), returning bytes written.
pub fn pack_bytes_option<T: BufMut>(buf: &mut T, v: Option<&[u8]>) -> usize {
    match v {
        None => {
            buf.put_u8(crate::format::Format::NIL);
            1
        }
        Some(slice) => pack_bytes(buf, slice),
    }
}

impl Packable for str {
    fn pack<T>(&self, buf: &mut T) -> usize
    where
        T: BufMut,
    {
        let n = if self.len() <= 31 {
            buf.put_u8((self.len() as u8 & 0x1f) | 0xa0);
            1
        } else if self.len() <= u8::MAX as usize {
            buf.put_slice(&[Format::STR8, self.len() as u8]);
            2
        } else if self.len() <= u16::MAX as usize {
            let mut s = [0u8; 3];
            s[0] = Format::STR16;
            s[1..].copy_from_slice(&(self.len() as u16).to_be_bytes());
            buf.put_slice(&s);
            3
        } else if self.len() <= u32::MAX as usize {
            let mut s = [0u8; 5];
            s[0] = Format::STR32;
            s[1..].copy_from_slice(&(self.len() as u32).to_be_bytes());
            buf.put_slice(&s);
            5
        } else {
            #[cfg(feature = "strict")]
            panic!("strict serialization enabled; the buffer is too large");
            #[allow(unreachable_code)]
            return 0;
        };
        buf.put_slice(self.as_bytes());
        n + self.len()
    }
}

impl Packable for String {
    fn pack<T>(&self, buf: &mut T) -> usize
    where
        T: BufMut,
    {
        self.as_str().pack(buf)
    }
}
