mod utils;
use lief;
use std::path::Path;

fn explore_method(meth: &lief::objc::Method) {
    println!(
        "{}{}{}{}{}",
        meth.name(),
        meth.mangled_type(),
        meth.address(),
        meth.address(),
        meth.is_instance()
    );
}

fn explore_proptocol(prot: &lief::objc::Protocol) {
    println!("{}", prot.to_decl());
    println!("{}", prot.to_decl_with_opt(&lief::objc::DeclOpt::default()));
    println!("{}", prot.mangled_name());

    for meth in prot.optional_methods() {
        explore_method(&meth);
    }

    for meth in prot.required_methods() {
        explore_method(&meth);
    }

    for prop in prot.properties() {
        explore_property(&prop);
    }
}

fn explore_property(prop: &lief::objc::Property) {
    println!("{}{}", prop.name(), prop.attribute());
}

fn explore_ivar(ivar: &lief::objc::IVar) {
    println!("{}{}", ivar.name(), ivar.mangled_type());
}

fn explore_class(class: &lief::objc::Class) {
    println!("{}", class.to_decl());
    println!("{}", class.to_decl_with_opt(&lief::objc::DeclOpt::default()));
    println!(
        "{}{}{:?}",
        class.name(),
        class.demangled_name(),
        class.is_meta()
    );
    if let Some(parent) = class.super_class() {
        explore_class(&parent);
    }

    for meth in class.methods() {
        explore_method(&meth);
    }

    for prot in class.protocols() {
        explore_proptocol(&prot);
    }

    for prop in class.properties() {
        explore_property(&prop);
    }

    for ivar in class.ivars() {
        explore_ivar(&ivar);
    }
}

fn explore_metadata(name: &str, metadata: &lief::objc::Metadata) {
    for cls in metadata.classes() {
        explore_class(&cls);
    }

    for prot in metadata.protocols() {
        explore_proptocol(&prot);
    }

    println!("{}", metadata.to_decl());
    println!("{}", metadata.to_decl_with_opt(&lief::objc::DeclOpt::default()));

    if name == "Module_Framework" {
        assert!(metadata.class_by_name("GADGestureRecognizer").is_some());
        assert!(metadata.class_by_name("GADGestureRecognizer_xxxx").is_none());

        assert!(metadata.protocol_by_name("PINCaching").is_some());
        assert!(metadata.protocol_by_name("PINCaching_xxx").is_none());
    }
}

fn test_with_str(name: &str, path_str: &str) {
    if let Some(lief::Binary::MachO(fat)) = lief::Binary::parse(path_str) {
        for bin in fat.iter() {
            if let Some(metadata) = bin.objc_metadata() {
                explore_metadata(name, &metadata);
            }
        }
    }
}

fn test_with_fullpath(name: &str, suffix: &str) {
    let path = utils::get_sample(Path::new(suffix)).unwrap();
    let path_str = path.to_str().unwrap();
    test_with_str(name, path_str);
}

#[test]
fn test_api() {
    if !lief::is_extended() {
        return;
    }

    test_with_fullpath("DebugHierarchyKit", "MachO/ios17/DebugHierarchyKit");
    test_with_fullpath(
        "NTKPrideWeaveFaceBundleCompanion",
        "MachO/ios17/NTKPrideWeaveFaceBundleCompanion",
    );
    test_with_fullpath("RNCryptor.bin", "MachO/RNCryptor.bin");
    test_with_fullpath("SingPass", "private/MachO/SingPass");
    test_with_fullpath("Module_Framework", "private/MachO/Module_Framework");
}
