use cagire::forth::Value;

use super::harness::{expect_error, expect_int, expect_stack};

fn ints(vals: &[i64]) -> Vec<Value> {
    vals.iter().map(|&v| Value::Int(v, None)).collect()
}

// Inversions

#[test]
fn invert_major_triad() {
    expect_stack("c4 maj inv", &ints(&[64, 67, 72]));
}

#[test]
fn invert_twice() {
    expect_stack("c4 maj inv inv", &ints(&[67, 72, 76]));
}

#[test]
fn down_invert_major_triad() {
    expect_stack("c4 maj dinv", &ints(&[55, 60, 64]));
}

#[test]
fn down_invert_min7() {
    expect_stack("c4 min7 dinv", &ints(&[58, 60, 63, 67]));
}

#[test]
fn invert_min7() {
    expect_stack("c4 min7 inv", &ints(&[63, 67, 70, 72]));
}

// Voicings

#[test]
fn drop2_maj7() {
    // c4 maj7 = [60, 64, 67, 71], 2nd from top = 67, drop to 55
    expect_stack("c4 maj7 drop2", &ints(&[55, 60, 64, 71]));
}

#[test]
fn drop3_maj7() {
    // c4 maj7 = [60, 64, 67, 71], 3rd from top = 64, drop to 52
    expect_stack("c4 maj7 drop3", &ints(&[52, 60, 67, 71]));
}

// Key

#[test]
fn key_sets_tonal_center() {
    expect_int("g3 key! 0 major", 55);
}

#[test]
fn key_with_degree() {
    // G3=55, degree 4 of major = semitone 7, so 55+7=62
    expect_int("g3 key! 4 major", 62);
}

#[test]
fn key_default_is_c4() {
    expect_int("0 major", 60);
}

#[test]
fn key_a3_minor() {
    expect_int("a3 key! 0 minor", 57);
}

// Diatonic triads

#[test]
fn diatonic_triad_degree_0() {
    // C major: degrees 0,2,4 = C,E,G = 60,64,67
    expect_stack("0 major triad", &ints(&[60, 64, 67]));
}

#[test]
fn diatonic_triad_degree_1() {
    // D minor: degrees 1,3,5 = D,F,A = 62,65,69
    expect_stack("1 major triad", &ints(&[62, 65, 69]));
}

#[test]
fn diatonic_triad_degree_3() {
    // F major: degrees 3,5,7(=0+12) = F,A,C = 65,69,72
    expect_stack("3 major triad", &ints(&[65, 69, 72]));
}

#[test]
fn diatonic_triad_degree_4() {
    // G major: degrees 4,6,8(=1+12) = G,B,D = 67,71,74
    expect_stack("4 major triad", &ints(&[67, 71, 74]));
}

// Diatonic sevenths

#[test]
fn diatonic_seventh_degree_0() {
    // C major7: degrees 0,2,4,6 = C,E,G,B = 60,64,67,71
    expect_stack("0 major seventh", &ints(&[60, 64, 67, 71]));
}

#[test]
fn diatonic_seventh_degree_1() {
    // D minor7: degrees 1,3,5,7(=0+12) = D,F,A,C = 62,65,69,72
    expect_stack("1 major seventh", &ints(&[62, 65, 69, 72]));
}

#[test]
fn diatonic_seventh_degree_4() {
    // G dom7: degrees 4,6,8(=1+12),10(=3+12) = G,B,D,F = 67,71,74,77
    expect_stack("4 major seventh", &ints(&[67, 71, 74, 77]));
}

// Combined

#[test]
fn key_with_diatonic_triad() {
    // G3=55 key, degree 0 major triad = G,B,D = 55,59,62
    expect_stack("g3 key! 0 major triad", &ints(&[55, 59, 62]));
}

#[test]
fn key_with_triad_inv() {
    // C4 key, degree 0 minor triad = C,Eb,G = 60,63,67, then inv = Eb,G,C+12 = 63,67,72
    expect_stack("0 minor triad inv", &ints(&[63, 67, 72]));
}

#[test]
fn key_degree_4_triad_in_g() {
    // G3=55, degree 4 of major = notes at degrees 4,6,8(=1+12)
    // major pattern: [0,2,4,5,7,9,11]
    // degree 4 -> semitone 7, degree 6 -> semitone 11, degree 8(=1+12) -> 12+2=14
    // 55+7=62, 55+11=66, 55+14=69 -> D, F#, A
    expect_stack("g3 key! 4 major triad", &ints(&[62, 66, 69]));
}

// Backwards compatibility

#[test]
fn scale_degree_still_works() {
    expect_int("0 major", 60);
    expect_int("7 major", 72);
}

#[test]
fn chords_still_work() {
    expect_stack("c4 maj", &ints(&[60, 64, 67]));
    expect_stack("c4 min7", &ints(&[60, 63, 67, 70]));
}

// Transpose

#[test]
fn transpose_major_triad() {
    expect_stack("c4 maj 3 tp", &ints(&[63, 67, 70]));
}

#[test]
fn transpose_down() {
    expect_stack("c4 maj -2 tp", &ints(&[58, 62, 65]));
}

#[test]
fn transpose_zero() {
    expect_stack("c4 maj 0 tp", &ints(&[60, 64, 67]));
}

#[test]
fn transpose_single_note() {
    expect_int("60 7 tp", 67);
}

#[test]
fn transpose_octave() {
    expect_stack("c4 min7 12 tp", &ints(&[72, 75, 79, 82]));
}

// Error cases

#[test]
fn triad_without_scale_errors() {
    expect_error("0 triad", "unknown word");
}

#[test]
fn seventh_without_scale_errors() {
    expect_error("0 seventh", "unknown word");
}
