use super::*;
use androscalpel_serializer::Instruction as InsFormat;
use androscalpel_serializer::*;
use serde_json as sj;
use std::collections::HashSet;
use std::fs::File;
use std::io;
use std::io::{Read, Write};
use std::ops::Deref;
use std::sync::{Mutex, OnceLock};
use std::time::Instant;

fn write_to_report(data: &str) {
    static REPORT_FILE: Mutex<Option<File>> = Mutex::new(None);
    let mut report_file = REPORT_FILE.lock().unwrap();
    let mut report_file = if let Some(report_file) = report_file.deref() {
        report_file
    } else {
        *report_file = Some(
            File::create(&format!("{}/test_repport.txt", env!("CARGO_MANIFEST_DIR"),)).unwrap(),
        );
        report_file.deref().as_ref().unwrap()
    };
    writeln!(report_file, "{data}").unwrap();
}

fn get_dex(filename: &str) -> Vec<u8> {
    let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
    let mut file = File::open(&hello_world_dex).expect(&format!("{} not found", filename));
    let mut data = vec![];
    io::copy(&mut file, &mut data).unwrap();
    data
}

fn get_hello_world_dex() -> &'static [u8] {
    static HELLO_WORLD_DEX: OnceLock<Vec<u8>> = OnceLock::new();
    HELLO_WORLD_DEX.get_or_init(|| get_dex("classes_hello_world.dex"))
}

fn get_hello_world_apk() -> &'static Apk {
    static HELLO_WORLD_APK: OnceLock<Apk> = OnceLock::new();
    HELLO_WORLD_APK.get_or_init(|| {
        let mut apk = Apk::new();
        let start = Instant::now();
        apk.add_dex_file("classes.dex", get_hello_world_dex(), |_, _, _| None, false)
            .unwrap();
        let duration = start.elapsed();
        write_to_report(&format!("Parsing classes_hello_world.dex: {duration:?}"));
        apk
    })
}

fn get_hello_world_recompilled() -> &'static [u8] {
    static HELLO_WORLD_RECOMP: OnceLock<Vec<u8>> = OnceLock::new();
    HELLO_WORLD_RECOMP.get_or_init(|| {
        let start = Instant::now();
        let dex = get_hello_world_apk()
            .gen_raw_dex()
            .unwrap()
            .remove("classes.dex")
            .unwrap();
        let duration = start.elapsed();
        write_to_report(&format!("Recompile classes_hello_world.dex: {duration:?}"));
        dex
    })
}

fn get_class_dex<'a, 'b>(name: &'a str, dex: &'b DexFileReader) -> Option<&'b ClassDefItem> {
    let name = name.into();
    for class in dex.get_class_defs() {
        let class_name = dex
            .get_string(
                dex.get_type_id(class.class_idx as usize)
                    .unwrap()
                    .descriptor_idx,
            )
            .unwrap();
        if class_name == name {
            return Some(class);
        }
    }
    None
}

fn get_method_code_dex(name: &str, dex: &DexFileReader) -> Option<CodeItem> {
    let method = IdMethod::from_smali(name).unwrap();
    let class_name: String = (&method.class_.0).try_into().unwrap();
    let class = get_class_dex(&class_name, dex);
    let class = if let Some(class) = class {
        class
    } else {
        return None;
    };
    if class.class_data_off == 0 {
        return None;
    }
    let data = dex
        .get_struct_at_offset::<ClassDataItem>(class.class_data_off)
        .unwrap();
    let mut idx = 0;
    for meth in data.direct_methods {
        idx += meth.method_idx_diff.0;
        let meth_id = Apk::get_id_method_from_idx(idx as usize, dex).unwrap();
        println!("{}", meth_id.__str__());
        if meth_id == method && meth.code_off.0 != 0 {
            return Some(
                dex.get_struct_at_offset::<CodeItem>(meth.code_off.0)
                    .unwrap(),
            );
        }
    }
    let mut idx = 0;
    for meth in data.virtual_methods {
        idx += meth.method_idx_diff.0;
        let meth_id = Apk::get_id_method_from_idx(idx as usize, dex).unwrap();
        println!("{}", meth_id.__str__());
        if meth_id == method && meth.code_off.0 != 0 {
            return Some(
                dex.get_struct_at_offset::<CodeItem>(meth.code_off.0)
                    .unwrap(),
            );
        }
    }
    None
}

