/**
 * @file hoststruct.c
 * @brief Sharing a game's own C structures with a script.
 *
 * This is the code in docs/15-structures.md's "Sharing a structure with a host"
 * section, kept here so it is compiled and run by every build rather than
 * rotting in a document.
 *
 * **The thing worth noticing is that there is no marshalling step.** The host
 * describes its struct once and binds an instance; the script then reads and
 * writes the game's real memory. `FOE@.HP# = FOE@.HP# - 10` decrements
 * `GOBLIN.hp` in place.
 *
 * The second thing worth noticing is that the language's own distinction --
 * assignment copies, `POINT` shares -- turns out to be exactly the distinction
 * a host needs, so there is only one API and which one a script used is visible
 * in the listing.
 */

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <akerror.h>

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

/* ------------------------------------------------------------ the game --- */

/** @brief The game's own struct. Nothing about it is akbasic's business. */
typedef struct
{
    char     name[32];
    int32_t  hp;
    float    x;
    float    y;
    bool     hostile;
} game_Enemy;

static game_Enemy GOBLIN = { "GOBLIN", 30, 10.0f, 20.0f, true };
static game_Enemy TROLL  = { "TROLL",  80, 50.0f, 60.0f, true };

/*
 * The description akbasic needs: one row per field, with the BASIC name and its
 * suffix, the C representation, and where it sits. AKBASIC_HOST_FIELD takes the
 * offset and the width from the member itself, so the two cannot drift apart --
 * writing them out by hand is two chances to name the wrong member and no way to
 * notice.
 */
static const akbasic_HostField ENEMY_FIELDS[] = {
    /*                  struct       member    BASIC name   C representation           */
    AKBASIC_HOST_FIELD( game_Enemy,  name,     "NAME$",     AKBASIC_HOSTFIELD_CSTRING ),
    AKBASIC_HOST_FIELD( game_Enemy,  hp,       "HP#",       AKBASIC_HOSTFIELD_INT32   ),
    AKBASIC_HOST_FIELD( game_Enemy,  x,        "X%",        AKBASIC_HOSTFIELD_FLOAT   ),
    AKBASIC_HOST_FIELD( game_Enemy,  y,        "Y%",        AKBASIC_HOSTFIELD_FLOAT   ),
    AKBASIC_HOST_FIELD( game_Enemy,  hostile,  "HOSTILE#",  AKBASIC_HOSTFIELD_BOOL    )
};

static const akbasic_HostType ENEMY_TYPE = {
    "ENEMY", sizeof(game_Enemy), ENEMY_FIELDS, 5
};

/* ------------------------------------------------------ the interpreter --- */

static akbasic_Runtime SCRIPT;
static akbasic_TextSink SINK;
static akbasic_StdioSink SINKSTATE;

/*
 * A script written against one name. The host points that name at each enemy in
 * turn, which is what akbasic_host_rebind() is for -- one script, one binding,
 * however many enemies.
 */
static const char *PROGRAM =
    "10 PRINT FOE@.NAME$ + \" HAS \" + FOE@.HP#\n"
    "20 FOE@.HP# = FOE@.HP# - 10\n"
    "30 PRINT \"  AFTER THE HIT: \" + FOE@.HP#\n";

static akerr_ErrorContext AKERR_NOIGNORE *run_against(game_Enemy *enemy)
{
    PREPARE_ERROR(errctx);

    PASS(errctx, akbasic_host_rebind(&SCRIPT, "FOE@", enemy));
    PASS(errctx, akbasic_runtime_start(&SCRIPT, AKBASIC_MODE_RUN));
    PASS(errctx, akbasic_runtime_run(&SCRIPT, 0));
    SUCCEED_RETURN(errctx);
}

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

    ATTEMPT {
	CATCH(errctx, akbasic_error_register());
	CATCH(errctx, akbasic_sink_init_stdio(&SINK, &SINKSTATE, stdout, NULL));
	CATCH(errctx, akbasic_runtime_init(&SCRIPT, &SINK));

	/*
	 * Register the type, then bind an instance. The descriptor is borrowed
	 * rather than copied, which is why it is `static const` -- it has to
	 * outlive the runtime.
	 */
	CATCH(errctx, akbasic_host_register_type(&SCRIPT, &ENEMY_TYPE));
	CATCH(errctx, akbasic_host_bind(&SCRIPT, "FOE@", "ENEMY", &GOBLIN));
	CATCH(errctx, akbasic_runtime_load(&SCRIPT, PROGRAM));

	CATCH(errctx, run_against(&GOBLIN));
	CATCH(errctx, run_against(&TROLL));

	/*
	 * And the game's own structs have changed. No marshalling step, no copy
	 * to write back -- the script was reading and writing these all along.
	 */
	printf("host sees GOBLIN.hp = %d\n", GOBLIN.hp);
	printf("host sees TROLL.hp  = %d\n", TROLL.hp);

	/*
	 * Unbind before the storage would go away. A binding is the only pointer
	 * this interpreter holds that it did not allocate, so this is the one
	 * lifetime a host has to think about -- and after it, a script reading
	 * the name is refused by name rather than reading freed memory.
	 */
	CATCH(errctx, akbasic_host_unbind(&SCRIPT, "FOE@"));
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE_DEFAULT(errctx) {
	LOG_ERROR_WITH_MESSAGE(errctx, "host structure example failed");
	rc = 1;
    } FINISH_NORETURN(errctx);

    return rc;
}
