//! Representation of a method.

use serde::{Deserialize, Serialize};
use std::collections::HashSet;

use pyo3::prelude::*;

use crate::{
    Code, DexAnnotationItem, DexString, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
    Result,
};
use androscalpel_serializer::consts::*;

/// Represent a method.
#[pyclass]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Method {
    /// The structure used to reference this method.
    #[pyo3(get)]
    pub descriptor: IdMethod,
    /// The field visibility.
    #[pyo3(get)]
    pub visibility: MethodVisibility,
    /// Static methods do not take this in argument.
    #[pyo3(get)]
    pub is_static: bool,
    /// Final methods are not averridable.
    #[pyo3(get)]
    pub is_final: bool,
    /// Synchronized method automatically acquire their associated lock around call.
    /// Can only be set in native method, (`[Self::is_native] = true`).
    #[pyo3(get)]
    pub is_synchronized: bool,
    /// Bridge are automatically added by the compiler as a type-safe bridge.
    #[pyo3(get)]
    pub is_bridge: bool,
    /// If the last argument should be treated as a "rest" argument by compiler
    /// (for method of variable number of argument).
    #[pyo3(get)]
    pub is_varargs: bool,
    /// If the method is a native method.
    #[pyo3(get)]
    pub is_native: bool,
    /// Abstract methods are not implemented by the class.
    #[pyo3(get)]
    pub is_abstract: bool,
    /// If the method must use strict rules for floating point arithmetic.
    #[pyo3(get)]
    pub is_strictfp: bool,
    /// Synthetic method are not directly defined in the source code.
    #[pyo3(get)]
    pub is_synthetic: bool,
    /// If the method is a constructor.
    #[pyo3(get)]
    pub is_constructor: bool,
    /// If the method is declared as synchronize (just indicatif)
    #[pyo3(get)]
    pub is_declared_syncrhonized: bool,
    /// The annotations for this method
    #[pyo3(get)]
    pub annotations: Vec<DexAnnotationItem>,
    /// The annotations for the parameters of this method method
    #[pyo3(get)]
    pub parameters_annotations: Vec<Vec<DexAnnotationItem>>,

    /// The code of the method
    #[pyo3(get)]
    pub code: Option<Code>,
}

/// Represent the visibility of a field
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum MethodVisibility {
    Public,
    Private,
    Protected,
    None_, // Actually quite common
}

#[pymethods]
impl Method {
    pub fn to_json(&self) -> Result<String> {
        Ok(serde_json::to_string(self)?)
    }

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

    #[new]
    pub fn new(descriptor: IdMethod) -> Self {
        // TODO: take code option as arg and set the default flags accordingly
        Self {
            descriptor,
            visibility: MethodVisibility::Public,
            is_static: false,
            is_final: false,
            is_synchronized: false,
            is_bridge: false,
            is_varargs: false,
            is_native: false,
            is_abstract: false,
            is_strictfp: false,
            is_synthetic: false,
            is_constructor: false,
            is_declared_syncrhonized: false,
            annotations: vec![],
            parameters_annotations: vec![],
            code: None,
        }
    }

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

    pub fn __repr__(&self) -> String {
        let dscr = self.descriptor.__str__();
        format!("Method({dscr})")
    }

    /// Return all strings referenced in the method.
    pub fn get_all_strings(&self) -> HashSet<DexString> {
        let mut strings = HashSet::new();
        strings.extend(self.descriptor.get_all_strings());
        for annot in &self.annotations {
            strings.extend(annot.get_all_strings());
        }
        for param_annots in &self.parameters_annotations {
            for annot in param_annots {
                strings.extend(annot.get_all_strings());
            }
        }
        if let Some(code) = &self.code {
            strings.extend(code.get_all_strings());
        }
        strings
    }

