//! Annotation items.

pub mod encoded_annotation;
pub use encoded_annotation::*;

use crate as androscalpel_serializer;
use crate::{ReadSeek, Result, Serializable};
use std::io::Write;

/// <https://source.android.com/docs/core/runtime/dex-format#referenced-from-class_def_item_1>
/// alignment: 4 bytes
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnnotationsDirectoryItem {
    /// 0 if they are no annotation, else offset of an [`crate::AnnotationSetItem`].
    pub class_annotations_off: u32,
    // pub fields_size: u32,
    // pub annotated_methods_size: u32,
    // pub annotated_parameters_size: u32,
    /// List of field annotation. The field annotations must be sorted by
    /// increasing order of [`crate::FieldAnnotation.field_idx`].
    pub field_annotations: Vec<FieldAnnotation>,
    /// List of method annotation. The method annotations must be sorted by
    /// increasing order of [`crate::MethodAnnotation.method_idx`].
    pub method_annotations: Vec<MethodAnnotation>,
    /// List of associated method parameter annotation. The parameter annotations
    /// must be sorted by increasing order of [`crate::ParameterAnnotation.parameter_size`].
    pub parameter_annotations: Vec<ParameterAnnotation>,
}

impl AnnotationsDirectoryItem {
    pub fn fields_size_field(&self) -> u32 {
        self.field_annotations.len() as u32
    }
    pub fn annotated_methods_size_field(&self) -> u32 {
        self.method_annotations.len() as u32
    }
    pub fn parameter_annotations_field(&self) -> u32 {
        self.parameter_annotations.len() as u32
    }
}

impl Serializable for AnnotationsDirectoryItem {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.class_annotations_off.serialize(output)?;
        self.fields_size_field().serialize(output)?;
        self.annotated_methods_size_field().serialize(output)?;
        self.parameter_annotations_field().serialize(output)?;

        for item in &self.field_annotations {
            item.serialize(output)?;
        }
        for item in &self.method_annotations {
            item.serialize(output)?;
        }
        for item in &self.parameter_annotations {
            item.serialize(output)?;
        }
        Ok(())
    }
    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let class_annotations_off = u32::deserialize(input)?;
        let fields_size = u32::deserialize(input)?;
        let annotated_methods_size = u32::deserialize(input)?;
        let annotated_parameters_size = u32::deserialize(input)?;
        let mut field_annotations = vec![];
        for _ in 0..fields_size {
            field_annotations.push(FieldAnnotation::deserialize(input)?);
        }
        let mut method_annotations = vec![];
        for _ in 0..annotated_methods_size {
            method_annotations.push(MethodAnnotation::deserialize(input)?);
        }
        let mut parameter_annotations = vec![];
        for _ in 0..annotated_parameters_size {
            parameter_annotations.push(ParameterAnnotation::deserialize(input)?);
        }

        Ok(Self {
            class_annotations_off,
            field_annotations,
            method_annotations,
            parameter_annotations,
        })
    }
    fn size(&self) -> usize {
        self.class_annotations_off.size()
            + self.fields_size_field().size()
            + self.annotated_methods_size_field().size()
            + self.parameter_annotations_field().size()
            + self
                .field_annotations
                .iter()
                .map(|val| val.size())
                .sum::<usize>()
            + self
                .method_annotations
                .iter()
                .map(|val| val.size())
                .sum::<usize>()
            + self
                .parameter_annotations
                .iter()
                .map(|val| val.size())
                .sum::<usize>()
    }
}

/// <https://source.android.com/docs/core/runtime/dex-format#field-annotation>
#[derive(Serializable, Debug, Clone, PartialEq, Eq)]
pub struct FieldAnnotation {
    /// Index of the field annotated in `field_ids`
    pub field_idx: u32,
    /// Offset to a [`AnnotationSetItem`]
    pub annotations_off: u32,
}

/// <https://source.android.com/docs/core/runtime/dex-format#method-annotation>
#[derive(Serializable, Debug, Clone, PartialEq, Eq)]
pub struct MethodAnnotation {
    /// Index of the method annotated in `method_ids`
    pub method_idx: u32,
    /// Offset to a [`AnnotationSetItem`]
    pub annotations_off: u32,
}

/// <https://source.android.com/docs/core/runtime/dex-format#parameter-annotation>
#[derive(Serializable, Debug, Clone, PartialEq, Eq)]
pub struct ParameterAnnotation {
    /// Index of the method whose parameters are annotated in `method_ids`
    pub method_idx: u32,
    /// Offset of a [`AnnotationSetRefList`]
    pub annotations_off: u32,
}

/// <https://source.android.com/docs/core/runtime/dex-format#set-ref-list>
/// alignment: 4 bytes
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnnotationSetRefList {
    // pub size: u32,
    pub list: Vec<AnnotationSetRefItem>,
}

