//! The class identifying dex structure.

use serde::{Deserialize, Serialize};
use std::cmp::{Ord, Ordering, PartialOrd};
use std::collections::HashSet;
use std::hash::Hash;

use anyhow::{anyhow, bail, Context};
#[cfg(feature = "python")]
use pyo3::prelude::*;

use crate::{scalar::*, DexString, DexValue, Result, Visitable, VisitableMut, Visitor, VisitorMut};
use androscalpel_serializer::{StringDataItem, Uleb128};

/// The type of a method. The shorty is formated as described in
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IdMethodType {
    /// Type formated as described by  <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
    pub(crate) shorty: DexString, // Redondant, but same as in the encoding, keep it in case we ever
    // need it
    pub(crate) return_type: IdType,
    pub(crate) parameters: Vec<IdType>,
}

impl<V: Visitor> Visitable<V> for IdMethodType {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        v.visit_string(&self.shorty)?;
        v.visit_type(&self.return_type)?;
        for ty in &self.parameters {
            v.visit_type(ty)?;
        }
        Ok(())
    }
}

impl<V: VisitorMut> VisitableMut<V> for IdMethodType {
    fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
        self.shorty = v.visit_string(self.shorty)?;
        self.return_type = v.visit_type(self.return_type)?;
        self.parameters = self
            .parameters
            .into_iter()
            .map(|ty| v.visit_type(ty))
            .collect::<Result<_>>()?;
        Ok(self)
    }
}

impl Ord for IdMethodType {
    fn cmp(&self, other: &Self) -> Ordering {
        self.return_type
            .cmp(&other.return_type)
            .then(self.parameters.cmp(&other.parameters))
            .then(self.shorty.cmp(&other.shorty)) //should not have an influence if generated correctly
    }
}

impl PartialOrd for IdMethodType {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

#[cfg_attr(feature = "python", pymethods)]
impl IdMethodType {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(return_type: IdType, parameters: Vec<IdType>) -> Self {
        Self {
            shorty: Self::compute_shorty(&return_type, &parameters),
            return_type,
            parameters,
        }
    }

    /// Try to parse a smali representation of a prototype into a IdMethodType.
    ///
    /// ```
    /// use androscalpel::{IdMethodType, IdType};
    ///
    /// let proto = IdMethodType::from_smali("(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z").unwrap();
    /// assert_eq!(
    ///   proto,
    ///   IdMethodType::new(
    ///     IdType::boolean(),
    ///     vec![
    ///       IdType::class("androidx/core/util/Predicate"),
    ///       IdType::class("androidx/core/util/Predicate"),
    ///       IdType::class("java/lang/Object")
    ///     ]
    ///   )
    /// );
    /// ```
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_smali(smali_repr: &str) -> Result<Self> {
        if smali_repr.len() < 2 {
            bail!("'{smali_repr}' is to short to be a prototype");
        }
        if &smali_repr[0..1] != "(" {
            bail!("'{smali_repr}' does not begin by a '('");
        }
        let closing_par_i = smali_repr.find(')');
        if closing_par_i.is_none() {
            bail!("'{smali_repr}' does not contians a ')'");
        }
        let closing_par_i = closing_par_i.unwrap();
        let parameters =
            IdType::get_list_from_str(&smali_repr[1..closing_par_i]).with_context(|| {
                format!("Failed to parse smali repr of parameters of prototype {smali_repr}")
            })?;
        let return_type =
            IdType::new((&smali_repr[closing_par_i + 1..]).into()).with_context(|| {
                format!("Failed to parse smali repr of return type of {smali_repr}")
            })?;
        let shorty = Self::compute_shorty(&return_type, &parameters);
        Ok(Self {
            shorty,
            return_type,
            parameters,
        })
    }

    pub fn __str__(&self) -> String {
        format!(
            "({}){}",
            self.parameters
                .iter()
                .map(|param| param.__str__())
                .collect::<Vec<String>>()
                .join(""),
            self.return_type.__str__()
        )
    }

    pub fn __repr__(&self) -> String {
        format!(
            "IdMethodType({}, [{}])",
            self.return_type.__repr__(),
            self.parameters
                .iter()
                .map(|param| param.__repr__())
                .collect::<Vec<_>>()
                .join(", ")
        )
    }