#[test]
fn test_generated_data_size() {
    let dex_bin = get_hello_world_recompilled();
    let dex = DexFileReader::new(dex_bin).unwrap();

    assert_eq!(
        dex.get_header().data_off + dex.get_header().data_size,
        dex_bin.len() as u32
    )
    // TODO: check for all pool concerned if the pool span outside the data section?
    //for item in dex.get_map_list().list() {}
}

#[test]
fn test_generated_apk_equivalence() {
    let new_dex = get_hello_world_recompilled();
    let mut new_apk = Apk::new();
    new_apk
        .add_dex_file("classes.dex", &new_dex, |_, _, _| None, false)
        .unwrap();

    /*
    use pretty_assertions::assert_eq;
    let method = IdMethod::from_smali(
        "Lcom/google/android/material/datepicker/DateFormatTextWatcher;->lambda$new$0$com-google-android-material-datepicker-DateFormatTextWatcher(Ljava/lang/String;)V"
    )
    .unwrap();
    assert_eq!(
        get_hello_world_apk()
            .get_class(&method.class_)
            .unwrap()
            .virtual_methods
            .get(&method)
            .unwrap(),
        new_apk
            .get_class(&method.class_)
            .unwrap()
            .virtual_methods
            .get(&method)
            .unwrap()
    );*/
    assert_eq!(get_hello_world_apk(), &new_apk);
}

#[test]
fn test_string_order_in_dex() {
    use std::collections::HashSet;
    let dex = DexFileReader::new(get_hello_world_dex()).unwrap();
    let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap();
    let mut string_set = HashSet::new();
    for i in 0..new_dex.get_string_ids().len() {
        string_set.insert(DexString(new_dex.get_string(i as u32).unwrap()));
    }
    for i in 0..dex.get_string_ids().len() {
        let string = DexString(dex.get_string(i as u32).unwrap());
        if !string_set.contains(&string) {
            println!("{}", string.__str__());
        }
    }
    assert_eq!(
        dex.get_string_ids().len(),
        new_dex.get_string_ids().len(),
        "Not the same number of strings"
    );
    /* this is hell to debug
    let strings: Vec<_> = (0..dex.get_string_ids().len())
        .map(|idx| DexString(dex.get_string(idx as u32).unwrap()))
        .collect();
    let new_strings: Vec<_> = (0..new_dex.get_string_ids().len())
        .map(|idx| DexString(new_dex.get_string(idx as u32).unwrap()))
        .collect();
    assert_eq!(strings, new_strings);
    */
    for idx in 0..dex.get_string_ids().len() {
        let string = DexString(dex.get_string(idx as u32).unwrap());
        let new_string = DexString(new_dex.get_string(idx as u32).unwrap());
        assert_eq!(
            string, new_string,
            "Strings {idx} do not match, left original, right recompiled"
        );
    }
}

#[test]
fn test_switch_table_alignement() {
    let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap();
    let code = get_method_code_dex(
        "Landroidx/appcompat/app/AppCompatViewInflater;\
        ->createView(\
          Landroid/view/View;Ljava/lang/String;\
          Landroid/content/Context;Landroid/util/AttributeSet;\
          ZZZZ\
        )Landroid/view/View;",
        &new_dex,
    )
    .unwrap();
    let mut addr = 0;
    for ins in code.insns {
        match ins {
            InsFormat::FormatPackedSwitchPayload { .. } => {
                assert_eq!(addr % 4, 0, "Switch table not aligned")
            }
            InsFormat::FormatSparseSwitchPayload { .. } => {
                assert_eq!(addr % 4, 0, "Switch table not aligned")
            }
            _ => (),
        }
        addr += ins.size();
    }
}

#[test]
fn test_array_table_alignement() {
    let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap();
    let code = get_method_code_dex(
        "Landroidx/constraintlayout/core/widgets/ConstraintWidget;-><init>()V",
        &new_dex,
    )
    .unwrap();
    let mut addr = 0;
    for ins in code.insns {
        match ins {
            InsFormat::FormatFillArrayDataPayload { .. } => {
                assert_eq!(addr % 4, 0, "Array Data table not aligned")
            }
            _ => (),
        }
        addr += ins.size();
    }
}

