/**
 * @file runtime_generator.c
 * @brief GEN, EMIT and END GEN, plus the machinery FOR EACH/DO EACH share.
 *
 * A GEN is a DEF that yields more than once. Its body is skipped on the
 * definitional pass exactly as a multi-line DEF's is -- `akbasic_parse_gen()`
 * arms `akbasic_environment_wait_for_command(env, "END GEN")` the same way
 * `akbasic_parse_def()` arms one for `RETURN` -- and it only ever really runs
 * when a `FOR EACH`/`DO EACH` invokes it.
 *
 * That invocation pushes one environment for the whole lifetime of the loop,
 * exactly as a GOSUB or a function call does, except that `EMIT` does not pop
 * it: it hands control back to the loop without releasing anything, so the
 * environment EMIT actually ran in -- which may be nested several levels
 * below the GEN's own call frame, inside a FOR/DO/GOSUB the body wrote --
 * still says exactly where to resume when `NEXT`/`LOOP` calls back into
 * akbasic_runtime_pump_generator(). Only `END GEN` reached for real --
 * meaning `obj->environment->isGenerator` is true and nothing is skipping
 * forward to it -- actually releases the call frame, the way `RETURN`
 * releases a DEF's call environment.
 */

#include <akerror.h>
#include <akstdlib.h>

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

#include "verbs.h"

/* Most verbs answer "did something happen"; this is that answer. */
#define SUCCEED_TRUE(__obj, __dest)			\
    do {						\
	*(__dest) = &(__obj)->staticTrueValue;		\
    } while ( 0 )

