#![forbid(unsafe_code)]
#![warn(rust_2018_idioms, single_use_lifetimes)]

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

// The rustc-cfg strings below are *not* public API. Please let us know by
// opening a GitHub issue if your build environment requires some way to enable
// these cfgs other than by executing our build script.
fn main() {
    let rustc = env::var_os("RUSTC").map_or_else(|| "rustc".into(), PathBuf::from);
    let version = match Version::from_rustc(&rustc) {
        Ok(version) => version.print(),
        Err(e) => {
            println!(
                "cargo:warning={}: unable to determine rustc version: {}",
                env!("CARGO_PKG_NAME"),
                e
            );
            return;
        }
    };

    let out_dir = env::var_os("OUT_DIR").map(PathBuf::from).expect("OUT_DIR not set");
    let out_file = out_dir.join("version.rs");
    fs::write(out_file, version).expect("failed to write version.rs");

    // Mark as build script has been run successfully.
    println!("cargo:rustc-cfg=const_fn_has_build_script");
}

struct Version {
    minor: u32,
    nightly: bool,
}

impl Version {
    // Based on https://github.com/cuviper/autocfg/blob/1.0.1/src/version.rs#L25-L59
    //
    // TODO: use autocfg if https://github.com/cuviper/autocfg/issues/28 merged
    // or https://github.com/taiki-e/const_fn/issues/27 rejected.
    fn from_rustc(rustc: &Path) -> Result<Self, String> {
        let output =
            Command::new(rustc).args(&["--version", "--verbose"]).output().map_err(|e| {
                format!("could not execute `{} --version --verbose`: {}", rustc.display(), e)
            })?;
        if !output.status.success() {
            return Err(format!(
                "process didn't exit successfully: `{} --version --verbose`",
                rustc.display()
            ));
        }
        let output = str::from_utf8(&output.stdout).map_err(|e| {
            format!("failed to parse output of `{} --version --verbose`: {}", rustc.display(), e)
        })?;

        // Find the release line in the verbose version output.
        let release = output
            .lines()
            .find(|line| line.starts_with("release: "))
            .map(|line| &line["release: ".len()..])
            .ok_or_else(|| {
                format!(
                    "could not find rustc release from output of `{} --version --verbose`: {}",
                    rustc.display(),
                    output
                )
            })?;

        // Split the version and channel info.
        let mut version_channel = release.split('-');
        let version = version_channel.next().unwrap();
        let channel = version_channel.next();

        let minor = (|| {
            // Split the version into semver components.
            let mut digits = version.splitn(3, '.');
            let major = digits.next()?;
            if major != "1" {
                return None;
            }
            let minor = digits.next()?.parse().ok()?;
            let _patch = digits.next()?;
            Some(minor)
        })()
        .ok_or_else(|| {
            format!("unexpected output from `{} --version --verbose`: {}", rustc.display(), output)
        })?;

        let nightly = channel.map_or(false, |c| c == "dev" || c == "nightly");
        Ok(Self { minor, nightly })
    }

    fn print(&self) -> String {
        format!("Version {{ minor: {}, nightly: {} }}\n", self.minor, self.nightly)
    }
}
