//! The structure that generate a .dex from classes.

use std::collections::HashMap;
use std::io::Cursor;

use anyhow::{anyhow, bail, Context};
use log::debug;

use crate::Result;
use crate::*;
use androscalpel_serializer::*;

use crate::dex_writer::DexIndex;
use crate::ins::{CallSite, Instruction};
use crate::instructions::*;
use androscalpel_serializer::Instruction as InsFormat;

/// A [`DexFragment`] is a 'precompile' class. Its is not compiled per-say,
/// but most structures are converted to a [`Serializable`] structs close to
/// the dalvik format. The structs still need to be linked before serialization.
///
/// Ids structs ([`IdType`], [`IdMethodType`], [`IdField`], ect...) are almost entirely
/// recomputed at the link stage and need to be sorted, so they are kept in the hight
/// level format.
#[derive(Debug, Clone)]
pub struct DexFragment {
    // /// The id of the class represented by this fragment.
    // class_id: IdType,
    /// The strings in the dex file, sorted.
    strings: Vec<DexString>,
    /// The types in the dex file, sorted.
    type_ids: Vec<IdType>,
    /// The prototypes in the dex file, sorted.
    proto_ids: Vec<IdMethodType>,
    /// The field ids in the dex file, sorted.
    field_ids: Vec<IdField>,
    /// The methods ids in the dex file, sorted.
    method_ids: Vec<IdMethod>,
    /// The call sites refered to in the bytecode.
    call_site_ids: Vec<CallSiteIdItem>,
    /// A struct that keep track of sections size, nb elements and offsets.
    section_manager: FragSectionManager,
    /// The class_def. The `annotations_off`, `class_data_off` and `static_values_off` field take
    /// the value of the offset from the start of their respective section +1 (or zero if the class
    /// does not have a value associated to the field).
    class_def: ClassDefItem,
    /// The class_data.
    class_data: Option<ClassDataItem>,
    /// The encoded_array_items section.
    encoded_array_items: Vec<EncodedArrayItem>,
    /// The annotations_directory_item.
    annotations_directory_item: Option<AnnotationsDirectoryItem>,
    /// The annotation_set_item section.
    annotation_set_items: Vec<AnnotationSetItem>,
    /// The annotation item section.
    annotation_items: Vec<AnnotationItem>,
    /// The annotation_set_ref_list section.
    annotation_set_lists: Vec<AnnotationSetRefList>,
    /// The method_handles section.
    method_handles: Vec<MethodHandleItem>,
    /// The code_items sections.
    code_items: Vec<CodeItem>,
    /// The debug info items section.
    debug_info_items: Vec<DebugInfoItem>,
    /// The list of interfaces of the class.
    interfaces: Vec<IdType>,
    /// The current link state of the fragment.
    link_state: FragLinkState,

    /// This should not be stored, but currently code items
    /// need to be generated when linking ids, and to avoid having
    /// fragmented data representations, the data used by the code
    /// must be generated "pre-linking". Idealy, code items should be
    /// precompiled in an intermediate state when creating the fragment,
    /// and the `FragIndex` would not be needed anymore.
    frag_index: Option<FragIndex>,
}

impl DexFragment {
    /// The strings in the dex fragment, sorted.
    pub fn strings(&self) -> &[DexString] {
        &self.strings
    }
    /// The types in the dex fragment, sorted.
    pub fn type_ids(&self) -> &[IdType] {
        &self.type_ids
    }
    /// The prototypes in the dex fragment, sorted.
    pub fn proto_ids(&self) -> &[IdMethodType] {
        &self.proto_ids
    }
    /// The field ids in the dex fragment, sorted.
    pub fn field_ids(&self) -> &[IdField] {
        &self.field_ids
    }
    /// The methods ids in the dex fragment, sorted.
    pub fn method_ids(&self) -> &[IdMethod] {
        &self.method_ids
    }
    /// The list of interfaces of the class.
    pub fn interfaces(&self) -> &[IdType] {
        &self.interfaces
    }
    /// The method_handles section.
    pub fn method_handles(&self) -> &[MethodHandleItem] {
        &self.method_handles
    }

    pub fn new(class: &Class) -> Result<Self> {
        debug!(
            "Building dex fragment for class {}",
            class.descriptor.__str__()
        );
        let mut frag = Self {
            //class_id: class.descriptor.clone(),
            strings: vec![],
            type_ids: vec![],
            proto_ids: vec![],
            field_ids: vec![],
            method_ids: vec![],
            class_def: ClassDefItem {
                class_idx: 0,
                access_flags: 0,
                superclass_idx: 0,
                interfaces_off: 0,
                source_file_idx: 0,
                annotations_off: 0,
                class_data_off: 0,
                static_values_off: 0,
            },
            call_site_ids: vec![],
            section_manager: FragSectionManager::default(),
            class_data: None,
            // type_lists_index: HashMap::new(),
            // type_lists_with_offset: vec![],
            encoded_array_items: vec![],
            method_handles: vec![],
            code_items: vec![],
            annotations_directory_item: None,
            annotation_set_items: vec![],
            annotation_items: vec![],
            annotation_set_lists: vec![],
            debug_info_items: vec![],
            interfaces: class.interfaces.clone(),
            link_state: FragLinkState::Unlinked,
            frag_index: None,
        };
        frag.strings = class.get_all_strings().into_iter().collect();
        frag.strings.sort();
        let strings_index = frag
            .strings
            .iter()
            .enumerate()
            .map(|(idx, string)| (string.clone(), idx))
            .collect::<HashMap<DexString, usize>>();
        frag.type_ids = class.get_all_types().into_iter().collect();
        frag.type_ids.sort();
        let types_index = frag
            .type_ids
            .iter()
            .enumerate()
            .map(|(idx, ty)| (ty.clone(), idx))
            .collect::<HashMap<IdType, usize>>();
        frag.proto_ids = class.get_all_protos().into_iter().collect();
        frag.proto_ids.sort();
        let protos_index = frag
            .proto_ids
            .iter()
            .enumerate()
            .map(|(idx, proto)| (proto.clone(), idx))
            .collect::<HashMap<IdMethodType, usize>>();
        frag.field_ids = class.get_all_field_ids().into_iter().collect();
        frag.field_ids.sort();
        let fields_index = frag
            .field_ids
            .iter()
            .enumerate()
            .map(|(idx, field)| (field.clone(), idx))
            .collect::<HashMap<IdField, usize>>();
        frag.method_ids = class.get_all_method_ids().into_iter().collect();
        frag.method_ids.sort();
        let methods_index = frag
            .method_ids
            .iter()
            .enumerate()
            .map(|(idx, method)| (method.clone(), idx))
            .collect::<HashMap<IdMethod, usize>>();

        let index = FragIndex {
            strings: strings_index,
            types: types_index,
            protos: protos_index,
            fields: fields_index,
            methods: methods_index,
        };

        frag.section_manager
            .add_elt(FragSection::ClassDefItem, None);

        frag.class_def.class_idx = *index.types.get(&class.descriptor).ok_or(anyhow!(
            "Type {} (type of class {}) not found in type list",
            class.descriptor.__str__(),
            class.__str__()
        ))? as u32;
        frag.class_def.access_flags = class.get_raw_access_flags();
        frag.class_def.superclass_idx = if let Some(sup) = &class.superclass {
            *index.types.get(sup).ok_or(anyhow!(
                "Type {} (superclass of class {}) not found in dex builder",
                sup.__str__(),
                class.__str__()
            ))? as u32
        } else {
            NO_INDEX.0
        };
        frag.class_def.interfaces_off = 0; // set when linking
        frag.class_def.source_file_idx = if let Some(file) = &class.source_file {
            *index.strings.get(file).ok_or(anyhow!(
                "String {} (source file of class {}) not found in dex builder",
                file.__str__(),
                class.__str__()
            ))? as u32
        } else {
            NO_INDEX.0
        };
        frag.class_def.annotations_off = if class.has_annotations() {
            frag.insert_annotations(class, &index)?;
            1
        } else {
            0
        };
        frag.class_def.class_data_off = if class.has_data_item() {
            frag.insert_class_data_item(class, &index)?;
            1
        } else {
            0
        };
        frag.class_def.static_values_off = if class.has_static_values_array() {
            let static_values_off = frag
                .section_manager
                .get_aligned_size(FragSection::EncodedArrayItem);
            frag.insert_class_static_values(class, &index)?;
            static_values_off + 1
        } else {
            0
        };
        frag.frag_index = Some(index);

        Ok(frag)
    }

