//! The corpus of extensions/platform-encode.md §10.1, `encode-conformance/`.
//!
//! ```text
//! wai_encode_vectors source <out.y4m>   the source every fixture stream is made from
//! wai_encode_vectors plan <out.txt>     the IDR plan they are made with
//! wai_encode_vectors gen <dir>          pin the fixture streams and write every generated file
//! wai_encode_vectors verify <dir>       require the pins, byte-identical regeneration, and every outcome
//! ```

use std::collections::BTreeMap;
use std::path::Path;
use std::process::ExitCode;
use wai::platform_encode::{corpus, sha256_hex};

fn streams(dir: &Path) -> Result<BTreeMap<String, Vec<u8>>, String> {
    let mut out = BTreeMap::new();
    for f in corpus::FIXTURES {
        let p = dir.join("streams").join(f.file);
        out.insert(f.file.to_string(), std::fs::read(&p).map_err(|e| format!("{}: {e}", p.display()))?);
    }
    let extra: Vec<String> = std::fs::read_dir(dir.join("streams")).map_err(|e| e.to_string())?.flatten().map(|e| e.file_name().to_string_lossy().into_owned()).filter(|n| !out.contains_key(n)).collect();
    if !extra.is_empty() {
        return Err(format!("streams/ holds files no fixture names: {extra:?}"));
    }
    Ok(out)
}

fn pins(s: &BTreeMap<String, Vec<u8>>) -> String {
    let m: serde_json::Map<String, serde_json::Value> = s.iter().map(|(k, v)| (k.clone(), serde_json::json!(sha256_hex(v)))).collect();
    serde_json::to_string_pretty(&m).expect("JSON") + "\n"
}

fn run(a: &[String]) -> Result<bool, String> {
    match (a.first().map(String::as_str), a.get(1)) {
        (Some("source"), Some(o)) => std::fs::write(o, corpus::source()).map(|_| true).map_err(|e| e.to_string()),
        (Some("plan"), Some(o)) => std::fs::write(o, corpus::plan_text()).map(|_| true).map_err(|e| e.to_string()),
        (Some("gen"), Some(d)) => {
            let dir = Path::new(d);
            let s = streams(dir)?;
            std::fs::write(dir.join("streams.json"), pins(&s)).map_err(|e| e.to_string())?;
            for sub in ["records", "mutated"] {
                let p = dir.join(sub);
                if p.exists() {
                    std::fs::remove_dir_all(&p).map_err(|e| e.to_string())?;
                }
                std::fs::create_dir_all(&p).map_err(|e| e.to_string())?;
            }
            let files = corpus::generate(&s);
            for (p, b) in &files {
                std::fs::write(dir.join(p), b).map_err(|e| format!("{p}: {e}"))?;
            }
            println!("wrote {} files", files.len());
            Ok(true)
        }
        (Some("verify"), Some(d)) => {
            let dir = Path::new(d);
            let s = streams(dir)?;
            let pinned = std::fs::read_to_string(dir.join("streams.json")).map_err(|e| e.to_string())?;
            if pinned != pins(&s) {
                println!("FAIL: a fixture stream is not the bytes streams.json pins");
                return Ok(false);
            }
            let files = corpus::generate(&s);
            for sub in ["records", "mutated"] {
                for e in std::fs::read_dir(dir.join(sub)).map_err(|e| e.to_string())?.flatten() {
                    let rel = format!("{sub}/{}", e.file_name().to_string_lossy());
                    if !files.contains_key(&rel) {
                        println!("FAIL: {rel} is not generated");
                        return Ok(false);
                    }
                }
            }
            for (p, b) in &files {
                if std::fs::read(dir.join(p)).ok().as_ref() != Some(b) {
                    println!("FAIL: {p} does not regenerate byte for byte");
                    return Ok(false);
                }
            }
            match corpus::check(dir) {
                Ok(n) => {
                    println!("PASS: {} fixture streams pinned, {} generated files byte for byte, {n} cases with their outcomes", s.len(), files.len());
                    Ok(true)
                }
                Err(e) => {
                    println!("FAIL: {e}");
                    Ok(false)
                }
            }
        }
        _ => Err("wai_encode_vectors source|plan <out> | gen|verify <dir>".into()),
    }
}

fn main() -> ExitCode {
    let a: Vec<String> = std::env::args().skip(1).collect();
    match run(&a) {
        Ok(true) => ExitCode::SUCCESS,
        Ok(false) => ExitCode::from(1),
        Err(e) => {
            eprintln!("error: {e}");
            ExitCode::from(2)
        }
    }
}
