//! Representation of an apk.

use anyhow::{anyhow, bail, Context};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{Cursor, Read, Seek};
use std::path::PathBuf;

use log::{error, info};
#[cfg(feature = "python")]
use pyo3::{prelude::*, types::PyBytes};

use crate::ins::CallSite;
use crate::instructions;
use crate::Result;
use crate::*;
use androscalpel_serializer::Instruction as InsFormat;
use androscalpel_serializer::*;
#[cfg(not(feature = "external-zip-reader"))]
use apk_frauder::ZipFileReader;
use rayon::prelude::*;
#[cfg(feature = "external-zip-reader")]
use zip::ZipArchive;

#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
pub struct DexFile {
    /// The classes in the dex file.
    pub classes: HashMap<IdType, Class>,
    /// Set of strings found in the dex file that are not referenced in classes.
    pub not_referenced_strings: HashSet<DexString>,
    /// The binary of the dexfile.
    #[serde(skip_serializing)]
    pub(crate) bin_cache: Option<Vec<u8>>, // TODO: invalidate the cache !!!
}

impl<V: Visitor> Visitable<V> for DexFile {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        for (id, class) in &self.classes {
            v.visit_type(id)?;
            v.visit_class(class)?;
        }
        for string in &self.not_referenced_strings {
            v.visit_string(string)?;
        }
        Ok(())
    }
}
impl<V: VisitorMut> VisitableMut<V> for DexFile {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        Ok(Self {
            classes: {
                let mut classes = HashMap::new();
                for (id, class) in self.classes.into_iter() {
                    classes.insert(v.visit_type(id)?, v.visit_class(class)?);
                }
                classes
            },
            not_referenced_strings: {
                let mut classes = HashSet::new();
                for string in self.not_referenced_strings.into_iter() {
                    classes.insert(v.visit_string(string)?);
                }
                classes
            },
            bin_cache: None,
        })
    }
}

/// Represent an apk.
#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
pub struct Apk {
    pub dex_files: HashMap<String, DexFile>, // TODO: use accessort for chache invalidation
}

impl<V: Visitor> Visitable<V> for Apk {
    fn default_visit(&self, v: &mut V) -> Result<()> {
        for file in self.dex_files.values() {
            v.visit_dex_file(file)?;
        }
        Ok(())
    }
}
impl<V: VisitorMut> VisitableMut<V> for Apk {
    fn default_visit_mut(self, v: &mut V) -> Result<Self> {
        Ok(Self {
            dex_files: {
                let mut dex_files = HashMap::new();
                for (name, file) in self.dex_files.into_iter() {
                    dex_files.insert(name, v.visit_dex_file(file)?);
                }
                dex_files
            },
        })
    }
}

impl Apk {
    /// Extract a class from a dex file reader.
    /// `class_item_idx` if the index of the `class_def_item` of the class, **not** the
    /// `class_idx`.
    fn get_class_from_dex_file<F>(
        &self,
        class_item: &ClassDefItem,
        class_item_idx: usize,
        dex: &DexFileReader,
        label_ins: F,
    ) -> Result<Class>
    where
        F: FnMut(&IdMethod, &instructions::Instruction, usize) -> Option<String> + Clone,
    {
        let descriptor = Self::get_id_type_from_idx(class_item.class_idx as usize, dex)?;
        let superclass = if class_item.superclass_idx == NO_INDEX.0 {
            None
        } else {
            Some(Self::get_id_type_from_idx(
                class_item.superclass_idx as usize,
                dex,
            )?)
        };
        let interfaces = if class_item.interfaces_off == 0 {
            vec![]
        } else {
            let type_list = dex.get_struct_at_offset::<TypeList>(class_item.interfaces_off)?;
            let mut list = vec![];
            for ty in type_list.list {
                list.push(Self::get_id_type_from_idx(ty.type_idx as usize, dex)?);
            }
            list
        };
        let source_file = if class_item.source_file_idx == NO_INDEX.0 {
            None
        } else {
            Some(dex.get_string(class_item.source_file_idx)?.into())
        };
        let is_public = (class_item.access_flags & ACC_PUBLIC) != 0;
        let is_final = (class_item.access_flags & ACC_FINAL) != 0;
        let is_interface = (class_item.access_flags & ACC_INTERFACE) != 0;
        let is_abstract = (class_item.access_flags & ACC_ABSTRACT) != 0;
        let is_synthetic = (class_item.access_flags & ACC_SYNTHETIC) != 0;
        let is_annotation = (class_item.access_flags & ACC_ANNOTATION) != 0;
        let is_enum = (class_item.access_flags & ACC_ENUM) != 0;
        if (class_item.access_flags
            & !(ACC_PUBLIC
                | ACC_FINAL
                | ACC_INTERFACE
                | ACC_ABSTRACT
                | ACC_SYNTHETIC
                | ACC_ANNOTATION
                | ACC_ENUM))
            != 0
        {
            info!(
                "Unexpected flags found in class_def_item.access_flags for {}: 0x{:x}",
                descriptor.get_name().__str__(),
                class_item.access_flags
            );
        }
        let annotations_directory = if class_item.annotations_off == 0 {
            None
        } else {
            Some(dex.get_struct_at_offset::<AnnotationsDirectoryItem>(class_item.annotations_off)?)
        };
        let mut annotations = vec![];
        if let Some(annotations_directory) = &annotations_directory {
            if annotations_directory.class_annotations_off != 0 {
                annotations = Self::get_annotation_items_from_annotation_set_off(
                    annotations_directory.class_annotations_off,
                    dex,
                )?;
            }
        }

        let hiddenapi = dex.get_class_hiddenapi_flags(class_item_idx)?;
        let mut hiddenapi_i = 0;
        let mut static_fields_list = vec![];
        let mut instance_fields_list = vec![];
        let mut direct_methods = HashMap::new();
        let mut virtual_methods = HashMap::new();
        let data_off = class_item.class_data_off;
        if data_off != 0 {
            let data = dex.get_struct_at_offset::<ClassDataItem>(data_off)?;
            static_fields_list =
                Self::get_field_list_from_encoded_field_list(&data.static_fields, dex)?;
            instance_fields_list =
                Self::get_field_list_from_encoded_field_list(&data.instance_fields, dex)?;
            if let Some(hiddenapi) = &hiddenapi {
                for field in &mut static_fields_list {
                    field.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
                    hiddenapi_i += 1;
                }
                for field in &mut instance_fields_list {
                    field.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
                    hiddenapi_i += 1;
                }
            }
            for mut method in Self::get_method_list_from_encoded_field_list(
                &data.direct_methods,
                dex,
                label_ins.clone(),
            )? {
                if let Some(hiddenapi) = &hiddenapi {
                    method.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
                    hiddenapi_i += 1;
                }
                direct_methods.insert(method.descriptor.clone(), method);
            }
            for mut method in Self::get_method_list_from_encoded_field_list(
                &data.virtual_methods,
                dex,
                label_ins.clone(),
            )? {
                if let Some(hiddenapi) = &hiddenapi {
                    method.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
                    hiddenapi_i += 1;
                }
                virtual_methods.insert(method.descriptor.clone(), method);
            }
        }

        if class_item.static_values_off != 0 {
            let values = dex
                .get_struct_at_offset::<EncodedArray>(class_item.static_values_off)?
                .values;
            if values.len() > static_fields_list.len() {
                return Err(anyhow!(
                    "Inconsistant static_values array found in {}: \
                        |static_values| = {}, |static_fields| = {}, \
                        |static_values| should be <= |static_fields|",
                    &descriptor.get_name().__str__(),
                    values.len(),
                    static_fields_list.len()
                ));
            }
            for (i, value) in values.iter().enumerate() {
                static_fields_list[i].value = Some(Self::encoded_value_to_dex_value(value, dex)?);
            }
            for field in static_fields_list.iter_mut().skip(values.len()) {
                field.value = None;
            }
        }
        let mut static_fields = HashMap::new();
        let mut instance_fields = HashMap::new();
        for field in static_fields_list {
            static_fields.insert(field.descriptor.clone(), field);
        }
        for field in instance_fields_list {
            instance_fields.insert(field.descriptor.clone(), field);
        }

        if let Some(annotations_directory) = annotations_directory {
            for field_annotation in annotations_directory.field_annotations {
                let field_id =
                    Self::get_id_field_from_idx(field_annotation.field_idx as usize, dex)?;
                let annotations = if field_annotation.annotations_off != 0 {
                    Self::get_annotation_items_from_annotation_set_off(
                        field_annotation.annotations_off,
                        dex,
                    )?
                } else {
                    vec![]
                };
                if field_id.class_ != descriptor {
                    info!(
                        "Annotation for field {} found in class {}, dropping it",
                        field_id.__str__(),
                        descriptor.get_name().__str__(),
                    );
                }
                instance_fields
                    .entry(field_id.clone())
                    .and_modify(|field| field.annotations = annotations.clone());
                static_fields
                    .entry(field_id.clone())
                    .and_modify(|field| field.annotations = annotations.clone());
                if !instance_fields.contains_key(&field_id)
                    && !static_fields.contains_key(&field_id)
                {
                    info!(
                        "Annotation found for field {} but could not find the field definition, dropping it",
                        field_id.__str__(),
                    );
                }
            }
            for method_annotation in annotations_directory.method_annotations {
                let method_id =
                    Self::get_id_method_from_idx(method_annotation.method_idx as usize, dex)?;
                let annotations = if method_annotation.annotations_off != 0 {
                    Self::get_annotation_items_from_annotation_set_off(
                        method_annotation.annotations_off,
                        dex,
                    )?
                } else {
                    vec![]
                };
                if method_id.class_ != descriptor {
                    info!(
                        "Annotation for method {} found in class {}, dropping it",
                        method_id.__str__(),
                        descriptor.get_name().__str__(),
                    );
                }
                direct_methods
                    .entry(method_id.clone())
                    .and_modify(|method| method.annotations = annotations.clone()); // TODO = or append?
                virtual_methods
                    .entry(method_id.clone())
                    .and_modify(|method| method.annotations = annotations.clone()); // TODO = or append?
                if !direct_methods.contains_key(&method_id)
                    && !virtual_methods.contains_key(&method_id)
                {
                    info!(
                        "Annotation found for method {} but could not find the method definition, dropping it",
                        method_id.__str__(),
                    );
                }
            }
            for parameter_annotation in annotations_directory.parameter_annotations {
                let method_id =
                    Self::get_id_method_from_idx(parameter_annotation.method_idx as usize, dex)?;
                let annotation_set_ref_list = dex.get_struct_at_offset::<AnnotationSetRefList>(
                    parameter_annotation.annotations_off,
                )?;
                let mut annotations_list = vec![];
                for annotation_set in annotation_set_ref_list.list {
                    if annotation_set.annotations_off != 0 {
                        annotations_list.push(Self::get_annotation_items_from_annotation_set_off(
                            annotation_set.annotations_off,
                            dex,
                        )?);
                    } else {
                        annotations_list.push(vec![]);
                    }
                }
                if method_id.class_ != descriptor {
                    info!(
                        "Annotation for parameter of method {} found in class {}, dropping it",
                        method_id.__str__(),
                        descriptor.get_name().__str__(),
                    );
                }
                direct_methods
                    .entry(method_id.clone())
                    .and_modify(|method| method.parameters_annotations = annotations_list.clone());
                virtual_methods
                    .entry(method_id.clone())
                    .and_modify(|method| method.parameters_annotations = annotations_list.clone());
                if !direct_methods.contains_key(&method_id)
                    && !virtual_methods.contains_key(&method_id)
                {
                    info!(
                        "Annotation found for parameter of method {} but could not find the method definition, dropping it",
                        method_id.__str__(),
                    );
                }
            }
        }
        Ok(Class {
            descriptor,
            superclass,
            interfaces,
            source_file,
            is_public,
            is_final,
            is_interface,
            is_abstract,
            is_synthetic,
            is_annotation,
            is_enum,
            instance_fields,
            static_fields,
            direct_methods,
            virtual_methods,
            annotations,
        })
    }

