/**
 * @file audio_verbs.c
 * @brief Tests the group I verbs and the PLAY queue against the mock backend.
 *
 * Two things here need saying about method. The conversion tables are asserted
 * against numbers derived independently -- the SID clock formula, equal
 * temperament from A440 -- rather than against whatever the code happens to
 * produce, because a table's whole failure mode is being plausibly wrong.
 *
 * And the PLAY queue is driven by hand: akbasic_runtime_settime() then
 * akbasic_runtime_step(), so the test owns the clock the same way a host does.
 * That is the only way to assert that a note is held for its written length
 * without the test sleeping through it.
 */

#include <string.h>

#include <akbasic/audio.h>
#include <akbasic/error.h>
#include <akbasic/runtime.h>

#include "harness.h"
#include "mockdevice.h"
#include "testutil.h"

/** @brief Bring up a runtime with the mock devices attached and a program loaded. */
static akerr_ErrorContext AKERR_NOIGNORE *run_program(const char *source)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, harness_start(NULL));
    mock_devices_init();
    PASS(errctx, akbasic_runtime_set_devices(&HARNESS_RUNTIME, &MOCK_GRAPHICS,
					     &MOCK_AUDIO, &MOCK_INPUT, NULL));
    PASS(errctx, akbasic_runtime_load(&HARNESS_RUNTIME, source));
    PASS(errctx, akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    PASS(errctx, akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    SUCCEED_RETURN(errctx);
}

/**
 * @brief The conversion tables, against numbers worked out independently.
 */
static void test_tables(void)
{
    double hz = 0.0;
    int ms = 0;

    /*
     * The SID computes Fout = Fn * Fclk / 2^24. At Fn = 16777 and an NTSC clock
     * of 1022730 that is 1022.7 Hz, which is the arithmetic and not a reading of
     * the implementation.
     */
    TEST_REQUIRE_OK(akbasic_audio_register_to_hz(16777, &hz));
    TEST_REQUIRE(hz > 1022.0 && hz < 1023.5, "register 16777 should be about 1022 Hz, got %f", hz);
    TEST_REQUIRE_OK(akbasic_audio_register_to_hz(0, &hz));
    TEST_REQUIRE_FEQ(hz, 0.0);
    TEST_REQUIRE_STATUS(akbasic_audio_register_to_hz(65536, &hz), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(akbasic_audio_register_to_hz(-1, &hz), AKBASIC_ERR_BOUNDS);

    /*
     * Every entry of both ADSR tables, not just the ends.
     *
     * Mutation testing is why: checking only rate 0 and rate 15 left the twelve
     * entries between them free to be any number at all, and the failure mode of
     * this file is a constant that is plausibly wrong rather than obviously
     * wrong. A corrupted middle entry makes one envelope decay at the wrong
     * speed, which nothing else in the suite would notice and no listener could
     * attribute.
     *
     * The values are the 6581/8580 datasheet's, transcribed here independently
     * of src/audio_tables.c rather than read back from it.
     */
    {
	static const int attack[16] = {
	       2,    8,   16,   24,   38,   56,   68,   80,
	     100,  250,  500,  800, 1000, 3000, 5000, 8000
	};
	int rate = 0;

	for ( rate = 0; rate < 16; rate++ ) {
	    TEST_REQUIRE_OK(akbasic_audio_rate_to_ms(rate, true, &ms));
	    TEST_REQUIRE_INT(ms, attack[rate]);
	    /*
	     * Decay and release are exactly three times attack. That is a
	     * property of the chip's envelope generator rather than a
	     * coincidence, so it is asserted as the relationship it is.
	     */
	    TEST_REQUIRE_OK(akbasic_audio_rate_to_ms(rate, false, &ms));
	    TEST_REQUIRE_INT(ms, attack[rate] * 3);
	}
    }
    TEST_REQUIRE_STATUS(akbasic_audio_rate_to_ms(16, true, &ms), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(akbasic_audio_rate_to_ms(-1, false, &ms), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(akbasic_audio_rate_to_ms(0, true, NULL), AKERR_NULLPOINTER);

    /*
     * Equal temperament, checked at the three points anybody would know: A4 is
     * 440, the A an octave up is exactly double, and middle C is 261.63.
     */
    TEST_REQUIRE_OK(akbasic_audio_note_hz(9, 4, &hz));
    TEST_REQUIRE_FEQ(hz, 440.0);
    TEST_REQUIRE_OK(akbasic_audio_note_hz(9, 5, &hz));
    TEST_REQUIRE_FEQ(hz, 880.0);
    TEST_REQUIRE_OK(akbasic_audio_note_hz(0, 4, &hz));
    TEST_REQUIRE(hz > 261.5 && hz < 261.7, "C4 should be about 261.63 Hz, got %f", hz);
    TEST_REQUIRE_STATUS(akbasic_audio_note_hz(12, 4, &hz), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(akbasic_audio_note_hz(0, 7, &hz), AKBASIC_ERR_BOUNDS);

    /* TEMPO 8 should put a quarter note at 500 ms, which is 120 bpm. */
    TEST_REQUIRE_OK(akbasic_audio_whole_note_ms(AKBASIC_TEMPO_DEFAULT, &ms));
    TEST_REQUIRE_INT(ms / 4, 500);
    TEST_REQUIRE_STATUS(akbasic_audio_whole_note_ms(0, &ms), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(akbasic_audio_whole_note_ms(256, &ms), AKBASIC_ERR_BOUNDS);
}

/** @brief SOUND converts its register and its jiffies, and checks its voice. */
static void test_sound(void)
{
    /* 60 jiffies is one second. */
    TEST_REQUIRE_OK(run_program("10 SOUND 1, 16777, 60\n"));
    TEST_REQUIRE(strstr(MOCK.log, "1000ms") != NULL,
		 "60 jiffies should be 1000 ms, got \"%s\"", MOCK.log);
    TEST_REQUIRE(strstr(MOCK.log, "tone v0 ") != NULL,
		 "BASIC voice 1 should reach backend voice 0, got \"%s\"", MOCK.log);
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 SOUND 4, 1000, 10\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "SOUND voice 4") != NULL,
		 "voice 4 should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /*
     * The frequency sweep reaches the device, which it could not until libakgl
     * 0.3.0 grew akgl_audio_sweep -- it was refused outright before, because
     * faking it would have meant re-issuing tones from step() and tying audible
     * pitch to how often the host calls us.
     *
     * Sweeping *down*: register 1000 is below register 16777, so the endpoints
     * decide the direction and both the SID and akgl_audio_sweep read them that
     * way.
     */
    TEST_REQUIRE_OK(run_program("10 SOUND 1, 16777, 60, 2, 1000, 10\n"));
    TEST_REQUIRE(strstr(MOCK.log, "sweep v0 ") != NULL,
		 "a sweep should reach the device, got \"%s\"", MOCK.log);
    TEST_REQUIRE(strstr(MOCK.log, "1000ms") != NULL,
		 "60 jiffies should still be 1000 ms on a sweep, got \"%s\"", MOCK.log);
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "") != NULL && HARNESS_OUTPUT[0] == '\0',
		 "a sweep should not be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* A direction outside 0..3 is a typo worth catching. */
    TEST_REQUIRE_OK(run_program("10 SOUND 1, 1000, 60, 9, 500, 10\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "SOUND direction 9") != NULL,
		 "direction 9 should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* A sweep needs all three of its arguments. */
    TEST_REQUIRE_OK(run_program("10 SOUND 1, 1000, 60, 1\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "direction, a minimum and a step") != NULL,
		 "a partial sweep should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* A sweep direction of zero is "no sweep" and must still play a held note. */
    TEST_REQUIRE_OK(run_program("10 SOUND 1, 1000, 60, 0\n"));
    TEST_REQUIRE(strstr(MOCK.log, "tone v0 ") != NULL,
		 "direction 0 is not a sweep and should play, got \"%s\"", MOCK.log);
    harness_stop();

    /*
     * A backend with no sweep is still a valid backend -- that is what a host
     * written against libakgl 0.2.0 looks like -- and SOUND refuses the swept
     * note rather than dereferencing a NULL entry point.
     */
    TEST_REQUIRE_OK(harness_start(NULL));
    mock_devices_init();
    MOCK_AUDIO.sweep = NULL;
    TEST_REQUIRE_OK(akbasic_runtime_set_devices(&HARNESS_RUNTIME, &MOCK_GRAPHICS,
						&MOCK_AUDIO, &MOCK_INPUT, NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 SOUND 1, 1000, 60, 1, 500, 10\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "audio device that can sweep") != NULL,
		 "a backend without sweep should refuse, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

/** @brief ENVELOPE maps the SID rate numbers, and sustain is a level not a time. */
static void test_envelope(void)
{
    TEST_REQUIRE_OK(run_program("10 ENVELOPE 3, 0, 0, 15, 0, 1\n"));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.envelopes[3].attack, 2);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.envelopes[3].decay, 6);
    TEST_REQUIRE_FEQ(HARNESS_RUNTIME.audio_state.envelopes[3].sustain, 1.0);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.envelopes[3].waveform, AKBASIC_WAVE_SAWTOOTH);
    harness_stop();

    /* Sustain 0 is silence, and is a legal thing to ask for. */
    TEST_REQUIRE_OK(run_program("10 ENVELOPE 0, 0, 0, 0\n"));
    TEST_REQUIRE_FEQ(HARNESS_RUNTIME.audio_state.envelopes[0].sustain, 0.0);
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 ENVELOPE 10\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "ENVELOPE 10") != NULL,
		 "preset 10 should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 ENVELOPE 0, 16\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "ENVELOPE rate 16") != NULL,
		 "rate 16 should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* Defining an instrument needs no device; a host may not have lent one yet. */
    TEST_REQUIRE_OK(harness_start(NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 ENVELOPE 1, 5\n20 VOL 8\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "");
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.envelopes[1].attack, 56);
    harness_stop();
}

/** @brief VOL and TEMPO record their setting and range-check it. */
static void test_vol_and_tempo(void)
{
    TEST_REQUIRE_OK(run_program("10 VOL 15\n"));
    TEST_REQUIRE_STR(MOCK.log, "vol 1.00\n");
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 VOL 0\n"));
    TEST_REQUIRE_STR(MOCK.log, "vol 0.00\n");
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 VOL 16\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "VOL 16") != NULL,
		 "VOL 16 should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /*
     * A tempo change rescales the *current* note length rather than resetting it
     * to a quarter note: a program that set eighth notes and then doubled the
     * tempo means faster eighth notes.
     */
    TEST_REQUIRE_OK(run_program("10 TEMPO 16\n"));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.tempo, 16);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.notems, 250);
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 TEMPO 0\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "TEMPO 0") != NULL,
		 "TEMPO 0 should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

/**
 * @brief Bring up a runtime and parse a PLAY string without draining the queue.
 *
 * akbasic_play_parse() is called directly rather than through a program, because
 * running one would also run akbasic_play_service() -- and with the clock left at
 * zero every note's duration has already expired, so the queue would be empty
 * again before the assertions could look at it. That draining is correct
 * behaviour, tested on its own in test_play_is_not_blocking(); here the question
 * is only what a string parses *to*.
 */
static akerr_ErrorContext AKERR_NOIGNORE *parse_notes(const char *notes)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, harness_start(NULL));
    mock_devices_init();
    PASS(errctx, akbasic_runtime_set_devices(&HARNESS_RUNTIME, NULL, &MOCK_AUDIO, NULL, NULL));
    PASS(errctx, akbasic_play_parse(&HARNESS_RUNTIME, notes));
    SUCCEED_RETURN(errctx);
}

/** @brief The PLAY string parser: notes, octaves, accidentals, rests and durations. */
static void test_play_parse(void)
{
    TEST_REQUIRE_OK(parse_notes("O4C"));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.count, 1);
    TEST_REQUIRE(HARNESS_RUNTIME.audio_state.queue[0].hz > 261.5 &&
		 HARNESS_RUNTIME.audio_state.queue[0].hz < 261.7,
		 "O4C should be middle C, got %f", HARNESS_RUNTIME.audio_state.queue[0].hz);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.queue[0].ms, 500);
    harness_stop();

    /* A sharp raises by a semitone; C# and D$ are the same pitch. */
    TEST_REQUIRE_OK(parse_notes("O4#CO4$D"));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.count, 2);
    TEST_REQUIRE_FEQ(HARNESS_RUNTIME.audio_state.queue[0].hz,
		     HARNESS_RUNTIME.audio_state.queue[1].hz);
    harness_stop();

    /*
     * B# belongs in the octave above and C$ in the one below. Wrapping the
     * semitone without moving the octave would put B# an octave too low, which
     * is the classic way to get this wrong.
     */
    TEST_REQUIRE_OK(parse_notes("O4#BO5C"));
    TEST_REQUIRE_FEQ(HARNESS_RUNTIME.audio_state.queue[0].hz,
		     HARNESS_RUNTIME.audio_state.queue[1].hz);
    harness_stop();

    /* Durations persist until changed, and a dot adds half again. */
    TEST_REQUIRE_OK(parse_notes("HCD.E"));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.count, 3);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.queue[0].ms, 1000);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.queue[1].ms, 1000);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.queue[2].ms, 1500);
    harness_stop();

    /* A rest occupies its time and makes no sound. */
    TEST_REQUIRE_OK(parse_notes("CRC"));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.count, 3);
    TEST_REQUIRE(HARNESS_RUNTIME.audio_state.queue[1].rest, "R should queue a rest");
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.queue[1].ms, 500);
    harness_stop();

    /* V selects the voice for everything after it. */
    TEST_REQUIRE_OK(parse_notes("CV2D"));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.queue[0].voice, 0);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.queue[1].voice, 1);
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 PLAY \"Z\"\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "does not understand") != NULL,
		 "an unknown PLAY letter should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    TEST_REQUIRE_OK(run_program("10 PLAY \"V9C\"\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "PLAY V9") != NULL,
		 "voice 9 should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    /* X selects the filter, which is the one thing here with nothing behind it. */
    TEST_REQUIRE_OK(run_program("10 PLAY \"X1C\"\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "filter") != NULL,
		 "PLAY X should be refused, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

/**
 * @brief PLAY does not block, and the queue drains against the host's clock.
 *
 * This is the assertion that matters most in the file. A C128 holds the program
 * inside PLAY until the last note ends; here PLAY returns immediately and the
 * notes come out of step(). The test owns the clock, so it can assert a note was
 * held for its full length without waiting through it.
 */
static void test_play_is_not_blocking(void)
{
    TEST_REQUIRE_OK(harness_start(NULL));
    mock_devices_init();
    TEST_REQUIRE_OK(akbasic_runtime_set_devices(&HARNESS_RUNTIME, NULL, &MOCK_AUDIO, NULL, NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 PLAY \"QCD\"\n20 PRINT \"AFTER\"\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));

    /*
     * The program ran to completion -- PRINT on line 20 happened -- with a
     * second of music still outstanding. That is the whole point: on a C128 the
     * program would have sat inside PLAY for that second.
     *
     * One note has already gone out. The clock was never set, so it still reads
     * zero, and the step that followed PLAY found the head note's start time
     * (also zero) already reached. Both notes are still counted; only the head
     * has moved.
     */
    TEST_REQUIRE_STR(HARNESS_OUTPUT, "AFTER\n");
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.count, 2);
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.head, 1);
    TEST_REQUIRE(strstr(MOCK.log, "tone v0 261") != NULL,
		 "the first note should have gone out during the run, got \"%s\"", MOCK.log);
    mock_log_reset();

    /* Halfway through it, nothing new comes out. */
    TEST_REQUIRE_OK(akbasic_runtime_settime(&HARNESS_RUNTIME, 250));
    TEST_REQUIRE_OK(akbasic_runtime_step(&HARNESS_RUNTIME));
    TEST_REQUIRE_STR(MOCK.log, "");

    /* At its end, the second note starts. */
    TEST_REQUIRE_OK(akbasic_runtime_settime(&HARNESS_RUNTIME, 500));
    TEST_REQUIRE_OK(akbasic_runtime_step(&HARNESS_RUNTIME));
    TEST_REQUIRE(strstr(MOCK.log, "tone v0 293") != NULL,
		 "the second note should have been released, got \"%s\"", MOCK.log);

    /* Once drained, the queue resets so the next PLAY starts from the front. */
    TEST_REQUIRE_OK(akbasic_runtime_settime(&HARNESS_RUNTIME, 1000));
    TEST_REQUIRE_OK(akbasic_runtime_step(&HARNESS_RUNTIME));
    TEST_REQUIRE_INT(HARNESS_RUNTIME.audio_state.count, 0);
    harness_stop();
}

/** @brief FILTER is refused rather than ignored, and says why. */
static void test_filter(void)
{
    TEST_REQUIRE_OK(run_program("10 FILTER 1000, 1, 0, 0, 5\n"));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "filter stage") != NULL,
		 "FILTER should be refused with a reason, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

/** @brief The verbs that need a device name themselves when there is none. */
static void test_no_device(void)
{
    TEST_REQUIRE_OK(harness_start(NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 SOUND 1, 1000, 10\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "SOUND needs an audio device") != NULL,
		 "SOUND without a device should name itself, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();

    TEST_REQUIRE_OK(harness_start(NULL));
    TEST_REQUIRE_OK(akbasic_runtime_load(&HARNESS_RUNTIME, "10 PLAY \"C\"\n"));
    TEST_REQUIRE_OK(akbasic_runtime_start(&HARNESS_RUNTIME, AKBASIC_MODE_RUN));
    TEST_REQUIRE_OK(akbasic_runtime_run(&HARNESS_RUNTIME, 0));
    TEST_REQUIRE(strstr(HARNESS_OUTPUT, "PLAY needs an audio device") != NULL,
		 "PLAY without a device should name itself, got \"%s\"", HARNESS_OUTPUT);
    harness_stop();
}

int main(void)
{
    test_tables();
    test_sound();
    test_envelope();
    test_vol_and_tempo();
    test_play_parse();
    test_play_is_not_blocking();
    test_filter();
    test_no_device();
    return akbasic_test_failures;
}
