/**
 * @file character.h
 * @brief The reusable half of an actor: movement constants and a state-to-sprite map.
 *
 * A character is the template; an akgl_Actor is the instance. Everything that is
 * the same for every goblin on the map -- top speed, acceleration, frame dwell
 * time, and which sprite to draw for which combination of actor-state bits --
 * lives here and is shared by pointer, so a hundred actors cost one character.
 *
 * Characters are pool objects (akgl_heap_next_character) and are published in
 * the #AKGL_REGISTRY_CHARACTER property registry under their name, which is how
 * akgl_actor_set_character finds them. akgl_registry_init must have run first.
 */

#ifndef _AKGL_CHARACTER_H_
#define _AKGL_CHARACTER_H_

#include <SDL3/SDL_properties.h>
#include <akgl/types.h>
#include <akgl/collision.h>
#include <akgl/sprite.h>

#define AKGL_CHARACTER_MAX_NAME_LENGTH  128

/** @brief Defines reusable movement parameters and actor-state sprite bindings. */
typedef struct akgl_Character {
    uint8_t refcount;                                   /**< Pool bookkeeping; 0 means the slot is free. */
    char name[AKGL_CHARACTER_MAX_NAME_LENGTH];   /**< Registry key. Truncated, not rejected, if the source name is longer. */
    SDL_PropertiesID state_sprites;                     /**< State bitmask (decimal, as a string) -> akgl_Sprite *. */
    uint64_t speedtime;                                 /**< Nanoseconds one sprite frame is held before advancing. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */
    float32_t ax;                                       /**< Acceleration along x, world units per second squared. Copied into an actor by akgl_actor_set_character. */
    float32_t ay;                                       /**< Acceleration along y. */
    float32_t az;                                       /**< Acceleration along z. Not read from JSON; stays 0 unless set by hand. */
    float32_t sx;                                       /**< Maximum speed along x, world units per second. */
    float32_t sy;                                       /**< Maximum speed along y. */
    float32_t sz;                                       /**< Maximum speed along z. Not read from JSON; stays 0 unless set by hand. */
    akgl_CollisionShape shape;                          /**< The collision volume every actor of this character gets, unless the actor overrides it. A zeroed shape (#AKGL_COLLISION_SHAPE_NONE) means actors of this character do not collide, which is what a character loaded before shapes existed still gets. */
    akerr_ErrorContext AKERR_NOIGNORE *(*sprite_add)(struct akgl_Character *, akgl_Sprite *, int);       /**< Bound to akgl_character_sprite_add by akgl_character_initialize. */
    akerr_ErrorContext AKERR_NOIGNORE *(*sprite_get)(struct akgl_Character *, int, akgl_Sprite **);      /**< Bound to akgl_character_sprite_get by akgl_character_initialize. */
} akgl_Character;


