/**
 * @file sink_stdio.c
 * @brief Tests the stdio sink, byte for byte.
 *
 * The double newline on an error line is the assertion that matters: the caller's
 * message already ends in \n and writeln adds another, and
 * tests/language/array_outofbounds.txt ends in 0a 0a because of it. See TODO.md
 * section 1.8.
 */

#include <stdio.h>
#include <string.h>

#include <akbasic/error.h>
#include <akbasic/sink.h>

#include "testutil.h"

static char OUTPUT[1024];

int main(void)
{
    akbasic_TextSink sink;
    akbasic_StdioSink state;
    FILE *out = NULL;
    FILE *in = NULL;
    char line[64];
    bool eof = false;

    TEST_REQUIRE_OK(akbasic_error_register());

    memset(OUTPUT, 0, sizeof(OUTPUT));
    out = fmemopen(OUTPUT, sizeof(OUTPUT), "w");
    TEST_REQUIRE(out != NULL, "could not open the output buffer");
    setvbuf(out, NULL, _IONBF, 0);
    in = fmemopen((void *)(uintptr_t)"first\nsecond\n", 13, "r");
    TEST_REQUIRE(in != NULL, "could not open the input buffer");

    TEST_REQUIRE_OK(akbasic_sink_init_stdio(&sink, &state, out, in));

    /* write adds nothing; writeln adds exactly one newline. */
    TEST_REQUIRE_OK(sink.write(&sink, "AB"));
    TEST_REQUIRE_OK(sink.write(&sink, "CD"));
    TEST_REQUIRE_OK(sink.writeln(&sink, "EF"));
    TEST_REQUIRE_STR(OUTPUT, "ABCDEF\n");

    /*
     * The error-line shape: a message that already ends in \n, handed to writeln,
     * produces two. Reproducing this exactly is what makes the golden corpus
     * pass.
     */
    memset(OUTPUT, 0, sizeof(OUTPUT));
    rewind(out);
    TEST_REQUIRE_OK(sink.writeln(&sink, "? 20 : RUNTIME ERROR something\n"));
    TEST_REQUIRE_STR(OUTPUT, "? 20 : RUNTIME ERROR something\n\n");
    TEST_REQUIRE_INT(strlen(OUTPUT), 32);
    TEST_REQUIRE_INT(OUTPUT[30], '\n');
    TEST_REQUIRE_INT(OUTPUT[31], '\n');

    /* readline strips the terminator and reports EOF without raising. */
    TEST_REQUIRE_OK(sink.readline(&sink, line, sizeof(line), &eof));
    TEST_REQUIRE(!eof, "the first line is not EOF");
    TEST_REQUIRE_STR(line, "first");
    TEST_REQUIRE_OK(sink.readline(&sink, line, sizeof(line), &eof));
    TEST_REQUIRE_STR(line, "second");
    TEST_REQUIRE_OK(sink.readline(&sink, line, sizeof(line), &eof));
    TEST_REQUIRE(eof, "running off the end must set eof, not raise");

    /* Argument validation. */
    TEST_REQUIRE_STATUS(sink.write(&sink, NULL), AKERR_NULLPOINTER);
    TEST_REQUIRE_STATUS(sink.readline(&sink, line, 1, &eof), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_STATUS(akbasic_sink_init_stdio(NULL, &state, out, in), AKERR_NULLPOINTER);
    TEST_REQUIRE_STATUS(akbasic_sink_init_stdio(&sink, NULL, out, in), AKERR_NULLPOINTER);

    fclose(out);
    fclose(in);
    return akbasic_test_failures;
}
