/**
 * @file game.h
 * @brief Process-wide game state, the startup sequence, and the per-frame tick.
 *
 * This is the top of the library. `akgl_game_init` brings up SDL, the pools, the
 * registries and the audio and font engines; `akgl_game_update` is one frame --
 * update every actor, step the physics, draw the world.
 *
 * There is exactly one of everything. `akgl_renderer`, `akgl_physics`, `akgl_camera`, and
 * `akgl_gamemap` are globals pointing at the `akgl_default_*` storage below them, so a
 * program can swap in its own instance by reassigning the pointer without the
 * rest of the library knowing. That is the whole extent of the indirection:
 * there is no notion of two worlds at once.
 *
 * The startup order that actually works:
 *
 * 1. fill in `akgl_game.name`, `akgl_game.version`, and `akgl_game.uri` -- akgl_game_init
 *    refuses to run without them;
 * 2. akgl_game_init();
 * 3. akgl_registry_load_properties() or akgl_set_property(), to configure
 *    screen size, physics constants and so on;
 * 4. akgl_render_2d_init(renderer) and akgl_physics_factory(physics, ...), both
 *    of which read that configuration;
 * 5. load assets, then loop on akgl_game_update().
 *
 * @warning None of this is thread-safe beyond the `akgl_game.state` mutex, and that
 *          mutex protects the state flags, not the pools or the registries.
 */

#ifndef _AKGL_GAME_H_
#define _AKGL_GAME_H_

#include <stdint.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <akgl/types.h>
#include <akgl/tilemap.h>
#include <akgl/renderer.h>
#include <akgl/physics.h>
// AKGL_VERSION used to be defined here by hand, which is how akgl.pc came to
// ship an empty Version field: nothing tied the two together.
#include <akgl/version.h>

/** @brief Slot in ::akgl_tracks reserved for background music. Note that slot 0 is unused. */
#define AKGL_GAME_AUDIO_TRACK_BGM        1
/** @brief Size of the ::akgl_tracks table. Every simultaneous sound needs its own slot. */
#define AKGL_GAME_AUDIO_MAX_TRACKS       64

/** @brief Nanoseconds in one second. The unit `SDL_GetTicksNS` reports in. */
#define AKGL_TIME_ONESEC_NS              1000000000
/**
 * @brief Nanoseconds in one millisecond. The scale factor from JSON durations to internal ones.
 *
 * Sprite and character definitions state frame durations in milliseconds
 * because that is what a human writing one wants to type; everything that
 * compares them against `SDL_GetTicksNS` needs nanoseconds.
 *
 * This was called `AKGL_TIME_ONESEC_MS` until 0.5.0, which said "one second in
 * milliseconds" and held 1000000 -- a name and a value that described two
 * different quantities. Both callers that meant the scale factor were right;
 * the one that read it as a one-second budget was wrong by a factor of a
 * thousand. See akgl_game_state_lock.
 */
#define AKGL_TIME_ONEMS_NS               1000000

/**
 * @brief How long akgl_game_state_lock keeps trying for the state mutex, in milliseconds.
 *
 * A deliberate ceiling rather than a blocking wait: a deadlock here reports an
 * error the caller can act on instead of hanging the process.
 */
#define AKGL_GAME_STATE_LOCK_BUDGET_MS   1000
/** @brief How long akgl_game_state_lock sleeps between attempts, in milliseconds. */
#define AKGL_GAME_STATE_LOCK_RETRY_MS    100

/* ==================== GAME STATE VARIABLES =================== */

/** @brief Stores application-defined game-state flags. */
typedef struct {
    int32_t flags;  /**< Meaning is entirely the application's; the library never reads it. Guard changes with akgl_game_state_lock. */
} akgl_GameState;

/** @brief Stores game metadata, timing, synchronization, and FPS accounting. */
typedef struct {
    char libversion[32];        /**< libakgl's version, stamped by akgl_game_init. Compared on load to refuse a save from another build. */
    char version[32];           /**< The *application's* version. Caller-supplied, required, and must be a semver string. */
    char name[256];             /**< Application name. Caller-supplied and required; also becomes SDL's app metadata. */
    char uri[256];              /**< Application URI, e.g. a reverse-DNS identifier. Caller-supplied, required, and used as the window title. */
    akgl_GameState state;       /**< The application's own state flags. */
    SDL_Mutex *statelock;       /**< Guards `state`. Created by akgl_game_init. */
    int16_t fps;                /**< Frames drawn during the last completed second. Recomputed once per second, not per frame. */
    SDL_Time gameStartTime;     /**< `SDL_GetTicksNS()` at akgl_game_init. */
    SDL_Time lastIterTime;      /**< Timestamp of the most recent akgl_game_update_fps call. */
    SDL_Time lastFPSTime;       /**< When `fps` was last recomputed. */
    int16_t framesSinceUpdate;  /**< Frames counted so far in the current second. */
    void (*lowfpsfunc)(void);   /**< Called every frame while `fps` is under 30. akgl_game_init installs akgl_game_lowfps, and akgl_game_update_fps installs it too if it finds this NULL -- an embedder that binds its own renderer rather than calling akgl_game_init used to crash here on frame one. Replace it to do something more useful than log. */
} akgl_Game;

