use std::borrow::Borrow;
use std::collections::HashMap;
use std::fmt::Write;
use std::str;

use bytes::{Buf, BufMut, Bytes, BytesMut};

pub struct TlvBox {
    m_objects: HashMap<i32, Bytes>,
    m_total_bytes: usize,
}

impl Clone for TlvBox {
    fn clone(&self) -> Self {
        TlvBox {
            m_objects: self.m_objects.clone(),
            m_total_bytes: self.m_total_bytes.clone(),
        }
    }
}

impl TlvBox {
    pub fn new() -> TlvBox {
        TlvBox {
            m_objects: HashMap::new(),
            m_total_bytes: 0,
        }
    }

    pub fn parse(buffer: Bytes, offset: i32, length: usize) -> TlvBox {
        let mut tlv_box = TlvBox::new();
        let mut parsed = 0;
        while parsed < length {
            let buffer_vec = buffer.to_vec();

            let type_start = offset as usize + parsed;
            let mut type_mut = BytesMut::with_capacity(4);
            type_mut.put(&buffer_vec[type_start..type_start + 4]);
            let typ = type_mut.get_i32();
            parsed += 4;

            let length_start = offset as usize + parsed;
            let mut size_mut = BytesMut::with_capacity(4);
            size_mut.put(&buffer_vec[length_start..length_start + 4]);
            let size = size_mut.get_i32();
            parsed += 4;

            let value_start = offset as usize + parsed;
            let mut value_mut = BytesMut::with_capacity(4);
            value_mut.put(&buffer_vec[value_start..value_start + size as usize]);

            tlv_box.put_bytes_value(typ as i32, value_mut.freeze());

            parsed += size as usize;
        }

        tlv_box
    }

    pub fn put_i16_value(&mut self, typ: i32, value: i16) {
        let mut buf = Vec::with_capacity(2);
        buf.put_i16(value);
        self.put_bytes_value(typ, Bytes::from(buf))
    }

    pub fn put_i32_value(&mut self, typ: i32, value: i32) {
        let mut buf = Vec::with_capacity(4);
        buf.put_i32(value);
        self.put_bytes_value(typ, Bytes::from(buf))
    }

    pub fn put_i64_value(&mut self, typ: i32, value: i64) {
        let mut buf = Vec::with_capacity(8);
        buf.put_i64(value);
        self.put_bytes_value(typ, Bytes::from(buf))
    }

    pub fn put_f32_value(&mut self, typ: i32, value: f32) {
        let mut buf = Vec::with_capacity(4);
        buf.put_f32(value);
        self.put_bytes_value(typ, Bytes::from(buf))
    }

    pub fn put_f64_value(&mut self, typ: i32, value: f64) {
        let mut buf = Vec::with_capacity(8);
        buf.put_f64(value);
        self.put_bytes_value(typ, Bytes::from(buf))
    }

    pub fn put_string_value(&mut self, typ: i32, value: String) {
        let len = value.clone().len();
        let mut byts = BytesMut::with_capacity(len);
        write!(byts, "{}", &value).unwrap();

        self.put_bytes_value(typ, byts.freeze());
    }

    pub fn put_object_value(&mut self, typ: i32, value: TlvBox) {
        self.put_bytes_value(typ, value.clone().serialize());
    }

    pub fn put_bytes_value(&mut self, typ: i32, value: Bytes) {
        self.m_objects.insert(typ, value.clone());
        self.m_total_bytes += value.len() + 8;
    }

    pub fn serialize(&mut self) -> Bytes {
        let mut offset = 0;
        let mut result = (0..u8::from(self.m_total_bytes as u8)).collect::<Vec<_>>();
        let keys = self.m_objects.keys();
        for key in keys.clone().into_iter() {
            let bytes = self.m_objects.get(&key.clone()).unwrap();
            let mut typ = BytesMut::with_capacity(4);
            typ.put_i32(key.clone());
            let mut length = BytesMut::with_capacity(4);
            length.put_i32(bytes.clone().len() as i32);

            result[offset..typ.len()].clone_from_slice(&typ.clone().to_vec());
            offset += 4;
            result[offset..offset + length.len()].clone_from_slice(&length.clone().to_vec());
            offset += 4;
            result[offset..offset + bytes.len()].clone_from_slice(&bytes.clone().to_vec());
            offset += bytes.len();
        }
        Bytes::from(result.clone())
    }

    pub fn get_bytes_value(&self, typ: i32) -> Option<Bytes> {
        let bytes = self.m_objects.get(typ.clone().borrow());
        match bytes {
            None => None,
            Some(byts) => Some(byts.clone()),
        }
    }

    pub fn get_i16_value(&self, typ: i32) -> Option<i16> {
        let bytes = self.m_objects.get(typ.clone().borrow());
        match bytes {
            Some(x) => Some(x.clone().get_i16()),
            None => None,
        }
    }

    pub fn get_i32_value(&self, typ: i32) -> Option<i32> {
        let bytes = self.m_objects.get(typ.clone().borrow());
        match bytes {
            Some(x) => Some(x.clone().get_i32()),
            None => None,
        }
    }

    pub fn get_i64_value(&self, typ: i32) -> Option<i64> {
        let bytes = self.m_objects.get(typ.clone().borrow());
        match bytes {
            Some(x) => Some(x.clone().get_i64()),
            None => None,
        }
    }
    pub fn get_f32_value(&self, typ: i32) -> Option<f32> {
        let bytes = self.m_objects.get(typ.clone().borrow());
        match bytes {
            Some(x) => Some(x.clone().get_f32()),
            None => None,
        }
    }
    pub fn get_f64_value(&self, typ: i32) -> Option<f64> {
        let bytes = self.m_objects.get(typ.clone().borrow());
        match bytes {
            Some(x) => Some(x.clone().get_f64()),
            None => None,
        }
    }
    pub fn get_string_value(&self, typ: i32) -> Option<String> {
        let value = self.get_bytes_value(typ);
        let buf = value.clone().unwrap().to_vec();

        match str::from_utf8(&buf) {
            Ok(v) => Some(String::from(v)),
            Err(_e) => None,
        }
    }

