/**
 * @file renderer.h
 * @brief The pluggable rendering backend: a record of function pointers plus an initializer.
 *
 * There is one backend shipped, the 2D SDL one, and `akgl_render_2d_init` is its
 * initializer -- it creates the window and the `SDL_Renderer` and then fills in
 * the six `akgl_render_2d_*` entry points. A different renderer is a different
 * initializer populating the same struct, not a branch inside these functions.
 *
 * Those are two separable jobs, and a host that already owns an `SDL_Renderer`
 * -- an embedded interpreter, which must not create the window -- wants only the
 * second. akgl_render_2d_bind() is that half on its own; akgl_render_2d_init()
 * makes the window and then calls it.
 *
 * Callers do not normally name the `akgl_render_2d_*` functions directly; they
 * go through the pointers on the backend (`renderer->frame_start(renderer)`),
 * which is what makes the swap possible. The global `akgl_renderer` in game.h is the
 * instance the rest of the library draws through.
 *
 * A frame is `frame_start` (clear), any number of `draw_*` calls, then
 * `frame_end` (present).
 */

#ifndef _AKGL_RENDERER_H_
#define _AKGL_RENDERER_H_

#include <SDL3/SDL.h>

#include <akerror.h>

#include <akgl/iterator.h>

/** @brief Defines a pluggable renderer backend and drawing callbacks. */
typedef struct akgl_RenderBackend {
    SDL_Renderer *sdl_renderer;         /**< The SDL renderer, created by the initializer. Owned by SDL, not freed here. */
    akerr_ErrorContext AKERR_NOIGNORE *(*shutdown)(struct akgl_RenderBackend *self);         /**< Tear the backend down. */
    akerr_ErrorContext AKERR_NOIGNORE *(*frame_start)(struct akgl_RenderBackend *self);      /**< Begin a frame: clear the target. */
    akerr_ErrorContext AKERR_NOIGNORE *(*frame_end)(struct akgl_RenderBackend *self);        /**< End a frame: present it. */
    akerr_ErrorContext AKERR_NOIGNORE *(*draw_texture)(struct akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip);  /**< Blit one texture. */
    akerr_ErrorContext AKERR_NOIGNORE *(*draw_mesh)(struct akgl_RenderBackend *self);        /**< Reserved for a 3D backend; the 2D one refuses. */
    akerr_ErrorContext AKERR_NOIGNORE *(*draw_world)(struct akgl_RenderBackend *self, akgl_Iterator *opflags);   /**< Draw the whole scene, layer by layer. */
} akgl_RenderBackend;

