use super::harness::*;

#[test]
fn quotation_on_stack() {
    // Quotation should be pushable to stack
    let f = forth();
    let result = f.evaluate("( 1 2 + )", &default_ctx());
    assert!(result.is_ok());
}

#[test]
fn when_true_executes() {
    let f = run("( 42 ) 1 ?");
    assert_eq!(stack_int(&f), 42);
}

#[test]
fn when_false_skips() {
    let f = run("99 ( 42 ) 0 ?");
    // Stack should still have 99, quotation not executed
    assert_eq!(stack_int(&f), 99);
}

#[test]
fn when_with_arithmetic() {
    let f = run("10 ( 5 + ) 1 ?");
    assert_eq!(stack_int(&f), 15);
}

#[test]
fn when_with_every() {
    // iter=0, every 2 executes quotation
    let ctx = ctx_with(|c| c.iter = 0);
    let f = run_ctx("( 100 ) 2 every", &ctx);
    assert_eq!(stack_int(&f), 100);

    // iter=1, every 2 skips quotation
    let ctx = ctx_with(|c| c.iter = 1);
    let f = run_ctx("50 ( 100 ) 2 every", &ctx);
    assert_eq!(stack_int(&f), 50); // quotation not executed
}

#[test]
fn when_with_chance_deterministic() {
    // 1.0 chance always executes quotation
    let f = run("( 42 ) 1.0 chance");
    assert_eq!(stack_int(&f), 42);

    // 0.0 chance never executes quotation
    let f = run("99 ( 42 ) 0.0 chance");
    assert_eq!(stack_int(&f), 99);
}

#[test]
fn nested_quotations() {
    let f = run("( ( 42 ) 1 ? ) 1 ?");
    assert_eq!(stack_int(&f), 42);
}

#[test]
fn quotation_with_param() {
    let outputs = expect_outputs(r#""kick" s ( 2 distort ) 1 ? ."#, 1);
    assert!(outputs[0].contains("distort/2"));
}

#[test]
fn quotation_skips_param() {
    let outputs = expect_outputs(r#""kick" s ( 2 distort ) 0 ? ."#, 1);
    assert!(!outputs[0].contains("distort"));
}

#[test]
fn quotation_with_emit() {
    // When true, . should fire
    let outputs = expect_outputs(r#""kick" s ( . ) 1 ?"#, 1);
    assert!(outputs[0].contains("kick"));
}

#[test]
fn quotation_skips_emit() {
    // When false, . should not fire
    let f = forth();
    let outputs = f
        .evaluate(r#""kick" s ( . ) 0 ?"#, &default_ctx())
        .unwrap();
    // No output since . was skipped and no implicit emit
    assert_eq!(outputs.len(), 0);
}

#[test]
fn missing_quotation_error() {
    expect_error("42 1 ?", "expected quotation");
}

#[test]
fn unclosed_quotation_error() {
    expect_error("( 1 2", "missing )");
}

#[test]
fn unexpected_close_error() {
    expect_error("1 2 )", "unexpected )");
}

#[test]
fn every_with_quotation_integration() {
    // ( 2 distort ) 2 every — on even iterations, distort is applied
    for iter in 0..4 {
        let ctx = ctx_with(|c| c.iter = iter);
        let f = forth();
        let outputs = f
            .evaluate(r#""kick" s ( 2 distort ) 2 every ."#, &ctx)
            .unwrap();
        if iter % 2 == 0 {
            assert!(
                outputs[0].contains("distort/2"),
                "iter {} should have distort",
                iter
            );
        } else {
            assert!(
                !outputs[0].contains("distort"),
                "iter {} should not have distort",
                iter
            );
        }
    }
}

// bjork with sound

#[test]
fn bjork_with_sound() {
    let ctx = ctx_with(|c| c.runs = 2); // position 2 is a hit for (3,8)
    let f = forth();
    let outputs = f
        .evaluate(r#""kick" s ( 2 distort ) 3 8 bjork ."#, &ctx)
        .unwrap();
    assert!(outputs[0].contains("distort/2"));
}

// Unless (!?) tests

#[test]
fn unless_false_executes() {
    let f = run("( 42 ) 0 !?");
    assert_eq!(stack_int(&f), 42);
}

#[test]
fn unless_true_skips() {
    let f = run("99 ( 42 ) 1 !?");
    assert_eq!(stack_int(&f), 99);
}

#[test]
fn when_and_unless_complementary() {
    // Using iter mod + ?/!? for if-else behavior (every no longer pushes bool)
    for iter in 0..4 {
        let ctx = ctx_with(|c| c.iter = iter);
        let f = forth();
        let outputs = f
            .evaluate(
                r#""kick" s ( 2 distort ) iter 2 mod 0 = ? ( 4 distort ) iter 2 mod 0 = !? ."#,
                &ctx,
            )
            .unwrap();
        if iter % 2 == 0 {
            assert!(
                outputs[0].contains("distort/2"),
                "iter {} should have distort/2",
                iter
            );
        } else {
            assert!(
                outputs[0].contains("distort/4"),
                "iter {} should have distort/4",
                iter
            );
        }
    }
}
