#![allow(unused)]

use std::env;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

fn wasi_sdk() -> PathBuf {
    Path::new(&env::var("WASI_SDK").unwrap_or("/opt/wasi-sdk".to_owned())).to_path_buf()
}

fn wasi_sysroot() -> PathBuf {
    match env::var("WASI_SYSROOT") {
        Ok(wasi_sysroot) => Path::new(&wasi_sysroot).to_path_buf(),
        Err(_) => {
            let mut path = wasi_sdk();
            path.push("share");
            path.push("wasi-sysroot");
            path
        }
    }
}

fn wasm_clang_root() -> PathBuf {
    match env::var("CLANG_ROOT") {
        Ok(clang) => Path::new(&clang).to_path_buf(),
        Err(_) => {
            let mut path = wasi_sdk();
            path.push("lib");
            path.push("clang");
            path.push("8.0.1");
            path
        }
    }
}

// `src/wasi_host.rs` is automatically generated using clang and
// wasi-libc headers. This requires these to be present, and installed
// at specific paths, which is not something we can rely on outside
// of our environment.
// So, we follow what most other tools using `bindgen` do, and provide
// a pre-generated version of the file, along with a way to update it.
// This is what the `update-bindings` feature do. It requires the WASI SDK
// to be either installed in `/opt/wasi-sdk`, or at a location defined by
// a `WASI_SDK` environment variable, as well as `clang` headers either
// being part of `WASI_SDK`, or found in a path defined by a
// `CLANG_ROOT` environment variable.
#[cfg(not(feature = "update-bindings"))]
fn main() {}

#[cfg(feature = "update-bindings")]
fn main() {
    let wasi_sysroot = wasi_sysroot();
    let wasm_clang_root = wasm_clang_root();
    assert!(
        wasi_sysroot.exists(),
        "wasi-sysroot not present at {:?}",
        wasi_sysroot
    );
    assert!(
        wasm_clang_root.exists(),
        "clang-root not present at {:?}",
        wasm_clang_root
    );

    let wasi_sysroot_core_h = wasi_sysroot.join("include/wasi/core.h");

    assert!(
        wasi_sysroot_core_h.exists(),
        "wasi-sysroot core.h not present at {:?}",
        wasi_sysroot_core_h
    );

    println!("cargo:rerun-if-changed={}", wasi_sysroot_core_h.display());

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());

    let core_h_path = out_path.join("core.h");
    let core_h = File::create(&core_h_path).unwrap();

    // `bindgen` doesn't understand typed constant macros like `UINT8_C(123)`, so this fun regex
    // strips them off to yield a copy of `wasi/core.h` with bare constants.
    let sed_result = Command::new("sed")
        .arg("-E")
        .arg(r#"s/U?INT[0-9]+_C\(((0x)?[0-9]+)\)/\1/g"#)
        .arg(wasi_sysroot_core_h)
        .stdout(Stdio::from(core_h))
        .status()
        .expect("can execute sed");

    if !sed_result.success() {
        // something failed, but how?
        match sed_result.code() {
            Some(code) => panic!("sed failed with code {}", code),
            None => panic!("sed exited abnormally"),
        }
    }

    let host_builder = bindgen::Builder::default()
        .clang_arg("-nostdinc")
        .clang_arg("-D__wasi__")
        .clang_arg(format!("-isystem={}/include/", wasi_sysroot.display()))
        .clang_arg(format!("-I{}/include/", wasm_clang_root.display()))
        .header(core_h_path.to_str().unwrap())
        .whitelist_type("__wasi_.*")
        .whitelist_var("__WASI_.*");

    let src_path = Path::new("src");
    host_builder
        .generate()
        .expect("can generate host bindings")
        .write_to_file(src_path.join("wasi_host.rs"))
        .expect("can write host bindings");
}
