use crate::{
    errors::{Error, Result},
    types::BoltWireFormat,
    version::Version,
    DeError,
};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::{borrow::Borrow, str::from_utf8};
use std::{fmt::Display, mem};
pub const TINY: u8 = 0x80;
pub const SMALL: u8 = 0xD0;
pub const MEDIUM: u8 = 0xD1;
pub const LARGE: u8 = 0xD2;
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct BoltString {
    pub value: String,
}
impl BoltString {
    pub fn new(value: &str) -> Self {
        BoltString {
            value: value.to_string(),
        }
    }
}
impl Display for BoltString {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.value)
    }
}
impl From<&str> for BoltString {
    fn from(v: &str) -> Self {
        BoltString::new(v)
    }
}
impl From<String> for BoltString {
    fn from(value: String) -> Self {
        BoltString { value }
    }
}
impl From<BoltString> for String {
    fn from(value: BoltString) -> Self {
        value.value
    }
}
impl Borrow<str> for BoltString {
    fn borrow(&self) -> &str {
        &self.value
    }
}
impl BoltWireFormat for BoltString {
    fn can_parse(_version: Version, input: &[u8]) -> bool {
        let marker = (*input)[0];
        (TINY..=(TINY | 0x0F)).contains(&marker)
            || marker == SMALL
            || marker == MEDIUM
            || marker == LARGE
    }
    fn parse(_version: Version, input: &mut Bytes) -> Result<Self> {
        let marker = input.get_u8();
        let length = match marker {
            0x80..=0x8F => 0x0F & marker as usize,
            SMALL => input.get_u8() as usize,
            MEDIUM => input.get_u16() as usize,
            LARGE => input.get_u32() as usize,
            _ => {
                return Err(Error::InvalidTypeMarker(format!(
                    "invalid string marker {}",
                    marker
                )))
            }
        };
        let bytes = input.split_to(length);
        match from_utf8(&bytes) {
            Ok(t) => Ok(t.into()),
            Err(e) => Err(Error::DeserializationError(DeError::Other(e.to_string()))),
        }
    }
    fn write_into(&self, _version: Version, bytes: &mut BytesMut) -> Result<()> {
        let required_bytes = self.value.len();
        match self.value.len() {
            0..=15 => {
                bytes.reserve(1 + required_bytes);
                bytes.put_u8(TINY | self.value.len() as u8);
            }
            16..=255 => {
                bytes.reserve(2 + required_bytes);
                bytes.put_u8(SMALL);
                bytes.put_u8(self.value.len() as u8);
            }
            256..=65_535 => {
                bytes.reserve(1 + mem::size_of::<u16>() + required_bytes);
                bytes.put_u8(MEDIUM);
                bytes.put_u16(self.value.len() as u16);
            }
            65_536..=4_294_967_295 => {
                bytes.reserve(1 + mem::size_of::<u32>() + required_bytes);
                bytes.put_u8(LARGE);
                bytes.put_u32(self.value.len() as u32);
            }
            _ => return Err(Error::StringTooLong),
        };
        bytes.put_slice(self.value.as_bytes());
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use bytes::Bytes;
    use super::*;
    #[test]
    fn should_serialize_empty_string() {
        let s = BoltString::new("");
        let b: Bytes = s.into_bytes(Version::V4_1).unwrap();
        assert_eq!(&b[..], Bytes::from_static(&[TINY]));
    }
    #[test]
    fn should_deserialize_empty_string() {
        let mut input = Bytes::from_static(&[TINY]);
        let s: BoltString = BoltString::parse(Version::V4_1, &mut input).unwrap();
        assert_eq!(s, "".into());
    }
    #[test]
    fn should_serialize_tiny_string() {
        let s = BoltString::new("a");
        let b: Bytes = s.into_bytes(Version::V4_1).unwrap();
        assert_eq!(&b[..], Bytes::from_static(&[0x81, 0x61]));
    }
    #[test]
    fn should_deserialize_tiny_string() {
        let mut serialized_bytes = Bytes::from_static(&[0x81, 0x61]);
        let result: BoltString = BoltString::parse(Version::V4_1, &mut serialized_bytes).unwrap();
        assert_eq!(result, "a".into());
    }
    #[test]
    fn should_serialize_small_string() {
        let s = BoltString::new(&"a".repeat(16));
        let mut b: Bytes = s.into_bytes(Version::V4_1).unwrap();
        assert_eq!(b.get_u8(), SMALL);
        assert_eq!(b.get_u8(), 0x10);
        assert_eq!(b.len(), 0x10);
        for value in b {
            assert_eq!(value, 0x61);
        }
    }
    #[test]
    fn should_deserialize_small_string() {
        let mut serialized_bytes = Bytes::from_static(&[SMALL, 0x01, 0x61]);
        let result: BoltString = BoltString::parse(Version::V4_1, &mut serialized_bytes).unwrap();
        assert_eq!(result, "a".into());
    }
    #[test]
    fn should_serialize_medium_string() {
        let s = BoltString::new(&"a".repeat(256));
        let mut b: Bytes = s.into_bytes(Version::V4_1).unwrap();
        assert_eq!(b.get_u8(), MEDIUM);
        assert_eq!(b.get_u16(), 0x100);
        assert_eq!(b.len(), 0x100);
        for value in b {
            assert_eq!(value, 0x61);
        }
    }
    #[test]
    fn should_deserialize_medium_string() {
        let mut serialized_bytes = Bytes::from_static(&[MEDIUM, 0x00, 0x01, 0x61]);
        let result: BoltString = BoltString::parse(Version::V4_1, &mut serialized_bytes).unwrap();
        assert_eq!(result, "a".into());
    }
    #[test]
    fn should_serialize_large_string() {
        let s = BoltString::new(&"a".repeat(65_536));
        let mut b: Bytes = s.into_bytes(Version::V4_1).unwrap();
        assert_eq!(b.get_u8(), LARGE);
        assert_eq!(b.get_u32(), 0x10000);
        assert_eq!(b.len(), 0x10000);
        for value in b {
            assert_eq!(value, 0x61);
        }
    }
    #[test]
    fn should_deserialize_large_string() {
        let mut serialized_bytes = Bytes::from_static(&[LARGE, 0x00, 0x00, 0x00, 0x01, 0x61]);
        let result: BoltString = BoltString::parse(Version::V4_1, &mut serialized_bytes).unwrap();
        assert_eq!(result, "a".into());
    }
}