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

struct Builder {
    include: String,
    capi: PathBuf,
}

impl Builder {
    fn new() -> Self {
        let output = Command::new("yosys-config")
            .args(["--datdir/include"])
            .output()
            .expect("failed to get yosys include dir");
        let stdout = String::from_utf8_lossy(&output.stdout);
        let include = stdout.trim().to_string();
        let capi = Path::new(&include).join("backends/cxxrtl/cxxrtl_capi.cc");
        Self { include, capi }
    }

    fn build(&self, source: PathBuf, dest: PathBuf) {
        let cc_file = dest.with_extension("cc");
        // touch(format!("/tmp/out/{}", tmp.file_name().unwrap().to_string_lossy()).as_str());
        Command::new("yosys")
            .args([
                "-p",
                &format!("write_cxxrtl -O0 {}", cc_file.to_string_lossy()),
            ])
            .arg(&source)
            .status()
            .expect("cargo:waring=failed generate cxxrtl code");

        Command::new("clang++")
            .args(["-g", "-O3", "-fPIC", "-shared", "-std=c++14"])
            .arg(format!("-I{}", &self.include))
            .arg(&self.capi)
            .arg(cc_file)
            .arg("-o")
            .arg(dest)
            .status()
            .expect("cargo:warning=failed generate cxxrtl code");
    }
}

fn main() {
    let builder = Builder::new();
    let out = PathBuf::from(env::var("OUT_DIR").unwrap());
    let designs = PathBuf::from("designs");
    println!("cargo:rerun-if-changed={}", designs.display());

    for file in std::fs::read_dir(designs)
        .into_iter()
        .flat_map(|r| r.filter_map(|f| f.ok()))
    {
        if let Ok(metadata) = file.metadata() {
            if metadata.is_file() {
                let file = file.path();
                if file.extension().and_then(|ext| ext.to_str()) == Some("sv") {
                    let file_name = file.file_stem().unwrap();
                    let dest = out.join(file_name).with_extension("so");
                    let modified = metadata.modified().unwrap();
                    let dest_modified = dest.metadata().and_then(|m| m.modified());
                    let duration = dest_modified
                        .ok()
                        .and_then(|m| m.duration_since(modified).ok());
                    if !dest.exists() || duration.is_none() {
                        builder.build(file, dest);
                    }
                }
            }
        }
    }
}