/**
 * @file environment.h
 * @brief Declares a scope and the per-line working state that rides with it.
 *
 * Ported from basicenvironment.go. An environment is both a variable scope and
 * the in-flight state of whatever block structure is executing: IF, FOR, GOSUB
 * and READ all park their bookkeeping here.
 *
 * The reference allocates one with new() per FOR and per GOSUB and never frees
 * it. Here they come from a pool the runtime owns and are released on pop, so a
 * long-running program exhausts a bounded resource and says so rather than
 * leaking quietly.
 */

#ifndef _AKBASIC_ENVIRONMENT_H_
#define _AKBASIC_ENVIRONMENT_H_

#include <akerror.h>

#include <akbasic/grammar.h>
#include <akbasic/symtab.h>
#include <akbasic/types.h>
#include <akbasic/value.h>
#include <akbasic/variable.h>

/** @brief A DO or LOOP with no condition on that end. */
#define AKBASIC_LOOPCOND_NONE	0
/** @brief `WHILE c` -- keep looping while the condition is true. */
#define AKBASIC_LOOPCOND_WHILE	1
/** @brief `UNTIL c` -- keep looping until the condition becomes true. */
#define AKBASIC_LOOPCOND_UNTIL	2

struct akbasic_Runtime;

typedef struct akbasic_Environment
{
    akbasic_SymbolTable variables;		/** name -> akbasic_Variable * */
    akbasic_SymbolTable functions;		/** name -> akbasic_FunctionDef * */
    akbasic_SymbolTable labels;			/** name -> line number */

    /* FOR state */
    akbasic_ASTLeaf *forStepLeaf;
    akbasic_Value forStepValue;
    akbasic_ASTLeaf *forToLeaf;
    akbasic_Value forToValue;
    akbasic_Variable *forNextVariable;

    /*
     * DO/LOOP state. The condition may sit on either end -- `DO WHILE c`,
     * `LOOP UNTIL c`, both, or neither -- so each end keeps its own, and the
     * leaf lives in this environment's pool because it is re-evaluated on every
     * iteration long after the line that held it was scanned away.
     */
    akbasic_ASTLeaf *doConditionLeaf;
    /* The condition is re-evaluated every iteration, long after its line was scanned away. */
    akbasic_ASTLeaf doLeafStorage[AKBASIC_MAX_CONDITION_LEAVES];
    akbasic_LeafPool doLeafPool;
    int doConditionKind;		/** AKBASIC_LOOPCOND_* */
    bool isDoLoop;			/** distinguishes DO/LOOP from FOR/NEXT for EXIT */

    /* Loop bounds */
    int64_t loopFirstLine;
    int64_t loopExitLine;
    /**
     * Set by `EXIT`, cleared by the `NEXT` that acts on it.
     *
     * `EXIT` cannot simply jump past the loop, because where the loop ends is
     * not known until a `NEXT` has run at least once -- and an `EXIT` on the
     * first pass is the normal case. So it skips forward to the `NEXT` using the
     * same waitingForCommand machinery a zero-iteration loop body uses, and this
     * is what tells that `NEXT` it is ending the loop rather than continuing it.
     */
    bool exiting;

    /*
     * Generator state (GEN / EMIT / FOR EACH / DO EACH).
     *
     * `isGenerator` is set on the environment a GEN call pushes -- the one
     * whose body is actually running the GEN's lines, as opposed to the loop's
     * own environment. `generatorFn` records *which* GEN it is running, so a
     * FOR EACH/DO EACH that would invoke a GEN currently running higher up the
     * parent chain (self-recursion) can be told apart from one invoking it
     * fresh, or invoking a sibling instance of the same GEN sitting detached in
     * someone else's `forGeneratorEnv`. It carries an akbasic_FunctionDef *, kept
     * as void * for the same reason akbasic_environment_get_function() does:
     * runtime.h includes this header, not the other way around.
     */
    bool isGenerator;
    void *generatorFn;
    /**
     * Set on a FOR EACH or DO EACH loop's own environment, distinguishing it
     * from a plain FOR/DO for verbs that need to know which kind of loop this
     * is -- EXIT, NEXT and LOOP all read it.
     */
    bool isEachLoop;
    /**
     * The generator environment a FOR EACH/DO EACH loop is suspended on
     * between iterations -- alive, detached from the step loop, but not
     * released, so its own `nextline` still says where to resume. NULL means
     * either "not an EACH loop" or "the generator is exhausted": both leave
     * nothing to resume, and by the time either becomes true the loop
     * environment itself is on its way out too.
     */
    struct akbasic_Environment *forGeneratorEnv;

    int64_t gosubReturnLine;

    /* READ state. The identifier leaves are deep copies, so they need storage. */
    int64_t readReturnLine;
    akbasic_ASTLeaf *readIdentifierLeaves[AKBASIC_MAX_LEAVES];
    int64_t readIdentifierIdx;
    akbasic_ASTLeaf readLeafStorage[AKBASIC_MAX_LEAVES];
    akbasic_LeafPool readLeafPool;

    /*
     * While this is set, no line executes until a COMMAND matching it is found.
     * It is what keeps the body of a loop that should not run at all from
     * running, given that the reference evaluates a loop's condition at the
     * *bottom* of the structure. Any reimplementation has to reproduce it or
     * restructure control flow deliberately.
     */
    char waitingForCommand[AKBASIC_SYMTAB_MAX_KEY];

    struct akbasic_Environment *parent;
    struct akbasic_Runtime *runtime;

    /* Runtime state */
    int64_t lineno;
    akbasic_Value values[AKBASIC_MAX_VALUES];
    int nextvalue;
    int64_t nextline;
    akbasic_Value returnValue;

    /* Parser state */
    akbasic_Token tokens[AKBASIC_MAX_TOKENS];
    int nexttoken;
    int curtoken;
    akbasic_ASTLeaf leaves[AKBASIC_MAX_LEAVES];
    int nextleaf;
    akbasic_Token *errorToken;

    bool used;					/** Pool bookkeeping */
} akbasic_Environment;