/** @brief The SDL window, created by akgl_render_2d_init. `NULL` until then. */
extern SDL_Window *akgl_window;
/** @brief The background music, loaded by akgl_load_start_bgm. `NULL` until then. */
extern MIX_Audio *akgl_bgm;
/** @brief The mixer device, created by akgl_game_init. Everything audio goes through it. */
extern MIX_Mixer *akgl_mixer;
/** @brief Playback tracks by slot. #AKGL_GAME_AUDIO_TRACK_BGM is the music track; the rest are the application's to assign. */
extern MIX_Track *akgl_tracks[AKGL_GAME_AUDIO_MAX_TRACKS];
/** @brief Storage behind the default `akgl_camera`. Point `akgl_camera` elsewhere rather than reaching for this. */
extern SDL_FRect akgl_default_camera;
/** @brief The one game object: metadata, timing, and FPS accounting. */
extern akgl_Game akgl_game;
/** @brief Storage behind the default `akgl_renderer`. */
extern akgl_RenderBackend akgl_default_renderer;
/** @brief Storage behind the default `akgl_physics`. */
extern akgl_PhysicsBackend akgl_default_physics;
/** @brief Storage behind the default `akgl_gamemap`. */
extern akgl_Tilemap akgl_default_gamemap;

/** @brief Currently active tilemap. */
extern akgl_Tilemap *akgl_gamemap;
/** @brief Currently active renderer. */
extern akgl_RenderBackend *akgl_renderer;
/** @brief Currently active physics backend. */
extern akgl_PhysicsBackend *akgl_physics;
/** @brief Currently active camera. */
extern SDL_FRect *akgl_camera;

/**
 * @brief True when every bit of `y` is set in `x`. Not "any of them" -- all of them.
 *
 * Fully parenthesized, so it composes: `!AKGL_BITMASK_HAS(a, b)` and
 * `AKGL_BITMASK_HAS(a, b) && cond` both mean what they read as. Until 0.5.0 it
 * expanded to a bare `(x & y) == y`, so a negation bound to the `&` and parsed
 * as `!(a & b) == b`. Nothing in the tree negated it -- #AKGL_BITMASK_HASNOT
 * exists for that -- which is the only reason it was latent rather than live.
 */
#define AKGL_BITMASK_HAS(x, y)    ((((x) & (y)) == (y)))
/** @brief True when at least one bit of `y` is missing from `x`. */
#define AKGL_BITMASK_HASNOT(x, y) ((((x) & (y)) != (y)))
/** @brief Set every bit of `y` in `x`. Modifies `x`. */
#define AKGL_BITMASK_ADD(x, y)    ((x) |= (y))
/** @brief Clear every bit of `y` in `x`. Modifies `x`. */
#define AKGL_BITMASK_DEL(x, y)    ((x) &= ~(y))
/**
 * @brief Clear every bit of `x`. Modifies `x`.
 *
 * Carries no trailing semicolon: write `AKGL_BITMASK_CLEAR(flags);` like any
 * other statement. It used to include one, so every ordinary use produced an
 * empty statement after it and a use as the whole body of an unbraced `if`
 * would have taken the following statement with it.
 */
#define AKGL_BITMASK_CLEAR(x)     ((x) = 0)

