// SPDX-FileCopyrightText: 2020 Robin Krahl <robin.krahl@ireas.org>
// SPDX-License-Identifier: MIT

//! Utility functions for executing tests with the output of different rustdoc versions.

use std::env;
use std::fmt;
use std::fs;
use std::path;
use std::process;
use std::sync;

static mut VERSIONS: Option<Vec<(semver::Version, Format, path::PathBuf)>> = None;
static INIT: sync::Once = sync::Once::new();

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Format {
    Html,
}

impl Format {
    pub fn all() -> &'static [Format] {
        FORMATS
    }
}

static FORMATS: &[Format] = &[Format::Html];

impl fmt::Display for Format {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Format::Html => "html",
        })
    }
}

fn get_versions() -> &'static [(semver::Version, Format, path::PathBuf)] {
    INIT.call_once(|| {
        let mut versions = Vec::new();
        for format in Format::all() {
            versions.append(&mut find_rust_versions(*format));
            if env::var("RUSTY_MAN_GENERATE").is_ok() {
                if let Some(docs) = generate_docs(*format) {
                    versions.push(docs);
                }
            }
        }
        unsafe {
            VERSIONS = Some(versions);
        }
    });

    unsafe { VERSIONS.as_ref().unwrap() }
}

fn find_rust_versions(format: Format) -> Vec<(semver::Version, Format, path::PathBuf)> {
    let mut path = path::PathBuf::from("tests");
    path.push(format.to_string());
    if !path.is_dir() {
        return Vec::new();
    }
    fs::read_dir(&path)
        .unwrap()
        .map(Result::unwrap)
        .filter(|e| e.file_type().unwrap().is_dir())
        .map(|e| {
            (
                semver::Version::parse(&e.file_name().into_string().unwrap()).unwrap(),
                format,
                e.path(),
            )
        })
        .collect()
}

fn generate_docs(format: Format) -> Option<(semver::Version, Format, path::PathBuf)> {
    if format != Format::Html {
        return None;
    }

    // TODO: check which rustc/rustdoc $CARGO is using
    let version = rustc_version::version().unwrap();
    let mut path = tempfile::tempdir().unwrap().into_path();
    process::Command::new(env::var_os("CARGO").unwrap())
        .arg("doc")
        .args(&["--package", "anyhow"])
        .args(&["--package", "kuchiki"])
        .args(&["--package", "log"])
        .args(&["--package", "rand_core"])
        .arg("--target-dir")
        .arg(&path)
        .arg("--no-deps")
        .output()
        .unwrap();
    path.push("doc");
    Some((version, format, path))
}

/// Calls the given function with all paths to rustdoc documentation generated by rustdoc versions
/// that match the given requirement string.
pub fn with_rustdoc<F>(version: &str, formats: &[Format], f: F)
where
    F: Fn(&semver::Version, Format, &path::Path),
{
    let version_req = semver::VersionReq::parse(version).unwrap();
    let versions = get_versions();
    for (version, format, path) in versions {
        if version_req.matches(&version) && formats.contains(format) {
            log::warn!(
                "Executing test case for version {} at {} (format {})",
                version,
                path.display(),
                format
            );
            f(version, *format, &path);
        }
    }
}
