use glob::glob;
use std::{
    env,
    fs::{self, File},
    io::Write,
    process::Command,
    str::{self},
};

const CADICAL_PATH: &str = "cadical-rel-2.0.0";

fn main() {
    if std::env::var("DOCS_RS").is_ok() {
        // don't build c++ library on docs.rs due to network restrictions
        return;
    }

    #[cfg(all(feature = "quiet", feature = "logging"))]
    compile_error!("cannot combine cadical features quiet and logging");

    let src_files = glob(&format!("{CADICAL_PATH}/src/*.cpp"))
        .unwrap()
        .filter_map(|res| {
            if let Ok(p) = res {
                if let Some(name) = p.file_name() {
                    if name == "cadical.cpp" || name == "mobical.cpp" || name == "ipasir.cpp" {
                        return None; // Filter out application files and IPASIR interface
                    }
                };
                Some(p)
            } else {
                None
            }
        })
        .collect::<Vec<_>>();
    let mut cadical_build = cc::Build::new();
    cadical_build.cpp(true).std("c++11");
    if cfg!(feature = "debug") && env::var("PROFILE").unwrap() == "debug" {
        cadical_build
            .opt_level(0)
            .define("DEBUG", None)
            .warnings(true)
            .debug(true)
            .define("NCONTRACTS", None) // --no-contracts
            .define("NTRACING", None); // --no-tracing
    } else {
        cadical_build
            .opt_level(3)
            .define("NDEBUG", None)
            .warnings(false);
    }
    #[cfg(feature = "safe")]
    cadical_build.define("SAFE", None); // --safe
    #[cfg(feature = "quiet")]
    cadical_build.define("QUIET", None); // --quiet
    #[cfg(feature = "logging")]
    cadical_build.define("LOGGING", None); // --log

    let mut build_header = File::create(format!("{CADICAL_PATH}/src/build.hpp"))
        .expect("Could not create kissat CaDiCaL header");
    let mut cadical_version =
        fs::read_to_string(format!("{CADICAL_PATH}/VERSION")).expect("Cannot read CaDiCaL version");
    cadical_version.retain(|c| c != '\n');
    let (compiler_desc, compiler_flags) = get_compiler_description(cadical_build.get_compiler());
    write!(
        build_header,
        "#define VERSION \"{}\"\n#define COMPILER \"{}\"\n#define FLAGS \"{}\"\n#define DATE \"{}\"",
        cadical_version, compiler_desc, compiler_flags, chrono::Utc::now()
    ).expect("Failed to write CaDiCaL build.hpp");
    // Build CaDiCaL
    cadical_build
        .include(format!("{CADICAL_PATH}/src"))
        .warnings(false)
        .files(&src_files)
        .compile("cadical");

    for file in &src_files {
        println!(
            "cargo:rerun-if-changed={}",
            file.as_os_str().to_string_lossy()
        );
    }

    #[cfg(target_os = "macos")]
    println!("cargo:rustc-flags=-l dylib=c++");

    #[cfg(not(target_os = "macos"))]
    println!("cargo:rustc-flags=-l dylib=stdc++");

    let out_dir = env::var("OUT_DIR").unwrap();
    println!("cargo:rustc-link-search={}", out_dir);
    println!("cargo:rustc-link-search={}/lib", out_dir);
}

/// Gets a description of the C(++) compiler used and the used flags
fn get_compiler_description(compiler: cc::Tool) -> (String, String) {
    let compiler_command = compiler.to_command();
    let mut first_line = true;
    let compiler_version = match Command::new(compiler_command.get_program())
        .arg("--version")
        .output()
    {
        Ok(output) => {
            let mut version = String::from_utf8(output.stdout).unwrap();
            version.retain(|c| {
                if first_line && c == '\n' {
                    first_line = false;
                    false
                } else {
                    first_line
                }
            });
            version
        }
        Err(_) => String::from(compiler_command.get_program().to_str().unwrap()),
    };
    let compiler_flags = compiler.cflags_env();
    (
        compiler_version,
        String::from(compiler_flags.to_str().unwrap()),
    )
}
