/**
 * @file harness.h
 * @brief A runtime wired to memory buffers, for tests that need a live one.
 *
 * The runtime is `static` because it carries every pool the interpreter owns and
 * will not fit on a default stack -- the same reason the driver's main() does it.
 */

#ifndef _AKBASIC_TEST_HARNESS_H_
#define _AKBASIC_TEST_HARNESS_H_

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

#include <akbasic/error.h>
#include <akbasic/parser.h>
#include <akbasic/runtime.h>
#include <akbasic/scanner.h>
#include <akbasic/sink.h>

#include "testutil.h"

static akbasic_Runtime HARNESS_RUNTIME;
static akbasic_TextSink HARNESS_SINK;
static akbasic_StdioSink HARNESS_SINKSTATE;
static char HARNESS_OUTPUT[16384];
static FILE *HARNESS_OUT;
static FILE *HARNESS_IN;

/**
 * @brief Bring up a runtime whose output lands in HARNESS_OUTPUT.
 * @param input Text the sink will serve to readline; may be NULL for none.
 */
static akerr_ErrorContext AKERR_NOIGNORE *harness_start(const char *input)
{
    PREPARE_ERROR(errctx);

    memset(HARNESS_OUTPUT, 0, sizeof(HARNESS_OUTPUT));
    HARNESS_OUT = fmemopen(HARNESS_OUTPUT, sizeof(HARNESS_OUTPUT), "w");
    FAIL_ZERO_RETURN(errctx, (HARNESS_OUT != NULL), AKERR_IO, "could not open the output buffer");
    setvbuf(HARNESS_OUT, NULL, _IONBF, 0);

    if ( input != NULL ) {
	HARNESS_IN = fmemopen((void *)(uintptr_t)input, strlen(input), "r");
	FAIL_ZERO_RETURN(errctx, (HARNESS_IN != NULL), AKERR_IO, "could not open the input buffer");
    } else {
	HARNESS_IN = fmemopen((void *)(uintptr_t)"", 0, "r");
    }

    PASS(errctx, akbasic_sink_init_stdio(&HARNESS_SINK, &HARNESS_SINKSTATE, HARNESS_OUT, HARNESS_IN));
    PASS(errctx, akbasic_runtime_init(&HARNESS_RUNTIME, &HARNESS_SINK));
    SUCCEED_RETURN(errctx);
}

__attribute__((unused))
static void harness_stop(void)
{
    if ( HARNESS_OUT != NULL ) {
	fclose(HARNESS_OUT);
	HARNESS_OUT = NULL;
    }
    if ( HARNESS_IN != NULL ) {
	fclose(HARNESS_IN);
	HARNESS_IN = NULL;
    }
}

/** @brief Scan and parse one line, yielding its first statement. */
__attribute__((unused))
static akerr_ErrorContext AKERR_NOIGNORE *harness_parse(const char *line, akbasic_ASTLeaf **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_Parser parser;

    PASS(errctx, akbasic_scanner_zero(&HARNESS_RUNTIME));
    PASS(errctx, akbasic_scanner_scan(&HARNESS_RUNTIME, line, NULL, 0));
    PASS(errctx, akbasic_parser_init(&parser, &HARNESS_RUNTIME));
    PASS(errctx, akbasic_parser_parse(&parser, dest));
    SUCCEED_RETURN(errctx);
}

#endif // _AKBASIC_TEST_HARNESS_H_