    pub fn get_shorty(&self) -> DexString {
        self.shorty.clone()
    }

    pub fn get_return_type(&self) -> IdType {
        self.return_type.clone()
    }

    pub fn get_parameters(&self) -> Vec<IdType> {
        self.parameters.clone()
    }

    /// Return all strings referenced in the Id.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        strings.insert(self.shorty.clone());
        strings.extend(self.return_type.get_all_strings());
        for ty in &self.parameters {
            strings.extend(ty.get_all_strings());
        }
        strings
    }

    /// Return all types referenced in the Id.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut types = HashSet::new();
        types.insert(self.return_type.clone());
        types.extend(self.parameters.clone());
        types
    }

    /// Return all prototypes referenced in the Id.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        let mut protos = HashSet::new();
        protos.insert(self.clone());
        protos
    }
}

impl SmaliName for IdMethodType {
    /// Convert a descriptor to its smali representation.
    fn try_to_smali(&self) -> Result<String> {
        Ok(format!(
            "({}){}",
            self.parameters
                .iter()
                .map(|param| param.try_to_smali())
                .collect::<Result<Vec<String>>>()?
                .join(""),
            self.return_type.try_to_smali()?
        ))
    }
    /// Convert a smali representation to its descriptor.
    fn try_from_smali(smali: &str) -> Result<Self> {
        Self::from_smali(smali)
    }
}

impl IdMethodType {
    /// Compute the format for the shorty as described in
    /// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
    pub fn compute_shorty(return_type: &IdType, parameters: &[IdType]) -> DexString {
        // TODO: computing on dex string instead of string? that a lot of doubious conversion
        let mut shorty: String = return_type.get_shorty().__str__();
        for ty in parameters {
            let ty: String = ty.get_shorty().__str__();
            shorty.push_str(&ty);
        }
        shorty.into()
    }
}

/// A type.
/// Type represented by [`DexString`] that follow the TypeDescriptor format
/// as described here <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
// Not a clean rust enum because we want to be compatible with python, and maybe support strange
// malware edge case?
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct IdType(pub(crate) DexString);

impl<V: Visitor> Visitable<V> for IdType {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        v.visit_string(&self.0)
    }
}

