// Standalone tests for reasampler::vst::note_entry — no VST3, no REAPER, no framework.
// Assert the S12 direct-numeric-entry parse for a zone's low/high/root MIDI note.
//
// Covers: plain decimal integers (with +/- sign + surrounding whitespace); note names under the
// C4==60 convention (C-1==0, sharps + flats, negative octaves); out-of-range values CLAMPING to
// [0,127] rather than rejecting; empty / whitespace-only / unparseable input returning nullopt;
// the integer path taking precedence over the note-name path for a leading digit.

#include "../src/vst/note_entry.h"

#include <cstdio>

using namespace reasampler::vst;

static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
    std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)

static void testPlainIntegers() {
    CHECK(parseNoteEntry("60") == 60);
    CHECK(parseNoteEntry("0") == 0);
    CHECK(parseNoteEntry("127") == 127);
    CHECK(parseNoteEntry(" 64 ") == 64);   // surrounding whitespace ignored
    CHECK(parseNoteEntry("+5") == 5);
}

static void testIntegerClamps() {
    CHECK(parseNoteEntry("200") == 127);   // over-range clamps to the ceiling
    CHECK(parseNoteEntry("-10") == 0);     // under-range clamps to the floor
    CHECK(parseNoteEntry("99999") == 127);
}

static void testNoteNames() {
    // C4 == 60 (MIDI 0 == C-1).
    CHECK(parseNoteEntry("C4") == 60);
    CHECK(parseNoteEntry("c4") == 60);       // case-insensitive
    CHECK(parseNoteEntry("A4") == 69);       // A4 = 69 (concert A)
    CHECK(parseNoteEntry("C-1") == 0);       // lowest MIDI note
    CHECK(parseNoteEntry("G9") == 127);      // G9 = 127
}

static void testAccidentals() {
    CHECK(parseNoteEntry("C#4") == 61);
    CHECK(parseNoteEntry("Db4") == 61);      // enharmonic of C#4
    CHECK(parseNoteEntry("F#3") == 54);
    CHECK(parseNoteEntry("Bb3") == 58);      // Bb3 = 58
}

static void testNoteNameClamps() {
    CHECK(parseNoteEntry("C10") == 127);     // above the range clamps
    CHECK(parseNoteEntry("C-5") == 0);       // below the range clamps
}

static void testRejects() {
    CHECK(parseNoteEntry("") == std::nullopt);
    CHECK(parseNoteEntry("   ") == std::nullopt);
    CHECK(parseNoteEntry("hello") == std::nullopt);
    CHECK(parseNoteEntry("C") == std::nullopt);    // a bare letter with no octave is ambiguous
    CHECK(parseNoteEntry("H4") == std::nullopt);   // H is not a note letter
    CHECK(parseNoteEntry("+") == std::nullopt);
}

int main() {
    testPlainIntegers();
    testIntegerClamps();
    testNoteNames();
    testAccidentals();
    testNoteNameClamps();
    testRejects();

    if (g_fail == 0) std::printf("note_entry: all tests passed\n");
    return g_fail != 0;
}