impl AnnotationSetRefList {
    pub fn size_field(&self) -> u32 {
        self.list.len() as u32
    }
}

impl Serializable for AnnotationSetRefList {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.size_field().serialize(output)?;
        for item in &self.list {
            item.serialize(output)?;
        }
        Ok(())
    }
    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let size = u32::deserialize(input)?;
        let mut list = vec![];
        for _ in 0..size {
            list.push(AnnotationSetRefItem::deserialize(input)?);
        }
        Ok(Self { list })
    }
    fn size(&self) -> usize {
        self.size_field().size() + self.list.iter().map(|val| val.size()).sum::<usize>()
    }
}

/// <https://source.android.com/docs/core/runtime/dex-format#set-ref-item>
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnnotationSetRefItem {
    /// 0 if there are no annotation, offset to a [`AnnotationSetItem`] else.
    pub annotations_off: u32,
}

/// <https://source.android.com/docs/core/runtime/dex-format#annotation-set-item>
/// alignment: 4
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnnotationSetItem {
    // pub size: u32,
    /// Must be sorted by increasing [`AnnotationOffItem.type_idx`].
    pub entries: Vec<AnnotationOffItem>,
}

impl AnnotationSetItem {
    pub fn size_field(&self) -> u32 {
        self.entries.len() as u32
    }
}

impl Serializable for AnnotationSetItem {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.size_field().serialize(output)?;
        for item in &self.entries {
            item.serialize(output)?;
        }
        Ok(())
    }
    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let size = u32::deserialize(input)?;
        let mut entries = vec![];
        for _ in 0..size {
            entries.push(AnnotationOffItem::deserialize(input)?);
        }
        Ok(Self { entries })
    }
    fn size(&self) -> usize {
        self.size_field().size() + self.entries.iter().map(|val| val.size()).sum::<usize>()
    }
}

/// <https://source.android.com/docs/core/runtime/dex-format#off-item>
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq)]
pub struct AnnotationOffItem {
    /// Offset to a [`crate::AnnotationItem`]
    pub annotation_off: u32,
}

/// <https://source.android.com/docs/core/runtime/dex-format#annotation-item>
#[derive(Serializable, Debug, Clone, PartialEq, Eq)]
pub struct AnnotationItem {
    pub visibility: AnnotationVisibility,
    pub annotation: EncodedAnnotation,
}

// TODO: Enum or flags ?
/// <https://source.android.com/docs/core/runtime/dex-format#annotation-item>
#[derive(Serializable, Debug, Clone, PartialEq, Eq)]
#[prefix_type(u8)]
pub enum AnnotationVisibility {
    #[prefix(0x00)]
    Build,
    #[prefix(0x01)]
    Runtime,
    #[prefix(0x02)]
    System,
}

#[cfg(test)]
mod test {
    use super::*;

    const ANNOTATION_ITEM: &[u8] = &[
        0x02, 0xbe, 0x37, 0x01, 0xa8, 0x9f, 0x03, 0x1d, 0xf6, 0x33, 0x01, 0xd3, 0xe8, 0x02, 0x3f,
    ];
    const ANNOTATION_ITEM_2: &[u8] = &[0x02, 0xbf, 0x37, 0x01, 0xa8, 0x9f, 0x03, 0x38, 0x38, 0x02];

    #[test]
    fn deserialize_annotation_item() {
        assert_eq!(
            AnnotationItem::deserialize_from_slice(ANNOTATION_ITEM).unwrap(),
            AnnotationItem {
                visibility: AnnotationVisibility::System,
                annotation: EncodedAnnotation::deserialize_from_slice(&ANNOTATION_ITEM[1..])
                    .unwrap(),
            }
        );
        assert_eq!(
            AnnotationItem::deserialize_from_slice(ANNOTATION_ITEM_2).unwrap(),
            AnnotationItem {
                visibility: AnnotationVisibility::System,
                annotation: EncodedAnnotation::deserialize_from_slice(&ANNOTATION_ITEM_2[1..])
                    .unwrap(),
            }
        );
    }

    #[test]
    fn serialize_annotation_item() {
        assert_eq!(
            ANNOTATION_ITEM,
            AnnotationItem {
                visibility: AnnotationVisibility::System,
                annotation: EncodedAnnotation::deserialize_from_slice(&ANNOTATION_ITEM[1..])
                    .unwrap(),
            }
            .serialize_to_vec()
            .unwrap()
            .as_slice()
        );
        assert_eq!(
            ANNOTATION_ITEM_2,
            AnnotationItem {
                visibility: AnnotationVisibility::System,
                annotation: EncodedAnnotation::deserialize_from_slice(&ANNOTATION_ITEM_2[1..])
                    .unwrap(),
            }
            .serialize_to_vec()
            .unwrap()
            .as_slice()
        );
    }
}
