/**
 * @file sprite.h
 * @brief Spritesheets (one texture, many frames) and the animations cut out of them.
 *
 * The split is deliberate: an akgl_SpriteSheet owns the `SDL_Texture` and the
 * frame grid, and any number of akgl_Sprite animations point at the same sheet
 * and name the frame indices they use. Loading two sprites from the same image
 * loads the image once -- akgl_sprite_load_json looks the sheet up in
 * #AKGL_REGISTRY_SPRITESHEET by resolved path before creating one.
 *
 * Both are pool objects (akgl_heap_next_sprite, akgl_heap_next_spritesheet) and
 * both publish themselves in a registry under their name, which is how
 * akgl_character_sprite_add and the tilemap loader find them. Because loading a
 * sheet uploads a texture, the renderer has to exist first -- so
 * akgl_render_2d_init, or akgl_game_init, before any of this.
 */

#ifndef _AKGL_SPRITE_H_
#define _AKGL_SPRITE_H_

#include <SDL3/SDL_properties.h>
#include <SDL3/SDL.h>
#include <akerror.h>


#define AKGL_SPRITE_MAX_FRAMES                 16
#define AKGL_SPRITE_MAX_NAME_LENGTH            128
#define AKGL_SPRITE_MAX_REGISTRY_SIZE          1024
#define AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH  512


/** @brief Stores a loaded spritesheet texture and frame geometry. */
typedef struct {
    uint8_t refcount;                                   /**< Pool bookkeeping; 0 means the slot is free. Destroys the texture when it reaches 0. */
    SDL_Texture *texture;                               /**< The whole sheet as one GPU texture. Owned by this struct. */
    char name[AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH];   /**< Registry key: the resolved path the image was loaded from. */
    uint16_t sprite_w;                                  /**< Frame width. Vestigial: nothing in the library writes or reads it; the grid comes from akgl_Sprite::width. */
    uint16_t sprite_h;                                  /**< Frame height. Vestigial, same as sprite_w. */
} akgl_SpriteSheet;

/** @brief Describes an animated sprite and its spritesheet reference. */
typedef struct {
    uint8_t refcount;                           /**< Pool bookkeeping; 0 means the slot is free. One reference per character that maps it. */
    uint8_t frameids[AKGL_SPRITE_MAX_FRAMES];   /**< Frame numbers on the sheet, in playback order. Counted left to right, then wrapping to the next row. */
    uint32_t frames;                            /**< How many entries of frameids are in use. */
    uint32_t width;                             /**< Frame width in pixels; also the horizontal stride used to find a frame on the sheet. */
    uint32_t height;                            /**< Frame height in pixels; the vertical stride once frames wrap to the next row. */
    uint32_t speed;                             /**< Nanoseconds one frame is held. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */
    bool loop;                                  /**< Restart from frame 0 when the last frame is reached, instead of holding it. */
    bool loopReverse;                           /**< Play back down to frame 0 instead of jumping to it -- a ping-pong loop. */
    char name[AKGL_SPRITE_MAX_NAME_LENGTH];     /**< Registry key, from the JSON `name` field. */
    akgl_SpriteSheet *sheet;                    /**< The sheet these frames are cut from. Borrowed, not owned. */
} akgl_Sprite;