impl<V: VisitorMut> VisitableMut<V> for IdType {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        Ok(Self(v.visit_string(self.0)?))
    }
}
#[cfg_attr(feature = "python", pymethods)]
impl IdType {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(
        #[cfg_attr(
            feature = "python",
            pyo3(from_py_with = "crate::dex_string::as_dex_string")
        )]
        ty: DexString,
    ) -> Result<Self> {
        // TODO: check format
        let ty = Self(ty);
        ty.check_format()?;
        Ok(ty)
    }

    /// Return a type from its string representation without checking its format
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn unchecked_new(ty: DexString) -> Self {
        Self(ty)
    }

    /// Try to parse a smali representation of type into a IdType.
    ///
    /// In practice, it's juste wrapper arround `Self::new()` for consistancy with other
    /// id types.
    ///
    /// ```
    /// use androscalpel::IdType;
    ///
    /// let id_type = IdType::from_smali(
    ///   "Landroidx/core/util/Predicate;"
    /// ).unwrap();
    ///
    /// assert_eq!(
    ///   id_type,
    ///   IdType::class("androidx/core/util/Predicate")
    /// );
    /// ```
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_smali(smali_repr: &str) -> Result<Self> {
        Self::new(smali_repr.into())
    }

    /// Return a list of types from a string of concatenated types (like the ones between
    /// parentheses in the repr of a prototype)
    ///
    /// # Example
    ///
    /// ```
    /// use androscalpel::IdType;
    ///
    /// let types = IdType::get_list_from_str("IILjava/lang/Object;Ljava/lang/Object;Z").unwrap();
    /// assert_eq!(types, vec![
    ///   IdType::int(),
    ///   IdType::int(),
    ///   IdType::class("java/lang/Object"),
    ///   IdType::class("java/lang/Object"),
    ///   IdType::boolean(),
    /// ])
    /// ```
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn get_list_from_str(type_list: &str) -> Result<Vec<Self>> {
        let mut lst = vec![];
        let mut chars = type_list.chars();
        let mut array_dimmention = 0;
        while let Some(c) = chars.next() {
            let new_type = match c {
                'V' => Some(Self::void()),
                'Z' => Some(Self::boolean()),
                'B' => Some(Self::byte()),
                'S' => Some(Self::short()),
                'C' => Some(Self::char()),
                'I' => Some(Self::int()),
                'J' => Some(Self::long()),
                'F' => Some(Self::float()),
                'D' => Some(Self::double()),
                '[' => { array_dimmention += 1; None },
                'L' => {
                    let mut class_name = String::new();
                    for cc in chars.by_ref(){
                        if cc == ';' { break;}
                        else {
                            class_name.push(cc);
                        }

                    }
                    Some(Self::class(&class_name))
                }
                _ => bail!(
                    "Could not parse {type_list} as a list of types, found invalid char {c} outside a class name"
                ),
            };
            if let Some(mut new_type) = new_type {
                if array_dimmention != 0 {
                    let mut data = vec![0u8; array_dimmention + new_type.0 .0.data.len()];
                    for c in &mut data[..array_dimmention] {
                        *c = 0x5b;
                    }
                    data[array_dimmention..].copy_from_slice(&new_type.0 .0.data);
                    new_type.0 .0.data = data;
                    new_type.0 .0.utf16_size.0 += array_dimmention as u32;
                }

                lst.push(new_type);
                array_dimmention = 0;
            }
        }
        if array_dimmention != 0 {
            bail!("Could not parse {type_list}: finishing with [")
        }
        Ok(lst)
    }

    /// Return the void type (for return type)
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn void() -> Self {
        Self("V".into())
    }

    /// Return the boolean type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn boolean() -> Self {
        Self("Z".into())
    }
    /// Return the byte type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn byte() -> Self {
        Self("B".into())
    }

    /// Return the short type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn short() -> Self {
        Self("S".into())
    }

    /// Return the char type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn char() -> Self {
        Self("C".into())
    }

    /// Return the int type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn int() -> Self {
        Self("I".into())
    }

    /// Return the long type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn long() -> Self {
        Self("J".into())
    }

    /// Return the float type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn float() -> Self {
        Self("F".into())
    }

    /// Return the double type
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn double() -> Self {
        Self("D".into())
    }

    /// Return the type for the class of fully qualified name `name`
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn class(name: &str) -> Self {
        Self(format!("L{name};").into())
    }

    /// Return the type for an array of the specify `type_`
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn array(type_: &IdType) -> Self {
        let mut ty = type_.clone();
        ty.0 .0.utf16_size.0 += 1;
        ty.0 .0.data.insert(0, 0x5b);
        ty
    }

    pub fn get_name(&self) -> DexString {
        self.0.clone()
    }

    pub fn __str__(&self) -> String {
        self.0.__str__()
    }

    pub fn __repr__(&self) -> String {
        let name: String = self.0.__str__();
        format!("IdType(\"{name}\")")
    }

    /// Check if the type is void (return type)
    pub fn is_void(&self) -> bool {
        self == &Self::void()
    }

    /// Check if the type is boolean
    pub fn is_boolean(&self) -> bool {
        self == &Self::boolean()
    }

    /// Check if the type is byte
    pub fn is_byte(&self) -> bool {
        self == &Self::byte()
    }

    /// Check if the type is short
    pub fn is_short(&self) -> bool {
        self == &Self::short()
    }

    /// Check if the type is char
    pub fn is_char(&self) -> bool {
        self == &Self::char()
    }

    /// Check if the type is int
    pub fn is_int(&self) -> bool {
        self == &Self::int()
    }

    /// Check if the type is long
    pub fn is_long(&self) -> bool {
        self == &Self::long()
    }

    /// Check if the type is float
    pub fn is_float(&self) -> bool {
        self == &Self::float()
    }

    /// Check if the type is double
    pub fn is_double(&self) -> bool {
        self == &Self::double()
    }

    /// Check if the type is a class
    pub fn is_class(&self) -> bool {
        self.0.get_utf16_size() >= 2
            && !self.0.get_bytes().len() > 2
            && self.0.get_bytes()[0] == b'L'
            && *self.0.get_bytes().last().unwrap() == b';'
    }

    /// Check if the type is an array
    pub fn is_array(&self) -> bool {
        self.0.get_utf16_size() != 0
            && !self.0.get_bytes().is_empty()
            && self.0.get_bytes()[0] == b'['
    }

    /// If the type is a class, return the name of the class,
    /// else None.
    pub fn get_class_name(&self) -> Option<DexString> {
        if self.is_class() {
            let bytes = self.0.get_bytes();
            Some(
                StringDataItem {
                    utf16_size: Uleb128(self.0.get_utf16_size() - 2),
                    data: bytes[1..bytes.len() - 1].to_vec(),
                }
                .into(),
            )
        } else {
            None
        }
    }

    /// If the type is a array, return the type of the elements,
    /// else None.
    pub fn get_element_type(&self) -> Option<Self> {
        if self.is_array() {
            Some(Self(
                StringDataItem {
                    utf16_size: Uleb128(self.0.get_utf16_size() - 1),
                    data: self.0.get_bytes()[1..].to_vec(),
                }
                .into(),
            ))
        } else {
            None
        }
    }

    /// Check if the type is a value representation.
    /// They should not be any needs to use this method externaly?
    pub fn check_format(&self) -> Result<()> {
        if self.is_void()
            || self.is_boolean()
            || self.is_byte()
            || self.is_short()
            || self.is_char()
            || self.is_int()
            || self.is_long()
            || self.is_float()
            || self.is_double()
            || self.is_class()
            || self
                .get_element_type()
                .map(|elt| elt.check_format().is_ok())
                .unwrap_or(false)
        {
            Ok(())
        } else {
            let format: String = self.0.__str__();
            Err(anyhow!("{format} is not a valid type"))
        }
    }

    /// Return the default value of the described type. (The value used for uninitialized field).
    /// It can be either `0` of `null` depending on the type.
    pub fn get_default_value(&self) -> Option<DexValue> {
        if self.is_void() {
            None
        } else if self.is_boolean() {
            Some(DexValue::Boolean(DexBoolean(false)))
        } else if self.is_byte() {
            Some(DexValue::Byte(DexByte(0)))
        } else if self.is_short() {
            Some(DexValue::Short(DexShort(0)))
        } else if self.is_char() {
            Some(DexValue::Char(DexChar(0)))
        } else if self.is_int() {
            Some(DexValue::Int(DexInt(0)))
        } else if self.is_long() {
            Some(DexValue::Long(DexLong(0)))
        } else if self.is_float() {
            Some(DexValue::Float(DexFloat(0.)))
        } else if self.is_double() {
            Some(DexValue::Double(DexDouble(0.)))
        } else if self.is_class() || self.is_array() {
            Some(DexValue::Null(DexNull))
        } else {
            None
        }
    }

    /// Return the shorty repr of this type (ID: the type if it's a scalar, 'L' else)
    pub fn get_shorty(&self) -> DexString {
        if self.is_void()
            || self.is_boolean()
            || self.is_byte()
            || self.is_short()
            || self.is_char()
            || self.is_int()
            || self.is_long()
            || self.is_float()
            || self.is_double()
        {
            self.0.clone()
        } else {
            "L".into()
        }
    }

    /// Return all strings referenced in the Id.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        strings.insert(self.0.clone());
        strings
    }

    /// Return all types referenced in the Id.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut types = HashSet::new();
        types.insert(self.clone());
        types
    }
    // TODO: TESTS
}

