//! `wai_format_probe` — header probes (`extensions/format-probes.md`).
//!
//! - `probe <capability> <file>`: the payload's parameters as JSON, or its refusal code
//!   (exit status 1).
//! - `gen <dir>`: writes `<dir>/gen/` and `<dir>/vectors.json` from `<dir>/ext/` (never in CI).
//! - `verify <dir> [--fetched <dir>]`: checks every vector, binding and fact of the corpus,
//!   that the generated files regenerate byte for byte, and with `--fetched` the LCEVC streams
//!   `fetch.sh` fetched, every one of which must be there.

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use wai::format_probe::{corpus, probe};

fn walk(root: &Path, dir: &Path, out: &mut BTreeMap<String, Vec<u8>>) -> std::io::Result<()> {
    for e in std::fs::read_dir(dir)? {
        let p = e?.path();
        if p.is_dir() {
            walk(root, &p, out)?;
        } else {
            let rel = p.strip_prefix(root).expect("under the root").to_string_lossy().replace('\\', "/");
            out.insert(rel, std::fs::read(&p)?);
        }
    }
    Ok(())
}

/// The corpus's files that the generator and the checker read: `vectors.json`, `ext/` and
/// `gen/`.
fn corpus_files(dir: &Path) -> std::io::Result<BTreeMap<String, Vec<u8>>> {
    let mut out = BTreeMap::new();
    for sub in ["ext", "gen"] {
        let d = dir.join(sub);
        if d.is_dir() {
            walk(dir, &d, &mut out)?;
        }
    }
    let v = dir.join("vectors.json");
    if v.is_file() {
        out.insert("vectors.json".into(), std::fs::read(v)?);
    }
    Ok(out)
}

fn run() -> Result<bool, String> {
    let args: Vec<String> = std::env::args().skip(1).collect();
    match args.first().map(String::as_str) {
        Some("probe") if args.len() == 3 => {
            let b = std::fs::read(&args[2]).map_err(|e| format!("{}: {e}", args[2]))?;
            match probe(&args[1], &b) {
                Ok(v) => {
                    println!("{}", serde_json::to_string_pretty(&v).map_err(|e| e.to_string())?);
                    Ok(true)
                }
                Err(r) => {
                    println!("{}", r.code());
                    Ok(false)
                }
            }
        }
        Some("gen") if args.len() == 2 => {
            let dir = PathBuf::from(&args[1]);
            let files = corpus_files(&dir).map_err(|e| e.to_string())?;
            let ext: BTreeMap<String, Vec<u8>> = files.into_iter().filter(|(k, _)| k.starts_with("ext/")).collect();
            let generated = corpus::generate(&ext)?;
            let gen_dir = dir.join("gen");
            if gen_dir.exists() {
                std::fs::remove_dir_all(&gen_dir).map_err(|e| e.to_string())?;
            }
            for (k, v) in &generated {
                let p = dir.join(k);
                if let Some(parent) = p.parent() {
                    std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
                }
                std::fs::write(&p, v).map_err(|e| format!("{}: {e}", p.display()))?;
            }
            eprintln!("wrote {} files", generated.len());
            Ok(true)
        }
        Some("verify") if args.len() == 2 || (args.len() == 4 && args[2] == "--fetched") => {
            let files = corpus_files(Path::new(&args[1])).map_err(|e| e.to_string())?;
            let mut fails = corpus::verify(&files);
            let vectors = files.keys().filter(|k| !k.ends_with(".json")).count();
            if args.len() == 4 {
                let mut fetched = BTreeMap::new();
                for e in std::fs::read_dir(&args[3]).map_err(|e| format!("{}: {e}", args[3]))? {
                    let p = e.map_err(|e| e.to_string())?.path();
                    if p.is_file() {
                        fetched.insert(p.file_name().unwrap().to_string_lossy().into_owned(), std::fs::read(&p).map_err(|e| e.to_string())?);
                    }
                }
                fails.extend(corpus::verify_fetched(&fetched));
            }
            for f in &fails {
                eprintln!("FAIL {f}");
            }
            eprintln!("{} files, {} failures", vectors, fails.len());
            Ok(fails.is_empty())
        }
        _ => Err("usage: wai_format_probe probe <capability> <file> | gen <dir> | verify <dir> [--fetched <dir>]".into()),
    }
}

fn main() -> ExitCode {
    match run() {
        Ok(true) => ExitCode::SUCCESS,
        Ok(false) => ExitCode::from(1),
        Err(e) => {
            eprintln!("{e}");
            ExitCode::from(2)
        }
    }
}