    /// Return a [`IdType`] from its idx.
    pub fn get_id_type_from_idx(idx: usize, dex: &DexFileReader) -> Result<IdType> {
        Ok(IdType(
            dex.get_string(dex.get_type_id(idx)?.descriptor_idx)?.into(),
        ))
    }

    /// Return a [`CallSite`] from its idx.
    pub fn get_call_site_from_idx(idx: usize, dex: &DexFileReader) -> Result<CallSite> {
        let call_site_id = dex.get_call_site_id(idx)?;
        let array = dex
            .get_struct_at_offset::<EncodedArrayItem>(call_site_id.call_site_off)?
            .value
            .values;
        if array.len() < 3 {
            bail!(
                "Invalid CallSite {idx}: the encoded array must contains \
                at least 3 elements, found {}",
                array.len()
            );
        }
        let array = {
            let mut array_aux = vec![];
            for val in array {
                array_aux.push(Self::encoded_value_to_dex_value(&val, dex)?);
            }
            array_aux
        };
        let method_handle = if let DexValue::MethodHandle(handle) = &array[0] {
            handle
        } else {
            bail!(
                "Invalid CallSite {idx}: the first value of \
            the encoded array must be a MethodHandle, found {:?}",
                array[0]
            )
        };
        let name = if let DexValue::String(string) = &array[1] {
            string
        } else {
            bail!(
                "Invalid CallSite {idx}: the second value of \
            the encoded array must be a String, found {:?}",
                array[1]
            )
        };
        let proto = if let DexValue::MethodType(ty) = &array[0] {
            ty
        } else {
            bail!(
                "Invalid CallSite {idx}: the third value of \
            the encoded array must be a MethodType, found {:?}",
                array[1]
            )
        };
        let args = array[3..].to_vec();
        Ok(CallSite::new(
            method_handle.clone(),
            name.clone(),
            proto.clone(),
            args,
        ))
    }

    /// Return a [`Vec<DexAnnotationItem>`] for the offset of an [`AnnotationSetItem`].
    pub fn get_annotation_items_from_annotation_set_off(
        annotations_set_off: u32,
        dex: &DexFileReader,
    ) -> Result<Vec<DexAnnotationItem>> {
        let mut annotations = vec![];
        for annotation_off in dex
            .get_struct_at_offset::<AnnotationSetItem>(annotations_set_off)?
            .entries
            .iter()
            .map(|entry| entry.annotation_off)
        {
            let item = dex.get_struct_at_offset::<AnnotationItem>(annotation_off)?;
            let (visibility_build, visibility_runtime, visibility_system) = match item.visibility {
                AnnotationVisibility::Build => (true, false, false),
                AnnotationVisibility::Runtime => (false, true, false),
                AnnotationVisibility::System => (false, false, true),
            };
            let annotation = Self::get_annotations_from_encoded_annotation(&item.annotation, dex)?;
            annotations.push(DexAnnotationItem {
                annotation,
                visibility_build,
                visibility_runtime,
                visibility_system,
            });
        }
        Ok(annotations)
    }

    /// Return a [`DexAnnotation`] from [`EncodedAnnotation`]
    pub fn get_annotations_from_encoded_annotation(
        encoded_annotation: &EncodedAnnotation,
        dex: &DexFileReader,
    ) -> Result<DexAnnotation> {
        let type_ = Self::get_id_type_from_idx(encoded_annotation.type_idx.0 as usize, dex)?;
        let mut elements = HashMap::new();
        for elt in &encoded_annotation.elements {
            let name: DexString = dex.get_string(elt.name_idx.0)?.into();
            let value = Self::encoded_value_to_dex_value(&elt.value, dex)?;
            elements.insert(name, value);
        }
        Ok(DexAnnotation { type_, elements })
    }

    /// Return a [`IdMethodType`] from its idx.
    pub fn get_id_method_type_from_idx(idx: usize, dex: &DexFileReader) -> Result<IdMethodType> {
        let proto = dex.get_proto_id(idx)?;
        let shorty: DexString = dex.get_string(proto.shorty_idx)?.into();
        let return_type: IdType = Self::get_id_type_from_idx(proto.return_type_idx as usize, dex)?;
        let parameters: Vec<IdType> = if proto.parameters_off == 0 {
            vec![]
        } else {
            let mut parameters = vec![];
            for ty in dex
                .get_struct_at_offset::<TypeList>(proto.parameters_off)?
                .list
            {
                parameters.push(Self::get_id_type_from_idx(ty.type_idx as usize, dex)?);
            }
            parameters
        };
        Ok(IdMethodType {
            shorty,
            return_type,
            parameters,
        })
    }

    /// Return a [`IdField`] from its idx.
    pub fn get_id_field_from_idx(idx: usize, dex: &DexFileReader) -> Result<IdField> {
        let field = dex.get_field_id(idx)?;
        let name = dex.get_string(field.name_idx)?.into();
        let type_ = IdType(
            dex.get_string(dex.get_type_id(field.type_idx as usize)?.descriptor_idx)?
                .into(),
        );
        let class_ = IdType(
            dex.get_string(dex.get_type_id(field.class_idx as usize)?.descriptor_idx)?
                .into(),
        );
        Ok(IdField {
            name,
            type_,
            class_,
        })
    }

    /// Return a [`IdMethod`] from its idx.
    pub fn get_id_method_from_idx(idx: usize, dex: &DexFileReader) -> Result<IdMethod> {
        let method_id = dex.get_method_id(idx)?;
        let class_ = IdType(
            dex.get_string(
                dex.get_type_id(method_id.class_idx as usize)?
                    .descriptor_idx,
            )?
            .into(),
        );
        let proto = Self::get_id_method_type_from_idx(method_id.proto_idx as usize, dex)?;
        let name = dex.get_string(method_id.name_idx)?.into();
        Ok(IdMethod {
            class_,
            proto,
            name,
        })
    }