// initializes a new sprite to use the given sheet and otherwise sets to zero
/**
 * @brief Zero a pooled sprite, bind it to a sheet, and publish it in the sprite registry.
 *
 * Sets the name and the sheet and takes the first reference; everything else --
 * frame list, geometry, speed, loop flags -- is left at zero for the caller, or
 * akgl_sprite_load_json, to fill in. The sheet is borrowed, so this does *not*
 * take a reference on it: releasing the sheet out from under a live sprite
 * leaves a dangling pointer.
 *
 * @param spr   Pooled sprite to initialize, normally from akgl_heap_next_sprite.
 *              Required. Any previous contents are discarded.
 * @param name  Registry key, NUL-terminated. Required. Copied at a fixed
 *              #AKGL_SPRITE_MAX_NAME_LENGTH bytes, so a shorter string reads
 *              past its end -- pass a name from an akgl_String or another
 *              buffer of at least that size. An existing entry with the same
 *              name is silently replaced.
 * @param sheet The spritesheet this sprite's frames come from. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p spr, @p name, or @p sheet is `NULL`.
 * @throws AKERR_KEY If the sprite cannot be written into #AKGL_REGISTRY_SPRITE
 *         -- in practice, because akgl_registry_init has not run.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_sprite_initialize(akgl_Sprite *spr, char *name, akgl_SpriteSheet *sheet);
// loads a given image file into a new spritesheet
/**
 * @brief Load an image file as a texture and publish it as a spritesheet.
 *
 * Uploads @p filename through SDL_image using the global `akgl_renderer`, so the
 * renderer must already be initialized. The sheet is registered in
 * #AKGL_REGISTRY_SPRITESHEET under @p filename, which is the key
 * akgl_sprite_load_json looks it up by to avoid loading the same image twice.
 *
 * @param sheet    Pooled spritesheet to initialize, normally from
 *                 akgl_heap_next_spritesheet. Required. Zeroed first, so a
 *                 texture it already held is leaked rather than destroyed.
 * @param sprite_w Frame width in pixels. Accepted and currently discarded --
 *                 the frame grid actually used at draw time comes from the
 *                 akgl_Sprite's `width`/`height`, not from here.
 * @param sprite_h Frame height in pixels. Same caveat as @p sprite_w.
 * @param filename Path to the image, in any format SDL_image can decode.
 *                 Required. Doubles as the registry key, so callers should pass
 *                 an already-resolved path; two spellings of the same file are
 *                 two sheets. Truncated at
 *                 #AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p sheet or @p filename is `NULL`.
 * @throws AKGL_ERR_SDL If the image cannot be loaded -- missing, unreadable, or
 *         a format SDL_image was not built with. The message carries
 *         `SDL_GetError()`.
 * @throws AKERR_KEY If the sheet cannot be written into
 *         #AKGL_REGISTRY_SPRITESHEET.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_spritesheet_initialize(akgl_SpriteSheet *sheet, int sprite_w, int sprite_h, char *filename);
/**
 * @brief Build a sprite -- and, if needed, its spritesheet -- from a JSON definition file.
 *
 * Reads `name`, `width`, `height`, `speed` (seconds, scaled to milliseconds),
 * `loop`, `loopReverse`, a `frames` array of frame indices, and a `spritesheet`
 * object holding `filename`, `frame_width`, and `frame_height`. The sheet's
 * `filename` is resolved *relative to the directory of @p filename*, so a sprite
 * definition can sit next to its image and move with it.
 *
 * If a spritesheet with that resolved path is already registered it is reused;
 * otherwise one is claimed from the pool and loaded. On failure both the sprite
 * and any sheet loaded for it are released again.
 *
 * @param filename Path to the JSON document. Required. Must be shorter than
 *                 #AKGL_MAX_STRING_LENGTH -- it is copied into a pooled string
 *                 so `dirname` can be taken without modifying the caller's
 *                 buffer.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p filename is `NULL`, or if the file cannot be
 *         opened or does not parse. The message carries jansson's line number
 *         and text.
 * @throws AKERR_OUTOFBOUNDS If @p filename is at least #AKGL_MAX_STRING_LENGTH
 *         bytes long, or if the `frames` array is indexed past its end.
 * @throws AKERR_KEY If a required key is absent, or if the sprite or sheet
 *         cannot be added to its registry. The message names the key.
 * @throws AKERR_TYPE If a key is present with the wrong JSON type -- `speed` as
 *         a string, `frames` as an object.
 * @throws AKGL_ERR_SDL If the spritesheet image fails to load.
 * @throws AKGL_ERR_HEAP If the sprite, spritesheet, or string pool is exhausted.
 *
 * @note The `frames` array is not bounded against #AKGL_SPRITE_MAX_FRAMES. A
 *       definition with more than 16 frames writes past `frameids` into the
 *       rest of the struct.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_sprite_load_json(char *filename);

/**
 * @brief Work out where one animation frame sits on its spritesheet.
 *
 * Frames are numbered in reading order across the sheet: multiply the frame
 * number by the sprite width, then wrap whole rows off the right-hand edge of
 * the texture and step down by the sprite height for each. Callers use the
 * result as the source rectangle for `SDL_RenderTexture`.
 *
 * @param self      The sprite whose sheet and frame geometry to use. Required,
 *                  along with its `sheet`.
 * @param srccoords Receives the frame's rectangle on the sheet, in pixels.
 *                  Required -- the return value is the error context.
 * @param frameid   Index into the sprite's `frameids` array, not a frame number
 *                  on the sheet. Not bounds-checked against `frames` or
 *                  #AKGL_SPRITE_MAX_FRAMES; an out-of-range index reads a
 *                  neighbouring struct member and yields a nonsense rectangle
 *                  rather than an error.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self, @p srccoords, or `self->sheet` is
 *         `NULL`. Note that `sheet->texture` is dereferenced without a check,
 *         so a registered-but-unloaded sheet is a crash rather than an error.
 */
akerr_ErrorContext *akgl_spritesheet_coords_for_frame(akgl_Sprite *self, SDL_FRect *srccoords, uint8_t frameid);

#endif //_AKGL_SPRITE_H_