    /// Return all types referenced in the method.
    pub fn get_all_types(&self) -> HashSet<IdType> {
        let mut types = HashSet::new();
        types.extend(self.descriptor.get_all_types());
        for annot in &self.annotations {
            types.extend(annot.get_all_types());
        }
        for param_annots in &self.parameters_annotations {
            for annot in param_annots {
                types.extend(annot.get_all_types());
            }
        }
        if let Some(code) = &self.code {
            types.extend(code.get_all_types());
        }

        types
    }

    /// Return all prototypes referenced in the method.
    pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
        let mut protos = HashSet::new();
        protos.extend(self.descriptor.get_all_protos());
        for annot in &self.annotations {
            protos.extend(annot.get_all_protos());
        }
        for param_annots in &self.parameters_annotations {
            for annot in param_annots {
                protos.extend(annot.get_all_protos());
            }
        }
        if let Some(code) = &self.code {
            protos.extend(code.get_all_protos());
        }
        protos
    }

    /// Return all field ids referenced in the method.
    pub fn get_all_field_ids(&self) -> HashSet<IdField> {
        let mut fields = HashSet::new();
        for annot in &self.annotations {
            fields.extend(annot.get_all_field_ids());
        }
        for param_annots in &self.parameters_annotations {
            for annot in param_annots {
                fields.extend(annot.get_all_field_ids());
            }
        }
        if let Some(code) = &self.code {
            fields.extend(code.get_all_field_ids());
        }
        fields
    }

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

        for annot in &self.annotations {
            methods.extend(annot.get_all_method_ids());
        }
        for param_annots in &self.parameters_annotations {
            for annot in param_annots {
                methods.extend(annot.get_all_method_ids());
            }
        }
        if let Some(code) = &self.code {
            methods.extend(code.get_all_method_ids());
        }
        methods
    }

    /// Return all method handles referenced in the method.
    pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
        let mut methods = HashSet::new();

        for annot in &self.annotations {
            methods.extend(annot.get_all_method_handles());
        }
        for param_annots in &self.parameters_annotations {
            for annot in param_annots {
                methods.extend(annot.get_all_method_handles());
            }
        }
        if let Some(code) = &self.code {
            methods.extend(code.get_all_method_handles());
        }
        methods
    }

    /// Return the binary representation of access flags.
    ///
    /// # Note
    ///
    /// The return value is a u32, not the Uleb128
    /// encoded value found in the dex file.
    pub fn get_raw_access_flags(&self) -> u32 {
        let mut flags = 0;
        match self.visibility {
            MethodVisibility::Public => flags |= ACC_PUBLIC,
            MethodVisibility::Private => flags |= ACC_PRIVATE,
            MethodVisibility::Protected => flags |= ACC_PROTECTED,
            MethodVisibility::None_ => (),
        }
        if self.is_static {
            flags |= ACC_STATIC;
        }
        if self.is_final {
            flags |= ACC_FINAL;
        }
        if self.is_synchronized {
            flags |= ACC_SYNCHRONIZED;
        }
        if self.is_bridge {
            flags |= ACC_BRIDGE;
        }
        if self.is_varargs {
            flags |= ACC_VARARGS;
        }
        if self.is_native {
            flags |= ACC_NATIVE;
        }
        if self.is_abstract {
            flags |= ACC_ABSTRACT;
        }
        if self.is_strictfp {
            flags |= ACC_STRICT;
        }
        if self.is_synthetic {
            flags |= ACC_SYNTHETIC;
        }
        if self.is_constructor {
            flags |= ACC_CONSTRUCTOR;
        }
        if self.is_declared_syncrhonized {
            flags |= ACC_DECLARED_SYNCHRONIZED;
        }

        flags
    }

    /// If the fields has annotations
    pub fn has_annotations(&self) -> bool {
        !self.annotations.is_empty()
            || self
                .parameters_annotations
                .iter()
                .any(|list| !list.is_empty())
    }

    pub fn __eq__(&self, other: &Self) -> bool {
        self == other
    }
}
