/**
 * @file structtype.h
 * @brief Declares the table of `TYPE` declarations and how a field is found in one.
 *
 * BASIC 7.0 has no records at all, so none of this is a port -- see TODO.md
 * section 5 for what was invented and why.
 *
 * **Declaring the type is what buys the storage model.** Because a `TYPE` states
 * its fields up front, an instance has a known slot count and is laid out
 * exactly as an array is: one contiguous run taken from the runtime's value pool
 * by akbasic_valuepool_take(). A field access is then offset arithmetic against
 * an offset this table already knows, and no new pool is needed for the data --
 * only this table of descriptors, which holds names and offsets rather than
 * values.
 *
 * A field nested **by value** flattens into its parent's run at an offset. A
 * field declared `PTR TO` occupies one slot holding a reference. That is the
 * whole difference, and it is why a `TYPE` may refer to itself only through a
 * pointer: by value it would have no finite size, which is refused at
 * declaration rather than discovered when the pool runs out.
 */

#ifndef _AKBASIC_STRUCTTYPE_H_
#define _AKBASIC_STRUCTTYPE_H_

#include <akerror.h>

#include <akbasic/grammar.h>
#include <akbasic/host.h>
#include <akbasic/types.h>

struct akbasic_Runtime;

/** @brief What a field holds, which is what says how much room it needs. */
typedef enum
{
    AKBASIC_FIELD_PRIMITIVE = 0,	/* one slot, type from the name's suffix */
    AKBASIC_FIELD_STRUCT,		/* nested by value; flattened into the parent */
    AKBASIC_FIELD_POINTER		/* one slot holding a reference */
} akbasic_FieldKind;

/** @brief One declared field: what it is called, what it holds, and where it sits. */
typedef struct
{
    char name[AKBASIC_MAX_STRUCT_NAME];	/** as written, suffix included	*/
    akbasic_FieldKind kind;
    akbasic_Type valuetype;		/** for a primitive, from the suffix	*/
    int typeindex;			/** for STRUCT and POINTER		*/
    int offset;				/** slots from the start of the instance */
    int slotcount;			/** slots this field occupies		*/
    /*
     * Where this field's bytes live in the *host's* struct, when the type came
     * from akbasic_host_register_type(). Unused for a type the script declared,
     * whose fields are value slots and have no other representation.
     */
    akbasic_HostFieldKind hostkind;
    size_t hostoffset;
    size_t hostwidth;
} akbasic_StructField;

/**
 * @brief One `TYPE` declaration.
 *
 * `firstline` and `lastline` are the `TYPE` and `END TYPE` source lines. They
 * are kept because the block has to be *skipped* at run time: its field lines
 * are declarations, and letting them execute would evaluate each field name as a
 * bare identifier and quietly create a global of that name.
 */
typedef struct
{
    char name[AKBASIC_MAX_STRUCT_NAME];
    akbasic_StructField fields[AKBASIC_MAX_STRUCT_FIELDS];
    int fieldcount;
    int slotcount;			/** total slots for one instance	*/
    int64_t firstline;
    int64_t lastline;
    bool used;
    /*
     * True when the host described this type rather than the script declaring
     * it. The difference is only in where a field's bytes are; everything the
     * language does with a structure is the same either way, which is the point
     * of putting both in one table.
     */
    bool ishost;
    size_t hostsize;
} akbasic_StructType;

/** @brief Every `TYPE` a program declared. Lives on the runtime. */
typedef struct
{
    akbasic_StructType types[AKBASIC_MAX_STRUCT_TYPES];
    int count;
} akbasic_StructTypeTable;

