/**
 * @file variable.h
 * @brief Declares a named, strongly-typed, optionally multi-dimensional slot.
 *
 * Ported from basicvariable.go. Type comes from the identifier's last character:
 * `$` string, `#` integer, `%` float. Note that `%` meaning *float* inverts the
 * Commodore convention where `%` is integer; the reference does it that way and
 * its README documents it, so it is kept.
 */

#ifndef _AKBASIC_VARIABLE_H_
#define _AKBASIC_VARIABLE_H_

#include <akerror.h>

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

typedef struct
{
    /*
     * Sized to AKBASIC_SYMTAB_MAX_KEY, not AKBASIC_MAX_STRING_LENGTH: this name
     * only ever gets here by surviving akbasic_symtab_set() first
     * (akbasic_environment_create() calls it right after this field is
     * populated), and that call refuses anything AKBASIC_SYMTAB_MAX_KEY
     * characters or longer with AKBASIC_ERR_BOUNDS. A variable whose name did
     * not fit could never exist, so the wider buffer was 232 bytes of headroom
     * nothing could ever put a byte into.
     */
    char name[AKBASIC_SYMTAB_MAX_KEY];
    akbasic_Type valuetype;
    akbasic_Value *values;			/** The pool, or `inlinevalue` for a scalar */
    int valuecount;
    /*
     * A scalar's storage, so that creating one costs the value pool nothing.
     *
     * The pool is a bump allocator with no free, and a scope exit hands the
     * variable *slot* back while its storage stays counted against the pool --
     * so a name first created inside a GOSUB used to spend slots on every call
     * and 4096 creations ended the run. Keeping the one value here instead
     * means a local, a FOR counter and a DEF parameter are all free.
     *
     * akbasic_variable_init() decides which of the two `values` points at, and
     * a `@` name is the one exclusion: see the comment there.
     */
    akbasic_Value inlinevalue;
    int64_t dimensions[AKBASIC_MAX_ARRAY_DEPTH];
    int dimensioncount;
    bool mutable_;
    bool used;					/** Pool bookkeeping */
    /*
     * What DIM ... AS recorded. A structure variable's `values` run is one
     * instance laid out by the type's offsets, exactly as an array's run is its
     * elements -- so these two are what tell the difference between the two
     * kinds of run.
     */
    int structtype;				/** type index, -1 when not a structure */
    bool ispointer;				/** declared PTR TO rather than a value  */
    void *hostbase;				/** the host's struct, for a binding     */
} akbasic_Variable;

/**
 * @brief Give a variable storage and a type.
 * @param obj Object to initialize, inspect, or modify.
 * @param pool Backing store the elements are drawn from.
 * @param sizes Dimension sizes; every one must be positive.
 * @param sizecount Number of dimensions.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `pool` is NULL.
 * @throws AKBASIC_ERR_VALUE When the name is empty or a dimension is not positive.
 * @throws AKBASIC_ERR_BOUNDS When the array is larger than the pool can serve.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_init(akbasic_Variable *obj, akbasic_ValuePool *pool, int64_t *sizes, int sizecount);

/**
 * @brief Mark the variable undefined and mutable without touching its storage.
 * @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_variable_zero(akbasic_Variable *obj);

/**
 * @brief Resolve a subscript list to the value it addresses.
 * @param obj Object to initialize, inspect, or modify.
 * @param subscripts Index per dimension.
 * @param subscriptcount Number of indices supplied; must equal the dimension count.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_get_subscript(akbasic_Variable *obj, int64_t *subscripts, int subscriptcount, akbasic_Value **dest);

/**
 * @brief Copy a value into the slot a subscript list addresses.
 * @param obj Object to initialize, inspect, or modify.
 * @param value Source value; cloned into the slot, not aliased.
 * @param subscripts Index per dimension.
 * @param subscriptcount Number of indices supplied; must equal the dimension count.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `value` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_subscript(akbasic_Variable *obj, akbasic_Value *value, int64_t *subscripts, int subscriptcount);

/**
 * @brief Store an integer into the slot a subscript list addresses.
 * @param obj Object to initialize, inspect, or modify.
 * @param value Value to store.
 * @param subscripts Index per dimension.
 * @param subscriptcount Number of indices supplied; must equal the dimension count.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_integer(akbasic_Variable *obj, int64_t value, int64_t *subscripts, int subscriptcount);
/**
 * @brief Store a float into the slot a subscript list addresses.
 * @param obj Object to initialize, inspect, or modify.
 * @param value Value to store.
 * @param subscripts Index per dimension.
 * @param subscriptcount Number of indices supplied; must equal the dimension count.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_float(akbasic_Variable *obj, double value, int64_t *subscripts, int subscriptcount);
/**
 * @brief Store a string into the slot a subscript list addresses.
 * @param obj Object to initialize, inspect, or modify.
 * @param value Value to store.
 * @param subscripts Index per dimension.
 * @param subscriptcount Number of indices supplied; must equal the dimension count.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `value` is NULL.
 * @throws AKBASIC_ERR_VALUE When the string exceeds the length limit.
 * @throws AKBASIC_ERR_BOUNDS When the count is wrong or an index is out of range.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_variable_set_string(akbasic_Variable *obj, const char *value, int64_t *subscripts, int subscriptcount);

#endif // _AKBASIC_VARIABLE_H_
