use super::harness::*;
use cagire::forth::Value;

#[test]
fn define_and_use_word() {
    expect_int(": double 2 * ; 5 double", 10);
}

#[test]
fn define_word_with_multiple_ops() {
    expect_int(": triple dup dup + + ; 3 triple", 9);
}

#[test]
fn define_word_using_another_user_word() {
    expect_int(": double 2 * ; : quad double double ; 3 quad", 12);
}

#[test]
fn redefine_word_overwrites() {
    expect_int(": foo 10 ; : foo 20 ; foo", 20);
}

#[test]
fn word_with_param() {
    let outputs = expect_outputs(": loud 0.9 gain ; \"kick\" s loud .", 1);
    assert!(outputs[0].contains("gain/0.9"));
}

#[test]
fn word_available_across_evaluations() {
    let f = forth();
    let ctx = default_ctx();
    f.evaluate(": hi 42 ;", &ctx).unwrap();
    f.evaluate("hi", &ctx).unwrap();
    assert_eq!(stack_int(&f), 42);
}

#[test]
fn word_defined_in_one_forth_available_in_same() {
    let f = forth();
    let ctx = default_ctx();
    f.evaluate(": ten 10 ;", &ctx).unwrap();
    f.clear_stack();
    f.evaluate("ten ten +", &ctx).unwrap();
    assert_eq!(stack_int(&f), 20);
}

#[test]
fn unknown_word_becomes_string() {
    expect_str("nosuchword", "nosuchword");
}

#[test]
fn missing_semicolon_errors() {
    expect_error(": foo 10", "missing ';'");
}

#[test]
fn missing_name_errors() {
    expect_error(":", "expected word name");
}

#[test]
fn unexpected_semicolon_errors() {
    expect_error(";", "unexpected ;");
}

#[test]
fn apply_executes_quotation() {
    expect_int("5 ( 2 * ) apply", 10);
}

#[test]
fn apply_with_stack_ops() {
    expect_int("3 4 ( + ) apply", 7);
}

#[test]
fn apply_empty_stack_errors() {
    expect_error("apply", "stack underflow");
}

#[test]
fn apply_non_quotation_errors() {
    expect_error("42 apply", "expected quotation");
}

#[test]
fn apply_nested() {
    expect_int("2 ( ( 3 * ) apply ) apply", 6);
}

#[test]
fn define_word_containing_quotation() {
    expect_int(": dbl ( 2 * ) apply ; 7 dbl", 14);
}

#[test]
fn define_word_with_sound() {
    let outputs = expect_outputs(": kick \"kick\" s . ; kick", 1);
    assert!(outputs[0].contains("sound/kick"));
}

#[test]
fn define_word_with_conditional() {
    let f = forth();
    let ctx = default_ctx();
    f.evaluate(": maybe-double dup 5 gt if 2 * then ;", &ctx).unwrap();
    f.clear_stack();
    f.evaluate("3 maybe-double", &ctx).unwrap();
    assert_eq!(stack_int(&f), 3);
    f.clear_stack();
    f.evaluate("10 maybe-double", &ctx).unwrap();
    assert_eq!(stack_int(&f), 20);
}

#[test]
fn forget_removes_word() {
    let f = forth();
    let ctx = default_ctx();
    f.evaluate(": double 2 * ;", &ctx).unwrap();
    f.evaluate("5 double", &ctx).unwrap();
    assert_eq!(stack_int(&f), 10);
    f.clear_stack();
    f.evaluate("\"double\" forget", &ctx).unwrap();
    f.evaluate("double", &ctx).unwrap();
    let stack = f.stack();
    assert_eq!(stack.len(), 1);
    match &stack[0] {
        Value::Str(s, _) => assert_eq!(s.as_ref(), "double"),
        other => panic!("expected Str, got {:?}", other),
    }
}

#[test]
fn forget_nonexistent_is_noop() {
    let f = forth();
    let ctx = default_ctx();
    f.evaluate("\"nosuchword\" forget", &ctx).unwrap();
    f.evaluate("42", &ctx).unwrap();
    assert_eq!(stack_int(&f), 42);
}

#[test]
fn forget_and_redefine() {
    let f = forth();
    let ctx = default_ctx();
    f.evaluate(": foo 10 ;", &ctx).unwrap();
    f.evaluate("foo", &ctx).unwrap();
    assert_eq!(stack_int(&f), 10);
    f.clear_stack();
    f.evaluate("\"foo\" forget", &ctx).unwrap();
    f.evaluate(": foo 20 ;", &ctx).unwrap();
    f.evaluate("foo", &ctx).unwrap();
    assert_eq!(stack_int(&f), 20);
}