/**
 * @brief Bring the whole library up: error codes, pools, registries, SDL, audio, fonts, gamepads.
 *
 * In order: claim the libakgl status band (so every later error has a name),
 * stamp the library version, start the frame clock, create the state mutex,
 * check that the caller filled in the three required `game` fields, zero the
 * pools, create the registries, hand SDL the app metadata, clear the control
 * maps, `SDL_Init` video/gamepad/audio, load the bundled controller database,
 * open any attached gamepads, start SDL_mixer and SDL_ttf, and finally point
 * `akgl_renderer`, `akgl_physics`, `akgl_camera`, and `akgl_gamemap` at their default storage.
 *
 * What it does *not* do: create the window, choose a physics backend, or load
 * any configuration. Those read properties, so they come after the caller has
 * set them. See the sequence at the top of this file.
 *
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If `akgl_game.name`, `akgl_game.version`, or `akgl_game.uri` is
 *         empty. All three are required and there are no defaults -- the
 *         window title, SDL's app metadata, and the savegame compatibility
 *         check are all built from them.
 * @throws AKGL_ERR_SDL If the state mutex cannot be created, if `SDL_Init`
 *         fails, if a controller-database entry is rejected, if SDL_mixer
 *         cannot start or open the default playback device, or if SDL_ttf
 *         cannot start. Each message carries `SDL_GetError()`.
 * @throws AKERR_STATUS_RANGE_OVERLAP If another component already owns part of
 *         the libakgl status band. See akgl_error_init.
 * @throws AKERR_* Whatever the heap, registry, and controller initializers raise.
 *
 * @note It takes the state lock on the way in and releases it on the way out, so
 *       a failure part-way through leaves the mutex held.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init(void);
/**
 * @brief Count this frame, and recompute the frame rate once a second has passed.
 *
 * Called at the top of akgl_game_update, so a caller running its own loop needs
 * to call it itself. `akgl_game.fps` is only refreshed when a full second has
 * elapsed, so it is a completed-second average rather than an instantaneous
 * figure -- and it reads 0 for the first second of the process, which is under
 * the low-FPS threshold and so fires `lowfpsfunc` on every frame until the first
 * second is up.
 */
void akgl_game_update_fps(void);
/**
 * @brief Write the game state and the name-to-pointer tables to a save file.
 *
 * Writes the `akgl_Game` struct verbatim, then four name tables -- actors,
 * sprites, spritesheets, characters -- each mapping a registered name to the
 * address the object had at save time, and each terminated by a zeroed name and
 * a zeroed pointer. The tables are what let akgl_game_load reconnect pointers
 * between objects that will be at different addresses next run.
 *
 * @param fpath Path to write. Required. Opened with `"wb"`; an existing file is
 *              truncated.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p fpath is `NULL`.
 * @throws ENOENT, EACCES Or whatever else `fopen(3)` reports, with the path in
 *         the message.
 * @throws AKERR_IO On a stream error or a short write.
 * @throws ENOSPC, EDQUOT Or whatever else the close reports. A record this size
 *         fits entirely in stdio's buffer, so nothing reaches the device until
 *         the close flushes it and that is the only place a full disk, an
 *         exceeded quota or a server going away can be seen. The close is
 *         checked; success here means the bytes are on the device.
 * @throws AKERR_OUTOFBOUNDS If a registered name does not fit its field.
 *
 * @note This is a partial implementation: the name tables are written but the
 *       objects themselves are not, so the file is not yet enough to restore a
 *       session. See also TODO.md, "Defects" -- the writer
 *       and the reader disagree on the name-field widths, so a save with any
 *       registered spritesheet cannot be read back.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath);
/**
 * @brief Read a save file back, refusing one that does not match this build.
 *
 * Reads the saved `akgl_Game`, then rejects the file unless the library version,
 * the game version, the game name, and the game URI all match the running
 * program -- versions by exact semver equality, name and URI by string compare.
 * Only then does it copy the saved state over `game` and rebuild the four
 * old-address-to-current-object maps.
 *
 * @param fpath Path to read. Required. Opened with `"rb"`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p fpath is `NULL`.
 * @throws ENOENT, EACCES Or whatever else `fopen(3)` reports.
 * @throws AKERR_EOF If a read runs off the end of the file -- which is what a
 *         truncated or corrupt name table looks like.
 * @throws AKERR_IO On a stream error.
 * @throws AKERR_VALUE If either version string in the save file, or either in
 *         the running game, is not valid semver.
 * @throws AKERR_API If the save file is from a different library version, game
 *         version, game name, or game URI.
 * @throws AKERR_IO If anything remains in the file after the four name tables.
 *         The tables carry no length prefix and end at a zeroed sentinel, so a
 *         reader whose field widths disagree with the writer's stops early
 *         inside an entry and would otherwise report success with silently
 *         wrong maps. Trailing data is the symptom of that disagreement.
 *
 * @note Like akgl_game_save, this is partial: it rebuilds the pointer maps but
 *       does not yet read back any objects. The four `SDL_CreateProperties`
 *       sets it builds are never destroyed, so each call leaks them.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath);
/**
 * @brief The default `akgl_game.lowfpsfunc`: log the current frame rate.
 *
 * Called from akgl_game_update_fps on every frame where `akgl_game.fps` is under 30.
 * It is a placeholder -- the point of the hook is that a game can replace it
 * with something that actually sheds work.
 */