#[test]
fn test_parse_type_list() {
    let types = IdType::get_list_from_str("IILjavalangObject;LjavalangObject;Z").unwrap();
    assert_eq!(
        types,
        vec![
            IdType::int(),
            IdType::int(),
            IdType::class("javalangObject"),
            IdType::class("javalangObject"),
            IdType::boolean(),
        ]
    )
}

#[test]
fn test_parse_proto_smali() {
    let proto = IdMethodType::from_smali(
        "(LandroidxcoreutilPredicate;LandroidxcoreutilPredicate;LjavalangObject;)Z",
    )
    .unwrap();
    assert_eq!(
        proto,
        IdMethodType::new(
            IdType::boolean(),
            vec![
                IdType::class("androidxcoreutilPredicate"),
                IdType::class("androidxcoreutilPredicate"),
                IdType::class("javalangObject")
            ]
        )
    );
}

#[test]
fn test_parse_method_smali() {
    let id_method = IdMethod::from_smali(
         "Landroidx/core/util/Predicate;->lambda$and$0(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z"
       ).unwrap();

    assert_eq!(
        id_method,
        IdMethod::new(
            "lambda$and$0".into(),
            IdMethodType::new(
                IdType::boolean(),
                vec![
                    IdType::class("androidx/core/util/Predicate"),
                    IdType::class("androidx/core/util/Predicate"),
                    IdType::class("java/lang/Object")
                ]
            ),
            IdType::class("androidx/core/util/Predicate")
        )
    );
}

#[test]
fn test_parse_field_smali() {
    let proto = IdField::from_smali(
        "Ljava/lang/annotation/ElementType;->METHOD:Ljava/lang/annotation/ElementType;",
    )
    .unwrap();
    assert_eq!(
        proto,
        IdField::new(
            "METHOD".into(),
            IdType::class("java/lang/annotation/ElementType"),
            IdType::class("java/lang/annotation/ElementType"),
        )
    );
}

#[test]
fn test_sort_fields() {
    let f1 = IdField::from_smali(
        "Landroidx/lifecycle/WithLifecycleStateKt$suspendWithStateAtLeastUnchecked$2$observer$1;\
        ->$co:Lkotlinx/coroutines/CancellableContinuation;",
    )
    .unwrap();
    let f2 = IdField::from_smali(
        "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;\
        ->f$0:Landroid/animation/Animator;",
    )
    .unwrap();
    let mut list1 = vec![f2.clone(), f1.clone()];
    let mut list2 = vec![f1.clone(), f2.clone()];
    list1.sort();
    list2.sort();
    assert_eq!(list1, list2);
    assert_eq!(list1, vec![f1.clone(), f2.clone()]);
}

#[test]
fn test_sort_types() {
    let t1 = IdType::from_smali(
        "Landroidx/lifecycle/WithLifecycleStateKt$suspendWithStateAtLeastUnchecked$2$observer$1;",
    )
    .unwrap();
    let t2 = IdType::from_smali(
        "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;",
    )
    .unwrap();
    let mut list1 = vec![t2.clone(), t1.clone()];
    let mut list2 = vec![t1.clone(), t2.clone()];
    list1.sort();
    list2.sort();
    assert_eq!(list1, list2);
    assert_eq!(list1, vec![t1.clone(), t2.clone()]);
}

#[test]
fn test_sort_strings() {
    let s1: DexString =
        "Landroidx/lifecycle/WithLifecycleStateKt$suspendWithStateAtLeastUnchecked$2$observer$1;"
            .into();
    let s2: DexString =
        "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;"
            .into();

    let mut list1 = vec![s2.clone(), s1.clone()];
    let mut list2 = vec![s1.clone(), s2.clone()];
    list1.sort();
    list2.sort();
    assert_eq!(list1, list2);
    assert_eq!(list1, vec![s1.clone(), s2.clone()]);
}