    /// Insert a class annotations (including field, methods and parameters annotations).
    ///
    /// # Note
    ///
    /// annotation_set_item objects are 4 bytes aligns, so their offset cannot be odd.
    ///
    /// To distinguish prelinked value (offset inside the code_item section) to actual values (offset
    /// in the whole file or 0), their value is set to the actual value prelink value + 1. This allow
    /// to distinguish the offset of the first item (equal to zero before linking) and the value
    /// 0 used to indicate an abscence of item.
    fn insert_annotations(&mut self, class: &Class, index: &FragIndex) -> Result<()> {
        let class_annotations_off = if !class.annotations.is_empty() {
            let class_annotations_off = self
                .section_manager
                .get_aligned_size(FragSection::AnnotationSetItem);
            self.insert_class_annotation_set(class, index)
                .with_context(|| {
                    format!(
                        "Failed to insert class annotation for class {}",
                        class.__str__()
                    )
                })?;
            class_annotations_off + 1
        } else {
            0
        };

        let mut field_ids = vec![];
        field_ids.extend(class.static_fields.keys().cloned());
        field_ids.extend(class.instance_fields.keys().cloned());
        field_ids.sort();
        let mut field_annotations = vec![];
        for field_id in field_ids {
            let static_field = class.static_fields.get(&field_id);
            let instance_field = class.instance_fields.get(&field_id);
            let field = match (static_field, instance_field) {
                (Some(field), None) => field,
                (None, Some(field)) => field,
                _ => bail!(
                    "Unexpected configuration: field {} is both a static and a instance field in {}", 
                    field_id.__str__(), class.__str__()),
            };
            if !field.annotations.is_empty() {
                let annotations_off = self
                    .section_manager
                    .get_aligned_size(FragSection::AnnotationSetItem)
                    + 1;
                self.insert_field_annotation_set(field, index)?;
                field_annotations.push(FieldAnnotation {
                    field_idx: *index.fields.get(&field_id).ok_or(anyhow!(
                        "Field {} in {} not found in dex builder",
                        field_id.__str__(),
                        class.__str__(),
                    ))? as u32,
                    annotations_off,
                });
            }
        }

        let mut method_ids = vec![];
        method_ids.extend(class.direct_methods.keys().cloned());
        method_ids.extend(class.virtual_methods.keys().cloned());
        method_ids.sort();
        let mut method_annotations = vec![];
        for method_id in &method_ids {
            let direct_method = class.direct_methods.get(method_id);
            let virtual_method = class.virtual_methods.get(method_id);
            let method = match (direct_method, virtual_method) {
                (Some(method), None) => method,
                (None, Some(method)) => method,
                _ => bail!(
                    "Unexpected configuration: method {} is both a direct and a virtual method in {}", 
                    method_id.__str__(), class.__str__()),
            };
            if !method.annotations.is_empty() {
                let annotations_off = self
                    .section_manager
                    .get_aligned_size(FragSection::AnnotationSetItem)
                    + 1;
                self.insert_method_annotation_set(method, index)?;
                method_annotations.push(MethodAnnotation {
                    method_idx: *index.methods.get(method_id).ok_or(anyhow!(
                        "Method {} in {} not found in dex builder",
                        method_id.__str__(),
                        class.__str__(),
                    ))? as u32,
                    annotations_off,
                });
            }
        }

        let mut parameter_annotations = vec![];
        for method_id in method_ids {
            let direct_method = class.direct_methods.get(&method_id);
            let virtual_method = class.virtual_methods.get(&method_id);
            let method = match (direct_method, virtual_method) {
                (Some(method), None) => method,
                (None, Some(method)) => method,
                _ => bail!(
                    "Unexpected configuration: method {} is both a direct and a virtual method in {}", 
                    method_id.__str__(), class.__str__()),
            };
            if !method.parameters_annotations.is_empty() {
                let annotations_off = self
                    .section_manager
                    .get_aligned_size(FragSection::AnnotationSetRefList)
                    + 1;
                self.insert_parameters_annotation_set_list(method, index)?;
                parameter_annotations.push(ParameterAnnotation {
                    method_idx: *index.methods.get(&method_id).ok_or(anyhow!(
                        "Method {} in {} not found in dex builder",
                        method_id.__str__(),
                        class.__str__(),
                    ))? as u32,
                    annotations_off,
                });
            }
        }

        let item = AnnotationsDirectoryItem {
            class_annotations_off,
            field_annotations,
            method_annotations,
            parameter_annotations,
        };
        self.section_manager
            .add_elt(FragSection::AnnotationsDirectoryItem, Some(item.size()));
        self.annotations_directory_item = Some(item);
        Ok(())
    }

    /// Insert the annnotations set for a class.
    fn insert_class_annotation_set(&mut self, class: &Class, index: &FragIndex) -> Result<()> {
        let mut annotations = class.annotations.clone();

        let mut annotation_set = AnnotationSetItem { entries: vec![] };
        annotations.sort_by_key(|annot| annot.annotation.type_.clone());
        for annot in annotations {
            annotation_set.entries.push(AnnotationOffItem {
                annotation_off: self
                    .section_manager
                    .get_aligned_size(FragSection::AnnotationItem)
                    + 1,
            });

            let item = AnnotationItem {
                visibility: match (
                    annot.visibility_build,
                    annot.visibility_runtime,
                    annot.visibility_system,
                ) {
                    (true, false, false) => AnnotationVisibility::Build,
                    (false, true, false) => AnnotationVisibility::Runtime,
                    (false, false, true) => AnnotationVisibility::System,
                    _ => bail!(
                        "Annotation need visibility set to one and only one of build, \
                        runtime or system"
                    ), // TODO: check if this is true
                },
                annotation: self.dex_annotation_to_encoded_annotation(annot.annotation, index)?,
            };
            self.section_manager
                .add_elt(FragSection::AnnotationItem, Some(item.size()));
            self.annotation_items.push(item);
        }
        self.section_manager
            .add_elt(FragSection::AnnotationSetItem, Some(annotation_set.size()));
        self.annotation_set_items.push(annotation_set);
        Ok(())
    }

    /// Insert the annnotations set for a class.
    fn insert_field_annotation_set(&mut self, field: &Field, index: &FragIndex) -> Result<()> {
        let mut annotations = field.annotations.clone();

        let mut annotation_set = AnnotationSetItem { entries: vec![] };
        annotations.sort_by_key(|annot| annot.annotation.type_.clone());
        for annot in annotations {
            annotation_set.entries.push(AnnotationOffItem {
                annotation_off: self
                    .section_manager
                    .get_aligned_size(FragSection::AnnotationItem),
            });

            let item = AnnotationItem {
                visibility: match (
                    annot.visibility_build,
                    annot.visibility_runtime,
                    annot.visibility_system,
                ) {
                    (true, false, false) => AnnotationVisibility::Build,
                    (false, true, false) => AnnotationVisibility::Runtime,
                    (false, false, true) => AnnotationVisibility::System,
                    _ => bail!(
                        "Annotation need visibility set to one and only one of build, \
                        runtime or system"
                    ), // TODO: check if this is true
                },
                annotation: self.dex_annotation_to_encoded_annotation(annot.annotation, index)?,
            };
            self.section_manager
                .add_elt(FragSection::AnnotationItem, Some(item.size()));
            self.annotation_items.push(item);
        }
        self.section_manager
            .add_elt(FragSection::AnnotationSetItem, Some(annotation_set.size()));
        self.annotation_set_items.push(annotation_set);
        Ok(())
    }

    /// Insert the annnotations set for a method (but not the parameters annotations).
    fn insert_method_annotation_set(&mut self, method: &Method, index: &FragIndex) -> Result<()> {
        let mut annotations = method.annotations.clone();

        let mut annotation_set = AnnotationSetItem { entries: vec![] };
        annotations.sort_by_key(|annot| annot.annotation.type_.clone());
        for annot in annotations {
            annotation_set.entries.push(AnnotationOffItem {
                annotation_off: self
                    .section_manager
                    .get_aligned_size(FragSection::AnnotationItem)
                    + 1,
            });

            let item = AnnotationItem {
                visibility: match (
                    annot.visibility_build,
                    annot.visibility_runtime,
                    annot.visibility_system,
                ) {
                    (true, false, false) => AnnotationVisibility::Build,
                    (false, true, false) => AnnotationVisibility::Runtime,
                    (false, false, true) => AnnotationVisibility::System,
                    _ => bail!(
                        "Annotation need visibility set to one and only one of build, \
                        runtime or system"
                    ), // TODO: check if this is true
                },
                annotation: self.dex_annotation_to_encoded_annotation(annot.annotation, index)?,
            };
            self.section_manager
                .add_elt(FragSection::AnnotationItem, Some(item.size()));
            self.annotation_items.push(item);
        }
        self.section_manager
            .add_elt(FragSection::AnnotationSetItem, Some(annotation_set.size()));
        self.annotation_set_items.push(annotation_set);
        Ok(())
    }

    /// Insert a [`MethodHandle`].
    fn insert_method_handle(&mut self, handle: &MethodHandle, index: &FragIndex) -> Result<()> {
        let (field_or_method_id, method_handle_type) = match handle {
            MethodHandle::StaticPut(StaticPut(field)) => (
                *index
                    .fields
                    .get(field)
                    .ok_or(anyhow!("Field {} not found in dex writer", field.__str__()))?
                    as u16,
                MethodHandleType::StaticPut,
            ),
            MethodHandle::StaticGet(StaticGet(field)) => (
                *index
                    .fields
                    .get(field)
                    .ok_or(anyhow!("Field {} not found in dex writer", field.__str__()))?
                    as u16,
                MethodHandleType::StaticGet,
            ),
            MethodHandle::InstancePut(InstancePut(field)) => (
                *index
                    .fields
                    .get(field)
                    .ok_or(anyhow!("Field {} not found in dex writer", field.__str__()))?
                    as u16,
                MethodHandleType::InstancePut,
            ),
            MethodHandle::InstanceGet(InstanceGet(field)) => (
                *index
                    .fields
                    .get(field)
                    .ok_or(anyhow!("Field {} not found in dex writer", field.__str__()))?
                    as u16,
                MethodHandleType::InstanceGet,
            ),
            MethodHandle::InvokeStatic(InvokeStatic(meth)) => (
                *index
                    .methods
                    .get(meth)
                    .ok_or(anyhow!("Method {} not found in dex writer", meth.__str__()))?
                    as u16,
                MethodHandleType::InvokeStatic,
            ),
            MethodHandle::InvokeInstance(InvokeInstance(meth)) => (
                *index
                    .methods
                    .get(meth)
                    .ok_or(anyhow!("Method {} not found in dex writer", meth.__str__()))?
                    as u16,
                MethodHandleType::InvokeInstance,
            ),
            MethodHandle::InvokeConstructor(InvokeConstructor(meth)) => (
                *index
                    .methods
                    .get(meth)
                    .ok_or(anyhow!("Method {} not found in dex writer", meth.__str__()))?
                    as u16,
                MethodHandleType::InvokeConstructor,
            ),
            MethodHandle::InvokeDirect(InvokeDirect(meth)) => (
                *index
                    .methods
                    .get(meth)
                    .ok_or(anyhow!("Method {} not found in dex writer", meth.__str__()))?
                    as u16,
                MethodHandleType::InvokeDirect,
            ),
            MethodHandle::InvokeInterface(InvokeInterface(meth)) => (
                *index
                    .methods
                    .get(meth)
                    .ok_or(anyhow!("Method {} not found in dex writer", meth.__str__()))?
                    as u16,
                MethodHandleType::InvokeInterface,
            ),
        };
        self.method_handles.push(MethodHandleItem {
            method_handle_type,
            field_or_method_id,
            unused1: 0,
            unused2: 0,
        });
        Ok(())
    }