impl SmaliName for IdType {
    /// Convert a descriptor to its smali representation.
    fn try_to_smali(&self) -> Result<String> {
        let r = (&self.0 .0).try_into()?; // Anyhow conversion stuff
        Ok(r)
    }
    /// Convert a smali representation to its descriptor.
    fn try_from_smali(smali: &str) -> Result<Self> {
        Self::from_smali(smali)
    }
}

#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IdField {
    /// The name of the field, format described at
    /// <https://source.android.com/docs/core/runtime/dex-format#membername>
    #[cfg_attr(feature = "python", pyo3(get))]
    pub name: DexString,
    /// The type of the field.
    #[cfg_attr(feature = "python", pyo3(get))]
    pub type_: IdType,
    /// The class that own the field.
    #[cfg_attr(feature = "python", pyo3(get))]
    pub class_: IdType,
}

impl<V: Visitor> Visitable<V> for IdField {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        v.visit_string(&self.name)?;
        v.visit_type(&self.type_)?;
        v.visit_type(&self.class_)?;
        Ok(())
    }
}

impl<V: VisitorMut> VisitableMut<V> for IdField {
    fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
        self.name = v.visit_string(self.name)?;
        self.type_ = v.visit_type(self.type_)?;
        self.class_ = v.visit_type(self.class_)?;
        Ok(self)
    }
}

