//! Conformance corpus for exact upscale (`live-conformance/upscale/`, extensions/exact-upscale.md §9).
//!
//!   wai_upscale_vectors gen    <corpus-dir>   write vectors.json, sha256.json and the generated
//!                                             priors, from fixed seeds and the corpus's two
//!                                             trained priors (priors/generic.wsr,
//!                                             priors/title.wsr), which must match their pins
//!   wai_upscale_vectors verify <corpus-dir>   the trained priors match their pins, every other
//!                                             file regenerates byte for byte, no other file but
//!                                             README.md is there, and every case gets the outcome
//!                                             it states
//!
//! The generator and this verifier are one implementation. The corpus's second
//! implementation is `live-conformance/upscale_verify.py`, written from the extension's text
//! and sharing no code with this binary.

use std::collections::BTreeMap;
use std::path::Path;
use wai::exact_upscale::corpus;

fn read_dir(dir: &Path) -> Result<BTreeMap<String, Vec<u8>>, String> {
    let mut files = BTreeMap::new();
    for sub in ["", "priors"] {
        let d = dir.join(sub);
        let entries = std::fs::read_dir(&d).map_err(|e| format!("read {}: {e}", d.display()))?;
        for e in entries.flatten() {
            if e.path().is_dir() {
                continue;
            }
            let name = e.file_name().to_string_lossy().into_owned();
            if sub.is_empty() && name == "README.md" {
                continue;
            }
            let key = if sub.is_empty() { name } else { format!("{sub}/{name}") };
            files.insert(key, std::fs::read(e.path()).map_err(|err| format!("read {}: {err}", e.path().display()))?);
        }
    }
    Ok(files)
}

fn inputs(files: &BTreeMap<String, Vec<u8>>) -> BTreeMap<String, Vec<u8>> {
    corpus::INPUT_PRIORS.iter().filter_map(|(n, _)| files.get(&format!("priors/{n}")).map(|b| (n.to_string(), b.clone()))).collect()
}

fn main() {
    let a: Vec<String> = std::env::args().collect();
    match (a.get(1).map(String::as_str), a.get(2)) {
        (Some("gen"), Some(d)) => {
            let files = read_dir(Path::new(d)).unwrap_or_else(|e| {
                eprintln!("{e}");
                std::process::exit(1)
            });
            let bad = corpus::check_input_priors(&inputs(&files));
            if !bad.is_empty() {
                eprintln!("the trained priors {bad:?} are missing or do not match their pins in corpus::INPUT_PRIORS");
                std::process::exit(1);
            }
            for (name, body) in corpus::generate(&inputs(&files)) {
                let p = Path::new(d).join(&name);
                if let Err(e) = std::fs::write(&p, body) {
                    eprintln!("write {}: {e}", p.display());
                    std::process::exit(1);
                }
            }
            println!("wrote {d}");
        }
        (Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))),
        _ => {
            eprintln!("usage: wai_upscale_vectors gen|verify <corpus-dir>");
            std::process::exit(2);
        }
    }
}

fn verify(dir: &Path) -> i32 {
    let files = match read_dir(dir) {
        Ok(f) => f,
        Err(e) => {
            eprintln!("{e}");
            return 1;
        }
    };
    let inputs = inputs(&files);
    let bad = corpus::check_input_priors(&inputs);
    println!("{} the trained priors match their pins", if bad.is_empty() { "PASS" } else { "FAIL" });
    if !bad.is_empty() {
        return 1;
    }
    let mut generated: BTreeMap<String, Vec<u8>> = corpus::generate(&inputs).into_iter().collect();
    for (n, _) in corpus::INPUT_PRIORS {
        let k = format!("priors/{n}");
        generated.insert(k.clone(), files[&k].clone());
    }
    let same = files == generated;
    println!("{} every file regenerates byte for byte from its seeds, and no other file is there", if same { "PASS" } else { "FAIL" });
    let fails = corpus::verify(&files);
    for f in &fails {
        println!("FAIL {f}");
    }
    println!("{} every case gets the outcome the corpus states", if fails.is_empty() { "PASS" } else { "FAIL" });
    (!same || !fails.is_empty()) as i32
}
