use crate::{EncodedValue, Error, ReadSeek, Result, Serializable, Uleb128};
use log::warn;
use std::io::Write;

// To derive Serializable
use crate as androscalpel_serializer;

/// <https://source.android.com/docs/core/runtime/dex-format#encoded-annotation>
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncodedAnnotation {
    /// Type of the annotation. This must be a class (not array or primitive) type.
    pub type_idx: Uleb128, // TODO: check it's a class
    //pub size: Uleb128,
    pub elements: Vec<AnnotationElement>,
}

impl EncodedAnnotation {
    /// Return the size field
    pub fn size_field(&self) -> Uleb128 {
        Uleb128(self.elements.len() as u32)
    }
}

/// <https://source.android.com/docs/core/runtime/dex-format#annotation-element>
#[derive(Serializable, Debug, Clone, PartialEq, Eq)]
pub struct AnnotationElement {
    pub name_idx: Uleb128,
    pub value: EncodedValue,
}

impl Serializable for EncodedAnnotation {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        let Self { type_idx, elements } = self;
        // Check if the element are sorted
        if !elements.is_empty() {
            let mut idx_0 = elements[0].name_idx;
            for idx_1 in elements.iter().map(|elt| elt.name_idx).skip(1) {
                if idx_0 > idx_1 {
                    return Err(Error::SerializationError(format!(
                        "Failed to serialize EncodedAnnotation: element must be sorted in increasing order by string_id index, found {idx_0:?} before {idx_1:?}"
                    )));
                }
                idx_0 = idx_1;
            }
        }
        type_idx.serialize(output)?;
        self.size_field().serialize(output)?;
        for elt in elements {
            elt.serialize(output)?;
        }
        Ok(())
    }
    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let type_idx = Uleb128::deserialize(input)?;
        let Uleb128(size) = Uleb128::deserialize(input)?;
        let mut elements = vec![];
        for _ in 0..size {
            elements.push(AnnotationElement::deserialize(input)?);
        }
        if !elements.is_empty() {
            let mut idx_0 = elements[0].name_idx;
            for idx_1 in elements.iter().map(|elt| elt.name_idx).skip(1) {
                if idx_0 > idx_1 {
                    warn!(
                        "while deserializing EncodedAnnotation: element must be sorted in increasing order by string_id index, found {idx_0:?} before {idx_1:?}"
                    );
                }
                idx_0 = idx_1;
            }
        }
        Ok(Self { type_idx, elements })
    }

    fn size(&self) -> usize {
        self.type_idx.size()
            + self.size_field().size()
            + self.elements.iter().map(|val| val.size()).sum::<usize>()
    }
}

// TODO: add more tests
#[cfg(test)]
mod test {
    use super::*;

    const ENCODED_ANNOTATION_RAW: &[u8] = &[
        0xbe, 0x37, 0x01, 0xa8, 0x9f, 0x03, 0x1d, 0xf6, 0x33, 0x01, 0xd3, 0xe8, 0x02, 0x3f,
    ];
    const ENCODED_ANNOTATION_RAW_2: &[u8] = &[0xf6, 0x33, 0x01, 0xd3, 0xe8, 0x02, 0x3f];
    const ENCODED_ANNOTATION_RAW_3: &[u8] = &[0xbf, 0x37, 0x01, 0xa8, 0x9f, 0x03, 0x38, 0x38, 0x02];
    const ANNOTATION_ELEMENT_RAW: &[u8] = &[
        0xa8, 0x9f, 0x03, 0x1d, 0xf6, 0x33, 0x01, 0xd3, 0xe8, 0x02, 0x3f,
    ];
    const ENCODED_VALUE_RAW: &[u8] = &[0x1d, 0xf6, 0x33, 0x01, 0xd3, 0xe8, 0x02, 0x3f];

    #[test]
    fn deserialize_encoded_annotation() {
        assert_eq!(
            EncodedAnnotation::deserialize_from_slice(ENCODED_ANNOTATION_RAW_3).unwrap(),
            EncodedAnnotation {
                type_idx: Uleb128(7103),
                elements: vec![AnnotationElement {
                    name_idx: Uleb128(53160),
                    value: EncodedValue::Type(0x0238)
                }],
            }
        );
        assert_eq!(
            EncodedAnnotation::deserialize_from_slice(ENCODED_ANNOTATION_RAW_2).unwrap(),
            EncodedAnnotation {
                type_idx: Uleb128(6646),
                elements: vec![AnnotationElement {
                    name_idx: Uleb128(46163),
                    value: EncodedValue::Boolean(true)
                }],
            }
        );
        assert_eq!(
            EncodedAnnotation::deserialize_from_slice(ENCODED_ANNOTATION_RAW).unwrap(),
            EncodedAnnotation {
                type_idx: Uleb128(7102),
                elements: vec![AnnotationElement {
                    name_idx: Uleb128(53160),
                    value: EncodedValue::Annotation(EncodedAnnotation {
                        type_idx: Uleb128(6646),
                        elements: vec![AnnotationElement {
                            name_idx: Uleb128(46163),
                            value: EncodedValue::Boolean(true)
                        }]
                    })
                }],
            }
        );
    }

    #[test]
    fn serialize_encoded_annotation() {
        assert_eq!(
            ENCODED_ANNOTATION_RAW_2,
            EncodedAnnotation {
                type_idx: Uleb128(6646),
                elements: vec![AnnotationElement {
                    name_idx: Uleb128(46163),
                    value: EncodedValue::Boolean(true)
                }],
            }
            .serialize_to_vec()
            .unwrap()
            .as_slice()
        );
        assert_eq!(
            ENCODED_ANNOTATION_RAW,
            EncodedAnnotation {
                type_idx: Uleb128(7102),
                elements: vec![AnnotationElement {
                    name_idx: Uleb128(53160),
                    value: EncodedValue::Annotation(EncodedAnnotation {
                        type_idx: Uleb128(6646),
                        elements: vec![AnnotationElement {
                            name_idx: Uleb128(46163),
                            value: EncodedValue::Boolean(true)
                        }]
                    })
                }],
            }
            .serialize_to_vec()
            .unwrap()
            .as_slice()
        );
    }

    #[test]
    fn deserialize_annotation_element() {
        assert_eq!(
            AnnotationElement::deserialize_from_slice(ANNOTATION_ELEMENT_RAW).unwrap(),
            AnnotationElement {
                name_idx: Uleb128(53160),
                value: EncodedValue::deserialize_from_slice(ENCODED_VALUE_RAW).unwrap(),
            }
        );
    }

    #[test]
    fn serialize_annotation_element() {
        assert_eq!(
            ANNOTATION_ELEMENT_RAW,
            AnnotationElement {
                name_idx: Uleb128(53160),
                value: EncodedValue::deserialize_from_slice(ENCODED_VALUE_RAW).unwrap(),
            }
            .serialize_to_vec()
            .unwrap()
            .as_slice(),
        );
    }
}