#[cfg_attr(feature = "python", pymethods)]
impl IdField {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(
        #[cfg_attr(
            feature = "python",
            pyo3(from_py_with = "crate::dex_string::as_dex_string")
        )]
        name: DexString,
        type_: IdType,
        class_: IdType,
    ) -> Self {
        // Todo: check that class_ is a class?
        Self {
            name,
            type_,
            class_,
        }
    }

    /// Try to parse a smali representation of a field into a IdField.
    ///
    /// ```
    /// use androscalpel::{IdField, IdType};
    ///
    /// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->FIELD:Ljava/lang/annotation/ElementType;").unwrap();
    /// assert_eq!(
    ///   proto,
    ///   IdField::new(
    ///     "FIELD".into(),
    ///     IdType::class("java/lang/annotation/ElementType"),
    ///     IdType::class("java/lang/annotation/ElementType"),
    ///   )
    /// );
    /// ```
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_smali(smali_repr: &str) -> Result<Self> {
        let arrow_i = smali_repr.find("->");
        if arrow_i.is_none() {
            bail!("'{smali_repr}' does not contains a '->'");
        }
        let arrow_i = arrow_i.unwrap();
        let dots_i = smali_repr.find(':');
        if dots_i.is_none() {
            bail!("'{smali_repr}' does not contains a ':(");
        }
        let dots_i = dots_i.unwrap();
        let type_ = IdType::new((&smali_repr[dots_i + 1..]).into())
            .with_context(|| format!("Failed to parse smali repr of type of field {smali_repr}"))?;
        let class_ = IdType::new((&smali_repr[..arrow_i]).into()).with_context(|| {
            format!("Failed to parse smali repr of class type of method {smali_repr}")
        })?;
        let name = (&smali_repr[arrow_i + 2..dots_i]).into();
        Ok(Self {
            name,
            type_,
            class_,
        })
    }

    pub fn __str__(&self) -> String {
        let class: String = self.class_.get_name().__str__();
        let name: String = self.name.__str__();
        let ty: String = self.type_.get_name().__str__();
        format!("{class}->{name}:{ty}")
    }

    pub fn __repr__(&self) -> String {
        let class: String = self.class_.__repr__();
        let name: String = self.name.__str__();
        let ty: String = self.type_.__repr__();
        format!("IdField(\"{name}\", {ty}, {class})")
    }

    /// Return all strings referenced in the Id.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        strings.insert(self.name.clone());
        strings.extend(self.type_.get_all_strings());
        strings.extend(self.class_.get_all_strings());
        strings
    }

    /// Return all types referenced in the Id.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut types = HashSet::new();
        types.insert(self.type_.clone());
        types.insert(self.class_.clone());
        types
    }

    /// Return all field ids referenced in the Id.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        let mut fields = HashSet::new();
        fields.insert(self.clone());
        fields
    }
}

impl Ord for IdField {
    fn cmp(&self, other: &Self) -> Ordering {
        self.class_
            .cmp(&other.class_)
            .then(self.name.cmp(&other.name))
            .then(self.type_.cmp(&other.type_))
    }
}