akerr_ErrorContext *akbasic_runtime_pump_generator(akbasic_Runtime *obj, akbasic_Environment *loopenv)
{
    PREPARE_ERROR(errctx);

    FAIL_ZERO_RETURN(errctx, (obj != NULL && loopenv != NULL), AKERR_NULLPOINTER,
		     "NULL argument in pump_generator");
    /*
     * The same per-line prologue akbasic_runtime_step() and
     * akbasic_runtime_call_function() run, driving process_line_run() directly
     * rather than through the ordinary step loop: a generator body is not the
     * top-level program and nothing else is going to advance it.
     */
    ATTEMPT {
	while ( obj->environment != loopenv && obj->mode == AKBASIC_MODE_RUN ) {
	    CATCH(errctx, akbasic_runtime_zero(obj));
	    CATCH(errctx, akbasic_scanner_zero(obj));
	    CATCH(errctx, akbasic_runtime_process_line_run(obj));
	}
    } CLEANUP {
	/*
	 * CLEANUP runs unconditionally -- it is not a `catch` -- so it is
	 * guarded on the one thing that tells success and failure apart here:
	 * whether `obj->environment` is still `loopenv`. On the ordinary
	 * success path it already is (that is the ATTEMPT loop's own exit
	 * condition), so this is a no-op there, exactly as it is meant to be.
	 * Only a genuine C-level failure -- the scanner or parser raised, or a
	 * runtime error escaped the swallow process_line_run() ordinarily does
	 * for a BASIC-level one -- leaves scopes active between here and
	 * loopenv, and only then does this force them back, taking
	 * `forGeneratorEnv` down with them since whatever it pointed at is
	 * among the scopes just released.
	 */
	if ( obj->environment != loopenv ) {
	    IGNORE(akbasic_runtime_unwind_to_environment(obj, loopenv));
	    loopenv->forGeneratorEnv = NULL;
	}
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

/**
 * @brief Release a live generator, however deep EMIT left it suspended.
 *
 * `loopenv->forGeneratorEnv` is the *resume point*, not necessarily the GEN's
 * own call frame -- EMIT may have run several levels down, inside a FOR/DO/
 * GOSUB the body wrote around it. Abandoning it (EXIT) has to give back every
 * environment from there up through the call frame itself, or everything
 * above the resume point leaks.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param env The suspended resume point; walks up through its own parents.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 */
akerr_ErrorContext *akbasic_runtime_release_generator(akbasic_Runtime *obj, akbasic_Environment *env)
{
    PREPARE_ERROR(errctx);
    akbasic_Environment *walk = env;
    akbasic_Environment *next = NULL;
    bool isgen = false;

    FAIL_ZERO_RETURN(errctx, (obj != NULL && env != NULL), AKERR_NULLPOINTER,
		     "NULL argument in release_generator");
    while ( walk != NULL ) {
	isgen = walk->isGenerator;
	next = walk->parent;
	/*
	 * A scope between the resume point and the call frame may be an EACH
	 * loop with its *own* generator suspended off to the side. Releasing
	 * the loop scope without releasing that generator strands it in the
	 * pool -- the walk goes through parents and a suspended generator is a
	 * child. Guarded on `used` so a generator already released as part of
	 * some enclosing teardown is not released twice.
	 */
	if ( walk->forGeneratorEnv != NULL && walk->forGeneratorEnv->used ) {
	    PASS(errctx, akbasic_runtime_release_generator(obj, walk->forGeneratorEnv));
	}
	walk->forGeneratorEnv = NULL;
	PASS(errctx, akbasic_runtime_release_environment(obj, walk));
	if ( isgen ) {
	    break;
	}
	walk = next;
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_runtime_generator_invoke(akbasic_Runtime *obj, akbasic_Environment *loopenv, akbasic_ASTLeaf *callexpr)
{
    PREPARE_ERROR(errctx);
    akbasic_Environment *callenv = NULL;
    akbasic_Environment *walk = NULL;
    akbasic_FunctionDef *fndef = NULL;
    akbasic_ASTLeaf *fnarg = NULL;
    akbasic_ASTLeaf *paramleaf = NULL;
    akbasic_Value *argvals[AKBASIC_MAX_CALL_ARGUMENTS];
    akbasic_Value *unused = NULL;
    void *fnptr = NULL;
    int nargs = 0;
    int i = 0;

    FAIL_ZERO_RETURN(errctx, (obj != NULL && loopenv != NULL && callexpr != NULL), AKERR_NULLPOINTER,
		     "NULL argument in generator_invoke");
    FAIL_ZERO_RETURN(errctx, (callexpr->leaftype == AKBASIC_LEAF_FUNCTION), AKBASIC_ERR_SYNTAX,
		     "Expected a generator call after IN");

    /*
     * GEN and DEF share the functions table (TODO.md's namespace decision for
     * this feature), so this is the same lookup akbasic_runtime_call_function()
     * does. The parser already proved the name resolves and the arity matches
     * when it parsed `callexpr` -- akbasic_parser_expression() would not have
     * produced an AKBASIC_LEAF_FUNCTION leaf otherwise -- so a miss here would
     * mean the function table changed out from under a leaf built against it,
     * which is not a case this needs its own message for.
     */
    PASS(errctx, akbasic_environment_get_function(loopenv, callexpr->identifier, &fnptr));
    fndef = (akbasic_FunctionDef *)fnptr;
    FAIL_ZERO_RETURN(errctx, fndef->isGenerator, AKBASIC_ERR_STATE,
		     "%s is a DEF, not a GEN -- FOR EACH/DO EACH needs a generator",
		     fndef->name);

    /*
     * Self-recursion: walk the *parent* chain, not the pool. An environment
     * reachable only through some other loop's `forGeneratorEnv` is a sibling
     * invocation sitting detached between its own iterations, not an ancestor
     * of this call -- nothing points from here to it via `parent`, so it never
     * matches and independent or nested FOR EACH/DO EACH over the same GEN
     * (even the same GEN with different arguments) is unaffected.
     */
    for ( walk = loopenv; walk != NULL; walk = walk->parent ) {
	if ( walk->isGenerator && walk->generatorFn == (void *)fndef ) {
	    FAIL_RETURN(errctx, AKBASIC_ERR_STATE,
			"GEN %s cannot FOR EACH/DO EACH over itself from its own body",
			fndef->name);
	}
    }

    /*
     * Evaluated in the caller's own scope, before anything is pushed -- the
     * same reason akbasic_runtime_user_function() evaluates every argument
     * before binding the first one: a later argument must not see an earlier
     * one already sitting in the callee's scope.
     */
    fnarg = akbasic_leaf_first_argument(callexpr);
    for ( ; fnarg != NULL; fnarg = fnarg->next ) {
	FAIL_ZERO_RETURN(errctx, (nargs < AKBASIC_MAX_CALL_ARGUMENTS), AKBASIC_ERR_BOUNDS,
			 "%s was called with more than %d arguments",
			 callexpr->identifier, AKBASIC_MAX_CALL_ARGUMENTS);
	PASS(errctx, akbasic_runtime_evaluate(obj, fnarg, &argvals[nargs]));
	nargs += 1;
    }

    /*
     * One environment for the whole lifetime of the loop, exactly as GOSUB and
     * a function call take one from the pool -- and unlike either, this one
     * survives past the verb that pushed it, held alive by `loopenv`'s own
     * reference until NEXT/LOOP exhausts or EXIT abandons it.
     */
    PASS(errctx, akbasic_runtime_new_environment(obj));
    callenv = obj->environment;
    callenv->isGenerator = true;
    callenv->generatorFn = (void *)fndef;
    callenv->nextline = fndef->lineno;
    loopenv->forGeneratorEnv = callenv;

    paramleaf = (fndef->arglist != NULL ? fndef->arglist->right : NULL);
    for ( i = 0; i < nargs && paramleaf != NULL; i++ ) {
	PASS(errctx, akbasic_environment_assign(callenv, paramleaf, argvals[i], &unused));
	paramleaf = paramleaf->next;
    }

    PASS(errctx, akbasic_runtime_pump_generator(obj, loopenv));
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_cmd_gen(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
    PREPARE_ERROR(errctx);

    (void)expr; (void)lval; (void)rval;
    /* The parse handler already installed the generator, exactly as DEF's does. */
    SUCCEED_TRUE(obj, dest);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_cmd_emit(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
    PREPARE_ERROR(errctx);
    akbasic_Environment *genenv = NULL;
    akbasic_Environment *loopenv = NULL;
    akbasic_Value *value = NULL;
    int64_t zerosubscript[1] = { 0 };

    (void)lval; (void)rval;
    FAIL_ZERO_RETURN(errctx, (expr != NULL && expr->right != NULL), AKBASIC_ERR_SYNTAX,
		     "Expected EMIT (expression)");
    /*
     * EMIT is not necessarily standing directly in the environment
     * akbasic_runtime_generator_invoke() pushed: a GEN body is ordinary BASIC
     * and may nest its own FOR, DO or GOSUB around an EMIT, each of which
     * pushes an environment of its own -- exactly what the issue's own
     * ROOMOBJECTS example does. Walk up to the nearest one that really is a
     * GEN's own call frame.
     */
    for ( genenv = obj->environment; genenv != NULL && !genenv->isGenerator; genenv = genenv->parent ) {
    }
    FAIL_ZERO_RETURN(errctx, (genenv != NULL), AKBASIC_ERR_STATE,
		     "EMIT outside the context of a GEN body");
    loopenv = genenv->parent;
    FAIL_ZERO_RETURN(errctx, (loopenv != NULL), AKBASIC_ERR_ENVIRONMENT,
		     "EMIT from an orphaned environment");

    PASS(errctx, akbasic_runtime_evaluate(obj, expr->right, &value));
    /*
     * Straight into the loop variable's storage, bypassing the arithmetic
     * akbasic_environment_assign() and evaluate_for_condition() carry for a
     * plain FOR: an EACH variable takes whatever type the GEN emits, string or
     * structure element included, and there is no TO/STEP to compare it
     * against.
     */
    PASS(errctx, akbasic_variable_set_subscript(loopenv->forNextVariable, value, zerosubscript, 1));
    loopenv->nextline = loopenv->loopFirstLine;
    /*
     * The resume point, which may be several levels below `genenv` -- whatever
     * nested FOR/DO/GOSUB environment this EMIT actually ran in. NEXT/LOOP
     * reactivates exactly this one, so the nested structure picks up exactly
     * where it left off rather than restarting at the top of the GEN body.
     */
    loopenv->forGeneratorEnv = obj->environment;
    /*
     * Not a pop, and not a single-level detach either: everything between here
     * and `loopenv` -- `genenv` and any of its own descendants -- has to
     * survive untouched to be resumed, so control moves to `loopenv` directly
     * rather than walking the chain one release at a time.
     */
    obj->environment = loopenv;
    SUCCEED_TRUE(obj, dest);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akbasic_cmd_end_gen(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value *lval, akbasic_Value *rval, akbasic_Value **dest)
{
    PREPARE_ERROR(errctx);
    bool waiting = false;

    (void)expr; (void)lval; (void)rval;
    /*
     * A END GEN reached while skipping forward to one is the end of a GEN
     * body's *definition*, not the end of a call -- the same distinction
     * RETURN draws for DEF.
     */
    PASS(errctx, akbasic_environment_is_waiting_for(obj->environment, "END GEN", &waiting));
    if ( waiting ) {
	PASS(errctx, akbasic_environment_stop_waiting(obj->environment, "END GEN"));
	SUCCEED_TRUE(obj, dest);
	SUCCEED_RETURN(errctx);
    }
    FAIL_ZERO_RETURN(errctx, (obj->environment->isGenerator), AKBASIC_ERR_STATE,
		     "END GEN outside the context of a generator invocation");
    /*
     * Real exhaustion: release this environment and detach in the same
     * motion prev_environment() always does, then clear the parent's
     * reference to it so a caller pumping this loop can tell "still alive"
     * apart from "nothing left to resume".
     */
    PASS(errctx, akbasic_runtime_prev_environment(obj));
    obj->environment->forGeneratorEnv = NULL;
    SUCCEED_TRUE(obj, dest);
    SUCCEED_RETURN(errctx);
}