/// Emulate test https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=e8da7cd1d0e7d3535c82f8d05adcef3edd43cd40;l=581
fn check_valid_offset_and_size(
    dex: &DexFileReader,
    offset: u32,
    size: u32,
    alignment: u32,
    label: &str,
) {
    if size == 0 {
        if offset != 0 {
            panic!("Offset 0x{offset:x} should be zero when size is zero for {label}");
        }
        return;
    }
    // offset < hdr_offset is not relevent (we index from hdr_offset=0)
    let file_size = dex.get_header().file_size;
    if file_size <= offset {
        panic!("Offset 0x{offset:x} sould be within file size 0x{file_size:x} for {label}");
    }
    if file_size - offset < size {
        // size + offset could overflow
        panic!(
            "Section end 0x{:x} should be within file size 0x{file_size:x} for {label}",
            size + offset
        );
    }
    if alignment != 0 && !(offset & (alignment - 1) == 0) {
        panic!("Offset 0x{offset:x} sould be aligned by {alignment} for {label}");
    }
}

#[test]
fn test_1_from_json() {
    let filename = "test_class1.json";
    let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
    let mut file = File::open(&hello_world_dex).expect(&format!("{} not found", filename));
    let mut json = String::new();
    file.read_to_string(&mut json).unwrap();
    let test_a: Class = serde_json::from_str(&json).unwrap();
    let mut apk = Apk::new();
    apk.add_class("classes.dex", test_a).unwrap();
    let dex = apk.gen_raw_dex().unwrap().remove("classes.dex").unwrap();
    let dex = DexFileReader::new(&dex).unwrap();

    check_valid_offset_and_size(
        &dex,
        dex.get_header().link_off,
        dex.get_header().link_size,
        0,
        "link",
    );
    check_valid_offset_and_size(&dex, dex.get_header().map_off, 4, 4, "map");
    check_valid_offset_and_size(
        &dex,
        dex.get_header().string_ids_off,
        dex.get_header().string_ids_size,
        4,
        "string-ids",
    );
    check_valid_offset_and_size(
        &dex,
        dex.get_header().type_ids_off,
        dex.get_header().type_ids_size,
        4,
        "type-ids",
    );
    check_valid_offset_and_size(
        &dex,
        dex.get_header().proto_ids_off,
        dex.get_header().proto_ids_size,
        4,
        "proto-ids",
    );
    check_valid_offset_and_size(
        &dex,
        dex.get_header().field_ids_off,
        dex.get_header().field_ids_size,
        4,
        "field-ids",
    );
    check_valid_offset_and_size(
        &dex,
        dex.get_header().method_ids_off,
        dex.get_header().method_ids_size,
        4,
        "method-ids",
    );
    check_valid_offset_and_size(
        &dex,
        dex.get_header().class_defs_off,
        dex.get_header().class_defs_size,
        4,
        "class-defs",
    );
    check_valid_offset_and_size(
        &dex,
        dex.get_header().data_off,
        dex.get_header().data_size,
        0,
        "data",
    );
    if dex.get_header().type_ids_size > (u16::MAX as u32) {
        panic!(
            "Size 0x{:x} should not exceed limit 0x{:x} for type-ids",
            dex.get_header().type_ids_size,
            u16::MAX
        )
    }
    if dex.get_header().proto_ids_size > (u16::MAX as u32) {
        panic!(
            "Size 0x{:x} should not exceed limit 0x{:x} for proto-ids",
            dex.get_header().proto_ids_size,
            u16::MAX
        )
    }

    let map = dex.get_map_list();
    let mut last_offset = 0;
    let mut last_type = MapItemType::UnkownType(0);
    let mut data_item_count = 0;
    let mut data_items_left = dex.get_header().data_size;
    let file_size = dex.get_header().file_size;
    let mut used_types = HashSet::new();
    for (i, item) in map.list.iter().enumerate() {
        if last_offset >= item.offset && i != 0 {
            panic!(
                "Out of order map item: 0x{:x} then 0x{:x} for type {:?} (last type was {:?})",
                last_offset, item.offset, item.type_, last_type
            )
        }
        if item.offset >= file_size {
            panic!(
                "Map item for type {:?} ends after file: 0x{:x} >= 0x{:x}",
                item.type_, item.offset, file_size
            );
        }
        if item.type_.is_data_section_type() {
            if item.size > data_items_left {
                panic!(
                    "Too many items in data section: {} > {} (item type: {:?}",
                    item.size + data_item_count,
                    dex.get_header().data_size,
                    item.type_
                );
            }
            data_items_left -= item.size;
            data_item_count += item.size;
        }
        if used_types.contains(&item.type_) {
            panic!("Duplicate map section of type {:?}", item.type_);
        }
        used_types.insert(item.type_);
        last_offset = item.offset;
        last_type = item.type_;
    }
    if !used_types.contains(&MapItemType::HeaderItem) {
        panic!("Map is missing Header entry");
    }
    if !used_types.contains(&MapItemType::MapList) {
        panic!("Map is missing Map List entry");
    }
    if !used_types.contains(&MapItemType::StringIdItem) && (dex.get_header().string_ids_off != 0) {
        panic!("Map is missing String Id entry");
    }
    if !used_types.contains(&MapItemType::TypeIdItem) && (dex.get_header().type_ids_off != 0) {
        panic!("Map is missing Type Id entry");
    }
    if !used_types.contains(&MapItemType::ProtoIdItem) && (dex.get_header().proto_ids_off != 0) {
        panic!("Map is missing Proto Id entry");
    }
    if !used_types.contains(&MapItemType::FieldIdItem) && (dex.get_header().field_ids_off != 0) {
        panic!("Map is missing Field Id entry");
    }
    if !used_types.contains(&MapItemType::MethodIdItem) && (dex.get_header().method_ids_off != 0) {
        panic!("Map is missing Method Id entry");
    }
    if !used_types.contains(&MapItemType::ClassDefItem) && (dex.get_header().class_defs_off != 0) {
        panic!("Map is missing Class Def entry");
    }
}

