extern crate bindgen;

fn create_bindings(
    apr_path: &std::path::Path,
    apu_path: &std::path::Path,
    out_path: &std::path::Path,
    apr_include_paths: &[&std::path::Path],
) {
    // Generate bindings using bindgen
    let mut builder = bindgen::Builder::default().use_core();
    // check if the pool-debug feature is present
    if std::env::var("CARGO_FEATURE_POOL_DEBUG").is_ok() {
        builder = builder.clang_arg("-DAPR_POOL_DEBUG");
    }
    let bindings = builder
        .header(apr_path.join("apr.h").to_str().unwrap())
        .header(apr_path.join("apr_allocator.h").to_str().unwrap())
        .header(apr_path.join("apr_general.h").to_str().unwrap())
        .header(apr_path.join("apr_errno.h").to_str().unwrap())
        .header(apr_path.join("apr_pools.h").to_str().unwrap())
        .header(apr_path.join("apr_version.h").to_str().unwrap())
        .header(apr_path.join("apr_tables.h").to_str().unwrap())
        .header(apr_path.join("apr_hash.h").to_str().unwrap())
        .header(apr_path.join("apr_file_info.h").to_str().unwrap())
        .header(apr_path.join("apr_file_io.h").to_str().unwrap())
        .header(apr_path.join("apr_getopt.h").to_str().unwrap())
        .header(apu_path.join("apr_uri.h").to_str().unwrap())
        .header(apr_path.join("apr_time.h").to_str().unwrap())
        .header(apu_path.join("apr_date.h").to_str().unwrap())
        .header(apr_path.join("apr_version.h").to_str().unwrap())
        .header(apu_path.join("apu_version.h").to_str().unwrap())
        .header(apr_path.join("apr_strings.h").to_str().unwrap())
        .header(apr_path.join("apr_thread_proc.h").to_str().unwrap())
        .header(apr_path.join("apr_thread_mutex.h").to_str().unwrap())
        .header(apr_path.join("apr_thread_cond.h").to_str().unwrap())
        .header(apr_path.join("apr_dso.h").to_str().unwrap())
        .header(apr_path.join("apr_env.h").to_str().unwrap())
        .header(apr_path.join("apr_network_io.h").to_str().unwrap())
        .header(apr_path.join("apr_mmap.h").to_str().unwrap())
        .header(apr_path.join("apr_user.h").to_str().unwrap())
        .header(apu_path.join("apr_md5.h").to_str().unwrap())
        .header(apu_path.join("apr_sha1.h").to_str().unwrap())
        .header(apu_path.join("apr_base64.h").to_str().unwrap())
        .header(apu_path.join("apr_uuid.h").to_str().unwrap())
        .header(apu_path.join("apr_strmatch.h").to_str().unwrap())
        .header(apu_path.join("apr_xlate.h").to_str().unwrap())
        .header(apu_path.join("apr_xml.h").to_str().unwrap())
        .header(apu_path.join("apr_crypto.h").to_str().unwrap())
        .header(apu_path.join("apr_queue.h").to_str().unwrap())
        .header_contents(
            "platform_headers.h",
            if cfg!(windows) {
                "#include <winsock2.h>\n#include <ws2tcpip.h>"
            } else {
                "#include <sys/socket.h>\n#include <sys/types.h>"
            },
        )
        .allowlist_file(".*[/\\\\]apr.h")
        .allowlist_file(".*[/\\\\]apr_general.h")
        .allowlist_file(".*[/\\\\]apr_allocator.h")
        .allowlist_file(".*[/\\\\]apr_version.h")
        .allowlist_file(".*[/\\\\]apr_errno.h")
        .allowlist_file(".*[/\\\\]apr_pools.h")
        .allowlist_file(".*[/\\\\]apr_tables.h")
        .allowlist_file(".*[/\\\\]apr_hash.h")
        .allowlist_file(".*[/\\\\]apr_file_info.h")
        .allowlist_file(".*[/\\\\]apr_file_io.h")
        .allowlist_file(".*[/\\\\]apr_getopt.h")
        .allowlist_file(".*[/\\\\]apr_uri.h")
        .allowlist_file(".*[/\\\\]apr_time.h")
        .allowlist_file(".*[/\\\\]apr_date.h")
        .allowlist_file(".*[/\\\\]apr_strings.h")
        .allowlist_file(".*[/\\\\]apr_version.h")
        .allowlist_file(".*[/\\\\]apu_version.h")
        .allowlist_file(".*[/\\\\]apr_thread_proc.h")
        .allowlist_file(".*[/\\\\]apr_thread_mutex.h")
        .allowlist_file(".*[/\\\\]apr_thread_cond.h")
        .allowlist_file(".*[/\\\\]apr_dso.h")
        .allowlist_file(".*[/\\\\]apr_env.h")
        .allowlist_file(".*[/\\\\]apr_network_io.h")
        .allowlist_file(".*[/\\\\]apr_mmap.h")
        .allowlist_file(".*[/\\\\]apr_user.h")
        .allowlist_file(".*[/\\\\]apr_md5.h")
        .allowlist_file(".*[/\\\\]apr_sha1.h")
        .allowlist_file(".*[/\\\\]apr_base64.h")
        .allowlist_file(".*[/\\\\]apr_uuid.h")
        .allowlist_file(".*[/\\\\]apr_strmatch.h")
        .allowlist_file(".*[/\\\\]apr_xlate.h")
        .allowlist_file(".*[/\\\\]apr_xml.h")
        .allowlist_file(".*[/\\\\]apr_crypto.h")
        .allowlist_file(".*[/\\\\]apr_queue.h")
        .allowlist_file(".*[/\\\\]apr_portable.h")
        .allowlist_file(".*[/\\\\]apr_support.h")
        // Explicitly allowlist fundamental APR types that may be defined via
        // typedef chains through system headers. On some platforms (e.g. EL9),
        // bindgen may attribute these to system headers rather than apr.h,
        // causing them to be filtered out by the allowlist_file rules above.
        .allowlist_type("apr_int16_t")
        .allowlist_type("apr_uint16_t")
        .allowlist_type("apr_int32_t")
        .allowlist_type("apr_uint32_t")
        .allowlist_type("apr_int64_t")
        .allowlist_type("apr_uint64_t")
        .allowlist_type("apr_size_t")
        .allowlist_type("apr_ssize_t")
        .allowlist_type("apr_off_t")
        .allowlist_type("apr_socklen_t")
        .allowlist_type("apr_ino_t")
        .allowlist_type("apr_uintptr_t")
        .allowlist_type("apr_intptr_t")
        .clang_args(
            apr_include_paths
                .iter()
                .map(|path| format!("-I{}", path.display())),
        )
        .generate()
        .expect("Failed to generate bindings");

    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Failed to write bindings");
}