impl PartialOrd for IdField {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl SmaliName for IdField {
    /// Convert a descriptor to its smali representation.
    fn try_to_smali(&self) -> Result<String> {
        let class: String = self.class_.try_to_smali()?;
        let name: String = (&self.name.0).try_into()?;
        let ty: String = self.type_.try_to_smali()?;
        Ok(format!("{class}->{name}:{ty}"))
    }
    /// Convert a smali representation to its descriptor.
    fn try_from_smali(smali: &str) -> Result<Self> {
        Self::from_smali(smali)
    }
}

/// The Id of a method.
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IdMethod {
    /// The class containing the method.
    #[cfg_attr(feature = "python", pyo3(get))]
    pub class_: IdType,
    /// The prototype (aka type or signature) of the method.
    #[cfg_attr(feature = "python", pyo3(get))]
    pub proto: IdMethodType,
    /// The name of the method.
    #[cfg_attr(feature = "python", pyo3(get))]
    pub name: DexString,
}

impl<V: Visitor> Visitable<V> for IdMethod {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        v.visit_string(&self.name)?;
        v.visit_method_type(&self.proto)?;
        v.visit_type(&self.class_)?;
        Ok(())
    }
}

impl<V: VisitorMut> VisitableMut<V> for IdMethod {
    fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
        self.name = v.visit_string(self.name)?;
        self.proto = v.visit_method_type(self.proto)?;
        self.class_ = v.visit_type(self.class_)?;
        Ok(self)
    }
}

#[cfg_attr(feature = "python", pymethods)]
impl IdMethod {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(
        #[cfg_attr(
            feature = "python",
            pyo3(from_py_with = "crate::dex_string::as_dex_string")
        )]
        name: DexString,
        proto: IdMethodType,
        class_: IdType,
    ) -> Self {
        Self {
            class_,
            proto,
            name,
        }
    }

    /// Try to parse a smali representation of method into a IdMethod.
    ///
    /// ```
    /// use androscalpel::{IdType, IdMethod, IdMethodType};
    ///
    /// let id_method = IdMethod::from_smali(
    ///   "Landroidx/core/util/Predicate;->lambda$and$0(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z"
    /// ).unwrap();
    ///
    /// assert_eq!(
    ///   id_method,
    ///   IdMethod::new(
    ///     "lambda$and$0".into(),
    ///     IdMethodType::new(
    ///       IdType::boolean(),
    ///       vec![
    ///         IdType::class("androidx/core/util/Predicate"),
    ///         IdType::class("androidx/core/util/Predicate"),
    ///         IdType::class("java/lang/Object")
    ///       ]
    ///     ),
    ///     IdType::class("androidx/core/util/Predicate")
    ///   )
    /// );
    /// ```
    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_smali(smali_repr: &str) -> Result<Self> {
        let arrow_i = smali_repr.find("->");
        if arrow_i.is_none() {
            bail!("'{smali_repr}' does not contains a '->'");
        }
        let arrow_i = arrow_i.unwrap();
        let openning_par_i = smali_repr.find('(');
        if openning_par_i.is_none() {
            bail!("'{smali_repr}' does not contains a '('");
        }
        let openning_par_i = openning_par_i.unwrap();
        let proto = IdMethodType::from_smali(&smali_repr[openning_par_i..]).with_context(|| {
            format!("Failed to parse smali repr of prototype of method {smali_repr}")
        })?;
        let class_ = IdType::new((&smali_repr[..arrow_i]).into()).with_context(|| {
            format!("Failed to parse smali repr of class type of method {smali_repr}")
        })?;
        let name = (&smali_repr[arrow_i + 2..openning_par_i]).into();
        Ok(Self {
            name,
            proto,
            class_,
        })
    }

    pub fn __str__(&self) -> String {
        format!(
            "{}->{}({}){}",
            self.class_.__str__(),
            self.name.__str__(),
            self.proto
                .parameters
                .iter()
                .map(|param| param.__str__())
                .collect::<Vec<String>>()
                .join(""),
            self.proto.return_type.__str__()
        )
    }

    pub fn __repr__(&self) -> String {
        format!(
            "IdMethod(\"{}\", {}, {})",
            self.name.__str__(),
            self.proto.__repr__(),
            self.class_.__repr__()
        )
    }

    /// Return all strings referenced in the Id.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        strings.insert(self.name.clone());
        strings.extend(self.proto.get_all_strings());
        strings.extend(self.class_.get_all_strings());
        strings
    }

    /// Return all types referenced in the Id.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut types = HashSet::new();
        types.extend(self.proto.get_all_types());
        types.insert(self.class_.clone());
        types
    }

    /// Return all prototypes referenced in the Id.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        let mut protos = HashSet::new();
        protos.insert(self.proto.clone());
        protos
    }

    /// Return all method ids referenced in the Id.
    pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
        let mut method_ids = HashSet::new();
        method_ids.insert(self.clone());
        method_ids
    }
}