/**
 * @brief Empty the table.
 * @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_structtype_table_init(akbasic_StructTypeTable *obj);

/**
 * @brief Find a declared type by name.
 * @param obj Table to search.
 * @param name Type name, without a suffix.
 * @param dest Output destination populated by the function; the index, or -1.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_find(akbasic_StructTypeTable *obj, const char *name, int *dest);

/**
 * @brief Resolve a field of a declared type, refusing one it does not declare.
 *
 * The refusal lists the fields the type *does* have, because the whole reason a
 * misspelled field can be caught at all is that the set is closed and written
 * down -- so the diagnostic may as well show it.
 *
 * @param obj Table the type lives in.
 * @param typeindex Which type.
 * @param name Field name, suffix included.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj`, `name` or `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When `typeindex` names no type.
 * @throws AKBASIC_ERR_UNDEFINED When the type does not declare that field.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_field(akbasic_StructTypeTable *obj, int typeindex, const char *name, akbasic_StructField **dest);

/**
 * @brief Read every `TYPE` block out of the program before it runs.
 *
 * Called from akbasic_runtime_set_mode() beside the label and `DATA` prescans,
 * and for the same reason all three are prescans: a declaration has to be in
 * effect wherever control happens to go, including when a branch skips over the
 * lines that made it. It also means `DIM P@ AS RECT` can never run before the
 * type it names exists.
 *
 * @param obj The runtime, whose source is read and whose table is filled.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 * @throws AKBASIC_ERR_SYNTAX When a block is malformed or unterminated.
 * @throws AKBASIC_ERR_VALUE When a type contains itself by value, or redeclares a name.
 * @throws AKBASIC_ERR_BOUNDS When the table or a field list is full.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_scan(struct akbasic_Runtime *obj);

/**
 * @brief Which `TYPE` block a source line opens, or -1.
 *
 * How `TYPE` skips its own body at run time: the verb asks what block it is
 * looking at and jumps past its `END TYPE`.
 *
 * @param obj Table to search.
 * @param lineno The line the interpreter is on.
 * @param dest Output destination populated by the function; the index, or -1.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `dest` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_structtype_block_at(akbasic_StructTypeTable *obj, int64_t lineno, int *dest);

/* --------------------------------------------------- the runtime half --- */

/**
 * @brief Fill @p dest with a description of an instance: which type, and where.
 *
 * What a structure-valued *expression* evaluates to. Not the data -- an instance
 * is a run of slots and a value is one slot -- so this is the reference that
 * lets assignment find the slots to copy, or lets `.` step into them.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_describe(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *base, bool pointer, akbasic_Value *dest);

/**
 * @brief Copy one instance's slots onto another's.
 *
 * Deep through nested values, and stops at pointers -- as it does for a C struct
 * holding a pointer. A `TYPE` cannot contain itself by value, so the depth is
 * fixed by the type graph before the program runs and this cannot recurse away.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_copy(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *src, akbasic_Value *dest);

/**
 * @brief Resolve a field-access leaf to the slot it addresses.
 *
 * Enforces the strict-pointer rule on the way: `.` requires a structure on its
 * left and `->` requires a pointer, and neither stands in for the other.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_resolve(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_StructField **fielddest, akbasic_Value **slotdest, void **hostdest);

/** @brief Evaluate a field access to the value it yields. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_evaluate_field(struct akbasic_Runtime *obj, akbasic_ASTLeaf *expr, akbasic_Value **dest);

/**
 * @brief Give every slot of a fresh instance the type its field declared.
 *
 * So a record that has only been `DIM`med prints as zeros rather than as
 * undefined values -- an answer instead of a puzzle. Recurses over the type
 * graph, which is finite because a `TYPE` cannot contain itself by value.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_init_slots(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *base);

/** @brief Render an instance the way PRINT does, bounded by depth. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_struct_to_string(struct akbasic_Runtime *obj, int typeindex, akbasic_Value *base, int depth, char *dest, size_t len);

/* ------------------------------------ host-backed instances (src/host.c) --- */

/** @brief Read one host field into a value, converting from its C type. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_read_field(struct akbasic_Runtime *obj, akbasic_StructField *field, void *hostbase, akbasic_Value *dest);

/** @brief Write a value back into one host field, refusing what will not fit. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_write_field(struct akbasic_Runtime *obj, akbasic_StructField *field, void *hostbase, akbasic_Value *src);

/** @brief Refresh a binding's whole shadow run from the host's struct. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_refresh(struct akbasic_Runtime *obj, int typeindex, void *hostbase, akbasic_Value *slots);

#endif // _AKBASIC_STRUCTTYPE_H_
