use std::path::PathBuf;

fn main() {
    if std::env::var_os("CARGO_FEATURE_VENDORED").is_none()
        && pkg_config::Config::new()
            // ssh_userauth_publickey_auto_get_current_identity
            // is not yet in a released version of libssh
            .atleast_version("0.9.7")
            .probe("libssh")
            .is_ok()
    {
        return;
    }

    let mut cfg = cc::Build::new();
    cfg.define("LIBSSH_STATIC", None);
    cfg.define("_GNU_SOURCE", None);
    cfg.include("vendored/include");
    cfg.flag_if_supported("-Wno-deprecated-declarations");

    let dst = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
    let include = dst.join("include");
    std::fs::create_dir_all(&include).unwrap();
    cfg.include(&include);
    println!("cargo:include={}", include.display());
    println!("cargo:root={}", dst.display());

    let openssl_version = std::env::var("DEP_OPENSSL_VERSION_NUMBER").unwrap();
    let openssl_version = u64::from_str_radix(&openssl_version, 16).unwrap();

    let target = std::env::var("TARGET").unwrap();
    let target_family = std::env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
    cfg.define("GLOBAL_CLIENT_CONFIG", Some("\"/etc/ssh/ssh_config\""));
    cfg.define(
        "GLOBAL_BIND_CONFIG",
        Some("\"/etc/ssh/libssh_server_config\""),
    );
    cfg.define("HAVE_GETADDRINFO", Some("1"));
    cfg.define("HAVE_LIBCRYPTO", Some("1"));
    cfg.define("HAVE_OPENSSL_AES_H", Some("1"));
    cfg.define("HAVE_OPENSSL_BLOWFISH_H", Some("1"));
    cfg.define("HAVE_OPENSSL_DES_H", Some("1"));
    cfg.define("HAVE_OPENSSL_ECC", Some("1"));
    cfg.define("HAVE_OPENSSL_ECDH_H", Some("1"));
    cfg.define("HAVE_OPENSSL_ECDSA_H", Some("1"));
    cfg.define("HAVE_ECC", Some("1"));
    cfg.define("HAVE_DSA", Some("1"));
    cfg.define("HAVE_OPENSSL_EC_H", Some("1"));

    if openssl_version >= 0x1_01_01_00_0 {
        cfg.define("HAVE_OPENSSL_EVP_CHACHA20", Some("1"));
    }

    /* Don't bother setting these: libssh has a fallback in any case,
     * and the documentation doesn't specify when they were introduced,
    cfg.define("HAVE_OPENSSL_EVP_DIGESTSIGN", Some("1"));
    cfg.define("HAVE_OPENSSL_EVP_DIGESTVERIFY", Some("1"));
    */

    if openssl_version < 0x1_01_00_00_0 {
        cfg.file("vendored/src/libcrypto-compat.c");
    }

    if false && openssl_version >= 0x3_00_00_00_0 {
        cfg.define("HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID", Some("1"));
    }
    // cfg.define("HAVE_OPENSSL_FIPS_MODE", Some("1"));

    cfg.define("HAVE_STDINT_H", Some("1"));
    cfg.define("WITH_ZLIB", Some("1"));
    cfg.define("WITH_GEX", Some("1"));
    cfg.define("WITH_SFTP", Some("1"));
    cfg.define("WITH_SERVER", Some("1"));

    if target.contains("windows") {
        cfg.define("HAVE_IO_H", Some("1"));
        // cfg.define("HAVE_MEMSET_S", Some("1"));
        cfg.define("HAVE_SECURE_ZERO_MEMORY", Some("1"));
        cfg.define("HAVE__SNPRINTF", Some("1"));
        cfg.define("HAVE__SNPRINTF_S", Some("1"));
        cfg.define("HAVE__STRTOUI64", Some("1"));
        cfg.define("HAVE__VSNPRINTF", Some("1"));
        cfg.define("HAVE__VSNPRINTF_S", Some("1"));
        cfg.define("HAVE_ISBLANK", Some("1"));
    } else {
        cfg.define("HAVE_ARPA_INET_H", Some("1"));
        cfg.define("HAVE_CLOCK_GETTIME", Some("1"));
        cfg.define("HAVE_PTHREAD_H", Some("1"));
        cfg.define("HAVE_PTHREAD", Some("1"));
        cfg.define("HAVE_SELECT", Some("1"));
        cfg.define("HAVE_SNPRINTF", Some("1"));
        cfg.define("HAVE_STRTOULL", Some("1"));
        cfg.define("HAVE_SYS_TIME_H", Some("1"));
        cfg.define("HAVE_TERMIOS_H", Some("1"));
        cfg.define("HAVE_UNISTD_H", Some("1"));
        cfg.define("HAVE_VSNPRINTF", Some("1"));

        if !target.contains("darwin") {
            cfg.define("HAVE_POLL", Some("1"));
        }
    }
    if target.contains("linux") {
        cfg.define("HAVE_STRNDUP", Some("1"));
    }
    if target.contains("darwin") {
        cfg.define("HAVE_NTOHLL", Some("1"));
        cfg.define("HAVE_HTONLL", Some("1"));
    }

    let compiler = cfg.get_compiler();
    if compiler.is_like_gnu() || compiler.is_like_clang() {
        cfg.define("HAVE_COMPILER__FUNCTION__", Some("1"));
        cfg.define("HAVE_COMPILER__FUNC__", Some("1"));
        cfg.define("HAVE_GCC_THREAD_LOCAL_STORAGE", Some("1"));
    }

    if compiler.is_like_msvc() {
        cfg.define("HAVE_COMPILER__FUNC__", Some("1"));
        cfg.define("HAVE_MSC_THREAD_LOCAL_STORAGE", Some("1"));
    }

    std::fs::write(include.join("config.h"), "// nothing").unwrap();

    let version = std::fs::read_to_string("vendored/include/libssh/libssh_version.h.cmake")
        .unwrap()
        .replace("@libssh_VERSION_MAJOR@", "0")
        .replace("@libssh_VERSION_MINOR@", "8")
        .replace("@libssh_VERSION_PATCH@", "90");

    std::fs::create_dir_all(include.join("libssh")).unwrap();
    std::fs::write(include.join("libssh/libssh_version.h"), version).unwrap();

    println!("cargo:rerun-if-env-changed=DEP_Z_INCLUDE");
    if let Some(path) = std::env::var_os("DEP_Z_INCLUDE") {
        cfg.include(path);
    }
    if let Some(zlib_root) = std::env::var_os("DEP_Z_ROOT") {
        println!(
            "cargo:rustc-link-search=native={}",
            PathBuf::from(zlib_root).join("lib").to_str().unwrap()
        );
    }

    println!("cargo:rerun-if-env-changed=DEP_OPENSSL_INCLUDE");
    if let Some(path) = std::env::var_os("DEP_OPENSSL_INCLUDE") {
        if let Some(path) = std::env::split_paths(&path).next() {
            if let Some(path) = path.to_str() {
                if !path.is_empty() {
                    cfg.include(path);
                }
            }
        }
    }
    if let Some(zlib_root) = std::env::var_os("DEP_OPENSSL_ROOT") {
        println!(
            "cargo:rustc-link-search=native={}",
            PathBuf::from(zlib_root).join("lib").to_str().unwrap()
        );
    }

    if false {
        for (k, v) in std::env::vars() {
            if k.starts_with("CARGO") || k.starts_with("DEP") {
                eprintln!("{}={}", k, v);
            }
        }
        panic!("boo");
    }

    cfg.warnings(false);
    for f in &[
        "agent.c",
        "auth.c",
        "base64.c",
        "bignum.c",
        "bind_config.c",
        "buffer.c",
        "callbacks.c",
        "chachapoly.c",
        "channels.c",
        "client.c",
        "config.c",
        "config_parser.c",
        "connect.c",
        "connector.c",
        "crypto_common.c",
        "curve25519.c",
        "dh-gex.c",
        "dh.c",
        "dh_crypto.c",
        "ecdh.c",
        "ecdh_crypto.c",
        "error.c",
        "external/bcrypt_pbkdf.c",
        "external/blowfish.c",
        "external/chacha.c",
        "external/curve25519_ref.c",
        "external/ed25519.c",
        "external/fe25519.c",
        "external/ge25519.c",
        "external/poly1305.c",
        "external/sc25519.c",
        "getpass.c",
        "getrandom_crypto.c",
        "gzip.c",
        "init.c",
        "kdf.c",
        "kex.c",
        "known_hosts.c",
        "knownhosts.c",
        "legacy.c",
        "libcrypto.c",
        "log.c",
        "match.c",
        "md_crypto.c",
        "messages.c",
        "misc.c",
        "options.c",
        "packet.c",
        "packet_cb.c",
        "packet_crypt.c",
        "pcap.c",
        "pki.c",
        "pki_container_openssh.c",
        "pki_crypto.c",
        "pki_ed25519.c",
        "pki_ed25519_common.c",
        "poll.c",
        "scp.c",
        "server.c",
        "session.c",
        "sftp.c",
        "socket.c",
        "string.c",
        "threads.c",
        "threads/libcrypto.c",
        "threads/noop.c",
        "token.c",
        "wrapper.c",
    ] {
        cfg.file(&format!("vendored/src/{}", f));
    }

    if target_family == "unix" {
        cfg.file("vendored/src/threads/pthread.c");
    }
    if target_family == "windows" {
        cfg.file("vendored/src/threads/winlocks.c");
    }
    cfg.compile("libssh");

    if target.contains("windows") {
        println!("cargo:rustc-link-lib=libcrypto");
        println!("cargo:rustc-link-lib=libssl");
        println!("cargo:rustc-link-lib=crypt32");
        println!("cargo:rustc-link-lib=user32");
        println!("cargo:rustc-link-lib=shell32");
        println!("cargo:rustc-link-lib=ntdll");
    } else {
        println!("cargo:rustc-link-lib=ssl");
        println!("cargo:rustc-link-lib=crypto");
    }
    println!("cargo:rustc-link-lib=z");
}