/**
 * @brief Bring an environment up as a child of another, or as the root.
 * @param obj Object to initialize, inspect, or modify.
 * @param runtime The runtime that owns the pools this scope draws from.
 * @param parent Enclosing scope, or NULL for the root.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `runtime` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_init(akbasic_Environment *obj, struct akbasic_Runtime *runtime, akbasic_Environment *parent);
/**
 * @brief Reset the per-line value pool without touching variables or scope.
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_zero(akbasic_Environment *obj);
/**
 * @brief Reset the per-line token and leaf pools and their cursors.
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_zero_parser(akbasic_Environment *obj);

/** @brief Take the next value from this environment's per-line pool. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_value(akbasic_Environment *obj, akbasic_Value **dest);

/** @brief Take the next leaf from this environment's per-line pool. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_new_leaf(akbasic_Environment *obj, akbasic_ASTLeaf **dest);

/**
 * @brief Suppress execution until a given verb is reached.
 *
 * The reference panics on a second pending wait. This raises instead, but it is
 * still a hard failure: two waits in one scope means the block structure is
 * already corrupt.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param command Verb to skip forward to, e.g. "NEXT" or "DATA".
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_STATE When this scope is already waiting for something.
 * @throws AKBASIC_ERR_BOUNDS When the verb name is too long to record.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_wait_for_command(akbasic_Environment *obj, const char *command);
/**
 * @brief Whether this scope or any enclosing one is skipping forward.
 *
 * The answer leaves through @p dest rather than the return value. Deciding it
 * reads a recorded verb name, that read can fail, and a `bool` return has
 * nowhere to report the failure -- so the signature changes rather than the
 * error being swallowed. libakstdlib #38 is the ruling; `probe` in `symtab.c`
 * and `loop_continues` in `runtime_structure.c` are the same shape.
 *
 * @param obj Scope to inspect; NULL is not waiting.
 * @param[out] dest `true` when execution is currently suppressed. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When dest is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for_any(akbasic_Environment *obj, bool *dest);
/**
 * @brief Whether this scope or an enclosing one is waiting for a given verb.
 *
 * Reports through @p dest for the same reason its sibling above does.
 *
 * @param obj Scope to inspect; NULL is not waiting.
 * @param command Verb to test for; NULL is not waiting.
 * @param[out] dest `true` when that verb is what execution is waiting on. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When dest is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_is_waiting_for(akbasic_Environment *obj, const char *command, bool *dest);
/**
 * @brief Clear a pending wait, searching the parent chain for it.
 *
 * A verb that is not being waited for is silently tolerated, matching the
 * reference -- which ignores the argument entirely and clears unconditionally
 * (TODO.md section 6 item 3).
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param command Verb whose wait should be cleared.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `command` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_stop_waiting(akbasic_Environment *obj, const char *command);

/**
 * @brief Find a variable, creating it in the active environment if it is absent.
 *
 * Parents do not create variables on behalf of their children: only the
 * runtime's currently active environment auto-creates. A lookup that misses in a
 * non-active environment returns NULL through `dest` without error, matching the
 * reference.
 *
 * **A host wanting a variable the script will still see later wants
 * akbasic_runtime_global() instead.** This one lands the variable in whatever
 * scope happens to be active, which during a suspended run is usually a `FOR` or
 * `GOSUB` body -- and it dies when that scope pops.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_get(akbasic_Environment *obj, const char *varname, akbasic_Variable **dest);

/**
 * @brief Evaluate an lvalue's subscript list, or yield {0} when it has none.
 *
 * A bare identifier addresses element zero, because every variable here is
 * really a one-element array. Shared so that a verb taking a variable by name
 * -- `SSHAPE` and `GSHAPE` do -- resolves a subscript the same way assignment
 * does, rather than reading the leaf and quietly using element zero for
 * everything.
 *
 * @param obj The environment the subscript expressions are evaluated in.
 * @param lval The identifier leaf, whose `.expr` carries any subscript list.
 * @param subscripts Output: one index per dimension, AKBASIC_MAX_ARRAY_DEPTH wide.
 * @param count Output: how many were written; at least one.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_TYPE When a subscript does not evaluate to an integer.
 * @throws AKBASIC_ERR_BOUNDS When there are more than AKBASIC_MAX_ARRAY_DEPTH.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_collect_subscripts(akbasic_Environment *obj, akbasic_ASTLeaf *lval, int64_t *subscripts, int *count);

/**
 * @brief Find a variable in exactly this scope, creating it here if it is absent.
 *
 * No walk up the parent chain in either direction: the caller has said which
 * scope it means, and no active-environment check stands in the way. This is
 * what akbasic_runtime_global() calls against the root.
 *
 * @param obj The scope to search and, on a miss, to create in.
 * @param varname Name including its type suffix.
 * @param dest Output destination populated by the function; never NULL on success.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 * @throws AKBASIC_ERR_BOUNDS When the name is too long, or no variable slot is free.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_create(akbasic_Environment *obj, const char *varname, akbasic_Variable **dest);

/**
 * @brief Create a variable slot without allocating value storage.
 *
 * Used when the caller knows the variable's representation before its first
 * initialization, such as a structure parameter. The caller must initialize
 * the variable before evaluating it.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_create_empty(akbasic_Environment *obj, const char *varname,
										akbasic_Variable **dest);

/**
 * @brief Resolve a label to the line number it marks.
 * @param obj Scope to search; the parent chain is walked.
 * @param label Label name, which carries no type suffix.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_UNDEFINED When no enclosing scope defines the label.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_get_label(akbasic_Environment *obj, const char *label, int64_t *dest);
/**
 * @brief Record a label against a line number.
 *
 * Labels are created only in the top-level scope, so one set inside a loop is
 * still visible after it.
 *
 * @param obj Scope the request came from; the search walks up from here.
 * @param label Label name.
 * @param value Line number to record.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_ENVIRONMENT When no top-level scope is reachable.
 * @throws AKBASIC_ERR_BOUNDS When the label table is full.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_set_label(akbasic_Environment *obj, const char *label, int64_t value);
/**
 * @brief Find a user-defined function by name, case-insensitively.
 * @param obj Scope to search; the parent chain is walked.
 * @param fname Function name; folded to upper case before lookup.
 * @param dest Output destination populated by the function; an akbasic_FunctionDef *.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_KEY When no enclosing scope defines the function.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_get_function(akbasic_Environment *obj, const char *fname, void **dest);

/** @brief Assign into the slot an lvalue leaf names, following any subscripts. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_environment_assign(akbasic_Environment *obj, akbasic_ASTLeaf *lval, akbasic_Value *rval, akbasic_Value **dest);

#endif // _AKBASIC_ENVIRONMENT_H_
