/**
 * @file environment_scope.c
 * @brief Tests scoping, label creation, the wait mechanism and pool release.
 */

#include "harness.h"

int main(void)
{
    akbasic_Environment *root = NULL;
    akbasic_Environment *child = NULL;
    akbasic_Variable *variable = NULL;
    akbasic_Variable *again = NULL;
    akbasic_Value *value = NULL;
    int64_t lineno = 0;
    bool waiting = false;
    int i = 0;

    TEST_REQUIRE_OK(harness_start(NULL));
    root = HARNESS_RUNTIME.environment;

    /* The active environment auto-creates on a miss. */
    TEST_REQUIRE_OK(akbasic_environment_get(root, "A#", &variable));
    TEST_REQUIRE(variable != NULL, "the active environment must auto-create a variable");
    TEST_REQUIRE_INT(variable->valuetype, AKBASIC_TYPE_INTEGER);

    /* And the second lookup finds the same one. */
    TEST_REQUIRE_OK(akbasic_environment_get(root, "A#", &again));
    TEST_REQUIRE(again == variable, "a repeat lookup must find the same variable");

    /* A child sees its parent's variables. */
    TEST_REQUIRE_OK(akbasic_runtime_new_environment(&HARNESS_RUNTIME));
    child = HARNESS_RUNTIME.environment;
    TEST_REQUIRE(child->parent == root, "the child must chain to its parent");
    TEST_REQUIRE_OK(akbasic_environment_get(child, "A#", &again));
    TEST_REQUIRE(again == variable, "the child must see the parent's variable");

    /*
     * A parent does not create variables on behalf of a child: looking up a
     * missing name in a non-active environment yields NULL without error.
     */
    TEST_REQUIRE_OK(akbasic_environment_get(root, "ZZ#", &again));
    TEST_REQUIRE(again == NULL, "a non-active environment must not auto-create");

    /* Labels are created only at the top level, and are visible from below. */
    TEST_REQUIRE_OK(akbasic_environment_set_label(child, "LOOPTOP", 30));
    TEST_REQUIRE_OK(akbasic_environment_get_label(child, "LOOPTOP", &lineno));
    TEST_REQUIRE_INT(lineno, 30);
    TEST_REQUIRE_STATUS(akbasic_environment_get_label(child, "MISSING", &lineno),
			AKBASIC_ERR_UNDEFINED);

    /* Popping releases the environment back to the pool. */
    TEST_REQUIRE_OK(akbasic_runtime_prev_environment(&HARNESS_RUNTIME));
    TEST_REQUIRE(HARNESS_RUNTIME.environment == root, "pop must restore the parent");
    TEST_REQUIRE(!child->used, "pop must release the environment to the pool");

    /* The root has no parent to pop to. */
    TEST_REQUIRE_STATUS(akbasic_runtime_prev_environment(&HARNESS_RUNTIME),
			AKBASIC_ERR_ENVIRONMENT);

    /*
     * The wait mechanism, including the parent-chain search. Both predicates
     * report through an out-parameter and return an error context, so each
     * assertion is a TEST_REQUIRE_OK on the call and a TEST_REQUIRE on the
     * answer. See libakstdlib #38.
     */
    TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
    TEST_REQUIRE(!waiting, "a fresh environment waits for nothing");
    TEST_REQUIRE_OK(akbasic_environment_wait_for_command(root, "NEXT"));
    TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
    TEST_REQUIRE(waiting, "the wait must register");
    TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(root, "NEXT", &waiting));
    TEST_REQUIRE(waiting, "the wait must match by name");
    TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(root, "DATA", &waiting));
    TEST_REQUIRE(!waiting, "a different verb must not match");

    /* Two pending waits in one environment is a hard error, not a panic. */
    TEST_REQUIRE_STATUS(akbasic_environment_wait_for_command(root, "DATA"), AKBASIC_ERR_STATE);

    TEST_REQUIRE_OK(akbasic_runtime_new_environment(&HARNESS_RUNTIME));
    child = HARNESS_RUNTIME.environment;
    TEST_REQUIRE_OK(akbasic_environment_is_waiting_for(child, "NEXT", &waiting));
    TEST_REQUIRE(waiting, "a child must see its parent's wait");
    TEST_REQUIRE_OK(akbasic_runtime_prev_environment(&HARNESS_RUNTIME));

    TEST_REQUIRE_OK(akbasic_environment_stop_waiting(root, "NEXT"));
    TEST_REQUIRE_OK(akbasic_environment_is_waiting_for_any(root, &waiting));
    TEST_REQUIRE(!waiting, "stop_waiting must clear it");

    /* The per-line value pool is bounded and says so. */
    for ( i = 0; i < AKBASIC_MAX_VALUES; i++ ) {
	TEST_REQUIRE_OK(akbasic_environment_new_value(root, &value));
    }
    TEST_REQUIRE_STATUS(akbasic_environment_new_value(root, &value), AKBASIC_ERR_BOUNDS);
    TEST_REQUIRE_OK(akbasic_environment_zero(root));
    TEST_REQUIRE_OK(akbasic_environment_new_value(root, &value));

    /* So is the environment pool. */
    for ( i = 0; i < AKBASIC_MAX_ENVIRONMENTS - 1; i++ ) {
	TEST_REQUIRE_OK(akbasic_runtime_new_environment(&HARNESS_RUNTIME));
    }
    TEST_REQUIRE_STATUS(akbasic_runtime_new_environment(&HARNESS_RUNTIME),
			AKBASIC_ERR_ENVIRONMENT);
    for ( i = 0; i < AKBASIC_MAX_ENVIRONMENTS - 1; i++ ) {
	TEST_REQUIRE_OK(akbasic_runtime_prev_environment(&HARNESS_RUNTIME));
    }
    TEST_REQUIRE(HARNESS_RUNTIME.environment == root, "the pool must unwind back to the root");

    harness_stop();
    return akbasic_test_failures;
}