    pub fn get_object_value(&self, typ: i32) -> Option<TlvBox> {
        let option = self.m_objects.get(&typ);
        match option {
            None => None,
            Some(bytes) => {
                let tlv_box = TlvBox::parse(bytes.clone(), 0, bytes.len());
                Some(tlv_box)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use bytes::{BufMut, Bytes, BytesMut};

    use crate::tlv::tlvbox::TlvBox;

    #[test]
    fn test_parse_bytes() {
        let mut tlv_box = TlvBox::new();

        let mut buf = BytesMut::with_capacity(1024);
        buf.put(&b"hello world"[..]);
        tlv_box.put_bytes_value(01, buf.clone().freeze());
        let value = tlv_box.get_bytes_value(01);

        assert_eq!(11, value.clone().unwrap().len());
        assert_eq!(&b"hello world"[..], value.clone().unwrap());
    }

    #[test]
    fn test_parse_strings() {
        let mut tlv_box = TlvBox::new();
        tlv_box.put_string_value(01, String::from("hello, world"));
        let value = tlv_box.get_bytes_value(01);

        assert_eq!(12, value.clone().unwrap().len());

        let a = format!("{:?}", Bytes::from(value.clone().unwrap()));

        assert_eq!("b\"hello, world\"", a);

        assert_eq!("hello, world", tlv_box.get_string_value(01).unwrap());

        assert_eq!(1, tlv_box.m_objects.len());
        assert_eq!(20, tlv_box.m_total_bytes);
    }

    #[test]
    fn test_convert_short() {
        let mut tlv_box = TlvBox::new();
        tlv_box.put_i16_value(01, 12);
        assert_eq!(12, tlv_box.get_i16_value(01).unwrap());
    }

    #[test]
    fn test_convert_double() {
        let mut tlv_box = TlvBox::new();
        let value = -179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0000000000000000;
        tlv_box.put_f64_value(01, value);
        assert_eq!(value, tlv_box.get_f64_value(01).unwrap());
    }

    #[test]
    fn test_convert_int() {
        let mut tlv_box = TlvBox::new();
        let value = 2332;
        tlv_box.put_i32_value(01, value);
        assert_eq!(value, tlv_box.get_i32_value(01).unwrap());
    }

    #[test]
    fn test_convert_long() {
        let mut tlv_box = TlvBox::new();
        let value = 2332;
        tlv_box.put_i64_value(01, value);
        assert_eq!(value, tlv_box.get_i64_value(01).unwrap());
    }

    #[test]
    fn test_convert_float() {
        let mut tlv_box = TlvBox::new();
        let value = 340282346638528859811704183484516925440.0000000000000000;
        tlv_box.put_f32_value(01, value);
        assert_eq!(value, tlv_box.get_f32_value(01).unwrap());
    }

    #[test]
    fn test_convert_object() {
        let mut tlv_test_obj = TlvBox::new();
        let value = 340282346638528859811704183484516925440.0000000000000000;
        tlv_test_obj.put_f32_value(02, value);
        assert_eq!(12, tlv_test_obj.m_total_bytes);

        let mut tlv_box = TlvBox::new();
        tlv_box.put_object_value(01, tlv_test_obj);

        assert_eq!(1, tlv_box.m_objects.len());
        assert_eq!(20, tlv_box.m_total_bytes);

        let serialized = tlv_box.serialize();
        let result_box = TlvBox::parse(serialized.clone(), 0, serialized.clone().len());

        assert_eq!(1, result_box.m_objects.len());
        assert_eq!(20, result_box.m_total_bytes);
    }

    #[test]
    fn test_convert_object_with_value() {
        let mut tlv_test_obj = TlvBox::new();
        let value = 1233.00;
        tlv_test_obj.put_f32_value(02, value);

        let serialized = tlv_test_obj.serialize();
        let result_box = TlvBox::parse(serialized.clone(), 0, serialized.clone().len());

        assert_eq!(tlv_test_obj.get_f32_value(02), result_box.get_f32_value(02));
    }

    #[test]
    fn test_convert_object_string() {
        let mut tlv_box1 = TlvBox::new();
        tlv_box1.put_string_value(01, String::from("helloo"));

        let mut tlv_box = TlvBox::new();
        tlv_box.put_object_value(02, tlv_box1);

        let tlv_box2 = tlv_box.get_object_value(02).unwrap();
        assert_eq!("helloo", tlv_box2.get_string_value(01).unwrap());
    }

    #[test]
    fn test_convert_object_floaw() {
        let mut tlv_box1 = TlvBox::new();
        tlv_box1.put_f32_value(01, 3333.33);

        let mut tlv_box = TlvBox::new();
        tlv_box.put_object_value(02, tlv_box1);

        let tlv_box2 = tlv_box.get_object_value(02).unwrap();
        assert_eq!(3333.33, tlv_box2.get_f32_value(01).unwrap());
    }

    #[test]
    fn test_encode_tlv() {
        let attrs_raw = vec![0, 0, 0, 6, 0, 0, 0, 5,  72, 101, 108, 108, 111];
        let mut tlv_box = TlvBox::new();
        tlv_box.put_string_value(6, String::from("Hello"));
        assert_eq!(tlv_box.serialize().to_vec(), attrs_raw);
    }
}