/**
 * @brief Zero a pooled character, name it, and publish it in the character registry.
 *
 * Wipes the struct, copies @p name into it, creates the empty state-to-sprite
 * property set, binds the `sprite_add`/`sprite_get` function pointers, inserts
 * the character into #AKGL_REGISTRY_CHARACTER under @p name, and takes the first
 * reference. Everything numeric (speeds, accelerations, `speedtime`) is left at
 * zero for the caller -- or akgl_character_load_json -- to fill in.
 *
 * @param obj Pooled character to initialize, normally straight from
 *                 akgl_heap_next_character. Required. Any previous contents are
 *                 discarded without releasing the sprites they referenced.
 * @param name     Registry key, NUL-terminated. Required. Truncated at
 *                 #AKGL_CHARACTER_MAX_NAME_LENGTH. A name already in the
 *                 registry is silently replaced, and the character it displaced
 *                 becomes unreachable rather than being released.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p basechar or @p name is `NULL`, or if SDL
 *         cannot create the state-to-sprite property set (out of memory).
 * @throws AKERR_KEY If the character cannot be written into
 *         #AKGL_REGISTRY_CHARACTER -- in practice, because akgl_registry_init
 *         has not run and the registry id is still 0.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_initialize(akgl_Character *obj, char *name);
/**
 * @brief Bind a sprite to one exact combination of actor-state bits.
 *
 * The map key is the decimal spelling of @p state, so lookups match the *whole*
 * value: a sprite added for `FACE_LEFT|MOVING_LEFT` is not found by a lookup for
 * `FACE_LEFT` alone. Callers are expected to register every combination they
 * intend to draw. Adding a sprite takes a reference on it.
 *
 * @param basechar Character to add the mapping to. Required. Must already have
 *                 been through akgl_character_initialize.
 * @param ref      Sprite to draw for @p state. Required. Its `refcount` is
 *                 incremented, so the character keeps it alive.
 * @param state    The exact actor-state bitmask (`AKGL_ACTOR_STATE_*`) this
 *                 sprite is for. 0 is accepted and is a usable key.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p basechar or @p ref is `NULL`.
 *
 * @note Re-adding a different sprite for a @p state that is already mapped
 *       replaces the entry without releasing the sprite it displaced, so the
 *       displaced sprite's reference is never given back.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_sprite_add(akgl_Character *basechar, akgl_Sprite *ref, int state);
/**
 * @brief Look up the sprite bound to one exact combination of actor-state bits.
 *
 * The counterpart to akgl_character_sprite_add, and matched the same way: an
 * exact match on the whole @p state value, with no fallback to a subset of the
 * bits and no default sprite. Unlike most lookups in this codebase, *not finding
 * one is an error* rather than a successful "nothing here" -- an actor with no
 * sprite for its current state cannot be drawn.
 *
 * @param basechar Character to search. Required.
 * @param state    The exact actor-state bitmask to look up.
 * @param dest     Receives the mapped sprite. Required -- the return value is
 *                 spoken for by the error context. Set to `NULL` when there is
 *                 no mapping, alongside the AKERR_KEY error.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p basechar or @p dest is `NULL`.
 * @throws AKERR_KEY If no sprite is bound to exactly that @p state. The message
 *         carries the state both as a decimal and as a bit pattern.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_sprite_get(akgl_Character *basechar, int state, akgl_Sprite **dest);

// This is an SDL iterator so we can't return our error state from it.
/**
 * @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one state-to-sprite entry.
 *
 * Currently implements exactly one operation: with #AKGL_ITERATOR_OP_RELEASE
 * set, it hands the mapped sprite back to its heap layer. Every other flag is
 * ignored here. Used to tear a character's sprite map down entry by entry.
 *
 * @param userdata The akgl_Iterator carrying the operation flags, passed through
 *                 by `SDL_EnumerateProperties`. Required despite the `void *` --
 *                 a `NULL` here is an error, not "no operations".
 * @param props    The character's `state_sprites` set, supplied by SDL.
 * @param name     The property key: one state bitmask in decimal. Required.
 *
 * @warning This is an SDL callback, so it returns `void` and has nowhere to
 *          propagate an error to. It ends in `FINISH_NORETURN`, which logs the
 *          stack trace and then calls libakerror's unhandled-error handler --
 *          whose default implementation **exits the process** with the error
 *          status. A missing sprite or a `NULL` @p userdata therefore terminates
 *          the game rather than skipping an entry. Install your own
 *          `akerr_handler_unhandled_error` if that is not what you want.
 */
void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID props, const char *name);

/**
 * @brief Build a character from a JSON definition file and register it.
 *
 * Claims a character from the pool, then reads: `name` (the registry key),
 * `sprite_mappings` (an array of `{ "sprite": <name>, "state": [<state names>] }`
 * objects), `speedtime` in seconds, `speed_x`, `speed_y`, `acceleration_x`, and
 * `acceleration_y`. Each mapping's `state` array is OR-ed together into one
 * bitmask by looking every name up in #AKGL_REGISTRY_ACTOR_STATE_STRINGS, so
 * `["FACE_LEFT", "MOVING_LEFT"]` becomes a single key.
 *
 * Every referenced sprite must already be in #AKGL_REGISTRY_SPRITE: this loads
 * characters, not sprites, so akgl_sprite_load_json runs first. On any failure
 * the pooled character is released again.
 *
 * @param filename Path to the JSON document. Required. Used verbatim -- it is
 *                 not resolved against `SDL_GetBasePath()`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p filename is `NULL`; if the file cannot be
 *         opened or does not parse (the message carries jansson's line number
 *         and text); or if a mapping names a sprite that is not in the sprite
 *         registry.
 * @throws AKERR_KEY If a `state` array contains a name that is not a known
 *         actor state, if a required top-level key is absent, or if the
 *         character cannot be added to the registry.
 * @throws AKERR_TYPE If a key is present but holds the wrong JSON type -- for
 *         example `speedtime` as a string. The message names the key.
 * @throws AKERR_OUTOFBOUNDS If a `state` array is indexed past its end.
 * @throws AKGL_ERR_HEAP If the character pool or the string pool is exhausted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_character_load_json(char *filename);

#endif // _AKGL_CHARACTER_H_