#[test]
fn test_2_from_json() {
    let filename = "app1.json";
    let json_path = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
    let mut file = File::open(&json_path).expect(&format!("{} not found", filename));
    let mut json = String::new();
    file.read_to_string(&mut json).unwrap();
    let apk: Apk = serde_json::from_str(&json).unwrap();
    let dex = apk.gen_raw_dex().unwrap().remove("classes.dex").unwrap();
    let mut new_apk = Apk::new();
    new_apk
        .add_dex_file("classes.dex", &dex, |_, _, _| None, false)
        .unwrap();
    assert_eq!(apk, new_apk);
}

#[test]
fn test_hidden_api() {
    let dex_raw = get_dex("core-oj-33_classes.dex");
    // The data are generated using src/tests/apktool_parse_hiddenapi.py src/tests/core-oj-33_classes.dex src/tests/core-oj-33_hiddenapi.json
    let apktool_result = format!(
        "{}/src/tests/{}",
        env!("CARGO_MANIFEST_DIR"),
        "core-oj-33_hiddenapi.json"
    );
    fn f_desc_to_pointer(f_dsc: &IdField) -> String {
        let name: String = String::try_from(&f_dsc.name.0).unwrap().replace("/", "~1");
        let ty: String = f_dsc.type_.try_to_smali().unwrap().replace("/", "~1");
        format!(
            "/{}/fields/{name}:{ty}",
            f_dsc.class_.try_to_smali().unwrap().replace("/", "~1")
        )
    }
    fn m_desc_to_pointer(m_dsc: &IdMethod) -> String {
        let name: String = String::try_from(&m_dsc.name.0).unwrap().replace("/", "~1");
        let ty: String = m_dsc.proto.try_to_smali().unwrap().replace("/", "~1");
        format!(
            "/{}/methods/{name}{ty}",
            m_dsc.class_.try_to_smali().unwrap().replace("/", "~1")
        )
    }
    fn compare_hidden_api(
        apktool_hiddenapi: &sj::Value,
        apktool_hiddenapi_domain: &sj::Value,
        parsed_api: &Option<HiddenApiData>,
        name: &str,
    ) {
        match (apktool_hiddenapi, apktool_hiddenapi_domain, parsed_api) {
            (sj::Value::Null, sj::Value::Null, None) => (),
            (
                sj::Value::String(apktool_hiddenapi),
                sj::Value::String(apktool_domain),
                Some(HiddenApiData { permission, domain }),
            ) if (permission.to_smali_name() == apktool_hiddenapi)
                && (&domain.to_smali_name() == apktool_domain) =>
            {
                ()
            }
            (
                sj::Value::String(apktool_hiddenapi),
                sj::Value::Null,
                Some(HiddenApiData {
                    permission,
                    domain,
                }),
            ) if (permission.to_smali_name() == apktool_hiddenapi) && &domain.to_smali_name() == "" => (),

            _ => panic!("Expected {apktool_hiddenapi:?} and {apktool_hiddenapi_domain:?}, found {parsed_api:?} in {name}"),
        }
    }
    let apktool_result = File::open(&apktool_result).expect("core-oj-33_hiddenapi.json not found");
    let apktool_result = std::io::BufReader::new(apktool_result);
    let apktool_result: sj::Value = sj::from_reader(apktool_result).unwrap();
    let mut apk = Apk::new();
    apk.add_dex_file("classes.dex", &dex_raw, |_, _, _| None, false)
        .unwrap();
    for cls in apktool_result.as_object().unwrap().keys() {
        assert!(
            apk.dex_files
                .get("classes.dex")
                .unwrap()
                .classes
                .get(&dex_id::IdType(cls.as_str().into()))
                .is_some(),
            "{cls} not found in core-oj-33_classes.dex"
        );
    }
    for cls in apk.dex_files.get("classes.dex").unwrap().classes.keys() {
        assert!(
            apktool_result
                .get::<String>((&cls.0).try_into().unwrap())
                .is_some(),
            "{} not found in core-oj-33_hiddenapi.json",
            cls.__str__()
        );
    }
    for (_, cls) in &apk.dex_files.get("classes.dex").unwrap().classes {
        for (f_dsc, field) in &cls.static_fields {
            let pointer = f_desc_to_pointer(f_dsc);
            let apktool_field = apktool_result.pointer(&pointer).unwrap();
            let apktool_hiddenapi = apktool_field.get("hiddenapi").unwrap();
            let apktool_hiddenapi_domain = apktool_field.get("hiddenapi_domain").unwrap();
            compare_hidden_api(
                apktool_hiddenapi,
                &apktool_hiddenapi_domain,
                &field.hiddenapi,
                &f_dsc.try_to_smali().unwrap(),
            );
        }

        for (f_dsc, field) in &cls.instance_fields {
            let pointer = f_desc_to_pointer(f_dsc);
            let apktool_field = apktool_result.pointer(&pointer).unwrap();
            let apktool_hiddenapi = apktool_field.get("hiddenapi").unwrap();
            let apktool_hiddenapi_domain = apktool_field.get("hiddenapi_domain").unwrap();
            compare_hidden_api(
                apktool_hiddenapi,
                &apktool_hiddenapi_domain,
                &field.hiddenapi,
                &f_dsc.try_to_smali().unwrap(),
            );
        }
        for (m_dsc, method) in &cls.direct_methods {
            let pointer = m_desc_to_pointer(m_dsc);
            let apktool_method = apktool_result.pointer(&pointer).unwrap();
            let apktool_hiddenapi = apktool_method.get("hiddenapi").unwrap();
            let apktool_hiddenapi_domain = apktool_method.get("hiddenapi_domain").unwrap();
            compare_hidden_api(
                apktool_hiddenapi,
                &apktool_hiddenapi_domain,
                &method.hiddenapi,
                &m_dsc.try_to_smali().unwrap(),
            );
        }
        for (m_dsc, method) in &cls.virtual_methods {
            let pointer = m_desc_to_pointer(m_dsc);
            let apktool_method = apktool_result.pointer(&pointer).unwrap();
            let apktool_hiddenapi = apktool_method.get("hiddenapi").unwrap();
            let apktool_hiddenapi_domain = apktool_method.get("hiddenapi_domain").unwrap();
            compare_hidden_api(
                apktool_hiddenapi,
                &apktool_hiddenapi_domain,
                &method.hiddenapi,
                &m_dsc.try_to_smali().unwrap(),
            );
        }
    }
}