    /// Return a [`MethodHandle`] from its idx.
    pub fn get_method_handle_from_idx(idx: usize, dex: &DexFileReader) -> Result<MethodHandle> {
        let handle = dex.get_method_handle(idx)?;
        match handle.method_handle_type {
            MethodHandleType::StaticPut => Ok(MethodHandle::StaticPut {
                field: Self::get_id_field_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::StaticGet => Ok(MethodHandle::StaticGet {
                field: Self::get_id_field_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::InstancePut => Ok(MethodHandle::InstancePut {
                field: Self::get_id_field_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::InstanceGet => Ok(MethodHandle::InstanceGet {
                field: Self::get_id_field_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::InvokeStatic => Ok(MethodHandle::InvokeStatic {
                method: Self::get_id_method_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::InvokeInstance => Ok(MethodHandle::InvokeInstance {
                method: Self::get_id_method_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::InvokeConstructor => Ok(MethodHandle::InvokeConstructor {
                method: Self::get_id_method_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::InvokeDirect => Ok(MethodHandle::InvokeDirect {
                method: Self::get_id_method_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
            MethodHandleType::InvokeInterface => Ok(MethodHandle::InvokeInterface {
                method: Self::get_id_method_from_idx(handle.field_or_method_id as usize, dex)?,
            }),
        }
    }

    pub fn encoded_value_to_dex_value(
        encoded_value: &EncodedValue,
        dex: &DexFileReader,
    ) -> Result<DexValue> {
        match encoded_value {
            EncodedValue::Byte(val) => Ok(DexValue::Byte(DexByte(*val))),
            EncodedValue::Short(val) => Ok(DexValue::Short(DexShort(*val))),
            EncodedValue::Char(val) => Ok(DexValue::Char(DexChar(*val))),
            EncodedValue::Int(val) => Ok(DexValue::Int(DexInt(*val))),
            EncodedValue::Long(val) => Ok(DexValue::Long(DexLong(*val))),
            EncodedValue::Float(val) => Ok(DexValue::Float(DexFloat(*val))),
            EncodedValue::Double(val) => Ok(DexValue::Double(DexDouble(*val))),
            EncodedValue::MethodType(val) => Ok(DexValue::MethodType(
                Self::get_id_method_type_from_idx(*val as usize, dex)?,
            )),
            EncodedValue::MethodHandle(val) => Ok(DexValue::MethodHandle(
                Self::get_method_handle_from_idx(*val as usize, dex)?,
            )),
            EncodedValue::String(val) => Ok(DexValue::String(dex.get_string(*val)?.into())),
            EncodedValue::Type(val) => Ok(DexValue::Type(Self::get_id_type_from_idx(
                *val as usize,
                dex,
            )?)),
            EncodedValue::Field(val) => Ok(DexValue::Field(Self::get_id_field_from_idx(
                *val as usize,
                dex,
            )?)),
            EncodedValue::Method(val) => Ok(DexValue::Method(Self::get_id_method_from_idx(
                *val as usize,
                dex,
            )?)),
            EncodedValue::Enum(val) => Ok(DexValue::Enum(IdEnum(Self::get_id_field_from_idx(
                *val as usize,
                dex,
            )?))),
            EncodedValue::Array(arr) => Ok(DexValue::Array(DexArray(
                arr.values
                    .iter()
                    .map(|val| Self::encoded_value_to_dex_value(val, dex))
                    .collect::<Result<_>>()?,
            ))),
            EncodedValue::Annotation(val) => Ok(DexValue::Annotation(
                Self::get_annotations_from_encoded_annotation(val, dex)?,
            )),
            EncodedValue::Null => Ok(DexValue::Null(DexNull)),
            EncodedValue::Boolean(val) => Ok(DexValue::Boolean(DexBoolean(*val))),
        }
    }

    /// Return a [`Field`] from it's idx (index in `field_ids`) in the dex file and its access flags
    /// ([`EncodedField.access_flags`])
    pub fn get_field_from_idx(
        idx: usize,
        Uleb128(access_flags): Uleb128,
        dex: &DexFileReader,
    ) -> Result<Field> {
        let descriptor = Self::get_id_field_from_idx(idx, dex)?;

        let is_public = (access_flags & ACC_PUBLIC) != 0;
        let is_private = (access_flags & ACC_PRIVATE) != 0;
        let is_protected = (access_flags & ACC_PROTECTED) != 0;
        let is_static = (access_flags & ACC_STATIC) != 0;
        let is_final = (access_flags & ACC_FINAL) != 0;
        let is_volatile = (access_flags & ACC_VOLATILE) != 0;
        let is_transient = (access_flags & ACC_TRANSIENT) != 0;
        let is_synthetic = (access_flags & ACC_SYNTHETIC) != 0;
        let is_enum = (access_flags & ACC_ENUM) != 0;
        let visibility = match (is_public, is_private, is_protected) {
            (true, false, false) => FieldVisibility::Public,
            (false, true, false) => FieldVisibility::Private,
            (false, false, true) => FieldVisibility::Protected,
            (false, false, false) => FieldVisibility::None_,
            (pbl, prv, prt) => {
                let class: String = descriptor.class_.0.__str__();
                let name: String = descriptor.name.__str__();
                return Err(anyhow!(
                    "Inconsistant visiblity found in {class}.{name}: \
                        (public: {pbl}, private: {prv}, protected: {prt})"
                ));
            }
        };
        if access_flags
            & !(ACC_PUBLIC
                | ACC_PRIVATE
                | ACC_PROTECTED
                | ACC_STATIC
                | ACC_FINAL
                | ACC_VOLATILE
                | ACC_TRANSIENT
                | ACC_SYNTHETIC
                | ACC_ENUM)
            != 0
        {
            info!(
                "Unexpected flags found in {} for : 0x{:x} (unknown flags: 0x{:x})",
                descriptor.__repr__(),
                access_flags,
                access_flags
                    & !(ACC_PUBLIC
                        | ACC_PRIVATE
                        | ACC_PROTECTED
                        | ACC_STATIC
                        | ACC_FINAL
                        | ACC_VOLATILE
                        | ACC_TRANSIENT
                        | ACC_SYNTHETIC
                        | ACC_ENUM)
            );
        }
        Ok(Field {
            descriptor,
            visibility,
            is_static,
            is_final,
            is_volatile,
            is_transient,
            is_synthetic,
            is_enum,
            value: None,
            hiddenapi: None,
            annotations: vec![],
        })
    }

    /// Return a list of field from a list of [`EncodedField`].
    ///
    /// # Warning
    ///
    /// The index of the fields is computed by summing the [`EncodedField.field_idx_diff`] of the
    /// previous element of the list the diff of the current field, so the list must be preserved
    /// as in the dex file.
    pub fn get_field_list_from_encoded_field_list(
        encoded_fields: &[EncodedField],
        dex: &DexFileReader,
    ) -> Result<Vec<Field>> {
        let mut idx = 0;
        let mut fields = vec![];
        for field in encoded_fields {
            idx += field.field_idx_diff.0;

            fields.push(Self::get_field_from_idx(
                idx as usize,
                field.access_flags,
                dex,
            )?);
        }
        Ok(fields)
    }

    /// Return a [`Method`] from it's idx (index in `method_ids`) in the dex file and its access flags
    /// ([`EncodedMethod.access_flags`]) and code offset ([`EncodedMethod.code_off`]).
    pub fn get_method_from_idx<F>(
        idx: usize,
        Uleb128(access_flags): Uleb128,
        Uleb128(code_off): Uleb128,
        dex: &DexFileReader,
        mut label_ins: F,
    ) -> Result<Method>
    where
        F: FnMut(&IdMethod, &instructions::Instruction, usize) -> Option<String> + Clone,
    {
        let descriptor = Self::get_id_method_from_idx(idx, dex)?;

        let is_public = (access_flags & ACC_PUBLIC) != 0;
        let is_private = (access_flags & ACC_PRIVATE) != 0;
        let is_protected = (access_flags & ACC_PROTECTED) != 0;
        let is_static = (access_flags & ACC_STATIC) != 0;
        let is_final = (access_flags & ACC_FINAL) != 0;
        let is_synchronized = (access_flags & ACC_SYNCHRONIZED) != 0;
        let is_bridge = (access_flags & ACC_BRIDGE) != 0;
        let is_varargs = (access_flags & ACC_VARARGS) != 0;
        let is_native = (access_flags & ACC_NATIVE) != 0;
        let is_abstract = (access_flags & ACC_ABSTRACT) != 0;
        let is_strictfp = (access_flags & ACC_STRICT) != 0;
        let is_synthetic = (access_flags & ACC_SYNTHETIC) != 0;
        let is_constructor = (access_flags & ACC_CONSTRUCTOR) != 0;
        let is_declared_syncrhonized = (access_flags & ACC_DECLARED_SYNCHRONIZED) != 0;
        let visibility = match (is_public, is_private, is_protected) {
            (true, false, false) => MethodVisibility::Public,
            (false, true, false) => MethodVisibility::Private,
            (false, false, true) => MethodVisibility::Protected,
            (false, false, false) => MethodVisibility::None_,
            (pbl, prv, prt) => {
                return Err(anyhow!(
                    "Inconsistant visiblity found in {}: \
                        (public: {pbl}, private: {prv}, protected: {prt})",
                    descriptor.__repr__()
                ));
                // TODO: replace by public?
            }
        };
        if access_flags
            & !(ACC_PUBLIC
                | ACC_PRIVATE
                | ACC_PROTECTED
                | ACC_STATIC
                | ACC_FINAL
                | ACC_SYNCHRONIZED
                | ACC_BRIDGE
                | ACC_VARARGS
                | ACC_NATIVE
                | ACC_ABSTRACT
                | ACC_STRICT
                | ACC_SYNTHETIC
                | ACC_CONSTRUCTOR
                | ACC_DECLARED_SYNCHRONIZED)
            != 0
        {
            info!(
                "Unexpected flags found in {} for : 0x{:x} (unknown flags: 0x{:x})",
                descriptor.__repr__(),
                access_flags,
                access_flags
                    & !(ACC_PUBLIC
                        | ACC_PRIVATE
                        | ACC_PROTECTED
                        | ACC_STATIC
                        | ACC_FINAL
                        | ACC_SYNCHRONIZED
                        | ACC_BRIDGE
                        | ACC_VARARGS
                        | ACC_NATIVE
                        | ACC_ABSTRACT
                        | ACC_STRICT
                        | ACC_SYNTHETIC
                        | ACC_CONSTRUCTOR
                        | ACC_DECLARED_SYNCHRONIZED)
            );
        }
        let code = if code_off == 0 {
            None
        } else {
            match Self::get_code_from_off(code_off, dex, |ins, addr| {
                label_ins(&descriptor, ins, addr)
            })
            .with_context(|| format!("Failed to parse code of method {}", descriptor.__str__()))
            {
                Err(e) => {
                    error!(
                        "Code of {} could not be loaded: {}",
                        descriptor.__str__(),
                        e
                    );
                    None
                }
                Ok(code) => Some(code),
            }
        };

        Ok(Method {
            descriptor,
            visibility,
            is_static,
            is_final,
            is_synchronized,
            is_bridge,
            is_varargs,
            is_native,
            is_abstract,
            is_strictfp,
            is_synthetic,
            is_constructor,
            is_declared_syncrhonized,
            annotations: vec![],
            parameters_annotations: vec![],
            hiddenapi: None,
            code,
        })
    }

    /// Convert an instruction format to an instruction.
    fn instruction_format_to_instruction<F>(
        format: &InsFormat,
        addr: usize,
        insns_ref: &HashMap<usize, &InsFormat>,
        dex: &DexFileReader,
        label_ins: &mut F,
    ) -> Result<Option<(instructions::Instruction, HashMap<usize, String>)>>
    where
        F: FnMut(&instructions::Instruction, usize) -> Option<String>,
    {
        use crate::instructions::*;
        use InsFormat::*;
        let mut labels = HashMap::new();
        let ins = match format.clone() {
            Format10X { op: 0x00 } => Instruction::Nop {},
            Format12X { op: 0x01, va, vb } => Instruction::Move {
                to: va as u16,
                from: vb as u16,
            },
            Format22X { op: 0x02, va, vb } => Instruction::Move {
                to: va as u16,
                from: vb,
            },
            Format32X { op: 0x03, va, vb } => Instruction::Move { to: va, from: vb },
            Format12X { op: 0x04, va, vb } => Instruction::MoveWide {
                to: va as u16,
                from: vb as u16,
            },
            Format22X { op: 0x05, va, vb } => Instruction::MoveWide {
                to: va as u16,
                from: vb,
            },
            Format32X { op: 0x06, va, vb } => Instruction::MoveWide { to: va, from: vb },
            Format12X { op: 0x07, va, vb } => Instruction::MoveObject {
                to: va as u16,
                from: vb as u16,
            },
            Format22X { op: 0x08, va, vb } => Instruction::MoveObject {
                to: va as u16,
                from: vb,
            },
            Format32X { op: 0x09, va, vb } => Instruction::MoveObject { to: va, from: vb },
            Format11X { op: 0x0a, va } => Instruction::MoveResult { to: va },
            Format11X { op: 0x0b, va } => Instruction::MoveResultWide { to: va },
            Format11X { op: 0x0c, va } => Instruction::MoveResultObject { to: va },
            Format11X { op: 0x0d, va } => Instruction::MoveException { to: va },
            Format10X { op: 0x0e } => Instruction::ReturnVoid {},
            Format11X { op: 0x0f, va } => Instruction::Return { reg: va },
            Format11X { op: 0x10, va } => Instruction::ReturnWide { reg: va },
            Format11X { op: 0x11, va } => Instruction::ReturnObject { reg: va },
            Format11N { op: 0x12, va, b } => Instruction::Const {
                reg: va,
                lit: b as i32,
            },
            Format21S { op: 0x13, va, b } => Instruction::Const {
                reg: va,
                lit: b as i32,
            },
            Format31I { op: 0x14, va, b } => Instruction::Const { reg: va, lit: b },
            Format21H { op: 0x15, va, b } => Instruction::Const {
                reg: va,
                lit: (b as i32) * 65536,
            }, // *2**16
            Format21S { op: 0x16, va, b } => Instruction::ConstWide {
                reg: va,
                lit: b as i64,
            },
            Format31I { op: 0x17, va, b } => Instruction::ConstWide {
                reg: va,
                lit: b as i64,
            },
            Format51L { op: 0x18, va, b } => Instruction::ConstWide { reg: va, lit: b },
            Format21H { op: 0x19, va, b } => {
                Instruction::ConstWide {
                    reg: va,
                    lit: (b as i64) * 281474976710656,
                }
                // * 2**48
            }
            Format21C { op: 0x1a, va, b } => Instruction::ConstString {
                reg: va,
                lit: dex.get_string(b as u32)?.into(),
            },
            Format31C { op: 0x1b, va, b } => Instruction::ConstString {
                reg: va,
                lit: dex.get_string(b)?.into(),
            },
            Format21C { op: 0x1c, va, b } => Instruction::ConstClass {
                reg: va,
                lit: Self::get_id_type_from_idx(b as usize, dex)?,
            },
            Format11X { op: 0x1d, va } => Instruction::MonitorEnter { reg: va },
            Format11X { op: 0x1e, va } => Instruction::MonitorExit { reg: va },
            Format21C { op: 0x1f, va, b } => Instruction::CheckCast {
                reg: va,
                lit: Self::get_id_type_from_idx(b as usize, dex)?,
            },
            Format22C {
                op: 0x20,
                va,
                vb,
                c,
            } => Instruction::InstanceOf {
                dest: va,
                obj: vb,
                lit: Self::get_id_type_from_idx(c as usize, dex)?,
            },
            Format12X { op: 0x21, va, vb } => Instruction::ArrayLength { dest: va, arr: vb },
            Format21C { op: 0x22, va, b } => Instruction::NewInstance {
                reg: va,
                lit: Self::get_id_type_from_idx(b as usize, dex)?,
            },
            Format22C {
                op: 0x23,
                va,
                vb,
                c,
            } => Instruction::NewArray {
                reg: va,
                size_reg: vb,
                lit: Self::get_id_type_from_idx(c as usize, dex)?,
            },
            Format35C {
                op: 0x24,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
            } => {
                let type_ = Self::get_id_type_from_idx(b as usize, dex)?;
                let reg_values: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::FilledNewArray { type_, reg_values }
            }
            Format3RC { op: 0x25, a, b, vc } => {
                let type_ = Self::get_id_type_from_idx(b as usize, dex)?;
                let reg_values = (vc..vc + a as u16).collect();
                Instruction::FilledNewArray { type_, reg_values }
            }
            Format31T { op: 0x26, va, b } => {
                if b < 0 && (-(b as i64)) as usize > addr {
                    bail!(
                        "Found fill-array-data-payload v{va} +{b} at {addr}, \
                        data location invalid (negative addresse)"
                    );
                }
                let data_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64) as usize)
                };
                if let Some(ins) = insns_ref.get(&data_addr) {
                    if let FormatFillArrayDataPayload { elt_width, data } = ins {
                        Instruction::FillArrayData {
                            arr: va,
                            elt_width: *elt_width,
                            data: data.clone(),
                        }
                    } else {
                        bail!(
                            "Found fill-array-data-payload v{va} +{b} at 0x{addr:x}, \
                         found {ins:?} at {data_addr:x}, expected a fill-array-data-payload"
                        )
                    }
                } else {
                    bail!(
                        "Found fill-array-data-payload v{va} +{b} at 0x{addr:x}, \
                         no instruction found at 0x{data_addr:x}"
                    )
                }
            }
            Format11X { op: 0x27, va } => Instruction::Throw { reg: va },
            Format10T { op: 0x28, a } => {
                if a < 0 && (-(a as i64)) as usize > addr {
                    bail!("Found goto {a} at 0x{addr:0x}: the destination is invalid (negative addresse)");
                }
                let dest_addr = if a > 0 {
                    addr + a as usize
                } else {
                    addr - (-(a as i64) as usize)
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::Goto { label }
            }
            Format20T { op: 0x29, a } => {
                if a < 0 && (-(a as i64)) as usize > addr {
                    bail!("Found goto {a} at 0x{addr:0x}: the destination is invalid (negative addresse)");
                }
                let dest_addr = if a > 0 {
                    addr + a as usize
                } else {
                    addr - (-(a as i64) as usize)
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::Goto { label }
            }
            Format30T { op: 0x2a, a } => {
                if a < 0 && (-(a as i64)) as usize > addr {
                    bail!("Found goto {a} at 0x{addr:0x}: the destination is invalid (negative addresse)");
                }
                let dest_addr = if a > 0 {
                    addr + a as usize
                } else {
                    addr - (-(a as i64) as usize)
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::Goto { label }
            }
            Format31T { op: 0x2b, va, b } => {
                if b < 0 && -(b as i64) as usize > addr {
                    bail!(
                        "Found packed-switch v{va} +{b} at {addr}, \
                        data location invalid (negative addresse)"
                    );
                }
                let data_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64) as usize)
                };
                if let Some(ins) = insns_ref.get(&data_addr) {
                    if let FormatPackedSwitchPayload { first_key, targets } = ins {
                        let mut branches = HashMap::new();
                        let mut key = *first_key;
                        for target in targets.iter().cloned() {
                            if target < 0 && (-(target as i64)) as usize > addr {
                                bail!(
                                    "Found switch branche to offset {target} at 0x{addr:0x}: \
                                    the destination is invalid (negative addresse)"
                                );
                            }
                            let dest_addr = if target > 0 {
                                addr + target as usize
                            } else {
                                addr - (-(target as i64) as usize)
                            };
                            let label = format!("label_{dest_addr:08X}");
                            if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                                if old_label != label {
                                    // TODO: internal error, panic?
                                    bail!(
                                        "There is already a label at 0x{dest_addr:08X} with \
                                        an invalid name"
                                    );
                                }
                            }
                            branches.insert(key, label);
                            key += 1;
                        }
                        Instruction::Switch { reg: va, branches }
                    } else {
                        bail!(
                            "Found packed-switch v{va} +{b} at 0x{addr:x}, \
                         found {ins:?} at {data_addr:x}, expected a packed-switch-payload"
                        )
                    }
                } else {
                    bail!(
                        "Found packed-switch v{va} +{b} at 0x{addr:x}, \
                         no instruction found at {data_addr:x}"
                    )
                }
            }
            Format31T { op: 0x2c, va, b } => {
                if b < 0 && -(b as i64) as usize > addr {
                    bail!(
                        "Found sparsed-switch v{va} +{b} at {addr}, \
                        data location invalid (negative addresse)"
                    );
                }
                let data_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64) as usize)
                };
                if let Some(ins) = insns_ref.get(&data_addr) {
                    if let FormatSparseSwitchPayload { key_targets } = ins {
                        let mut branches = HashMap::new();
                        for (key, target) in key_targets.iter().cloned() {
                            if target < 0 && (-target) as usize > addr {
                                bail!(
                                    "Found switch branche to offset {target} at 0x{addr:0x}: \
                                    the destination is invalid (negative addresse)"
                                );
                            }
                            let dest_addr = if target > 0 {
                                addr + target as usize
                            } else {
                                addr - (-target as usize)
                            };
                            let label = format!("label_{dest_addr:08X}");
                            if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                                if old_label != label {
                                    // TODO: internal error, panic?
                                    bail!(
                                        "There is already a label at 0x{dest_addr:08X} with \
                                        an invalid name"
                                    );
                                }
                            }
                            branches.insert(key, label);
                        }
                        Instruction::Switch { reg: va, branches }
                    } else {
                        bail!(
                            "Found packed-switch v{va} +{b} at 0x{addr:x}, \
                         found {ins:?} at {data_addr:x}, expected a packed-switch-payload"
                        )
                    }
                } else {
                    bail!(
                        "Found packed-switch v{va} +{b} at 0x{addr:x}, \
                         no instruction found at {data_addr:x}"
                    )
                }
            }
            Format23X {
                op: 0x2d,
                va,
                vb,
                vc,
            } => Instruction::CmpLFloat {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x2e,
                va,
                vb,
                vc,
            } => Instruction::CmpGFloat {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x2f,
                va,
                vb,
                vc,
            } => Instruction::CmpLDouble {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x30,
                va,
                vb,
                vc,
            } => Instruction::CmpGDouble {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x31,
                va,
                vb,
                vc,
            } => Instruction::CmpLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format22T {
                op: 0x32,
                va,
                vb,
                c,
            } => {
                if c < 0 && (-(c as i64)) as usize > addr {
                    bail!(
                        "Found if-eq vA vB {c} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if c > 0 {
                    addr + c as usize
                } else {
                    addr - (-(c as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfEq {
                    a: va,
                    b: vb,
                    label,
                }
            }
            Format22T {
                op: 0x33,
                va,
                vb,
                c,
            } => {
                if c < 0 && (-(c as i64)) as usize > addr {
                    bail!(
                        "Found if-ne vA vB {c} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if c > 0 {
                    addr + c as usize
                } else {
                    addr - (-(c as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfNe {
                    a: va,
                    b: vb,
                    label,
                }
            }
            Format22T {
                op: 0x34,
                va,
                vb,
                c,
            } => {
                if c < 0 && (-(c as i64)) as usize > addr {
                    bail!(
                        "Found if-lt vA vB {c} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if c > 0 {
                    addr + c as usize
                } else {
                    addr - (-(c as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfLt {
                    a: va,
                    b: vb,
                    label,
                }
            }
            Format22T {
                op: 0x35,
                va,
                vb,
                c,
            } => {
                if c < 0 && (-(c as i64)) as usize > addr {
                    bail!(
                        "Found if-ge vA vB {c} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if c > 0 {
                    addr + c as usize
                } else {
                    addr - (-(c as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfGe {
                    a: va,
                    b: vb,
                    label,
                }
            }
            Format22T {
                op: 0x36,
                va,
                vb,
                c,
            } => {
                if c < 0 && (-(c as i64)) as usize > addr {
                    bail!(
                        "Found if-gt vA vB {c} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if c > 0 {
                    addr + c as usize
                } else {
                    addr - (-(c as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfGt {
                    a: va,
                    b: vb,
                    label,
                }
            }
            Format22T {
                op: 0x37,
                va,
                vb,
                c,
            } => {
                if c < 0 && (-(c as i64)) as usize > addr {
                    bail!(
                        "Found if-le vA vB {c} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if c > 0 {
                    addr + c as usize
                } else {
                    addr - (-(c as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfLe {
                    a: va,
                    b: vb,
                    label,
                }
            }
            Format21T { op: 0x38, va, b } => {
                if b < 0 && (-(b as i64)) as usize > addr {
                    bail!(
                        "Found if-eqz vA vB {b} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfEqZ { a: va, label }
            }
            Format21T { op: 0x39, va, b } => {
                if b < 0 && (-(b as i64)) as usize > addr {
                    bail!(
                        "Found if-nez vA vB {b} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfNeZ { a: va, label }
            }
            Format21T { op: 0x3a, va, b } => {
                if b < 0 && (-(b as i64)) as usize > addr {
                    bail!(
                        "Found if-ltz vA vB {b} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfLtZ { a: va, label }
            }
            Format21T { op: 0x3b, va, b } => {
                if b < 0 && (-(b as i64)) as usize > addr {
                    bail!(
                        "Found if-gez vA vB {b} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfGeZ { a: va, label }
            }
            Format21T { op: 0x3c, va, b } => {
                if b < 0 && (-(b as i64)) as usize > addr {
                    bail!(
                        "Found if-gtz vA vB {b} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfGtZ { a: va, label }
            }
            Format21T { op: 0x3d, va, b } => {
                if b < 0 && (-(b as i64)) as usize > addr {
                    bail!(
                        "Found if-lez vA vB {b} at 0x{addr:0x}: \
                        the destination is invalid (negative addresse)"
                    );
                }
                let dest_addr = if b > 0 {
                    addr + b as usize
                } else {
                    addr - (-(b as i64)) as usize
                };
                let label = format!("label_{dest_addr:08X}");
                if let Some(old_label) = labels.insert(dest_addr, label.clone()) {
                    if old_label != label {
                        // TODO: internal error, panic?
                        bail!("There is already a label at 0x{dest_addr:08X} with an invalid name");
                    }
                }
                Instruction::IfLeZ { a: va, label }
            }
            Format23X {
                op: 0x44,
                va,
                vb,
                vc,
            } => Instruction::AGet {
                dest: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x45,
                va,
                vb,
                vc,
            } => Instruction::AGetWide {
                dest: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x46,
                va,
                vb,
                vc,
            } => Instruction::AGetObject {
                dest: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x47,
                va,
                vb,
                vc,
            } => Instruction::AGetBoolean {
                dest: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x48,
                va,
                vb,
                vc,
            } => Instruction::AGetByte {
                dest: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x49,
                va,
                vb,
                vc,
            } => Instruction::AGetChar {
                dest: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x4a,
                va,
                vb,
                vc,
            } => Instruction::AGetShort {
                dest: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x4b,
                va,
                vb,
                vc,
            } => Instruction::APut {
                from: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x4c,
                va,
                vb,
                vc,
            } => Instruction::APutWide {
                from: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x4d,
                va,
                vb,
                vc,
            } => Instruction::APutObject {
                from: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x4e,
                va,
                vb,
                vc,
            } => Instruction::APutBoolean {
                from: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x4f,
                va,
                vb,
                vc,
            } => Instruction::APutByte {
                from: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x50,
                va,
                vb,
                vc,
            } => Instruction::APutChar {
                from: va,
                arr: vb,
                idx: vc,
            },
            Format23X {
                op: 0x51,
                va,
                vb,
                vc,
            } => Instruction::APutShort {
                from: va,
                arr: vb,
                idx: vc,
            },
            Format22C {
                op: 0x52,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IGet {
                    to: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x53,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IGetWide {
                    to: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x54,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IGetObject {
                    to: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x55,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IGetBoolean {
                    to: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x56,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IGetByte {
                    to: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x57,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IGetChar {
                    to: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x58,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IGetShort {
                    to: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x59,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IPut {
                    from: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x5a,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IPutWide {
                    from: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x5b,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IPutObject {
                    from: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x5c,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IPutBoolean {
                    from: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x5d,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IPutByte {
                    from: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x5e,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IPutChar {
                    from: va,
                    obj: vb,
                    field,
                }
            }
            Format22C {
                op: 0x5f,
                va,
                vb,
                c,
            } => {
                let field = Self::get_id_field_from_idx(c as usize, dex)?;
                Instruction::IPutShort {
                    from: va,
                    obj: vb,
                    field,
                }
            }
            Format21C { op: 0x60, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SGet { to: va, field }
            }
            Format21C { op: 0x61, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SGetWide { to: va, field }
            }
            Format21C { op: 0x62, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SGetObject { to: va, field }
            }
            Format21C { op: 0x63, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SGetBoolean { to: va, field }
            }
            Format21C { op: 0x64, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SGetByte { to: va, field }
            }
            Format21C { op: 0x65, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SGetChar { to: va, field }
            }
            Format21C { op: 0x66, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SGetShort { to: va, field }
            }
            Format21C { op: 0x67, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SPut { from: va, field }
            }
            Format21C { op: 0x68, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SPutWide { from: va, field }
            }
            Format21C { op: 0x69, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SPutObject { from: va, field }
            }
            Format21C { op: 0x6a, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SPutBoolean { from: va, field }
            }
            Format21C { op: 0x6b, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SPutByte { from: va, field }
            }
            Format21C { op: 0x6c, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SPutChar { from: va, field }
            }
            Format21C { op: 0x6d, va, b } => {
                let field = Self::get_id_field_from_idx(b as usize, dex)?;
                Instruction::SPutShort { from: va, field }
            }
            Format35C {
                op: 0x6e,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
            } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::InvokeVirtual { method: meth, args }
            }
            Format35C {
                op: 0x6f,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
            } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::InvokeSuper { method: meth, args }
            }
            Format35C {
                op: 0x70,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
            } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::InvokeDirect { method: meth, args }
            }
            Format35C {
                op: 0x71,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
            } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::InvokeStatic { method: meth, args }
            }
            Format35C {
                op: 0x72,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
            } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::InvokeInterface { method: meth, args }
            }
            Format3RC { op: 0x74, a, b, vc } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args = (vc..vc + a as u16).collect();
                Instruction::InvokeVirtual { method: meth, args }
            }
            Format3RC { op: 0x75, a, b, vc } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args = (vc..vc + a as u16).collect();
                Instruction::InvokeSuper { method: meth, args }
            }
            Format3RC { op: 0x76, a, b, vc } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args = (vc..vc + a as u16).collect();
                Instruction::InvokeDirect { method: meth, args }
            }
            Format3RC { op: 0x77, a, b, vc } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args = (vc..vc + a as u16).collect();
                Instruction::InvokeStatic { method: meth, args }
            }
            Format3RC { op: 0x78, a, b, vc } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let args = (vc..vc + a as u16).collect();
                Instruction::InvokeInterface { method: meth, args }
            }
            Format12X { op: 0x7b, va, vb } => Instruction::NegInt { dest: va, val: vb },
            Format12X { op: 0x7c, va, vb } => Instruction::NotInt { dest: va, val: vb },
            Format12X { op: 0x7d, va, vb } => Instruction::NegLong { dest: va, val: vb },
            Format12X { op: 0x7e, va, vb } => Instruction::NotLong { dest: va, val: vb },
            Format12X { op: 0x7f, va, vb } => Instruction::NegFloat { dest: va, val: vb },
            Format12X { op: 0x80, va, vb } => Instruction::NegDouble { dest: va, val: vb },
            Format12X { op: 0x81, va, vb } => Instruction::IntToLong { dest: va, val: vb },
            Format12X { op: 0x82, va, vb } => Instruction::IntToFloat { dest: va, val: vb },
            Format12X { op: 0x83, va, vb } => Instruction::IntToDouble { dest: va, val: vb },
            Format12X { op: 0x84, va, vb } => Instruction::LongToInt { dest: va, val: vb },
            Format12X { op: 0x85, va, vb } => Instruction::LongToFloat { dest: va, val: vb },
            Format12X { op: 0x86, va, vb } => Instruction::LongToDouble { dest: va, val: vb },
            Format12X { op: 0x87, va, vb } => Instruction::FloatToInt { dest: va, val: vb },
            Format12X { op: 0x88, va, vb } => Instruction::FloatToLong { dest: va, val: vb },
            Format12X { op: 0x89, va, vb } => Instruction::FloatToDouble { dest: va, val: vb },
            Format12X { op: 0x8a, va, vb } => Instruction::DoubleToInt { dest: va, val: vb },
            Format12X { op: 0x8b, va, vb } => Instruction::DoubleToLong { dest: va, val: vb },
            Format12X { op: 0x8c, va, vb } => Instruction::DoubleToFloat { dest: va, val: vb },
            Format12X { op: 0x8d, va, vb } => Instruction::IntToByte { dest: va, val: vb },
            Format12X { op: 0x8e, va, vb } => Instruction::IntToChar { dest: va, val: vb },
            Format12X { op: 0x8f, va, vb } => Instruction::IntToShort { dest: va, val: vb },
            Format23X {
                op: 0x90,
                va,
                vb,
                vc,
            } => Instruction::AddInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x91,
                va,
                vb,
                vc,
            } => Instruction::SubInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x92,
                va,
                vb,
                vc,
            } => Instruction::MulInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x93,
                va,
                vb,
                vc,
            } => Instruction::DivInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x94,
                va,
                vb,
                vc,
            } => Instruction::RemInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x95,
                va,
                vb,
                vc,
            } => Instruction::AndInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x96,
                va,
                vb,
                vc,
            } => Instruction::OrInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x97,
                va,
                vb,
                vc,
            } => Instruction::XorInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x98,
                va,
                vb,
                vc,
            } => Instruction::ShlInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x99,
                va,
                vb,
                vc,
            } => Instruction::ShrInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x9a,
                va,
                vb,
                vc,
            } => Instruction::UshrInt {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x9b,
                va,
                vb,
                vc,
            } => Instruction::AddLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x9c,
                va,
                vb,
                vc,
            } => Instruction::SubLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x9d,
                va,
                vb,
                vc,
            } => Instruction::MulLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x9e,
                va,
                vb,
                vc,
            } => Instruction::DivLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0x9f,
                va,
                vb,
                vc,
            } => Instruction::RemLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa0,
                va,
                vb,
                vc,
            } => Instruction::AndLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa1,
                va,
                vb,
                vc,
            } => Instruction::OrLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa2,
                va,
                vb,
                vc,
            } => Instruction::XorLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa3,
                va,
                vb,
                vc,
            } => Instruction::ShlLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa4,
                va,
                vb,
                vc,
            } => Instruction::ShrLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa5,
                va,
                vb,
                vc,
            } => Instruction::UshrLong {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa6,
                va,
                vb,
                vc,
            } => Instruction::AddFloat {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa7,
                va,
                vb,
                vc,
            } => Instruction::SubFloat {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa8,
                va,
                vb,
                vc,
            } => Instruction::MulFloat {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xa9,
                va,
                vb,
                vc,
            } => Instruction::DivFloat {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xaa,
                va,
                vb,
                vc,
            } => Instruction::RemFloat {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xab,
                va,
                vb,
                vc,
            } => Instruction::AddDouble {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xac,
                va,
                vb,
                vc,
            } => Instruction::SubDouble {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xad,
                va,
                vb,
                vc,
            } => Instruction::MulDouble {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xae,
                va,
                vb,
                vc,
            } => Instruction::DivDouble {
                dest: va,
                b: vb,
                c: vc,
            },
            Format23X {
                op: 0xaf,
                va,
                vb,
                vc,
            } => Instruction::RemDouble {
                dest: va,
                b: vb,
                c: vc,
            },
            Format12X { op: 0xb0, va, vb } => Instruction::AddInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb1, va, vb } => Instruction::SubInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb2, va, vb } => Instruction::MulInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb3, va, vb } => Instruction::DivInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb4, va, vb } => Instruction::RemInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb5, va, vb } => Instruction::AndInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb6, va, vb } => Instruction::OrInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb7, va, vb } => Instruction::XorInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb8, va, vb } => Instruction::ShlInt2Addr { dest: va, b: vb },
            Format12X { op: 0xb9, va, vb } => Instruction::ShrInt2Addr { dest: va, b: vb },
            Format12X { op: 0xba, va, vb } => Instruction::UshrInt2Addr { dest: va, b: vb },
            Format12X { op: 0xbb, va, vb } => Instruction::AddLong2Addr { dest: va, b: vb },
            Format12X { op: 0xbc, va, vb } => Instruction::SubLong2Addr { dest: va, b: vb },
            Format12X { op: 0xbd, va, vb } => Instruction::MulLong2Addr { dest: va, b: vb },
            Format12X { op: 0xbe, va, vb } => Instruction::DivLong2Addr { dest: va, b: vb },
            Format12X { op: 0xbf, va, vb } => Instruction::RemLong2Addr { dest: va, b: vb },
            Format12X { op: 0xc0, va, vb } => Instruction::AndLong2Addr { dest: va, b: vb },
            Format12X { op: 0xc1, va, vb } => Instruction::OrLong2Addr { dest: va, b: vb },
            Format12X { op: 0xc2, va, vb } => Instruction::XorLong2Addr { dest: va, b: vb },
            Format12X { op: 0xc3, va, vb } => Instruction::ShlLong2Addr { dest: va, b: vb },
            Format12X { op: 0xc4, va, vb } => Instruction::ShrLong2Addr { dest: va, b: vb },
            Format12X { op: 0xc5, va, vb } => Instruction::UshrLong2Addr { dest: va, b: vb },
            Format12X { op: 0xc6, va, vb } => Instruction::AddFloat2Addr { dest: va, b: vb },
            Format12X { op: 0xc7, va, vb } => Instruction::SubFloat2Addr { dest: va, b: vb },
            Format12X { op: 0xc8, va, vb } => Instruction::MulFloat2Addr { dest: va, b: vb },
            Format12X { op: 0xc9, va, vb } => Instruction::DivFloat2Addr { dest: va, b: vb },
            Format12X { op: 0xca, va, vb } => Instruction::RemFloat2Addr { dest: va, b: vb },
            Format12X { op: 0xcb, va, vb } => Instruction::AddDouble2Addr { dest: va, b: vb },
            Format12X { op: 0xcc, va, vb } => Instruction::SubDouble2Addr { dest: va, b: vb },
            Format12X { op: 0xcd, va, vb } => Instruction::MulDouble2Addr { dest: va, b: vb },
            Format12X { op: 0xce, va, vb } => Instruction::DivDouble2Addr { dest: va, b: vb },
            Format12X { op: 0xcf, va, vb } => Instruction::RemDouble2Addr { dest: va, b: vb },
            Format22S {
                op: 0xd0,
                va,
                vb,
                c,
            } => Instruction::AddIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22S {
                op: 0xd1,
                va,
                vb,
                c,
            } => Instruction::RsubIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22S {
                op: 0xd2,
                va,
                vb,
                c,
            } => Instruction::MulIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22S {
                op: 0xd3,
                va,
                vb,
                c,
            } => Instruction::DivIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22S {
                op: 0xd4,
                va,
                vb,
                c,
            } => Instruction::RemIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22S {
                op: 0xd5,
                va,
                vb,
                c,
            } => Instruction::AndIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22S {
                op: 0xd6,
                va,
                vb,
                c,
            } => Instruction::OrIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22S {
                op: 0xd7,
                va,
                vb,
                c,
            } => Instruction::XorIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22B {
                op: 0xd8,
                va,
                vb,
                c,
            } => Instruction::AddIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xd9,
                va,
                vb,
                c,
            } => Instruction::RsubIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xda,
                va,
                vb,
                c,
            } => Instruction::MulIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xdb,
                va,
                vb,
                c,
            } => Instruction::DivIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xdc,
                va,
                vb,
                c,
            } => Instruction::RemIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xdd,
                va,
                vb,
                c,
            } => Instruction::AndIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xde,
                va,
                vb,
                c,
            } => Instruction::OrIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xdf,
                va,
                vb,
                c,
            } => Instruction::XorIntLit {
                dest: va,
                b: vb,
                lit: c as i16,
            },
            Format22B {
                op: 0xe0,
                va,
                vb,
                c,
            } => Instruction::ShlIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22B {
                op: 0xe1,
                va,
                vb,
                c,
            } => Instruction::ShrIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format22B {
                op: 0xe2,
                va,
                vb,
                c,
            } => Instruction::UshrIntLit {
                dest: va,
                b: vb,
                lit: c,
            },
            Format45CC {
                op: 0xfa,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
                h,
            } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let proto = Self::get_id_method_type_from_idx(h as usize, dex)?;
                let args: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::InvokePolymorphic {
                    method: meth,
                    proto,
                    args,
                }
            }
            Format4RCC {
                op: 0xfb,
                a,
                b,
                vc,
                h,
            } => {
                let meth = Self::get_id_method_from_idx(b as usize, dex)?;
                let proto = Self::get_id_method_type_from_idx(h as usize, dex)?;
                let args = (vc..vc + a as u16).collect();
                Instruction::InvokePolymorphic {
                    method: meth,
                    proto,
                    args,
                }
            }
            Format35C {
                op: 0xfc,
                a,
                vc,
                vd,
                ve,
                vf,
                vg,
                b,
            } => {
                let call_site = Self::get_call_site_from_idx(b as usize, dex)?;
                let args: Vec<_> =
                    [vc as u16, vd as u16, ve as u16, vf as u16, vg as u16][..a as usize].into();
                Instruction::InvokeCustom { call_site, args }
            }
            Format3RC { op: 0xfd, a, b, vc } => {
                let call_site = Self::get_call_site_from_idx(b as usize, dex)?;
                let args = (vc..vc + a as u16).collect();
                Instruction::InvokeCustom { call_site, args }
            }
            Format21C { op: 0xfe, va, b } => {
                let meth_handle = Self::get_method_handle_from_idx(b as usize, dex)?;
                Instruction::ConstMethodHandle {
                    to: va,
                    handle: meth_handle,
                }
            }
            Format21C { op: 0xff, va, b } => {
                let proto = Self::get_id_method_type_from_idx(b as usize, dex)?;
                Instruction::ConstMethodType { to: va, proto }
            }
            FormatPackedSwitchPayload { .. } => return Ok(None),
            //bail!("{format:?} is a pseudo instruction"),
            FormatSparseSwitchPayload { .. } => return Ok(None),
            //bail!("{format:?} is a pseudo instruction"),
            FormatFillArrayDataPayload { .. } => return Ok(None),
            //bail!("{format:?} is a pseudo instruction"),
            _ => bail!("Unknwon instruction found at {addr}: {format:?}"),
        };
        ins.sanity_check()
            .with_context(|| anyhow!("Invalid instruction {ins:?} found at {addr}: {format:?}"))?;
        if let Some(label) = label_ins(&ins, addr) {
            //let label = format!("label_{addr:08X}");
            labels.insert(addr, label);
        }

        Ok(Some((ins, labels)))
    }

    /// Return a [`Code`] from it's offset in the dex file.
    pub fn get_code_from_off<F>(offset: u32, dex: &DexFileReader, mut label_ins: F) -> Result<Code>
    where
        F: FnMut(&instructions::Instruction, usize) -> Option<String>,
    {
        use crate::instructions::Instruction;
        let code_item = dex.get_struct_at_offset::<CodeItem>(offset)?;

        let debug_info = if code_item.debug_info_off == 0 {
            None
        } else {
            Some(dex.get_struct_at_offset::<DebugInfoItem>(code_item.debug_info_off)?)
        };
        let parameter_names = if let Some(ref debug_info) = debug_info {
            let mut parameter_names = vec![];
            for name_idx in &debug_info.parameter_names {
                if name_idx == &NO_INDEX {
                    parameter_names.push(None);
                } else {
                    parameter_names.push(Some(dex.get_string(name_idx.0)?.into()));
                }
            }
            Some(parameter_names)
        } else {
            None
        };
        let (mut current_debug_info, mut debug_infos) = if let Some(debug_info) = debug_info {
            let mut debug_infos = DebugInfoReader::new(debug_info);
            let current_debug_info = debug_infos.next_info();
            (current_debug_info, Some(debug_infos))
        } else {
            (DebugInfo::EndOfData, None)
        };
        let mut labels: HashMap<usize, String> = HashMap::new();
        let mut tries = HashMap::new();
        for TryItem {
            start_addr,
            insn_count,
            handler_off,
        } in code_item.tries
        {
            let EncodedCatchHandler {
                handlers,
                catch_all_addr,
            } = code_item
                .handlers
                .as_ref()
                .ok_or(anyhow!(
                    "Inconsistant code_item: found try blocks but no handler list"
                ))?
                .get_handler_at_offset(handler_off)?;
            let default_handler = if let Some(Uleb128(addr)) = *catch_all_addr {
                let label = format!("label_{addr:08X}");
                if let Some(label_) = labels.insert(addr as usize, label.clone()) {
                    if label_ != label {
                        // TODO: that's an internal error
                        bail!("Label collision at 0x{addr:08X}: {label_} and {label}");
                    }
                }
                Some(label)
            } else {
                None
            };
            let mut handlers_ = vec![];
            for EncodedTypeAddrPair {
                type_idx: Uleb128(type_idx),
                addr: Uleb128(addr),
            } in handlers.iter().cloned()
            {
                let label = format!("label_{addr:08X}");
                if let Some(label_) = labels.insert(addr as usize, label.clone()) {
                    if label_ != label {
                        // TODO: that's an internal error
                        bail!("Label collision at 0x{addr:08X}: {label_} and {label}");
                    }
                }
                handlers_.push((Self::get_id_type_from_idx(type_idx as usize, dex)?, label))
            }
            let dest_addr = start_addr + insn_count as u32;
            let end_label = format!("label_{dest_addr:08X}");
            if let Some(label_) = labels.insert(dest_addr as usize, end_label.clone()) {
                if label_ != end_label {
                    // TODO: that's an internal error
                    bail!("Label collision at 0x{dest_addr:08X}: {label_} and {end_label}");
                }
            }
            let try_ = Instruction::Try {
                end_label,
                handlers: handlers_,
                default_handler,
            };
            if let Some(try__) = tries.insert(start_addr as usize, try_.clone()) {
                bail!(
                    "Found two try blocks at the same address 0x{start_addr:08X}: \
                    {try__:?}, {try_:?}"
                );
            }
        }

        let mut instructions_raw = HashMap::new();
        let mut addr = 0;
        for ins_f in &code_item.insns {
            instructions_raw.insert(addr, ins_f);
            addr += ins_f.size() / 2;
        }
        let mut instructions = vec![];
        addr = 0;
        for ins_f in &code_item.insns {
            if let Some((ins, ins_labels)) = Self::instruction_format_to_instruction(
                ins_f,
                addr,
                &instructions_raw,
                dex,
                &mut label_ins,
            )? {
                instructions.push((addr, ins));
                addr += ins_f.size() / 2;
                for (key, val) in &ins_labels {
                    if let Some(val_) = ins_labels.get(key) {
                        if val_ != val {
                            // TODO: internal error, panic?
                            bail!("Label collision at 0x{key:08X}: {val_} and {val}");
                        }
                    }
                }
                labels.extend(ins_labels);
            }
        }
        let mut insns = vec![];
        for (addr, ins) in instructions {
            while current_debug_info != DebugInfo::EndOfData
                && current_debug_info.get_addr() as usize <= addr
            {
                insns.push(match current_debug_info {
                    DebugInfo::DefLocal { reg, val, .. } => Instruction::DebugLocal {
                        reg,
                        name: val
                            .name_idx
                            .map(|idx| dex.get_string(idx))
                            .transpose()?
                            .map(DexString),
                        type_: val
                            .type_idx
                            .map(|idx| Self::get_id_type_from_idx(idx as usize, dex))
                            .transpose()?,
                        signature: val
                            .sig_idx
                            .map(|idx| dex.get_string(idx))
                            .transpose()?
                            .map(DexString),
                    },
                    DebugInfo::EndLocal { reg, .. } => Instruction::DebugEndLocal { reg },
                    DebugInfo::PrologueEnd { .. } => Instruction::DebugEndPrologue {},
                    DebugInfo::EpilogueBegin { .. } => Instruction::DebugBeginEpilogue {},
                    DebugInfo::SetSourceFile {
                        source_file_idx, ..
                    } => Instruction::DebugSourceFile {
                        file: source_file_idx
                            .map(|idx| dex.get_string(idx))
                            .transpose()?
                            .map(DexString),
                    },
                    DebugInfo::SetLineNumber { line_num, .. } => Instruction::DebugLine {
                        number: line_num as usize,
                    },
                    DebugInfo::EndOfData => {
                        panic!("Found EndOfData debug info, that should no happend here.")
                    }
                });
                current_debug_info = debug_infos
                    .as_mut()
                    .expect(
                        "Something whent wrong: debug_infos should not be \
                        None when current_debug_info is not DebugInfo::EndOfData",
                    )
                    .next_info();
            }
            if let Some(try_) = tries.remove(&addr) {
                insns.push(try_);
            }
            if let Some(label) = labels.remove(&addr) {
                insns.push(Instruction::Label { name: label });
            }
            insns.push(ins);
        }
        // notice: addr is not the same as the one in the loop, this one is computed previously
        // and is the addr after the last ins.
        if let Some(try_) = tries.remove(&addr) {
            insns.push(try_);
        }
        if let Some(label) = labels.remove(&addr) {
            insns.push(Instruction::Label { name: label });
        }
        if !labels.is_empty() {
            bail!("Could not put all label as instructions (label out of insns?)");
        }
        if !tries.is_empty() {
            bail!("Could not put all try block as instructions (addr not aligned with ins?)");
        }
        Ok(Code {
            registers_size: code_item.registers_size,
            ins_size: code_item.ins_size,
            outs_size: code_item.outs_size,
            parameter_names,
            insns,
        })
    }

    /// Return a list of field from a list of [`EncodedMethod`].
    ///
    /// # Warning
    ///
    /// The index of the fields is computed by summing the [`EncodedMethod.field_idx_diff`] of the
    /// previous element of the list the diff of the current field, so the list must be preserved
    /// as in the dex file.
    pub fn get_method_list_from_encoded_field_list<F>(
        encoded_methods: &[EncodedMethod],
        dex: &DexFileReader,
        label_ins: F,
    ) -> Result<Vec<Method>>
    where
        F: FnMut(&IdMethod, &instructions::Instruction, usize) -> Option<String> + Clone,
    {
        let mut idx = 0;
        let mut methods = vec![];
        for method in encoded_methods {
            idx += method.method_idx_diff.0;

            methods.push(Self::get_method_from_idx(
                idx as usize,
                method.access_flags,
                method.code_off,
                dex,
                label_ins.clone(),
            )?);
        }
        Ok(methods)
    }

    pub fn gen_raw_dex(&self) -> Result<HashMap<String, Vec<u8>>> {
        /*
        let mut dex_writers = vec![];
        let mut dex_writer = DexWriter::new();
        for class_ in self.classes.values() {
            match dex_writer.add_class(class_) {
                Ok(()) => (),
                Err(DexWritterError::OutOfSpace(_)) => {
                    dex_writers.push(dex_writer);
                    dex_writer = DexWriter::new();
                    match dex_writer.add_class(class_) {
                        Ok(()) => (),
                        Err(DexWritterError::OutOfSpace(err)) => {
                            bail!(
                                "Class {} contains to many references to fit in one .dex: {err}",
                                class_.__str__()
                            )
                        }
                    }
                }
            }
        }
        dex_writers.push(dex_writer);
        for dex_writer in dex_writers.iter_mut() {
            for string in &self.not_referenced_strings {
                dex_writer.add_string(string.clone());
            }
        }
        dex_writers
            .into_iter()
            .map(|mut dex_writer| dex_writer.gen_dex_file_to_vec())
            .collect()
        */
        let mut bin_dex_files = HashMap::new();
        // TODO: Multithread
        for (
            name,
            DexFile {
                classes,
                not_referenced_strings,
                bin_cache,
            },
        ) in self.dex_files.iter()
        {
            if let Some(bin_cache) = bin_cache {
                bin_dex_files.insert(name.clone(), bin_cache.clone());
            } else {
                let mut writer = DexWriter::new();
                for class_ in classes.values() {
                    match writer.add_class(class_) {
                        Ok(()) => (),
                        Err(DexWritterError::OutOfSpace(_)) => bail!(
                            "{name} dex file has to many class/method/field to be serialize in dex format"
                        ),
                    }
                }
                for string in not_referenced_strings {
                    writer.add_string(string.clone());
                }
                bin_dex_files.insert(name.clone(), writer.gen_dex_file_to_vec()?);
            };
        }
        Ok(bin_dex_files)
    }

    // TODO: check for android platform classes?
    /// Search for the given class. If several classes with the same name are found,
    /// return the class used by android (classes.dex over classes2.dex over classes3.dex until
    /// classesN.dex does not exists),
    /// and if not found in files used by android, look in any other files (classes0.dex,
    /// classses1.dex, classes02.dex, assets/stuff.dex, ...), and select the first one in
    /// alphabetical order of dex file name.
    pub fn get_class_mut(&mut self, class_id: &IdType) -> Option<&mut Class> {
        self.get_file_of_class(class_id, false).and_then(|file| {
            self.dex_files
                .get_mut(&file)
                .expect("`get_file_of_class()` return a string not in `dex_files`")
                .classes
                .get_mut(class_id)
        })
    }

    // TODO: check for android platform classes?
    /// Search for the given class. If several classes with the same name are found,
    /// return the class used by android (classes.dex over classes2.dex over classes3.dex until
    /// classesN.dex does not exists),
    /// and if not found in files used by android, look in any other files (classes0.dex,
    /// classses1.dex, classes02.dex, assets/stuff.dex, ...), and select the first one in
    /// alphabetical order of dex file name.
    pub fn get_class(&self, class_id: &IdType) -> Option<&Class> {
        self.get_file_of_class(class_id, false).and_then(|file| {
            self.dex_files
                .get(&file)
                .expect("`get_file_of_class()` return a string not in `dex_files`")
                .classes
                .get(class_id)
        })
    }

    /// Search for the dex file of a given class. If several classes with the same name exist,
    /// return the file used by android (classes.dex over classes2.dex over classes3.dex until
    /// classesN.dex does not exists), and if `valid_file_only` is set to false, look in any
    /// other files (classes0.dex, classses1.dex, classes02.dex, assets/stuff.dex, ...), and
    /// select the first one with the class in alphabetical order of dex file name.
    fn get_file_of_class(&self, class_id: &IdType, valid_file_only: bool) -> Option<String> {
        let mut files: HashSet<&String> = self.dex_files.keys().collect();
        let mut i = 0;
        let mut name: String = "classes.dex".into();
        while files.contains(&name) {
            if self
                .dex_files
                .get(&name)
                .expect(
                    "Something went wrong: `name` is from \
                `dex_files.keys()` but `dex_files.get(&name)` \
                failled",
                )
                .classes
                .contains_key(class_id)
            {
                return Some(name);
            }
            files.remove(&name);
            i += 1;
            name = format!("classes{}.dex", i + 1);
        }
        if valid_file_only {
            return None;
        }
        let mut files = files.into_iter().collect::<Vec<&String>>();
        files.sort();
        for name in files.into_iter() {
            if self
                .dex_files
                .get(name)
                .expect(
                    "Something went wrong: `name` is from \
                    `dex_files.keys()` but `dex_files.get(&name)` \
                     failled",
                )
                .classes
                .contains_key(class_id)
            {
                return Some(name.to_string());
            }
        }

        None
    }

    /// Load all android files in an application.
    /// This **does not include any .dex file that android would not load.
    ///
    /// - `label_ins` is a function that take an method id and instruction and return
    ///      true is a label "label_{addr:08X}" should be added befor the instruction.
    #[cfg(feature = "external-zip-reader")]
    pub fn load_apk<F>(apk: impl Read + Seek, label_ins: F, cache: bool) -> Result<Self>
    where
        F: FnMut(&IdMethod, &instructions::Instruction, usize) -> Option<String>
            + Clone
            + Send
            + Sync,
    {
        let mut zip = ZipArchive::new(apk)?;
        let mut apk = Self::default();
        let file_names: HashSet<String> = zip.file_names().map(|s| s.into()).collect();
        let mut dex_names: HashSet<String> = HashSet::new();
        let mut i = 0;
        loop {
            let name = if i == 0 {
                "classes.dex".into()
            } else {
                format!("classes{}.dex", i + 1)
            };
            i += 1;
            if file_names.contains(&name) {
                dex_names.insert(name);
            } else {
                break;
            }
        }
        for name in dex_names.into_iter() {
            let mut data = vec![];
            // TODO: It sould not be necessary to clone data, but right now DexFileReader takes a &[u8]
            std::io::copy(&mut zip.by_name(&name)?, &mut data)?;
            apk.add_dex_file(&name, &data, label_ins.clone(), cache)?;
        }
        Ok(apk)
    }

    /// Load all android files in an application.
    /// This **does not include any .dex file that android would not load.
    ///
    /// - `label_ins`: Function that take a method id, instruction and address and return
    ///      a label, if a label needs to be inserted before the instruction.
    /// - `cache`: if set to true, copy and cache the binary data format.
    #[cfg(not(feature = "external-zip-reader"))]
    pub fn load_apk<F>(apk: impl Read + Seek, label_ins: F, cache: bool) -> Result<Self>
    where
        F: FnMut(&IdMethod, &instructions::Instruction, usize) -> Option<String>
            + Clone
            + Send
            + Sync,
    {
        let mut apk_z = ZipFileReader::new(apk);
        let mut apk = Self::default();
        let dex_names = apk_z
            .get_classes_file_info()
            .into_iter()
            .map(|info| info.get_name())
            .collect::<Vec<_>>();
        for name in dex_names {
            let data = apk_z.read_file_as_vec(&name);
            apk.add_dex_file(&name, &data, label_ins.clone(), cache)?;
        }
        Ok(apk)
    }

    /// Add the content of a dex file to the apk.
    ///
    /// # Parameters
    /// - `name`: the name of the dex file
    /// - `data`: the dex file binary
    /// - `label_ins`: Function that take a method id, instruction and address and return
    ///      a label, if a label needs to be inserted before the instruction.
    /// - `cache`: if set to true, copy and cache the binary data format.
    pub fn add_dex_file<F>(
        &mut self,
        name: &str,
        data: &[u8],
        label_ins: F,
        cache: bool,
    ) -> Result<()>
    where
        F: FnMut(&IdMethod, &instructions::Instruction, usize) -> Option<String>
            + Clone
            + Send
            + Sync,
    {
        let name: String = name.into();
        if self.dex_files.contains_key(&name) {
            bail!("{name} already exist in the application")
        }
        let mut dex = DexFileReader::new(data)?;
        let classes = dex
            .get_class_defs()
            .par_iter()
            .enumerate()
            .map(|(idx, class)| self.get_class_from_dex_file(class, idx, &dex, label_ins.clone()))
            .map(|class| class.map(|class| (class.descriptor.clone(), class)))
            .collect::<Result<HashMap<IdType, Class>, _>>()?;
        let dex_file = DexFile {
            classes,
            not_referenced_strings: dex
                .get_not_resolved_strings()?
                .into_iter()
                .map(DexString)
                .collect(),
            bin_cache: if cache { Some(data.to_vec()) } else { None },
        };
        self.dex_files.insert(name, dex_file);
        Ok(())
    }
}

#[cfg_attr(feature = "python", pymethods)]
impl Apk {
    #[cfg_attr(feature = "python", new)]
    pub fn new() -> Self {
        // TODO take argument and dispatch to load_apk or load_apk_bin
        Self {
            dex_files: HashMap::new(),
        }
    }

    /// Load all android files in an application.
    /// This **does not include any .dex file that android would not load.
    #[cfg_attr(feature = "python", staticmethod)]
    #[cfg_attr(feature = "python", pyo3(signature = (apk, label_each_ins=false, cache=false)))]
    pub fn load_apk_path(apk: PathBuf, label_each_ins: bool, cache: bool) -> Result<Self> {
        let file = File::open(apk)?;
        Self::load_apk(
            file,
            |_, _, addr| {
                if label_each_ins {
                    Some(format!("label_{addr:08X}"))
                } else {
                    None
                }
            },
            cache,
        )
    }

    /// Load all android files in an application.
    /// This **does not include any .dex file that android would not load.
    #[cfg_attr(feature = "python", staticmethod)]
    #[cfg_attr(feature = "python", pyo3(signature = (apk, label_each_ins=false, cache=false)))]
    pub fn load_apk_bin(apk: &[u8], label_each_ins: bool, cache: bool) -> Result<Self> {
        Self::load_apk(
            Cursor::new(apk),
            |_, _, addr| {
                if label_each_ins {
                    Some(format!("label_{addr:08X}"))
                } else {
                    None
                }
            },
            cache,
        )
    }

    // TODO: change cache to true when cache invalidation is setup
    /// Add the content of a dex file to the apk.
    ///
    /// # Parameters
    /// - `name`: the name of the dex file
    /// - `data`: the dex file binary
    /// - `label_each_ins`: if set to true, insert a label before each instruction
    ///     indicating the instruction address
    /// - `cache`: if set to true, copy and cache the binary data format.
    #[cfg_attr(feature = "python", pyo3(name = "add_dex_file", signature = (name, data, label_each_ins=false, cache=false)))]
    pub fn add_dex_file_py(
        &mut self,
        name: &str,
        data: &[u8],
        label_each_ins: bool,
        cache: bool,
    ) -> Result<()> {
        self.add_dex_file(
            name,
            data,
            |_, _, addr| {
                if label_each_ins {
                    Some(format!("label_{addr:08X}"))
                } else {
                    None
                }
            },
            cache,
        )
    }

    /// List the classes implemented in the application.
    pub fn list_classes(&self) -> HashSet<IdType> {
        self.dex_files
            .values()
            .flat_map(|dex| dex.classes.keys())
            .cloned()
            .collect()
    }

    pub fn add_class(&mut self, dex_file: &str, class: Class) -> Result<()> {
        let file: String = dex_file.into();
        let id = class.descriptor.clone();
        if !self.dex_files.contains_key(&file) {
            self.dex_files.insert(file.clone(), DexFile::default());
        }
        if self.dex_files.get(&file).unwrap().classes.contains_key(&id) {
            bail!("class {} already exists in {}", id.__str__(), &file);
        }
        self.dex_files
            .get_mut(&file)
            .unwrap()
            .classes
            .insert(id, class);
        Ok(())
    }

    #[cfg_attr(feature = "python", pyo3(signature = (method_id, code=None, dex_file=None)))]
    pub fn set_method_code(
        &mut self,
        method_id: IdMethod,
        code: Option<Code>,
        dex_file: Option<&str>,
    ) -> Result<()> {
        let ty = &method_id.class_;

        let class = if let Some(dex_file) = dex_file {
            self.dex_files
                .get_mut(dex_file)
                .with_context(|| format!("file {} not found in apk", dex_file))?
                .classes
                .get_mut(&method_id.class_)
                .ok_or_else(|| {
                    anyhow!(
                        "Class {} not found in file {}",
                        method_id.class_.__repr__(),
                        dex_file
                    )
                })?
        } else {
            self.get_class_mut(ty)
                .with_context(|| format!("{} not found in apk", method_id.class_.__repr__()))?
        };
        let method = class
            .direct_methods
            .get_mut(&method_id)
            .or_else(|| class.virtual_methods.get_mut(&method_id))
            .ok_or_else(|| anyhow!("Method {} not found", method_id.__repr__()))?;
        method.code = code;
        Ok(())
    }

    #[cfg(feature = "python")]
    #[pyo3(name = "gen_raw_dex")] //Sad GIL noise
    pub fn py_gen_raw_dex(&self, py: Python<'_>) -> Result<HashMap<String, PyObject>> {
        Ok(self
            .gen_raw_dex()?
            .into_iter()
            .map(|(file_name, bytes)| (file_name, PyBytes::new(py, &bytes).into()))
            .collect())
    }

    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", pyo3(signature = (class, dex_file=None)))]
    pub fn remove_class(&mut self, class: &IdType, dex_file: Option<&str>) -> Result<()> {
        if let Some(dex_file) = dex_file {
            self.dex_files
                .get_mut(dex_file)
                .with_context(|| format!("file {} not found in apk", dex_file))?
                .classes
                .remove(class);
        } else {
            for DexFile { classes, .. } in self.dex_files.values_mut() {
                classes.remove(class);
            }
        }
        Ok(())
    }
}
