/**
 * @file embed.c
 * @brief Embedding akbasic in a host program: the whole surface, in one file.
 *
 * This is the code in README.md's "Embedding the interpreter" section, kept here
 * so it is compiled by every build rather than rotting in a document. Build it
 * with -DAKBASIC_BUILD_EXAMPLES=ON and run `./build/akbasic_example_embed`.
 */

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

#include <akerror.h>

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

/*
 * The runtime carries every pool the interpreter owns -- a few megabytes -- so it
 * goes in static storage or inside the host's own state, never on the stack.
 */
static akbasic_Runtime SCRIPT;

/* ----------------------------------------------------------- a custom sink -- */

/*
 * A sink is a record of function pointers plus whatever state the host needs.
 * This one collects output into a fixed buffer, which is what a game would do
 * before blitting it to a text layer. A real one would draw through the akgl
 * renderer the host already initialized.
 */
typedef struct
{
    char buffer[4096];
    size_t used;
} ConsoleState;

static ConsoleState CONSOLE;
static akbasic_TextSink CONSOLE_SINK;

static akerr_ErrorContext AKERR_NOIGNORE *console_write(akbasic_TextSink *self, const char *text)
{
    PREPARE_ERROR(errctx);
    ConsoleState *state = NULL;
    size_t length = 0;

    FAIL_ZERO_RETURN(errctx, (self != NULL && text != NULL), AKERR_NULLPOINTER,
		     "NULL argument in console write");
    state = (ConsoleState *)self->self;
    length = strlen(text);
    FAIL_ZERO_RETURN(errctx, (length < sizeof(state->buffer) - state->used),
		     AKBASIC_ERR_BOUNDS, "console buffer is full");
    memcpy(state->buffer + state->used, text, length);
    state->used += length;
    state->buffer[state->used] = '\0';
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext AKERR_NOIGNORE *console_writeln(akbasic_TextSink *self, const char *text)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, console_write(self, text));
    PASS(errctx, console_write(self, "\n"));
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext AKERR_NOIGNORE *console_readline(akbasic_TextSink *self, char *dest, size_t len, bool *eof)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (self != NULL && dest != NULL && eof != NULL), AKERR_NULLPOINTER,
		     "NULL argument in console readline");
    /* No keyboard wired up: INPUT sees end-of-stream rather than blocking. */
    (void)len;
    dest[0] = '\0';
    *eof = true;
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext AKERR_NOIGNORE *console_clear(akbasic_TextSink *self)
{
    PREPARE_ERROR(errctx);
    ConsoleState *state = NULL;

    FAIL_ZERO_RETURN(errctx, (self != NULL), AKERR_NULLPOINTER, "NULL sink in console clear");
    state = (ConsoleState *)self->self;
    state->used = 0;
    state->buffer[0] = '\0';
    SUCCEED_RETURN(errctx);
}

static akerr_ErrorContext AKERR_NOIGNORE *console_sink_init(void)
{
    PREPARE_ERROR(errctx);

    CONSOLE.used = 0;
    CONSOLE.buffer[0] = '\0';
    CONSOLE_SINK.self = &CONSOLE;
    CONSOLE_SINK.write = console_write;
    CONSOLE_SINK.writeln = console_writeln;
    CONSOLE_SINK.readline = console_readline;
    CONSOLE_SINK.clear = console_clear;
    SUCCEED_RETURN(errctx);
}

/* ------------------------------------------------------------- the driver -- */

static const char *PROGRAM =
    "10 PRINT \"COUNTING:\"\n"
    "20 FOR I# = 1 TO 5\n"
    "30   PRINT I# * I#\n"
    "40 NEXT I#\n"
    "50 PRINT \"DONE\"\n";

/*
 * The same thing with no line numbers, which is what a game script actually
 * looks like: written in a text editor, branching by LABEL, with nothing to
 * renumber. A loaded line that arrives without a number is given the slot after
 * the last one filed, so the two spellings load and run identically. Only the
 * prompt still needs numbers, because there a number is the one thing separating
 * program text from a statement to run now.
 */
