import Testing
@testable import MusicModel

@Suite struct TickTests {
    @Test func baseValues() {
        #expect(Ticks.forBaseValue(4) == 960)
        #expect(Ticks.forBaseValue(1) == 3840)
        #expect(Ticks.forBaseValue(32) == 120)
        #expect(Ticks.forBaseValue(3) == nil)
    }

    @Test func tripletsAreExact() {
        // Triplet eighth: 480 * 2/3
        #expect(Ticks.eighth * 2 % 3 == 0)
        #expect(Ticks.eighth * 2 / 3 == 320)
        // Quintuplet sixteenth: 240 * 4/5
        #expect(Ticks.sixteenth * 4 % 5 == 0)
    }

    @Test func reverseLookup() {
        #expect(Ticks.baseValue(for: 960)! == (4, false))
        #expect(Ticks.baseValue(for: 1440)! == (4, true))
        #expect(Ticks.baseValue(for: 320) == nil) // tuplet member, not a plain value
    }
}

@Suite struct PitchTests {
    @Test func midiAndFrequency() {
        let a4 = Pitch(step: .a, octave: 4)
        #expect(a4.midiNote == 69)
        #expect(abs(a4.frequency - 440.0) < 1e-9)

        let c4 = Pitch(step: .c, octave: 4)
        #expect(c4.midiNote == 60)

        let fSharp4 = Pitch(step: .f, alter: 1, octave: 4)
        let gFlat4 = Pitch(step: .g, alter: -1, octave: 4)
        #expect(fSharp4.midiNote == gFlat4.midiNote)  // enharmonic
        #expect(fSharp4 != gFlat4)                     // but distinct spelling
    }

    @Test func parsing() {
        #expect(Pitch.parse("C4") == Pitch(step: .c, octave: 4))
        #expect(Pitch.parse("F#5") == Pitch(step: .f, alter: 1, octave: 5))
        #expect(Pitch.parse("Bb3") == Pitch(step: .b, alter: -1, octave: 3))
        #expect(Pitch.parse("F##4") == Pitch(step: .f, alter: 2, octave: 4))
        #expect(Pitch.parse("Dbb2") == Pitch(step: .d, alter: -2, octave: 2))
        #expect(Pitch.parse("C-1") == Pitch(step: .c, octave: -1))
        #expect(Pitch.parse("H4") == nil)
        #expect(Pitch.parse("C") == nil)
        #expect(Pitch.parse("") == nil)
    }

    @Test func roundTripNames() {
        for text in ["C4", "F#5", "Bb3", "F##4", "Dbb2", "A0", "G9"] {
            #expect(Pitch.parse(text)?.name == text)
        }
    }
}

@Suite struct TimeSignatureTests {
    @Test func ticksPerMeasure() {
        #expect(TimeSignature(numerator: 4, denominator: 4)!.ticksPerMeasure == 3840)
        #expect(TimeSignature(numerator: 3, denominator: 4)!.ticksPerMeasure == 2880)
        #expect(TimeSignature(numerator: 6, denominator: 8)!.ticksPerMeasure == 2880)
        #expect(TimeSignature(numerator: 2, denominator: 2)!.ticksPerMeasure == 3840)
    }

    @Test func compoundMeter() {
        #expect(TimeSignature(numerator: 6, denominator: 8)!.isCompound)
        #expect(TimeSignature(numerator: 9, denominator: 8)!.isCompound)
        #expect(!TimeSignature(numerator: 3, denominator: 8)!.isCompound)
        #expect(!TimeSignature(numerator: 4, denominator: 4)!.isCompound)

        let sixEight = TimeSignature(numerator: 6, denominator: 8)!
        #expect(sixEight.clicksPerMeasure == 6)
        #expect(sixEight.strongClickInterval == 3)
    }
}

@Suite struct KeySignatureTests {
    @Test func parsing() {
        #expect(KeySignature.parse("C major")?.fifths == 0)
        #expect(KeySignature.parse("G major")?.fifths == 1)
        #expect(KeySignature.parse("F major")?.fifths == -1)
        #expect(KeySignature.parse("Bb major")?.fifths == -2)
        #expect(KeySignature.parse("F# minor")?.fifths == 3)
        #expect(KeySignature.parse("A minor")?.fifths == 0)
        #expect(KeySignature.parse("Eb minor")?.fifths == -6)
        #expect(KeySignature.parse("C")?.fifths == 0)  // defaults to major
        #expect(KeySignature.parse("H major") == nil)
    }

    @Test func alteredSteps() {
        let dMajor = KeySignature.parse("D major")!
        #expect(dMajor.alteredSteps == [.f, .c])
        #expect(dMajor.alter(for: .f) == 1)
        #expect(dMajor.alter(for: .g) == 0)

        let bbMajor = KeySignature.parse("Bb major")!
        #expect(bbMajor.alteredSteps == [.b, .e])
        #expect(bbMajor.alter(for: .b) == -1)
    }

    @Test func namesRoundTrip() {
        for name in ["C major", "G major", "D major", "F# major", "F major", "Bb major", "Gb major",
                     "A minor", "E minor", "F# minor", "D minor", "Bb minor"] {
            #expect(KeySignature.parse(name)?.name == name, "\(name)")
        }
    }
}