/**
 * @brief Tear the 2D backend down.
 *
 * A no-op placeholder that always succeeds. The window and `SDL_Renderer` this
 * backend created are still owned by SDL and are reclaimed by `SDL_Quit`, so
 * there is nothing here to release yet; the hook exists so a backend that *does*
 * own resources has somewhere to free them.
 *
 * @param self The backend to shut down. Ignored -- not even dereferenced, so
 *             `NULL` is currently harmless.
 * @return `NULL` always. It has no failure path today; check it anyway, because
 *         a backend that acquires anything will grow one.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_shutdown(akgl_RenderBackend *self);
/**
 * @brief Begin a frame: clear the render target to opaque black.
 * @param self The backend to draw through. Required, and dereferenced *before*
 *             it is checked -- a `NULL` @p self is a crash, not an error.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
 *         backend was never run through akgl_render_2d_init.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_start(akgl_RenderBackend *self);
/**
 * @brief End a frame: present everything drawn since akgl_render_2d_frame_start.
 * @param self The backend to draw through. Required, and dereferenced *before*
 *             it is checked -- a `NULL` @p self is a crash, not an error.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If `self->sdl_renderer` is `NULL`, which means the
 *         backend was never run through akgl_render_2d_init.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_frame_end(akgl_RenderBackend *self);
/**
 * @brief Blit a texture, rotating it only when asked to.
 *
 * A rotation of exactly 0 takes the plain `SDL_RenderTexture` path, which is why
 * @p center and @p flip are only consulted when @p angle is non-zero.
 *
 * @param self    The backend to draw through. Required.
 * @param texture The texture to blit. Required.
 * @param src     The rectangle of @p texture to take, in pixels. Optional --
 *                `NULL` means the whole texture.
 * @param dest    Where to put it on the render target, in pixels. Optional --
 *                `NULL` means stretch to fill the whole target. Not an output
 *                parameter despite the name.
 * @param angle   Clockwise rotation in degrees. Exactly 0 skips rotation
 *                entirely, along with @p center and @p flip.
 * @param center  Pivot for the rotation, relative to @p dest. Required *when*
 *                @p angle is non-zero, ignored otherwise. SDL's own "NULL means
 *                the centre of dest" convention is not available here.
 * @param flip    Horizontal/vertical mirroring. Applied only on the rotated
 *                path; ignored when @p angle is 0.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or @p texture is `NULL`; if @p angle is
 *         non-zero and @p center is `NULL`; or if the SDL draw call itself
 *         fails, in which case the message carries `SDL_GetError()`. That last
 *         case is a reused status rather than a pointer problem.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_texture(akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip);
/**
 * @brief Draw geometry. Not implemented by the 2D backend.
 *
 * The hook exists so a 3D backend has a slot to fill; this one refuses every
 * call. It is not a stub that quietly does nothing -- it fails loudly, so a
 * caller that reaches it finds out at once.
 *
 * @param self The backend. Ignored.
 * @return Never `NULL`.
 * @throws AKERR_API Always, with the message "Not implemented".
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_mesh(akgl_RenderBackend *self);
/**
 * @brief Draw the whole scene: every tilemap layer, and the actors standing on it.
 *
 * Walks layers from 0 to #AKGL_TILEMAP_MAX_LAYERS. For each, it draws that layer
 * of the global `akgl_gamemap` through `akgl_camera` (if the map has that many layers),
 * then sweeps the actor pool and calls `renderfunc` on every live actor whose
 * `layer` matches. Sweeping in layer order is what puts actors in front of the
 * scenery they stand on and behind the scenery they walk under.
 *
 * It reads the globals `akgl_gamemap`, `akgl_camera`, and `akgl_heap_actors` directly rather
 * than taking them as arguments, so there is exactly one world to draw.
 *
 * @param self    The backend to draw through. Required.
 * @param opflags Iterator flags. Optional -- `NULL` substitutes a zeroed set.
 *                Currently ignored either way: this function sweeps the actor
 *                pool itself instead of going through
 *                akgl_registry_iterate_actor, so no `AKGL_ITERATOR_OP_*` bit
 *                reaches anything. The parameter is here for the backend
 *                signature and for the layer-mask support that has yet to be
 *                wired up.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self is `NULL`.
 * @throws AKERR_* Whatever akgl_tilemap_draw or an actor's own `renderfunc`
 *         raises; the first failure aborts the frame and propagates unchanged.
 *
 * @warning Neither the global `akgl_gamemap` nor a live actor's `renderfunc` is
 *          checked before use, so drawing before akgl_tilemap_load, or with a
 *          hand-built actor that was never run through akgl_actor_initialize,
 *          is a crash rather than an error context.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend *self, akgl_Iterator *opflags);

/**
 * @brief Install the 2D backend's methods on a backend the caller owns.
 *
 * The vtable half of akgl_render_2d_init(), with no window, no renderer, and no
 * property registry involved: it points @p self's six function pointers at the
 * `akgl_render_2d_*` entry points and returns. `sdl_renderer` is not touched,
 * so a caller who has already put its own `SDL_Renderer` there keeps it, and a
 * caller who has not gets a backend whose entry points all report
 * `AKERR_NULLPOINTER` rather than crash.
 *
 * This is the entry point for a host that owns its own window -- it can drive
 * akgl_actor_render(), akgl_tilemap_draw() and the rest through a backend
 * libakgl never created. akgl_render_2d_init() is the same thing with a window
 * in front of it.
 *
 * @param self The backend to bind. Required. Only the method pointers are
 *             written.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_bind(akgl_RenderBackend *self);

/**
 * @brief Create the window and SDL renderer, and bind the 2D backend's methods.
 *
 * Reads `akgl_game.screenwidth` and `akgl_game.screenheight` from the property registry
 * (both defaulting to the string "0", which asks SDL for a zero-sized window),
 * creates the window and renderer with `akgl_game.uri` as the title, points `akgl_camera`
 * at the full screen rectangle, and then calls akgl_render_2d_bind() to install
 * the six `akgl_render_2d_*` function pointers on @p self.
 *
 * Because the dimensions come from the registry, akgl_registry_init_properties
 * and the property writes have to happen first -- see the note on
 * akgl_registry_init about the properties registry not being initialized for
 * callers that skip akgl_game_init.
 *
 * @param self The backend to initialize. Required. Its `sdl_renderer` and method
 *             pointers are overwritten; anything it held before is not released.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self is `NULL`.
 * @throws AKERR_VALUE If `akgl_game.screenwidth` or `akgl_game.screenheight` is set to
 *         something that is not a base-10 integer.
 * @throws ERANGE If either dimension does not fit in an `int`.
 * @throws AKGL_ERR_SDL If the window and renderer cannot be created. The message
 *         carries `SDL_GetError()`.
 * @throws AKGL_ERR_HEAP If the string pool is exhausted while reading the two
 *         properties.
 *
 * @note The two pooled strings holding the dimensions are only released on the
 *       success path, so each failed initialization leaks two string slots.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_init(akgl_RenderBackend *self);

#endif // _AKGL_RENDERER_H_