    // TODO: find if there is a way to efficiently link code item.
    /// Insert a code_item.
    ///
    /// This item cannot be cached, because the jump instructions depend on the size of
    /// instructions that depend on the size of the descriptor ids that depend on the
    /// list of all descriptors in the dex file.
    fn insert_code_item(
        &mut self,
        code: &Code,
        index: &DexIndex,
        nb_call_site_before_fragment: usize,
        nb_method_handle_before_fragment: usize,
        frag_index: &FragIndex,
    ) -> Result<()> {
        // Estimate instructions addresses
        let mut min_addr = 0;
        let mut max_addr = 0;
        let mut label_min_max_addrs: HashMap<String, (usize, usize)> = HashMap::new();

        for ins in &code.insns {
            match ins {
                Instruction::Label(Label { name }) => {
                    label_min_max_addrs.insert(name.clone(), (min_addr, max_addr));
                    min_addr += ins.min_ins_size() / 2;
                    max_addr += ins.max_ins_size() / 2;
                }
                Instruction::ConstString(ins) => {
                    let string_idx = index.strings.get(&ins.lit).ok_or(anyhow!(
                        "String {} not found in dex builder",
                        ins.lit.__str__(),
                    ))?;
                    let size = ins.get_raw_ins(*string_idx).size() / 2;
                    min_addr += size;
                    max_addr += size;
                }
                _ => {
                    min_addr += ins.min_ins_size() / 2;
                    max_addr += ins.max_ins_size() / 2;
                }
            }
        }
        // Compute instruction size and precise addresses
        let mut addr = 0;
        let mut label_addrs = HashMap::new();
        let mut goto_sizes = vec![];
        for ins in &code.insns {
            match ins {
                Instruction::Label(Label { name }) => {
                    label_addrs.insert(name.clone(), addr);
                    addr += ins.max_ins_size() / 2;
                }
                Instruction::ConstString(ins) => {
                    let string_idx = index.strings.get(&ins.lit).ok_or(anyhow!(
                        "String {} not found in dex builder",
                        ins.lit.__str__(),
                    ))?;
                    addr += ins.get_raw_ins(*string_idx).size() / 2;
                }
                Instruction::Goto(Goto { label }) => {
                    let (min_addr, max_addr) = label_min_max_addrs
                        .get(label)
                        .ok_or(anyhow!("Label {label} not found in label estimation map"))?;
                    let size = Goto::size_from_branch_offset_interval(addr, *min_addr, *max_addr)?;
                    goto_sizes.push(size);
                    addr += size / 2;
                }
                _ => addr += ins.ins_size()? / 2,
            }
        }
        // Serialize instructions
        let mut tries = vec![];
        let mut handlers = EncodedCatchHandlerList { list: vec![] };
        let mut handler_off = 0;
        let mut insns = vec![];
        let mut payloads = vec![];
        let mut goto_idx = 0;
        let mut payload_addr = addr;
        if payload_addr % 2 != 0 {
            payload_addr += 1; // For switch and array table alignment
        }
        addr = 0;
        for ins in &code.insns {
            match ins {
                Instruction::ConstString(ins) => {
                    let string_idx = index.strings.get(&ins.lit).ok_or(anyhow!(
                        "String {} not found in dex fragment",
                        ins.lit.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*string_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::ConstClass(ins) => {
                    let class_idx = *index.types.get(&ins.lit).ok_or(anyhow!(
                        "Class {} not found in dex fragment",
                        ins.lit.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(class_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::CheckCast(ins) => {
                    let class_idx = *index.types.get(&ins.lit).ok_or(anyhow!(
                        "Class {} not found in dex fragment",
                        ins.lit.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(class_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InstanceOf(ins) => {
                    let class_idx = *index.types.get(&ins.lit).ok_or(anyhow!(
                        "Class {} not found in dex fragment",
                        ins.lit.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(class_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::NewInstance(ins) => {
                    let class_idx = *index.types.get(&ins.lit).ok_or(anyhow!(
                        "Class {} not found in dex fragment",
                        ins.lit.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(class_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::NewArray(ins) => {
                    let class_idx = *index.types.get(&ins.lit).ok_or(anyhow!(
                        "Type {} not found in dex fragment",
                        ins.lit.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(class_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::FilledNewArray(ins) => {
                    let class_idx = *index.types.get(&ins.type_).ok_or(anyhow!(
                        "Type {} not found in dex fragment",
                        ins.type_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(class_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::FillArrayData(ins) => {
                    let payload = InsFormat::FormatFillArrayDataPayload {
                        elt_width: ins.elt_width,
                        data: ins.data.clone(),
                    };
                    if payload_addr % 2 != 0 {
                        // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=e8c3e7be783937a340cd4f3280b69962d6f1ea0c;l=1347
                        // The ART check if the array data table is 4 bytes aligned (= 2 ins alligned)
                        // TODO: check how it is donne in android and other dex generation code.
                        let nop = Instruction::Nop(Nop).get_raw_ins()?;
                        payload_addr += nop.size() / 2;
                        payloads.push(nop);
                    }
                    let data_offset = payload_addr as i32 - addr as i32;
                    payload_addr += payload.size() / 2;
                    payloads.push(payload);
                    let ins = ins.get_raw_ins(data_offset);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::Goto(ins) => {
                    let goto_size = goto_sizes[goto_idx];
                    goto_idx += 1;
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found goto with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    let ins = ins.get_raw_ins(branch_offset, goto_size);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::Switch(ins) => {
                    let mut key_targets = vec![];
                    for (key, label) in &ins.branches {
                        let label_addr = label_addrs.get(label).ok_or(anyhow!(
                            "Label {} not found in code, but found goto with this label",
                            label,
                        ))?;
                        let branch_offset = *label_addr as i32 - addr as i32;
                        key_targets.push((*key, branch_offset));
                    }
                    key_targets.sort_by_key(|(key, _)| *key);
                    let payload = if ins.is_packed() {
                        let (first_key, _) = *key_targets
                            .first()
                            .ok_or(anyhow!("Found empty swith in code"))?;
                        let targets: Vec<_> =
                            key_targets.into_iter().map(|(_, target)| target).collect();
                        InsFormat::FormatPackedSwitchPayload { first_key, targets }
                    } else {
                        InsFormat::FormatSparseSwitchPayload { key_targets }
                    };
                    if payload_addr % 2 != 0 {
                        // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=e8c3e7be783937a340cd4f3280b69962d6f1ea0c;l=1464
                        // The ART check if the switch table is 4 bytes aligned (= 2 ins alligned)
                        // TODO: check how it is donne in android and other dex generation code.
                        let nop = Instruction::Nop(Nop).get_raw_ins()?;
                        payload_addr += nop.size() / 2;
                        payloads.push(nop);
                    }
                    let data_offset = payload_addr as i32 - addr as i32;
                    payload_addr += payload.size() / 2;
                    payloads.push(payload);
                    let ins = ins.get_raw_ins(data_offset);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfEq(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfNe(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfLt(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfGe(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfGt(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfLe(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfEqZ(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfNeZ(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfLtZ(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfGeZ(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfGtZ(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IfLeZ(ins) => {
                    let label_addr = label_addrs.get(&ins.label).ok_or(anyhow!(
                        "Label {} not found in code, but found if with this label",
                        ins.label,
                    ))?;
                    let branch_offset = *label_addr as i32 - addr as i32;
                    if branch_offset > i16::MAX as i32 || branch_offset < i16::MIN as i32 {
                        bail!("Found an if that jump to far from the instruction in code");
                    }
                    let ins = ins.get_raw_ins(branch_offset as i16);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IGet(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IGetWide(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IGetObject(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IGetBoolean(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IGetByte(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IGetChar(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IGetShort(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IPut(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IPutWide(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IPutObject(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IPutBoolean(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IPutByte(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IPutChar(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::IPutShort(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SGet(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SGetWide(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SGetObject(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SGetBoolean(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SGetByte(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SGetChar(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SGetShort(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SPut(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SPutWide(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SPutObject(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SPutBoolean(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SPutByte(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SPutChar(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::SPutShort(ins) => {
                    let field = &ins.field;
                    let field_idx = index.fields.get(field).ok_or(anyhow!(
                        "Field {} (field of class {}) not found in dex fragment",
                        field.__str__(),
                        field.class_.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*field_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InvokeVirtual(ins) => {
                    let meth = &ins.method;
                    let meth_idx = index.methods.get(meth).ok_or(anyhow!(
                        "Method {} (method of class {}) not found in dex fragment",
                        meth.__str__(),
                        meth.class_.__str__(),
                    ))?;
                    debug_assert!(
                        *meth_idx <= u16::MAX as usize,
                        "methode id too big for invoke instruction"
                    );
                    let meth_idx = *meth_idx as u16;
                    let ins = ins.get_raw_ins(meth_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InvokeSuper(ins) => {
                    let meth = &ins.method;
                    let meth_idx = index.methods.get(meth).ok_or(anyhow!(
                        "Method {} (method of class {}) not found in dex fragment",
                        meth.__str__(),
                        meth.class_.__str__(),
                    ))?;
                    debug_assert!(
                        *meth_idx <= u16::MAX as usize,
                        "methode id too big for invoke instruction"
                    );
                    let meth_idx = *meth_idx as u16;
                    let ins = ins.get_raw_ins(meth_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InvokeDirect(ins) => {
                    let meth = &ins.method;
                    let meth_idx = index.methods.get(meth).ok_or(anyhow!(
                        "Method {} (method of class {}) not found in dex fragment",
                        meth.__str__(),
                        meth.class_.__str__(),
                    ))?;
                    debug_assert!(
                        *meth_idx <= u16::MAX as usize,
                        "methode id too big for invoke instruction"
                    );
                    let meth_idx = *meth_idx as u16;
                    let ins = ins.get_raw_ins(meth_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InvokeStatic(ins) => {
                    let meth = &ins.method;
                    let meth_idx = index.methods.get(meth).ok_or(anyhow!(
                        "Method {} (method of class {}) not found in dex fragment",
                        meth.__str__(),
                        meth.class_.__str__(),
                    ))?;
                    debug_assert!(
                        *meth_idx <= u16::MAX as usize,
                        "methode id too big for invoke instruction"
                    );
                    let meth_idx = *meth_idx as u16;
                    let ins = ins.get_raw_ins(meth_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InvokeInterface(ins) => {
                    let meth = &ins.method;
                    let meth_idx = index.methods.get(meth).ok_or(anyhow!(
                        "Method {} (method of class {}) not found in dex fragment",
                        meth.__str__(),
                        meth.class_.__str__(),
                    ))?;
                    debug_assert!(
                        *meth_idx <= u16::MAX as usize,
                        "methode id too big for invoke instruction"
                    );
                    let meth_idx = *meth_idx as u16;
                    let ins = ins.get_raw_ins(meth_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InvokePolymorphic(ins) => {
                    let meth = &ins.method;
                    let meth_idx = index.methods.get(meth).ok_or(anyhow!(
                        "Method {} (method of class {}) not found in dex fragment",
                        meth.__str__(),
                        meth.class_.__str__(),
                    ))?;
                    let proto_idx = index.protos.get(&ins.proto).ok_or(anyhow!(
                        "Prototype {} (found in code) not found in dex fragment",
                        ins.proto.__str__(),
                    ))?;
                    debug_assert!(
                        *meth_idx <= u16::MAX as usize,
                        "methode id too big for invoke instruction"
                    );
                    debug_assert!(
                        *proto_idx <= u16::MAX as usize,
                        "proto id too big for invoke instruction"
                    );
                    let meth_idx = *meth_idx as u16;
                    let proto_idx = *proto_idx as u16;
                    let ins = ins.get_raw_ins(meth_idx, proto_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::InvokeCustom(ins) => {
                    let call_site_idx = self.call_site_ids.len() + nb_call_site_before_fragment;
                    self.insert_call_site_item(&ins.call_site, frag_index)?;
                    let ins = ins.get_raw_ins(call_site_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::ConstMethodHandle(ins) => {
                    let method_handle_idx =
                        self.method_handles.len() + nb_method_handle_before_fragment;
                    self.insert_method_handle(&ins.handle, frag_index)?;
                    let ins = ins.get_raw_ins(method_handle_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::ConstMethodType(ins) => {
                    let proto_idx = index.protos.get(&ins.proto).ok_or(anyhow!(
                        "Prototype {} (found in code) not found in dex fragment",
                        ins.proto.__str__(),
                    ))?;
                    let ins = ins.get_raw_ins(*proto_idx);
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
                Instruction::Try(try_) => {
                    let end_block_addr = *label_addrs.get(&try_.end_label).ok_or(anyhow!(
                        "Label {} not found in code, but found try with this label",
                        &try_.end_label,
                    ))?;
                    if end_block_addr < addr {
                        bail!("Found end label of a try block before the try instruction in code")
                    }
                    let try_item = TryItem {
                        start_addr: addr as u32,
                        insn_count: (end_block_addr - addr) as u16,
                        handler_off: handler_off as u16, // will be ajusted once the size of the
                                                         // handler list object is known
                    };
                    tries.push(try_item);
                    let mut catches = EncodedCatchHandler {
                        handlers: vec![],
                        catch_all_addr: None,
                    };
                    for (ty, label) in &try_.handlers {
                        let type_idx = Uleb128(*index.types.get(ty).ok_or(anyhow!(
                            "Could not found type {} in dex fragment captured by a try block",
                            ty.__str__(),
                        ))? as u32);
                        let addr = Uleb128(*label_addrs.get(label).ok_or(anyhow!(
                            "Label {} not found in code, but found try \
                            with this label as catch for type {}",
                            &try_.end_label,
                            ty.__str__(),
                        ))? as u32);
                        catches
                            .handlers
                            .push(EncodedTypeAddrPair { type_idx, addr });
                    }
                    if let Some(ref label) = try_.default_handler {
                        let catch_all_addr = *label_addrs.get(label).ok_or(anyhow!(
                            "Label {} not found in code, but found try \
                            with this label as catch all",
                            &try_.end_label,
                        ))?;
                        catches.catch_all_addr = Some(Uleb128(catch_all_addr as u32));
                    }
                    handler_off += catches.size();
                    handlers.list.push(catches);
                }
                Instruction::Label(_) => (),
                _ => {
                    let ins = ins.get_raw_ins().with_context(|| {
                        format!(
                            "Failed to convert instruction {} (found in code) to raw instruction",
                            ins.__str__(),
                        )
                    })?;
                    addr += ins.size() / 2;
                    insns.push(ins);
                }
            }
        }
        if addr % 2 != 0 {
            // make sure the payload section is 4 bytes aligned
            let nop = Instruction::Nop(Nop).get_raw_ins()?;
            //addr += nop.size() / 2;
            insns.push(nop);
        }
        insns.extend(payloads);

        for try_ in &mut tries {
            try_.handler_off += handlers.size_field().size() as u16;
        }

        let debug_info_off = if code.debug_info.1.is_empty() && code.parameter_names.is_none() {
            0
        } else {
            let debug_info_off = self
                .section_manager
                .get_aligned_size(FragSection::DebugInfoItem);
            let mut cursor = Cursor::new(&code.debug_info.1);
            let mut item = DebugInfoItem {
                line_start: Uleb128(code.debug_info.0),
                parameter_names: vec![],
                bytecode: Vec::<DbgBytecode>::deserialize(&mut cursor, DbgBytecode::EndSequence)?,
            };
            if let Some(parameter_names) = &code.parameter_names {
                for name in parameter_names {
                    if let Some(name) = name {
                        item.parameter_names
                            .push(Uleb128p1(*index.strings.get(name).ok_or(anyhow!(
                                "String {} (name of parameter) not found",
                                name.__str__(),
                            ))? as u32));
                    } else {
                        item.parameter_names.push(NO_INDEX);
                    }
                }
            }
            self.section_manager
                .add_elt(FragSection::DebugInfoItem, Some(item.size()));
            self.debug_info_items.push(item);
            debug_info_off + 1
        };
        let handlers = if handlers.list.is_empty() {
            None
        } else {
            Some(handlers)
        };
        let item = CodeItem {
            registers_size: code.registers_size,
            ins_size: code.ins_size,
            outs_size: code.outs_size,
            debug_info_off, // linked in link_debug_info()
            insns,
            tries,
            handlers,
        };
        self.section_manager
            .add_elt(FragSection::CodeItem, Some(item.size()));
        self.code_items.push(item);
        Ok(())
    }

    /// Insert annotation associated to a class.
    ///

    /// Insert a class_data_item in the class_data section (in data).
    ///
    /// # Note
    ///
    /// code_item objects are 4 bytes aligns, so their offset cannot be odd.
    ///
    /// To distinguish prelinked value (offset inside the code_item section) to actual values (offset
    /// in the whole file or 0), their value is set to the actual value prelink value + 1. This allow
    /// to distinguish the offset of the first item (equal to zero before linking) and the value
    /// 0 used to indicate an abscence of item.
    fn insert_class_data_item(&mut self, class: &Class, index: &FragIndex) -> Result<()> {
        let mut data = ClassDataItem::default();

        let mut static_fields: Vec<IdField> = class.static_fields.keys().cloned().collect();
        static_fields.sort();
        let mut last_field_id = 0;
        for id in &static_fields {
            let idx = index.fields.get(id).ok_or(anyhow!(
                "Field {} (field of class {}) not found in dex builder",
                id.__str__(),
                class.__str__()
            ))?;
            let field_idx_diff = Uleb128((idx - last_field_id) as u32);
            last_field_id = *idx;
            let access_flags = Uleb128(class.static_fields.get(id).unwrap().get_raw_access_flags());
            data.static_fields.push(EncodedField {
                field_idx_diff,
                access_flags,
            });
        }

        let mut instance_fields: Vec<IdField> = class.instance_fields.keys().cloned().collect();
        instance_fields.sort();
        let mut last_field_id = 0;
        for id in &instance_fields {
            let idx = index.fields.get(id).ok_or(anyhow!(
                "Field {} (field of class {}) not found in dex builder",
                id.__str__(),
                class.__str__()
            ))?;
            let field_idx_diff = Uleb128((idx - last_field_id) as u32);
            last_field_id = *idx;
            let access_flags = Uleb128(
                class
                    .instance_fields
                    .get(id)
                    .unwrap()
                    .get_raw_access_flags(),
            );
            data.instance_fields.push(EncodedField {
                field_idx_diff,
                access_flags,
            });
        }

        let mut direct_methods: Vec<IdMethod> = class.direct_methods.keys().cloned().collect();
        direct_methods.sort();
        let mut last_method_id = 0;
        for id in &direct_methods {
            // &mut vs & of self and class make things difficult...
            let idx = index.methods.get(id).ok_or(anyhow!(
                "Method {} (method of class {}) not found in dex builder",
                id.__str__(),
                class.__str__()
            ))?;
            let method_idx_diff = Uleb128((idx - last_method_id) as u32);
            last_method_id = *idx;
            let access_flags =
                Uleb128(class.direct_methods.get(id).unwrap().get_raw_access_flags());
            let code_off = if let Some(code) = &class.direct_methods.get(id).unwrap().code {
                // CodeItems depend to much on the complte descriptor list to be generated
                // prior to linking
                //let code_off = self.section_manager.get_aligned_size(FragSection::CodeItem);
                //self.insert_code_item(code, index).with_context(|| {
                //    format!("Failed to convert serialize code of {}", id.__str__())
                //})?;
                //Uleb128(code_off + 1)
                Uleb128(1)
            } else {
                Uleb128(0)
            };
            data.direct_methods.push(EncodedMethod {
                method_idx_diff,
                access_flags,
                code_off, // Will be relinked once the offset of the code item section is known
            });
        }

        let mut virtual_methods: Vec<IdMethod> = class.virtual_methods.keys().cloned().collect();
        virtual_methods.sort();
        let mut last_method_id = 0;
        for id in &virtual_methods {
            let idx = index.methods.get(id).ok_or(anyhow!(
                "Method {} (method of class {}) not found in dex builder",
                id.__str__(),
                class.__str__()
            ))?;
            let method_idx_diff = Uleb128((idx - last_method_id) as u32);
            last_method_id = *idx;
            let access_flags = Uleb128(
                class
                    .virtual_methods
                    .get(id)
                    .unwrap()
                    .get_raw_access_flags(),
            );
            let code_off = if let Some(code) = &class.virtual_methods.get(id).unwrap().code {
                // CodeItems depend to much on the complte descriptor list to be generated
                // prior to linking
                // let code_off = self.section_manager.get_aligned_size(FragSection::CodeItem);
                // self.insert_code_item(code, index).with_context(|| {
                //    format!("Failed to convert serialize code of {}", id.__str__())
                // })?;
                // Uleb128(code_off + 1)
                Uleb128(1)
            } else {
                Uleb128(0)
            };
            data.virtual_methods.push(EncodedMethod {
                method_idx_diff,
                access_flags,
                code_off, // Will be relinked once the offset of the code item section is known
            });
        }
        self.section_manager
            .add_elt(FragSection::ClassDataItem, Some(data.size()));
        //assert_eq!(data.size(), data.serialize_to_vec().unwrap().len());
        self.class_data = Some(data);
        Ok(())
    }

    /// Convert a [`DexValue`] to an [`EncodedValue`].
    ///
    /// # Warning
    ///
    /// This method can insert element in the dex file like method_handles.
    fn dex_value_to_encoded_value(
        &mut self,
        value: &DexValue,
        index: &FragIndex,
    ) -> Result<EncodedValue> {
        match value {
            DexValue::Byte(DexByte(val)) => Ok(EncodedValue::Byte(*val)),
            DexValue::Short(DexShort(val)) => Ok(EncodedValue::Short(*val)),
            DexValue::Char(DexChar(val)) => Ok(EncodedValue::Char(*val)),
            DexValue::Int(DexInt(val)) => Ok(EncodedValue::Int(*val)),
            DexValue::Long(DexLong(val)) => Ok(EncodedValue::Long(*val)),
            DexValue::Float(DexFloat(val)) => Ok(EncodedValue::Float(*val)),
            DexValue::Double(DexDouble(val)) => Ok(EncodedValue::Double(*val)),
            DexValue::MethodType(val) => Ok(EncodedValue::MethodType(
                *index.protos.get(val).ok_or(anyhow!(
                    "Prototype {} not found in dex writer",
                    val.__str__()
                ))? as u32,
            )),
            DexValue::MethodHandle(val) => {
                let idx = self.method_handles.len() as u32;
                self.insert_method_handle(val, index)?;
                Ok(EncodedValue::MethodHandle(idx))
            }
            DexValue::String(val) => Ok(EncodedValue::String(
                *index
                    .strings
                    .get(val)
                    .ok_or(anyhow!("String {} not found in dex writer", val.__str__()))?
                    as u32,
            )),
            DexValue::Type(val) => Ok(EncodedValue::Type(
                *index
                    .types
                    .get(val)
                    .ok_or(anyhow!("Type {} not found in dex writer", val.__str__()))?
                    as u32,
            )),
            DexValue::Field(val) => Ok(EncodedValue::Field(
                *index
                    .fields
                    .get(val)
                    .ok_or(anyhow!("Field {} not found in dex writer", val.__str__()))?
                    as u32,
            )),
            DexValue::Method(val) => Ok(EncodedValue::Method(
                *index
                    .methods
                    .get(val)
                    .ok_or(anyhow!("Method {} not found in dex writer", val.__str__()))?
                    as u32,
            )),
            DexValue::Enum(IdEnum(val)) => Ok(EncodedValue::Enum(
                *index
                    .fields
                    .get(val)
                    .ok_or(anyhow!("Field {} not found in dex writer", val.__str__()))?
                    as u32,
            )),
            DexValue::Array(DexArray(arr)) => {
                let mut values = vec![];
                for val in arr {
                    values.push(
                        self.dex_value_to_encoded_value(val, index)
                            .context("Error while serializing a array")?,
                    );
                }
                Ok(EncodedValue::Array(EncodedArray { values }))
            }
            DexValue::Annotation(val) => Ok(EncodedValue::Annotation(
                self.dex_annotation_to_encoded_annotation(val.clone(), index)?,
            )),
            DexValue::Null(DexNull) => Ok(EncodedValue::Null),
            DexValue::Boolean(DexBoolean(val)) => Ok(EncodedValue::Boolean(*val)),
        }
    }

    /// Insert an encoded_array in the encoded_array_item section.
    fn insert_encoded_array_item(
        &mut self,
        DexArray(array): DexArray,
        index: &FragIndex,
    ) -> Result<()> {
        let mut values = vec![];
        for value in array {
            values.push(self.dex_value_to_encoded_value(&value, index)?);
        }
        let item = EncodedArrayItem {
            value: EncodedArray { values },
        };
        self.section_manager
            .add_elt(FragSection::EncodedArrayItem, Some(item.size()));
        self.encoded_array_items.push(item);
        Ok(())
    }

    /// Insert a [`CallSite`] to the encoded array items
    ///
    /// # Warning
    ///
    /// This method can insert element in the dex file like method_handles.
    fn insert_call_site_item(&mut self, call_site: &CallSite, index: &FragIndex) -> Result<()> {
        let mut values = vec![];
        values.push(DexValue::MethodHandle(call_site.method_handle.clone()));
        values.push(DexValue::String(call_site.name.clone()));
        values.push(DexValue::MethodType(call_site.type_.clone()));
        values.extend(call_site.args.iter().cloned());
        self.call_site_ids.push(CallSiteIdItem {
            call_site_off: self
                .section_manager
                .get_aligned_size(FragSection::EncodedArrayItem),
        });
        self.section_manager
            .add_elt(FragSection::CallSiteIdItem, None);
        self.insert_encoded_array_item(DexArray(values), index)
    }

    /// Insert the encoded_array_item encoding the static_values of a class.
    fn insert_class_static_values(&mut self, class: &Class, index: &FragIndex) -> Result<()> {
        let mut static_fields: Vec<IdField> = class.static_fields.keys().cloned().collect();
        static_fields.sort();
        let mut array = vec![];
        let mut last_defined_field_index = 0;
        for (idx, f) in static_fields.iter().enumerate() {
            if class.static_fields.get(f).unwrap().value.is_some() {
                last_defined_field_index = idx;
            }
        }
        for f in &static_fields[..=last_defined_field_index] {
            let field = class.static_fields.get(f).unwrap();
            if let Some(val) = field.value.as_ref() {
                array.push(val.clone());
            } else {
                array.push(field.descriptor.type_.get_default_value().ok_or(anyhow!(
                    "The type {} (for field {} in class {}) does not have a default value",
                    field.descriptor.type_.__str__(),
                    field.descriptor.__str__(),
                    class.__str__()
                ))?);
            }
        }
        self.insert_encoded_array_item(DexArray(array), index)
            .with_context(|| {
                format!(
                    "Failed to serialize static values of class {}",
                    class.__str__()
                )
            })
    }

    fn dex_annotation_to_encoded_annotation(
        &mut self,
        DexAnnotation { type_, elements }: DexAnnotation,
        index: &FragIndex,
    ) -> Result<EncodedAnnotation> {
        let mut encoded_elements = vec![];

        let mut elements_names: Vec<_> = elements.keys().collect();
        elements_names.sort();

        for name in elements_names {
            let elt = elements.get(name).unwrap();
            encoded_elements.push(AnnotationElement {
                name_idx: Uleb128(*index.strings.get(name).ok_or(anyhow!(
                    "{} (annotation element name) not found in dex builder",
                    name.__str__()
                ))? as u32),
                value: self.dex_value_to_encoded_value(elt, index)?,
            });
        }
        Ok(EncodedAnnotation {
            type_idx: Uleb128(*index.types.get(&type_).ok_or(anyhow!(
                "Annotation type {} not found in dex builder",
                type_.__str__(),
            ))? as u32),
            elements: encoded_elements,
        })
    }

    /// Insert the annotations set for a method parameter.
    fn insert_parameters_annotation_set(
        &mut self,
        method: &Method,
        parameter_idx: usize,
        index: &FragIndex,
    ) -> Result<()> {
        let mut annotations = method.parameters_annotations[parameter_idx].clone();

        let mut annotation_set = AnnotationSetItem { entries: vec![] };
        annotations.sort_by_key(|annot| annot.annotation.type_.clone());
        for annot in annotations {
            annotation_set.entries.push(AnnotationOffItem {
                annotation_off: self
                    .section_manager
                    .get_aligned_size(FragSection::AnnotationItem)
                    + 1,
            });

            let item = AnnotationItem {
                visibility: match (
                    annot.visibility_build,
                    annot.visibility_runtime,
                    annot.visibility_system,
                ) {
                    (true, false, false) => AnnotationVisibility::Build,
                    (false, true, false) => AnnotationVisibility::Runtime,
                    (false, false, true) => AnnotationVisibility::System,
                    _ => bail!(
                        "Annotation need visibility set to one and only one of build, \
                           runtime or system"
                    ), // TODO: check if this is true
                },
                annotation: self.dex_annotation_to_encoded_annotation(annot.annotation, index)?,
            };
            self.section_manager
                .add_elt(FragSection::AnnotationItem, Some(item.size()));
            self.annotation_items.push(item);
        }
        self.section_manager
            .add_elt(FragSection::AnnotationSetItem, Some(annotation_set.size()));
        self.annotation_set_items.push(annotation_set);
        Ok(())
    }

    /// Insert the annotations set list for a method parameters.
    fn insert_parameters_annotation_set_list(
        &mut self,
        method: &Method,
        index: &FragIndex,
    ) -> Result<()> {
        let mut list = AnnotationSetRefList { list: vec![] };
        let param_has_annotation: Vec<_> = method
            .parameters_annotations
            .iter()
            .map(|annots| !annots.is_empty())
            .collect();
        for (param_idx, has_annotation) in param_has_annotation.into_iter().enumerate() {
            list.list.push(AnnotationSetRefItem {
                annotations_off: if has_annotation {
                    let annotation_off = self
                        .section_manager
                        .get_aligned_size(FragSection::AnnotationSetItem);
                    self.insert_parameters_annotation_set(method, param_idx, index)?;
                    annotation_off + 1
                } else {
                    0
                },
            });
        }

        self.section_manager
            .add_elt(FragSection::AnnotationSetRefList, Some(list.size()));
        self.annotation_set_lists.push(list);
        Ok(())
    }

    /// Return `(nb_call_site, nb_method_handle)` the numbers of call sites and method handles in
    /// the fragment. This is a non-trivial task that because some are generated during
    /// [`Self::link_global_ids()`].
    pub fn get_nb_call_site_and_method_handle(&self, class: &Class) -> (usize, usize) {
        fn get_nb_call_site_and_method_handle_in_method(method: &Method) -> (usize, usize) {
            let mut nb_call_site = 0;
            let mut nb_method_handle = 0;
            if let Some(Code { insns, .. }) = method.code {
                for ins in insns {
                    match ins {
                        Instruction::ConstMethodHandle(_) => nb_method_handle += 1,
                        Instruction::InvokeCustom(ins) => {
                            nb_call_site += 1;
                            let mut val_to_process: Vec<&DexValue> =
                                ins.call_site.args.iter().collect();
                            while let Some(val) = val_to_process.pop() {
                                match val {
                                    DexValue::MethodHandle(_) => nb_method_handle += 1,
                                    DexValue::Array(DexArray(array)) => {
                                        val_to_process.extend(array)
                                    }
                                    DexValue::Annotation(DexAnnotation { elements, .. }) => {
                                        val_to_process.extend(elements.values())
                                    }

                                    _ => (),
                                }
                            }
                        }
                        _ => (),
                    }
                }
            };
            (nb_call_site, nb_method_handle)
        }

        match self.link_state {
            FragLinkState::Unlinked => {
                let mut nb_call_site = self.call_site_ids.len();
                let mut nb_method_handle = self.method_handles.len();
                for method in class.direct_methods.values() {
                    let (nnb_call_site, nnb_method_handle) =
                        get_nb_call_site_and_method_handle_in_method(method);
                    nb_call_site += nnb_call_site;
                    nb_method_handle += nnb_method_handle
                }
                for method in class.virtual_methods.values() {
                    let (nnb_call_site, nnb_method_handle) =
                        get_nb_call_site_and_method_handle_in_method(method);
                    nb_call_site += nnb_call_site;
                    nb_method_handle += nnb_method_handle
                }
                (nb_call_site, nb_method_handle)
            }
            // after Self::link_global_ids(), the lists are complete
            _ => (self.call_site_ids.len(), self.method_handles.len()),
        }
    }

    /// Some structure change size depending of the value of the index they refere to.
    /// The new size needs to be known before the offset can be linked, so the idx are
    /// linked before.
    ///
    /// `call_site_id_item`s are sorted by offset of the associated array, so they are naturally
    /// sorted, and `method_handle_item`s are not sorted. This means linking those id is done
    /// simply by adding the number of ids before the fragement.
    ///
    /// ## Warning
    ///
    /// The `nb_call_site_before_fragment` and `nb_method_handle_before_fragment` must be computed
    /// by [`Self::get_nb_call_site_and_method_handle()`] because some of those values are not
    /// generated before linking id (because code items are generated when linking id, not good but
    /// all we have right now).
    pub fn link_global_ids(
        &mut self,
        class: &Class,
        index: &DexIndex,
        nb_call_site_before_fragment: usize,
        nb_method_handle_before_fragment: usize,
    ) -> Result<()> {
        let frag_index = if let Some(frag_index) = self.frag_index {
            self.frag_index = None;
            frag_index
        } else {
            bail!("Fragment cannot be linked without the frag index (see DexFragment::frag_index doc)")
        };
        self.link_state = self.link_state.start_linking_idx()?;
        let string_reindex = Vec::with_capacity(self.strings.len());
        // TODO: considering we have the map, this can be simplified a lot
        let mut global_idx = 0;
        for s in self.strings {
            while global_idx < index.strings_list.len() && index.strings_list[global_idx] != s {
                global_idx += 1;
            }
            if global_idx == index.strings_list.len() {
                bail!("String {} not found in global index", s.__str__());
            }
            string_reindex.push(global_idx as u32);
        }
        let type_reindex = Vec::with_capacity(self.type_ids.len());
        let mut global_idx = 0;
        for ty in self.type_ids {
            while global_idx < index.type_ids_list.len() && index.type_ids_list[global_idx] != ty {
                global_idx += 1;
            }
            if global_idx == index.type_ids_list.len() {
                bail!("Type {} not found in global index", ty.__str__());
            }
            type_reindex.push(global_idx as u32);
        }
        let proto_reindex = Vec::with_capacity(self.proto_ids.len());
        let mut global_idx = 0;
        for proto in self.proto_ids {
            while global_idx < index.proto_ids_list.len()
                && index.proto_ids_list[global_idx] != proto
            {
                global_idx += 1;
            }
            if global_idx == index.proto_ids_list.len() {
                bail!("Prototype {} not found in global index", proto.__str__());
            }
            proto_reindex.push(global_idx as u32);
        }
        let field_reindex = Vec::with_capacity(self.field_ids.len());
        let mut global_idx = 0;
        for field in self.field_ids {
            while global_idx < index.field_ids_list.len()
                && index.field_ids_list[global_idx] != field
            {
                global_idx += 1;
            }
            if global_idx == index.field_ids_list.len() {
                bail!("Field {} not found in global index", field.__str__());
            }
            field_reindex.push(global_idx as u16);
        }
        let method_reindex = Vec::with_capacity(self.method_ids.len());
        let mut global_idx = 0;
        for meth in self.method_ids {
            while global_idx < index.method_ids_list.len()
                && index.method_ids_list[global_idx] != meth
            {
                global_idx += 1;
            }
            if global_idx == index.method_ids_list.len() {
                bail!("Method {} not found in global index", meth.__str__());
            }
            method_reindex.push(global_idx as u16);
        }

        self.link_id_class_data_and_gen_code(
            class,
            &field_reindex,
            &method_reindex,
            index,
            nb_call_site_before_fragment,
            nb_method_handle_before_fragment,
            &frag_index,
        )?;
        self.link_id_class_def(&string_reindex, &type_reindex);
        self.link_id_method_handle(&field_reindex, &method_reindex);
        self.link_id_annotation(
            &string_reindex,
            &type_reindex,
            &field_reindex,
            &method_reindex,
            &proto_reindex,
            nb_method_handle_before_fragment,
        );
        self.link_id_encoded_array_item(
            &string_reindex,
            &type_reindex,
            &field_reindex,
            &method_reindex,
            &proto_reindex,
            nb_method_handle_before_fragment,
        );
        self.link_id_annotation_dir(&field_reindex, &method_reindex);
        Ok(())
    }

    fn link_id_class_def(&mut self, string_reindex: &[u32], type_reindex: &[u32]) {
        self.class_def.class_idx = type_reindex[self.class_def.class_idx as usize];
        if self.class_def.superclass_idx != NO_INDEX.0 {
            self.class_def.superclass_idx = type_reindex[self.class_def.superclass_idx as usize];
        }
        if self.class_def.source_file_idx != NO_INDEX.0 {
            self.class_def.source_file_idx =
                string_reindex[self.class_def.source_file_idx as usize];
        }
    }

    fn link_id_method_handle(&mut self, field_reindex: &[u16], method_reindex: &[u16]) {
        for handle in self.method_handles {
            let reindexer = match handle.method_handle_type {
                MethodHandleType::StaticPut => field_reindex,
                MethodHandleType::StaticGet => field_reindex,
                MethodHandleType::InstancePut => field_reindex,
                MethodHandleType::InstanceGet => field_reindex,
                MethodHandleType::InvokeStatic => method_reindex,
                MethodHandleType::InvokeInstance => method_reindex,
                MethodHandleType::InvokeConstructor => method_reindex,
                MethodHandleType::InvokeDirect => method_reindex,
                MethodHandleType::InvokeInterface => method_reindex,
            };
            handle.field_or_method_id = reindexer[handle.field_or_method_id as usize];
        }
    }

    fn link_id_annotation(
        &mut self,
        string_reindex: &[u32],
        type_reindex: &[u32],
        field_reindex: &[u16],
        method_reindex: &[u16],
        proto_reindex: &[u32],
        nb_method_handle_before_fragment: usize,
    ) {
        let mut annotation_item_relocation = if let FragLinkState::LinkedIdx {
            annotation_item_relocation,
            ..
        } = self.link_state
        {
            annotation_item_relocation
        } else {
            panic!("link_id_annotation() should only be called by link_global_ids()");
        };
        // TODO: can we update the new value directly in the direstoryies/set?
        // The size of the elements change, so the section size must be recomputed
        self.section_manager
            .reset_section(FragSection::AnnotationItem);
        let mut initial_offset = 0;
        let mut new_offset = 0;
        for item in self.annotation_items {
            let old_size = item.size() as u32;
            item.annotation.type_idx.0 = type_reindex[item.annotation.type_idx.0 as usize];
            for elt in item.annotation.elements {
                elt.name_idx.0 = string_reindex[elt.name_idx.0 as usize];
                Self::link_id_encoded_value(
                    &mut elt.value,
                    string_reindex,
                    type_reindex,
                    field_reindex,
                    method_reindex,
                    proto_reindex,
                    nb_method_handle_before_fragment,
                )
            }
            let new_size = item.size() as u32;
            annotation_item_relocation.insert(initial_offset, new_offset);
            initial_offset += old_size;
            new_offset += new_size;
            self.section_manager
                .add_elt(FragSection::AnnotationItem, Some(new_size as usize));
        }
    }

    fn link_id_encoded_annotation(
        annotation: &mut EncodedAnnotation,
        string_reindex: &[u32],
        type_reindex: &[u32],
        field_reindex: &[u16],
        method_reindex: &[u16],
        proto_reindex: &[u32],
        nb_method_handle_before_fragment: usize,
    ) {
        annotation.type_idx.0 = type_reindex[annotation.type_idx.0 as usize];
        for elt in annotation.elements {
            elt.name_idx.0 = string_reindex[elt.name_idx.0 as usize];
            Self::link_id_encoded_value(
                &mut elt.value,
                string_reindex,
                type_reindex,
                field_reindex,
                method_reindex,
                proto_reindex,
                nb_method_handle_before_fragment,
            )
        }
    }

    fn link_id_encoded_value(
        val: &mut EncodedValue,
        string_reindex: &[u32],
        type_reindex: &[u32],
        field_reindex: &[u16],
        method_reindex: &[u16],
        proto_reindex: &[u32],
        nb_method_handle_before_fragment: usize,
    ) {
        match val {
            EncodedValue::MethodType(idx) => *idx = proto_reindex[*idx as usize],
            EncodedValue::MethodHandle(idx) => *idx += nb_method_handle_before_fragment as u32,
            EncodedValue::String(idx) => *idx = string_reindex[*idx as usize],
            EncodedValue::Type(idx) => *idx = type_reindex[*idx as usize],
            EncodedValue::Field(idx) => *idx = field_reindex[*idx as usize] as u32,
            EncodedValue::Method(idx) => *idx = method_reindex[*idx as usize] as u32,
            EncodedValue::Enum(idx) => *idx = field_reindex[*idx as usize] as u32,
            EncodedValue::Array(arr) => Self::link_id_encoded_array(
                &mut arr,
                string_reindex,
                type_reindex,
                field_reindex,
                method_reindex,
                proto_reindex,
                nb_method_handle_before_fragment,
            ),
            EncodedValue::Annotation(annotation) => Self::link_id_encoded_annotation(
                &mut annotation,
                string_reindex,
                type_reindex,
                field_reindex,
                method_reindex,
                proto_reindex,
                nb_method_handle_before_fragment,
            ),
            _ => (),
        }
    }

    fn link_id_encoded_array(
        array: &mut EncodedArray,
        string_reindex: &[u32],
        type_reindex: &[u32],
        field_reindex: &[u16],
        method_reindex: &[u16],
        proto_reindex: &[u32],
        nb_method_handle_before_fragment: usize,
    ) {
        for val in &mut array.values {
            Self::link_id_encoded_value(
                val,
                string_reindex,
                type_reindex,
                field_reindex,
                method_reindex,
                proto_reindex,
                nb_method_handle_before_fragment,
            )
        }
    }

    fn link_id_encoded_array_item(
        &mut self,
        string_reindex: &[u32],
        type_reindex: &[u32],
        field_reindex: &[u16],
        method_reindex: &[u16],
        proto_reindex: &[u32],
        nb_method_handle_before_fragment: usize,
    ) {
        let mut encoded_array_relocation = if let FragLinkState::LinkedIdx {
            encoded_array_relocation,
            ..
        } = self.link_state
        {
            encoded_array_relocation
        } else {
            panic!("link_id_encoded_arrays() should only be called by link_global_ids()");
        };
        // The size of the elements change, so the section size must be recomputed
        self.section_manager
            .reset_section(FragSection::EncodedArrayItem);
        let mut initial_offset = 0;
        let mut relocated_offset = 0;
        for array in self.encoded_array_items {
            let old_size = array.size() as u32;
            Self::link_id_encoded_array(
                &mut array.value,
                string_reindex,
                type_reindex,
                field_reindex,
                method_reindex,
                proto_reindex,
                nb_method_handle_before_fragment,
            );
            let new_size = array.size() as u32;
            encoded_array_relocation.insert(old_size, new_size);
            initial_offset += old_size;
            relocated_offset += new_size;
            self.section_manager
                .add_elt(FragSection::EncodedArrayItem, Some(new_size as usize));
        }
    }

    fn link_id_annotation_dir(&mut self, field_reindex: &[u16], method_reindex: &[u16]) {
        if let Some(dir) = self.annotations_directory_item {
            for annot in dir.field_annotations {
                annot.field_idx = field_reindex[annot.field_idx as usize] as u32;
            }
            for annot in dir.method_annotations {
                annot.method_idx = method_reindex[annot.method_idx as usize] as u32;
            }
            for annot in dir.parameter_annotations {
                annot.method_idx = method_reindex[annot.method_idx as usize] as u32;
            }
        }
    }

    /// Link ids in [`ClassDataItem`] *and* generate the [`CodeItem`].
    fn link_id_class_data_and_gen_code(
        &mut self,
        class: &Class,
        field_reindex: &[u16],
        method_reindex: &[u16],
        index: &DexIndex,
        nb_call_site_before_fragment: usize,
        nb_method_handle_before_fragment: usize,
        frag_index: &FragIndex,
    ) -> Result<()> {
        if let Some(data) = self.class_data {
            let mut last_local_id = 0;
            let mut last_global_id = 0;
            for field in data.static_fields {
                let new_local_id = last_local_id + field.field_idx_diff.0;
                let new_global_id = field_reindex[new_local_id as usize];
                field.field_idx_diff.0 = (new_global_id - last_global_id) as u32;
                last_local_id = new_local_id;
                last_global_id = new_global_id;
            }
            let mut last_local_id = 0;
            let mut last_global_id = 0;
            for field in data.instance_fields {
                let new_local_id = last_local_id + field.field_idx_diff.0;
                let new_global_id = field_reindex[new_local_id as usize];
                field.field_idx_diff.0 = (new_global_id - last_global_id) as u32;
                last_local_id = new_local_id;
                last_global_id = new_global_id;
            }

            let mut last_local_id = 0;
            let mut last_global_id = 0;
            for meth in data.direct_methods {
                let new_local_id = last_local_id + meth.method_idx_diff.0;
                let new_global_id = field_reindex[new_local_id as usize];
                meth.method_idx_diff.0 = (new_global_id - last_global_id) as u32;
                if meth.code_off.0 != 0 {
                    let meth_id = index.method_ids_list[new_global_id as usize];
                    let code = class
                        .direct_methods
                        .get(&meth_id)
                        .ok_or(anyhow!(
                            "direct method {} expected from fragment but not found in {}",
                            meth_id.__str__(),
                            class.__str__()
                        ))?
                        .code;
                    if let Some(code) = code {
                        let code_off = self.section_manager.get_aligned_size(FragSection::CodeItem);
                        self.insert_code_item(
                            &code,
                            index,
                            nb_call_site_before_fragment,
                            nb_method_handle_before_fragment,
                            frag_index,
                        )?;
                        meth.code_off.0 = code_off + 1;
                    } else {
                        bail!(
                            "Inconsistant fragment: fragment expect a code item for {}\
                            but none was found in {}",
                            meth_id.__str__(),
                            class.__str__()
                        );
                    }
                }
                last_local_id = new_local_id;
                last_global_id = new_global_id;
            }
            let mut last_local_id = 0;
            let mut last_global_id = 0;
            for meth in data.virtual_methods {
                let new_local_id = last_local_id + meth.method_idx_diff.0;
                let new_global_id = field_reindex[new_local_id as usize];
                meth.method_idx_diff.0 = (new_global_id - last_global_id) as u32;
                if meth.code_off.0 != 0 {
                    let meth_id = index.method_ids_list[new_global_id as usize];
                    let code = class
                        .virtual_methods
                        .get(&meth_id)
                        .ok_or(anyhow!(
                            "virtual method {} expected from fragment but not found in {}",
                            meth_id.__str__(),
                            class.__str__()
                        ))?
                        .code;
                    if let Some(code) = code {
                        let code_off = self.section_manager.get_aligned_size(FragSection::CodeItem);
                        self.insert_code_item(
                            &code,
                            index,
                            nb_call_site_before_fragment,
                            nb_method_handle_before_fragment,
                            frag_index,
                        )?;
                        meth.code_off.0 = code_off + 1;
                    } else {
                        bail!(
                            "Inconsistant fragment: fragment expect a code item for {}\
                            but none was found in {}",
                            meth_id.__str__(),
                            class.__str__()
                        );
                    }
                }
                last_local_id = new_local_id;
                last_global_id = new_global_id;
            }
        }
        Ok(())
    }

    pub fn get_section_manager(&self) -> &FragSectionManager {
        &self.section_manager
    }

    pub fn get_interfaces(&self) -> &Vec<IdType> {
        &self.interfaces
    }

    /// Link the offsets in the fragment except for the class_def_item.class_data_off
    /// that needs the size of the linked class_data_items of the previous fragments.
    pub fn link_offsets_but_class_data(
        &mut self,
        offsets: &SectionOffsets,
        type_lists: &HashMap<Vec<IdType>, u32>,
    ) -> Result<()> {
        self.link_state = self.link_state.start_linking_offset()?;
        self.link_off_call_site_ids(offsets);
        self.link_off_class_def(offsets, type_lists)?;
        Ok(())
    }

    fn link_off_call_site_ids(&mut self, offsets: &SectionOffsets) {
        for id in &mut self.call_site_ids {
            id.call_site_off += offsets.get_offset(Section::EncodedArrayItem);
        }
    }

    fn link_off_class_def(
        &mut self,
        offsets: &SectionOffsets,
        type_lists: &HashMap<Vec<IdType>, u32>,
    ) -> Result<()> {
        if self.class_def.annotations_off != 0 {
            self.class_def.annotations_off +=
                offsets.get_offset(Section::AnnotationsDirectoryItem) - 1;
        }
        if self.class_def.static_values_off != 0 {
            self.class_def.static_values_off += offsets.get_offset(Section::EncodedArrayItem) - 1;
        }
        if !self.interfaces.is_empty() {
            self.class_def.interfaces_off = offsets.get_offset(Section::TypeList)
                + type_lists.get(&self.interfaces).ok_or(anyhow!(
                    "Interface list {:?} not found in type list",
                    self.interfaces
                ))?;
        }
        // class data offset not set here
        Ok(())
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FragSection {
    ClassDefItem,
    CallSiteIdItem,
    MethodHandleItem,
    AnnotationSetRefList,
    AnnotationSetItem,
    CodeItem,
    StringDataItem,
    DebugInfoItem,
    AnnotationItem,
    EncodedArrayItem,
    AnnotationsDirectoryItem,
    ClassDataItem,
    // HiddenapiClassDataItem,
}

impl FragSection {
    pub(crate) const VARIANT_LIST: &'static [Self] = &[
        Self::ClassDefItem,
        Self::CallSiteIdItem,
        Self::MethodHandleItem,
        Self::AnnotationSetRefList,
        Self::AnnotationSetItem,
        Self::CodeItem,
        Self::StringDataItem,
        Self::DebugInfoItem,
        Self::AnnotationItem,
        Self::EncodedArrayItem,
        Self::AnnotationsDirectoryItem,
        // /!\ contains offsets in Uleb, so size change when linking !
        Self::ClassDataItem,
        // Self::HiddenapiClassDataItem,
    ];

    fn get_index(&self) -> usize {
        match self {
            Self::ClassDefItem => 0,
            Self::CallSiteIdItem => 1,
            Self::MethodHandleItem => 2,
            Self::AnnotationSetRefList => 3,
            Self::AnnotationSetItem => 4,
            Self::CodeItem => 5,
            Self::StringDataItem => 6,
            Self::DebugInfoItem => 7,
            Self::AnnotationItem => 8,
            Self::EncodedArrayItem => 9,
            Self::AnnotationsDirectoryItem => 10,
            Self::ClassDataItem => 11,
            // Self::HiddenapiClassDataItem => 12,
        }
    }

    fn get_elt_size(&self, default_size: Option<usize>) -> usize {
        let fixed_size = match self {
            Self::ClassDefItem => Some(0x20),
            Self::CallSiteIdItem => Some(4),
            Self::MethodHandleItem => Some(8),
            Self::AnnotationSetRefList => None,
            Self::AnnotationSetItem => None,
            Self::CodeItem => None,
            Self::StringDataItem => None,
            Self::DebugInfoItem => None,
            Self::AnnotationItem => None,
            Self::EncodedArrayItem => None,
            Self::AnnotationsDirectoryItem => None,
            Self::ClassDataItem => None,
            //Self::HiddenapiClassDataItem => None,
        };
        if let (Some(fixed_size), Some(default_size)) = (fixed_size, default_size) {
            if fixed_size == default_size {
                default_size
            } else {
                panic!(
                    "Element in {:?} have a size of {}, not {}",
                    self, fixed_size, default_size
                )
            }
        } else {
            fixed_size.or(default_size).unwrap_or_else(|| {
                panic!(
                    "Element of {:?} don't have a fixed size, you need to provide one",
                    self
                )
            })
        }
    }

    /// Return the alignment of the item in byte.
    fn get_item_alignment(&self) -> u32 {
        match self {
            Self::ClassDefItem => 4,
            Self::CallSiteIdItem => 1,
            Self::MethodHandleItem => 4,
            Self::AnnotationSetRefList => 4,
            Self::AnnotationSetItem => 4,
            Self::CodeItem => 4,
            Self::StringDataItem => 1,
            Self::DebugInfoItem => 1,
            Self::AnnotationItem => 1,
            Self::EncodedArrayItem => 1,
            Self::AnnotationsDirectoryItem => 4,
            Self::ClassDataItem => 1,
            //Self::HiddenapiClassDataItem => 1,
        }
    }
}

#[derive(Debug, Default, Clone)]
pub(crate) struct FragSectionManager {
    sizes: [u32; Self::NB_SECTION],
    nb_elt: [usize; Self::NB_SECTION],
}

impl FragSectionManager {
    const NB_SECTION: usize = 12;

    fn add_elt(&mut self, section: FragSection, size: Option<usize>) {
        if (section == FragSection::ClassDefItem
            || section == FragSection::ClassDataItem
            || section == FragSection::AnnotationsDirectoryItem)
            && (self.nb_elt[section.get_index()] >= 1)
        {
            panic!("{section:#?} cannot contain more than one element in a dex fragment");
        }
        while self.sizes[section.get_index()] % section.get_item_alignment() != 0 {
            self.sizes[section.get_index()] += 1;
        }
        self.sizes[section.get_index()] += section.get_elt_size(size) as u32;
        self.nb_elt[section.get_index()] += 1;
    }

    fn reset_section(&mut self, section: FragSection) {
        self.sizes[section.get_index()] = 0;
        self.nb_elt[section.get_index()] = 0;
    }

    fn get_unaligned_size(&self, section: FragSection) -> u32 {
        self.sizes[section.get_index()]
    }

    /// The position of a potential new item in the section considering alignment.
    pub(crate) fn get_aligned_size(&self, section: FragSection) -> u32 {
        let mut size = self.get_unaligned_size(section);
        while size % section.get_item_alignment() != 0 {
            size += 1;
        }
        size
    }

    pub(crate) fn get_nb_elt(&self, section: FragSection) -> usize {
        self.nb_elt[section.get_index()]
    }

    /// Generate a new section manager with the content of an added fragment.
    pub(crate) fn merge_fragment(&self, frag_sections: &FragSectionManager) -> Self {
        let mut new_section = self.clone();
        for section in FragSection::VARIANT_LIST {
            new_section.nb_elt[section.get_index()] += frag_sections.get_nb_elt(*section);
            // Noticed the "aligned"
            new_section.sizes[section.get_index()] =
                new_section.get_aligned_size(*section) + frag_sections.get_aligned_size(*section);
        }
        new_section
    }

    /// Display the sections informations.
    #[allow(dead_code)]
    fn show(&self) {
        let mut offset = 0;
        for section in FragSection::VARIANT_LIST {
            let size = self.get_unaligned_size(*section);
            let new_offset = offset + size;
            let nb_elt = self.get_nb_elt(*section);
            println!(
                "{section:?}: 0x{offset:x} -> 0x{new_offset:x} (size: 0x{size:x}, \
                nb elt: {nb_elt})"
            );
            offset = new_offset;
        }
    }
}

/// Index that associate descriptors to their local id in a fragment.
#[derive(Debug, Clone)]
struct FragIndex {
    pub strings: HashMap<DexString, usize>,
    pub types: HashMap<IdType, usize>,
    pub protos: HashMap<IdMethodType, usize>,
    pub fields: HashMap<IdField, usize>,
    pub methods: HashMap<IdMethod, usize>,
}

/// The link state of a fragment. Describe the linking of a fragment and the related data.
#[derive(Debug, Clone)]
enum FragLinkState {
    /// The fragment is not linked
    Unlinked,
    /// The index used in the fragment are linked of beeing linked.
    /// The new local addresses of the element whose size depend on the value
    /// of the index are stored here.
    LinkedIdx {
        // Not relocation: the code are entirely generated at link id time
        //code_item_relocation: HashMap<u32, u32>,
        //debug_info_item_relocation: HashMap<u32, u32>,
        annotation_item_relocation: HashMap<u32, u32>,
        encoded_array_relocation: HashMap<u32, u32>,
    },
    /// The index used in the fragment are linked and offset are in the process of beeing linked.
    LinkingOffset {
        // Not relocation: the code are entirely generated at link id time
        //code_item_relocation: HashMap<u32, u32>,
        //debug_info_item_relocation: HashMap<u32, u32>,
        annotation_item_relocation: HashMap<u32, u32>,
        encoded_array_relocation: HashMap<u32, u32>,
    },
}

impl FragLinkState {
    fn start_linking_idx(self) -> Result<Self> {
        match self {
            Self::Unlinked => {
                Ok(Self::LinkedIdx {
                    // code_item_relocation: HashMap::<u32, u32>::new(),
                    // debug_info_item_relocation: HashMap::<u32, u32>::new(),
                    annotation_item_relocation: HashMap::<u32, u32>::new(),
                    encoded_array_relocation: HashMap::<u32, u32>::new(),
                })
            }
            _ => {
                bail!("Cannot link the idx in a fragment that is already linked");
            }
        }
    }

    fn start_linking_offset(self) -> Result<Self> {
        match self {
            Self::LinkedIdx {
                annotation_item_relocation,
                encoded_array_relocation,
            } => {
                Ok(Self::LinkingOffset {
                    // code_item_relocation: HashMap::<u32, u32>::new(),
                    // debug_info_item_relocation: HashMap::<u32, u32>::new(),
                    annotation_item_relocation,
                    encoded_array_relocation,
                })
            }
            _ => {
                bail!("Cannot link the offsets in a fragment that have already linked offset of not linked ids");
            }
        }
    }
}
