use std::{
    fs,
    path::{Path, PathBuf},
    process::{Command, Output},
    sync::atomic::{AtomicUsize, Ordering},
};

static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);

#[test]
fn add_fixture_prints_expected_parse_tree() {
    let compiler = env!("CARGO_BIN_EXE_glagol");
    let fixture = Path::new("../examples/add.slo");
    let expected = fs::read_to_string("../tests/add.sexpr").expect("read add.sexpr");

    let output = run_tree_printer(compiler, fixture);

    assert!(
        output.status.success(),
        "tree printer failed\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr),
    );

    let actual = String::from_utf8(output.stdout).expect("tree printer output is UTF-8");
    assert_eq!(actual, expected);
    assert!(
        output.stderr.is_empty(),
        "tree printer wrote stderr:\n{}",
        String::from_utf8_lossy(&output.stderr),
    );
}

#[test]
fn parser_atoms_print_stably() {
    let compiler = env!("CARGO_BIN_EXE_glagol");
    let fixture = write_fixture(
        "parser-atoms",
        r#"
(module syntax)
(sample () 3.5 "line\nquote\"slash\\" ->)
"#,
    );
    let expected =
        fs::read_to_string("../tests/parser-atoms.sexpr").expect("read parser-atoms.sexpr");

    let output = run_tree_printer(compiler, &fixture);

    assert!(
        output.status.success(),
        "tree printer failed\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr),
    );

    let actual = String::from_utf8(output.stdout).expect("tree printer output is UTF-8");
    assert_eq!(actual, expected);
    assert!(
        output.stderr.is_empty(),
        "tree printer wrote stderr:\n{}",
        String::from_utf8_lossy(&output.stderr),
    );
}

#[test]
fn top_level_test_prints_expected_parse_tree() {
    let compiler = env!("CARGO_BIN_EXE_glagol");
    let fixture = Path::new("../tests/top-level-test.slo");
    let expected =
        fs::read_to_string("../tests/top-level-test.sexpr").expect("read top-level-test.sexpr");

    let output = run_tree_printer(compiler, fixture);

    assert!(
        output.status.success(),
        "tree printer failed\nstdout:\n{}\nstderr:\n{}",
        String::from_utf8_lossy(&output.stdout),
        String::from_utf8_lossy(&output.stderr),
    );

    let actual = String::from_utf8(output.stdout).expect("tree printer output is UTF-8");
    assert_eq!(actual, expected);
    assert!(
        output.stderr.is_empty(),
        "tree printer wrote stderr:\n{}",
        String::from_utf8_lossy(&output.stderr),
    );
}

fn run_tree_printer(compiler: &str, fixture: &Path) -> Output {
    Command::new(compiler)
        .arg("--print-tree")
        .arg(fixture)
        .output()
        .unwrap_or_else(|err| {
            panic!(
                "run glagol --print-tree on `{}`: {}",
                fixture.display(),
                err
            )
        })
}

fn write_fixture(name: &str, source: &str) -> PathBuf {
    let mut path = std::env::temp_dir();
    let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
    path.push(format!(
        "glagol-tree-printer-{}-{}-{}.slo",
        std::process::id(),
        id,
        name
    ));
    fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
    path
}