void akgl_game_lowfps(void);
/**
 * @brief Take the game-state mutex, retrying rather than blocking.
 *
 * Polls with `SDL_TryLockMutex` on a 100 ms cadence instead of blocking
 * outright, so a deadlock reports an error rather than hanging the process.
 *
 * @return `NULL` once the lock is held, otherwise an error context owned by the
 *         caller.
 * @throws AKGL_ERR_SDL If #AKGL_GAME_STATE_LOCK_BUDGET_MS elapses with the lock
 *         still held elsewhere. `SDL_GetError()` is appended for whatever it is
 *         worth, but after a failed `SDL_TryLockMutex` it is usually stale or
 *         empty -- contention is reported by the return value, not by an error
 *         string. The status is the signal, not the text.
 *
 * @note Before 0.5.0 the loop counted against a constant named
 *       `AKGL_TIME_ONESEC_MS` that held 1000000, so it retried 10,000 times at
 *       100 ms and blocked for roughly sixteen minutes rather than the one
 *       second documented here.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_lock(void);
/**
 * @brief Release the game-state mutex.
 *
 * @return `NULL`. `SDL_UnlockMutex` reports nothing, so there is no failure path
 *         -- including for the case that matters, unlocking a mutex this thread
 *         does not hold, which is undefined behaviour in SDL rather than an
 *         error here.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void);
/**
 * @brief One frame: update every actor layer by layer, step the physics, draw the world.
 *
 * Takes the state lock, counts the frame, then calls each live actor's
 * `updatefunc` **exactly once**, optionally rescaling it to the tilemap first.
 * Then it steps `akgl_physics` and draws through `akgl_renderer`, and releases
 * the lock.
 *
 * Until 0.5.0 the sweep sat inside a walk over #AKGL_TILEMAP_MAX_LAYERS that
 * never compared an actor's `layer` to the layer it was on, so every actor
 * updated sixteen times a frame. Updating an actor is not a per-layer
 * operation; drawing is, and akgl_render_2d_draw_world walks the layers itself.
 *
 * @param opflags Iterator flags. Optional -- `NULL` selects
 *                #AKGL_ITERATOR_OP_UPDATE alone, which is every live actor
 *                once. Two bits are read here:
 *                #AKGL_ITERATOR_OP_TILEMAPSCALE, without which every actor's
 *                `scale` is forced to 1.0; and #AKGL_ITERATOR_OP_LAYERMASK,
 *                which restricts the sweep to the actors whose `layer` matches
 *                `layerid`. Note that the flags are *not* forwarded to the
 *                physics or render calls, both of which are passed `NULL`.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKGL_ERR_SDL If the state lock cannot be taken.
 * @throws AKERR_* Whatever an actor's `updatefunc`, akgl_tilemap_scale_actor,
 *                 the physics backend, or the renderer raises.
 *
 * @warning Every failure path returns with the state lock still held, and a live
 *          actor's `updatefunc` is called without a `NULL` check -- a hand-built
 *          actor that never went through akgl_actor_initialize crashes here.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags);

/*
 * These functions are part of the internal API and should not be called by the
 * user. They are only exposed here for unit testing.
 */

/**
 * @brief Write all four name-to-address tables, each with its terminator.
 *
 * Actors, sprites, spritesheets, characters, in that order -- which is the order
 * akgl_game_load reads them back in. Each table is the registry enumerated one
 * entry at a time, followed by a zeroed name field and a zeroed pointer that the
 * reader stops on.
 *
 * @param fp The open save-game stream. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p fp is `NULL`.
 * @throws AKERR_IO On a stream error or a short write while emitting a
 *         terminator.
 *
 * @note Only the terminators are written through the error-reporting path. The
 *       entries themselves go through `SDL_EnumerateProperties`, whose callbacks
 *       cannot report failure upward and instead terminate the process -- so a
 *       write error mid-table never reaches this function's return value.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp);
/**
 * @brief Refuse a save file unless its version matches the running one exactly.
 *
 * Both strings are parsed as semver and compared with `"="` -- exact equality,
 * not compatibility. That is deliberate and strict: a save file is a raw memory
 * image of `akgl_Game` and the object tables, so any change to a struct layout
 * invalidates it, and semver has no way to say "the layout did not move".
 *
 * @param versiontype What is being compared -- `"library"` or `"game"`. Used
 *                    only to build the message, but required, since a bare
 *                    "incompatible version" error would not say which.
 * @param newversion  The version read out of the save file. Required.
 * @param curversion  The version of the running program or library. Required.
 * @return `NULL` when the two match, otherwise an error context owned by the
 *         caller.
 * @throws AKERR_NULLPOINTER If any of the three arguments is `NULL`.
 * @throws AKERR_VALUE If either string is not valid semver. The message says
 *         which side it came from.
 * @throws AKERR_API If both parse but are not equal. The message quotes both.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion);

#endif //_AKGL_GAME_H_
