/**
 * @file host.h
 * @brief Sharing a host program's own C structures with a script.
 *
 * A BASIC `TYPE` and a host C struct are the same thing seen from two sides, so
 * both live in one type table and everything the language already does --
 * copy on assign, `PTR TO`, `.` and `->` -- works across the boundary with no
 * second set of rules.
 *
 * The host describes its struct once as a table of field descriptors and binds
 * an instance to a name. A script then reads and writes the game's real memory:
 *
 * ```
 * FOE@.HP# = FOE@.HP# - 10
 * ```
 *
 * decrements the host's `hp` in place, with no marshalling step the host has to
 * remember to run.
 *
 * **How the sharing works.** A binding takes a shadow run of value slots from
 * the same pool a `DIM`med record uses, and every field read refreshes its slot
 * from host memory while every write converts back and stores. So the script
 * always sees current values and its writes always land, without the
 * interpreter having to hold two storage models for one language.
 *
 * **Conversion refuses rather than truncates.** Writing 70000 into an `int16_t`
 * field or 40 characters into a `char[32]` is an error naming the field, not a
 * silent wrap. Two conversions are lossy and say so in the documentation: a host
 * `float` round-trips through BASIC's doubles, and a `CSTRING` has a width BASIC
 * strings do not.
 *
 * **This is the one place akbasic holds a pointer it did not allocate.**
 * Everything else is pool-bounded and diagnosable. See akbasic_host_bind().
 */

#ifndef _AKBASIC_HOST_H_
#define _AKBASIC_HOST_H_

#include <stddef.h>

#include <akerror.h>

#include <akbasic/types.h>

struct akbasic_Runtime;

/** @brief How a host field is represented in C, which is what says how to convert it. */
typedef enum
{
    AKBASIC_HOSTFIELD_INT8 = 0,
    AKBASIC_HOSTFIELD_INT16,
    AKBASIC_HOSTFIELD_INT32,
    AKBASIC_HOSTFIELD_INT64,
    AKBASIC_HOSTFIELD_UINT8,
    AKBASIC_HOSTFIELD_UINT16,
    AKBASIC_HOSTFIELD_UINT32,
    AKBASIC_HOSTFIELD_BOOL,
    AKBASIC_HOSTFIELD_FLOAT,		/* C float; BASIC floats are double	*/
    AKBASIC_HOSTFIELD_DOUBLE,
    AKBASIC_HOSTFIELD_CSTRING,		/* fixed char[]; `width` is its size	*/
    AKBASIC_HOSTFIELD_STRUCT		/* nested host struct; `typename_` names it */
} akbasic_HostFieldKind;

/** @brief One field of a host structure, in BASIC terms and in C terms. */
typedef struct
{
    const char *name;			/** BASIC field name, suffix included	*/
    akbasic_HostFieldKind kind;
    size_t offset;			/** offsetof() within the host struct	*/
    size_t width;			/** sizeof() the member; CSTRING needs it */
    const char *typename_;		/** for STRUCT, the registered type name */
} akbasic_HostField;

/** @brief A host structure type: a name, a size, and its fields. */
typedef struct
{
    const char *name;			/** BASIC type name, no suffix		*/
    size_t size;			/** sizeof() the host struct		*/
    const akbasic_HostField *fields;
    int fieldcount;
} akbasic_HostType;

/**
 * @brief Build a field descriptor from the member itself.
 *
 * Takes the offset and the width from the same member, so the two cannot drift
 * apart -- writing them out by hand is two chances to name the wrong member and
 * no way to notice. That is the only reason this is a macro.
 */
#define AKBASIC_HOST_FIELD(__struct, __member, __name, __kind)		\
    { (__name), (__kind), offsetof(__struct, __member),			\
      sizeof(((__struct *)0)->__member), NULL }

/** @brief The same, for a field that is itself a registered host structure. */
#define AKBASIC_HOST_FIELD_STRUCT(__struct, __member, __name, __type)	\
    { (__name), AKBASIC_HOSTFIELD_STRUCT, offsetof(__struct, __member),	\
      sizeof(((__struct *)0)->__member), (__type) }

/**
 * @brief Register a host structure type under its BASIC name.
 *
 * The descriptor is **borrowed, not copied**: it must outlive the runtime, which
 * is why every example makes it `static const`.
 *
 * Registering before the script is loaded is the normal case. A host type and a
 * `TYPE` the script declares share one namespace, so a script cannot declare a
 * type the host already registered -- and would be refused if it tried.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param type The host's description of its own struct.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When either argument is NULL.
 * @throws AKBASIC_ERR_VALUE When a field name carries no type suffix, a nested
 *         type is not registered, or the name is already taken.
 * @throws AKBASIC_ERR_BOUNDS When the type table or a field list is full.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_register_type(struct akbasic_Runtime *obj, const akbasic_HostType *type);

/**
 * @brief Bind one instance of a registered host type to a script variable.
 *
 * Creates the variable in the script's *outermost* scope, for the reason
 * akbasic_runtime_global() exists: a binding made during a suspended run would
 * otherwise land in whatever `FOR` or `GOSUB` body happens to be active and die
 * with it.
 *
 * **`instance` is borrowed and never copied.** It must outlive the binding. A
 * struct on a stack frame that returns, or one the host frees, leaves the script
 * reading freed memory -- and this is the only way to get a wild pointer into an
 * interpreter that otherwise cannot produce one. Bind globals or arena objects,
 * and call akbasic_host_unbind() before the storage goes.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param name Script variable name, `@`-suffixed.
 * @param typename_ A type registered by akbasic_host_register_type().
 * @param instance The host's struct. Borrowed; must outlive the binding.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 * @throws AKBASIC_ERR_UNDEFINED When `typename_` is not registered.
 * @throws AKBASIC_ERR_VALUE When `name` does not end in `@`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_bind(struct akbasic_Runtime *obj, const char *name, const char *typename_, void *instance);

/**
 * @brief Point an existing binding at a different instance of the same type.
 *
 * The per-frame call. A host iterating its enemies rebinds one name rather than
 * creating eight, which keeps the binding flat and lets one script be written
 * against `FOE@` and run once per enemy.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param name A name already bound by akbasic_host_bind().
 * @param instance The host's struct. Borrowed; must outlive the binding.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When any argument is NULL.
 * @throws AKBASIC_ERR_UNDEFINED When `name` is not bound.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_rebind(struct akbasic_Runtime *obj, const char *name, void *instance);

/**
 * @brief Break a binding, leaving the name pointing at nothing.
 *
 * Call it before the instance's storage goes away. After this a script reading
 * that name is refused by name rather than reading freed memory, which is the
 * difference between a diagnosable script error and undefined behaviour.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param name A name already bound by akbasic_host_bind().
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When either argument is NULL.
 * @throws AKBASIC_ERR_UNDEFINED When `name` is not bound.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_host_unbind(struct akbasic_Runtime *obj, const char *name);

#endif // _AKBASIC_HOST_H_