fn main() {
    let deps = system_deps::Config::new().probe().unwrap();

    let apr = deps.get_by_name("apr-1").unwrap();

    let apr_util = deps.get_by_name("apr-util-1").unwrap();

    let apr_path = apr
        .include_paths
        .iter()
        .find(|x| x.join("apr.h").exists())
        .expect("Failed to find apr.h");

    let apr_util_path = apr_util
        .include_paths
        .iter()
        .find(|x| x.join("apu.h").exists())
        .expect("Failed to find apu.h");

    let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
    create_bindings(
        apr_path.as_path(),
        apr_util_path.as_path(),
        out_path.as_path(),
        apr.include_paths
            .iter()
            .map(|x| x.as_path())
            .collect::<Vec<_>>()
            .as_slice(),
    );

    // Check if APU_HAVE_CRYPTO is defined and non-zero in apu.h
    let apu_h = std::fs::read_to_string(apr_util_path.join("apu.h")).expect("Failed to read apu.h");
    let has_crypto = apu_h.lines().any(|line| {
        let trimmed = line.trim();
        if let Some(rest) = trimmed.strip_prefix("#define") {
            let rest = rest.trim();
            if let Some(value) = rest.strip_prefix("APU_HAVE_CRYPTO") {
                return value.trim() != "0";
            }
        }
        false
    });
    if has_crypto {
        println!("cargo:rustc-cfg=apu_have_crypto");
    }
}