impl Ord for IdMethod {
    fn cmp(&self, other: &Self) -> Ordering {
        self.class_
            .cmp(&other.class_)
            .then(self.name.cmp(&other.name))
            .then(self.proto.cmp(&other.proto))
    }
}

impl PartialOrd for IdMethod {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl SmaliName for IdMethod {
    /// Convert a descriptor to its smali representation.
    fn try_to_smali(&self) -> Result<String> {
        let name: String = (&self.name.0).try_into()?;
        Ok(format!(
            "{}->{}{}",
            self.class_.try_to_smali()?,
            name,
            self.proto.try_to_smali()?,
        ))
    }
    /// Convert a smali representation to its descriptor.
    fn try_from_smali(smali: &str) -> Result<Self> {
        Self::from_smali(smali)
    }
}

#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct IdEnum(pub IdField);

impl<V: Visitor> Visitable<V> for IdEnum {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        v.visit_field_id(&self.0)
    }
}

impl<V: VisitorMut> VisitableMut<V> for IdEnum {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        Ok(Self(v.visit_field_id(self.0)?))
    }
}

#[cfg_attr(feature = "python", pymethods)]
impl IdEnum {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

    #[cfg_attr(feature = "python", staticmethod)]
    pub fn from_json(json: &str) -> Result<Self> {
        Ok(serde_json::from_str(json)?)
    }

    #[cfg_attr(feature = "python", new)]
    pub fn new(val: IdField) -> Self {
        Self(val)
    }

    pub fn get_value(&self) -> IdField {
        self.0.clone()
    }

    pub fn __str__(&self) -> String {
        self.0.__str__()
    }

    pub fn __repr__(&self) -> String {
        format!("DexEnum({})", self.0.__repr__())
    }

    /// Return all strings referenced in the Id.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        self.0.get_all_strings()
    }

    /// Return all types referenced in the Id.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        self.0.get_all_types()
    }

    /// Return all field ids referenced in the Id.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        self.0.get_all_field_ids()
    }
}

// Not to sure about this one
impl SmaliName for IdEnum {
    /// Convert a descriptor to its smali representation.
    fn try_to_smali(&self) -> Result<String> {
        self.0.try_to_smali()
    }
    /// Convert a smali representation to its descriptor.
    fn try_from_smali(smali: &str) -> Result<Self> {
        Ok(Self(IdField::from_smali(smali)?))
    }
}

pub trait SmaliName: Sized {
    /// Convert a descriptor to its smali representation.
    fn try_to_smali(&self) -> Result<String>;
    /// Convert a smali representation to its descriptor.
    fn try_from_smali(smali: &str) -> Result<Self>;
}

macro_rules! serde_serialize_to_smali {
    // This macro takes an argument of designator `ident` and
    // implement Serialize and Deserialize for the type, assuming
    // it implement SmaliName.
    ($type_name:ident) => {
        impl serde::Serialize for $type_name {
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
            where
                S: serde::Serializer,
            {
                Serialize::serialize(
                    &self
                        .try_to_smali()
                        .expect(&format!("Failed to convert {} to smali", self.__str__())),
                    serializer,
                )
            }
        }

        impl<'de> serde::Deserialize<'de> for $type_name {
            fn deserialize<D>(deserializer: D) -> Result<$type_name, D::Error>
            where
                D: serde::Deserializer<'de>,
            {
                <String as Deserialize>::deserialize(deserializer).map(|string| {
                    $type_name::try_from_smali(&string)
                        .expect(&format!("Failed to convert {string} as smali"))
                })
            }
        }
    };
}

serde_serialize_to_smali!(IdMethodType);
serde_serialize_to_smali!(IdType);
serde_serialize_to_smali!(IdMethod);
serde_serialize_to_smali!(IdField);
serde_serialize_to_smali!(IdEnum);
