//! ```cargo
//! [dependencies]
//! libbpf-sys = { version = "0.6.1-1" }
//! anyhow = "1"
//! ```

use std::{
    env,
    fs::{self, OpenOptions},
    io::Write,
    path::Path,
    process::Command,
    string::String,
};
use anyhow::{bail, Context, Result};
static CLANG_DEFAULT: &str = "/usr/bin/clang";

/// Extract vendored libbpf headers from libbpf-sys.
fn extract_libbpf_headers<P: AsRef<Path>>(include_path: P) -> Result<()> {
    let dir = include_path.as_ref().join("bpf");
    fs::create_dir_all(&dir)?;
    for (filename, contents) in libbpf_sys::API_HEADERS.iter() {
        let path = dir.as_path().join(filename);
        let mut file = OpenOptions::new().write(true).create(true).open(path)?;
        file.write_all(contents.as_bytes())?;
    }

    Ok(())
}

/// Build eBPF programs with clang and libbpf headers.
fn build_ebpf<P: Clone + AsRef<Path>>(in_file: P, out_file: P, include_path: P) -> Result<()> {
    extract_libbpf_headers(include_path.clone())?;
    let clang = match env::var("CLANG") {
        Ok(val) => val,
        Err(_) => String::from(CLANG_DEFAULT),
    };
    let arch = match std::env::consts::ARCH {
        "x86_64" => "x86",
        "aarch64" => "arm64",
        _ => std::env::consts::ARCH,
    };
    let mut cmd = Command::new(clang);
    cmd.arg(format!("-I{}", include_path.as_ref().to_string_lossy()))
        .arg("-g")
        .arg("-O2")
        .arg("-target")
        .arg("bpf")
        .arg("-c")
        .arg(format!("-D__TARGET_ARCH_{}", arch))
        .arg(in_file.as_ref().as_os_str())
        .arg("-o")
        .arg(out_file.as_ref().as_os_str());

    let output = cmd.output().context("Failed to execute clang")?;
    if !output.status.success() {
        bail!(
            "Failed to compile eBPF programs\n \
            stdout=\n \
            {}\n \
            stderr=\n \
            {}\n",
            String::from_utf8(output.stdout).unwrap(),
            String::from_utf8(output.stderr).unwrap()
        );
    }

    Ok(())
}

fn main() -> Result<()> {
    let args: Vec<String> = env::args().collect();
    if args.len() != 3 {
        bail!("requires 2 arguments. src and dst")
    }
    let path = env::current_dir()?;
    let src = Path::new(&args[1]);
    let dst = Path::new(&args[2]);

    let include_path = path.join("include");
    fs::create_dir_all(include_path.clone())?;
    build_ebpf(src, dst, &include_path)?;

    Ok(())
}