static const char *UNNUMBERED_PROGRAM =
    "PRINT \"COUNTING:\"\n"
    "FOR I# = 1 TO 5\n"
    "  PRINT I# * I#\n"
    "NEXT I#\n"
    "GOTO DONE\n"
    "PRINT \"NOT REACHED\"\n"
    "LABEL DONE\n"
    "PRINT \"DONE\"\n";

/*
 * One frame's worth of script. The interpreter never surrenders control: it runs
 * at most `budget` source lines and returns, so a host game keeps its frame rate
 * no matter what the script does -- including an infinite loop.
 */
static akerr_ErrorContext AKERR_NOIGNORE *step_script(int budget)
{
    PREPARE_ERROR(errctx);

    if ( SCRIPT.mode == AKBASIC_MODE_QUIT ) {
	SUCCEED_RETURN(errctx);
    }
    PASS(errctx, akbasic_runtime_run(&SCRIPT, budget));
    SUCCEED_RETURN(errctx);
}

/*
 * The host's game loop. A loop that calls an error-returning function has to be
 * its own function using PASS: CATCH expands to a C `break`, so inside a loop
 * within an ATTEMPT it would leave the loop rather than the ATTEMPT and let the
 * rest of the block run with an error pending.
 */
static akerr_ErrorContext AKERR_NOIGNORE *game_loop(int frames, int budget, int *ran)
{
    PREPARE_ERROR(errctx);

    for ( *ran = 0; *ran < frames && SCRIPT.mode != AKBASIC_MODE_QUIT; (*ran)++ ) {
	PASS(errctx, step_script(budget));
    }
    SUCCEED_RETURN(errctx);
}

int main(void)
{
    PREPARE_ERROR(errctx);
    int frames = 0;
    int rc = 0;

    ATTEMPT {
	CATCH(errctx, console_sink_init());
	CATCH(errctx, akbasic_runtime_init(&SCRIPT, &CONSOLE_SINK));
	CATCH(errctx, akbasic_runtime_load(&SCRIPT, PROGRAM));
	CATCH(errctx, akbasic_runtime_start(&SCRIPT, AKBASIC_MODE_RUN));

	/*
	 * Ten frames at two source lines each. The budget is artificially mean to
	 * make the point visible: the script gets partway through and stops, and
	 * the host stayed in control the whole time.
	 */
	CATCH(errctx, game_loop(10, 2, &frames));
	printf("--- after %d frames of 2 lines each ---\n%s\n", frames, CONSOLE.buffer);

	/*
	 * Then let it finish. A budget of 0 is unbounded -- fine for a tool, not
	 * for a game. Note the interpreter only reaches MODE_QUIT after walking to
	 * the end of the line table, which is AKBASIC_MAX_SOURCE_LINES steps and
	 * is what the reference does too.
	 */
	CATCH(errctx, game_loop(AKBASIC_MAX_SOURCE_LINES, 8, &frames));
	printf("--- run to completion ---\n%s", CONSOLE.buffer);

	/*
	 * Then the same program again with no line numbers on it. A fresh runtime
	 * rather than a second load into this one, because load() adds to whatever
	 * is already filed rather than replacing it.
	 */
	CATCH(errctx, console_sink_init());
	CATCH(errctx, akbasic_runtime_init(&SCRIPT, &CONSOLE_SINK));
	CATCH(errctx, akbasic_runtime_load(&SCRIPT, UNNUMBERED_PROGRAM));
	CATCH(errctx, akbasic_runtime_start(&SCRIPT, AKBASIC_MODE_RUN));
	CATCH(errctx, game_loop(AKBASIC_MAX_SOURCE_LINES, 8, &frames));
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE_DEFAULT(errctx) {
	LOG_ERROR_WITH_MESSAGE(errctx, "the embedded script failed");
	rc = 1;
    } FINISH_NORETURN(errctx);

    printf("--- the same program with no line numbers ---\n%s", CONSOLE.buffer);
    return rc;
}
