//! Class definitions.

use std::hash::Hash;

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

/// <https://source.android.com/docs/core/runtime/dex-format#class-def-item>
/// alignment: 4 bytes
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClassDefItem {
    /// Index of a [`crate::TypeIdItem`] in `type_ids`, must be a class.
    pub class_idx: u32,
    /// See <https://source.android.com/docs/core/runtime/dex-format#access-flags>
    pub access_flags: u32,
    /// Either index of a [`crate::TypeIdItem`] in `type_ids`, must be a class or
    /// [`crate::NO_INDEX`]
    pub superclass_idx: u32,
    /// 0 if no interfaces, else offset to a [`crate::TypeList`].
    pub interfaces_off: u32,
    /// Index of a [`crate::StringIdItem`] in `string_ids` or [`crate::NO_INDEX`].
    pub source_file_idx: u32,
    /// 0 if no annotation, else offset to a [`crate::AnnotationsDirectoryItem`].
    pub annotations_off: u32,
    /// 0 if no data for this class, else offset to a [`crate::ClassDataItem`].
    pub class_data_off: u32,
    /// 0 if no static fields, else offset to an [`crate::EncodedArrayItem`].
    ///
    /// Notes:
    ///
    /// > The size of the array must be no larger than the number of static
    /// > fields declared by this class, and the elements correspond to the
    /// > static fields in the same order as declared in the corresponding
    /// > field_list. The type of each array element must match the declared
    /// > type of its corresponding field. If there are fewer elements in the
    /// > array than there are static fields, then the leftover fields are
    /// > initialized with a type-appropriate 0 or null.
    pub static_values_off: u32,
}

/// <https://source.android.com/docs/core/runtime/dex-format#method-handle-item>
/// alignment: 4 bytes
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq)]
pub struct MethodHandleItem {
    pub method_handle_type: MethodHandleType,
    pub unused1: u16,
    pub field_or_method_id: u16,
    pub unused2: u16,
}

/// <https://source.android.com/docs/core/runtime/dex-format#method-handle-type-codes>
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq)]
#[prefix_type(u16)]
pub enum MethodHandleType {
    #[prefix(0x00)]
    StaticPut,
    #[prefix(0x01)]
    StaticGet,
    #[prefix(0x02)]
    InstancePut,
    #[prefix(0x03)]
    InstanceGet,
    #[prefix(0x04)]
    InvokeStatic,
    #[prefix(0x05)]
    InvokeInstance,
    #[prefix(0x06)]
    InvokeConstructor,
    #[prefix(0x07)]
    InvokeDirect,
    #[prefix(0x08)]
    InvokeInterface,
}

/// <https://source.android.com/docs/core/runtime/dex-format#class-data-item>
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ClassDataItem {
    // pub static_fields_size: Uleb128,
    // pub instance_fields_size: Uleb128,
    // pub direct_methods_size: Uleb128,
    // pub virtual_methods_size: Uleb128,
    pub static_fields: Vec<EncodedField>,
    pub instance_fields: Vec<EncodedField>,
    pub direct_methods: Vec<EncodedMethod>,
    pub virtual_methods: Vec<EncodedMethod>,
}

impl ClassDataItem {
    pub fn static_fields_size_field(&self) -> Uleb128 {
        Uleb128(self.static_fields.len() as u32)
    }
    pub fn instance_fields_size_field(&self) -> Uleb128 {
        Uleb128(self.instance_fields.len() as u32)
    }
    pub fn direct_methods_size_field(&self) -> Uleb128 {
        Uleb128(self.direct_methods.len() as u32)
    }
    pub fn virtual_methods_size_field(&self) -> Uleb128 {
        Uleb128(self.virtual_methods.len() as u32)
    }
}

impl Serializable for ClassDataItem {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.static_fields_size_field().serialize(output)?;
        self.instance_fields_size_field().serialize(output)?;
        self.direct_methods_size_field().serialize(output)?;
        self.virtual_methods_size_field().serialize(output)?;
        for field in &self.static_fields {
            field.serialize(output)?;
        }
        for field in &self.instance_fields {
            field.serialize(output)?;
        }
        for method in &self.direct_methods {
            method.serialize(output)?;
        }
        for method in &self.virtual_methods {
            method.serialize(output)?;
        }
        Ok(())
    }

    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let Uleb128(static_fields_size) = Uleb128::deserialize(input)?;
        let Uleb128(instance_fields_size) = Uleb128::deserialize(input)?;
        let Uleb128(direct_methods_size) = Uleb128::deserialize(input)?;
        let Uleb128(virtual_methods_size) = Uleb128::deserialize(input)?;
        let mut static_fields = vec![];
        let mut instance_fields = vec![];
        let mut direct_methods = vec![];
        let mut virtual_methods = vec![];
        for _ in 0..static_fields_size {
            static_fields.push(EncodedField::deserialize(input)?);
        }
        for _ in 0..instance_fields_size {
            instance_fields.push(EncodedField::deserialize(input)?);
        }
        for _ in 0..direct_methods_size {
            direct_methods.push(EncodedMethod::deserialize(input)?);
        }
        for _ in 0..virtual_methods_size {
            virtual_methods.push(EncodedMethod::deserialize(input)?);
        }
        Ok(Self {
            // static_fields_size: Uleb128(static_fields_size),
            // instance_fields_size: Uleb128(instance_fields_size),
            // direct_methods_size: Uleb128(direct_methods_size),
            // virtual_methods_size: Uleb128(virtual_methods_size),
            static_fields,
            instance_fields,
            direct_methods,
            virtual_methods,
        })
    }

    fn size(&self) -> usize {
        self.static_fields_size_field().size()
            + self.instance_fields_size_field().size()
            + self.direct_methods_size_field().size()
            + self.virtual_methods_size_field().size()
            + self
                .static_fields
                .iter()
                .map(|val| val.size())
                .sum::<usize>()
            + self
                .instance_fields
                .iter()
                .map(|val| val.size())
                .sum::<usize>()
            + self
                .direct_methods
                .iter()
                .map(|val| val.size())
                .sum::<usize>()
            + self
                .virtual_methods
                .iter()
                .map(|val| val.size())
                .sum::<usize>()
    }
}

/// <https://source.android.com/docs/core/runtime/dex-format#encoded-field-format>
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncodedField {
    /// Index into the `field_ids` list for the identity of this field
    /// (includes the name and descriptor), represented as a difference
    /// from the index of previous element in the list. The index of the
    /// first element in a list is represented directly.
    pub field_idx_diff: Uleb128,
    pub access_flags: Uleb128,
}

/// <https://source.android.com/docs/core/runtime/dex-format#encoded-method>
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncodedMethod {
    /// Index into the `method_ids list` for the identity of this method
    /// (includes the name and descriptor), represented as a difference
    /// from the index of previous element in the list. The index of the
    /// first element in a list is represented directly.
    pub method_idx_diff: Uleb128,
    pub access_flags: Uleb128,
    /// 0 if abstract or native, else offset to a [`crate::CodeItem`].
    pub code_off: Uleb128,
}

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

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

impl Serializable for TypeList {
    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 = i32::deserialize(input)?;
        let mut list = vec![];
        for _ in 0..size {
            list.push(TypeItem::deserialize(input)?);
        }
        Ok(Self {
            // size,
            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#type-item-format>
#[derive(Serializable, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeItem {
    /// Index of a [`crate::TypeIdItem`] in the `type_ids` list.
    pub type_idx: u16,
}
