/**
 * @file collision.h
 * @brief Collision shapes: what an actor occupies, and what it will collide with.
 *
 * A shape is a convex volume positioned relative to an actor's origin. It lives
 * on the akgl_Character, so every goblin sharing a character shares one shape
 * definition, exactly as they share speeds and the state-to-sprite map -- and an
 * individual actor may override it when it needs to.
 *
 * @section collision_leaf This header includes nothing of ours but types.h
 *
 * `actor.h` and `character.h` both need akgl_CollisionShape by value, so this
 * header cannot include either of them without a cycle. It names actors and
 * tilemaps as incomplete struct pointers instead, which is why they need struct
 * tags and why `akgl_Tilemap` grew one. The `headers` suite compiles this file
 * as the first include of a translation unit and would fail immediately if that
 * ever stopped being true.
 *
 * @section collision_extrusion Why a 2D shape has a depth
 *
 * The narrowphase behind this is three-dimensional, which is what lets the same
 * shapes carry into a 3D game later. It answers with the **minimum** translation
 * that separates two volumes -- and if a 2D shape were extruded into a thin
 * slab, the cheapest way to separate two of them would be along z. The
 * narrowphase would report a contact, the resolver would push the actor into the
 * screen, and on screen nothing would happen at all while the actor sank through
 * the floor.
 *
 * So the setters give every shape a depth of #AKGL_COLLISION_DEPTH_RATIO times
 * its largest planar half-extent unless told otherwise. That is not a large
 * number chosen for comfort; it is the smallest ratio for which the z overlap of
 * any two shapes built this way is provably larger than any planar penetration
 * they can reach, so z can never be the minimum axis. Pass a depth by hand only
 * if you know what that costs.
 */

#ifndef _AKGL_COLLISION_H_
#define _AKGL_COLLISION_H_

#include <stdbool.h>
#include <stdint.h>

#include <SDL3/SDL_rect.h>
#include <akerror.h>

#include <akgl/types.h>

struct akgl_Actor;
struct akgl_Tilemap;

/** @brief No shape. The actor takes no part in collision. */
#define AKGL_COLLISION_SHAPE_NONE       0
/** @brief An axis-aligned box. The common case, and the one with a closed-form answer. */
#define AKGL_COLLISION_SHAPE_BOX        1
/** @brief A circle in the xy plane, extruded along z. `hx` is the radius and `hy` is ignored. */
#define AKGL_COLLISION_SHAPE_CIRCLE     2
/** @brief A capsule whose long axis is x: a box with semicircular caps left and right. */
#define AKGL_COLLISION_SHAPE_CAPSULE_X  3
/** @brief A capsule whose long axis is y: a box with semicircular caps top and bottom. */
#define AKGL_COLLISION_SHAPE_CAPSULE_Y  4

/** @brief No flags. */
#define AKGL_COLLISION_FLAG_NONE        0x00000000u
/** @brief Never moved by the resolver and never re-inserted into the broad phase. */
#define AKGL_COLLISION_FLAG_STATIC      0x00000001u
/** @brief Reports a contact and never pushes. Pickups, trigger volumes, damage zones. */
#define AKGL_COLLISION_FLAG_SENSOR      0x00000002u
/** @brief Present but ignored this step. Cheaper than dropping the shape and putting it back. */
#define AKGL_COLLISION_FLAG_DISABLED    0x00000004u
/**
 * @brief Reserved for a swept narrowphase. **Not implemented; setting it does nothing.**
 *
 * Sub-stepping bounds how far an actor moves between collision tests, which
 * stops anything moving at ordinary speeds from passing through a wall. A
 * projectile is not moving at ordinary speeds. See `TODO.md`.
 */
#define AKGL_COLLISION_FLAG_BULLET      0x00000008u

/** @brief On no layer. A shape with this `layermask` collides with nothing. */
#define AKGL_COLLISION_LAYER_NONE       0x00000000u
/** @brief Map geometry: solid tiles and static proxies. The default `collidemask`. */
#define AKGL_COLLISION_LAYER_STATIC     (1u << 0)
/** @brief Ordinary actors. The default `layermask`. */
#define AKGL_COLLISION_LAYER_ACTOR      (1u << 1)
/** @brief Suggested layer for the player. Nothing in the library treats it specially. */
#define AKGL_COLLISION_LAYER_PLAYER     (1u << 2)
/** @brief Suggested layer for hostiles. */
#define AKGL_COLLISION_LAYER_ENEMY      (1u << 3)
/** @brief Suggested layer for things picked up rather than bumped into. */
#define AKGL_COLLISION_LAYER_PICKUP     (1u << 4)
/** @brief Every layer, including the ones a game defines for itself in bits 5 and above. */
#define AKGL_COLLISION_LAYER_ALL        0xFFFFFFFFu

/**
 * @brief Depth given to a 2D shape, as a multiple of its largest planar half-extent.
 *
 * 2 and not 1. At 1, two identical squares fully overlapped have the same
 * overlap on z as in the plane, and which axis the narrowphase calls "minimum"
 * comes down to floating-point luck. At 2 the z overlap of any pair is at least
 * twice the largest planar half-extent either can reach, and the planar
 * penetration is at most that -- so z loses every time, by construction rather
 * than by margin.
 */
#define AKGL_COLLISION_DEPTH_RATIO      2.0f

/**
 * @brief A convex volume, positioned relative to an actor's origin.
 *
 * Stored as a centre and half-extents rather than as an `SDL_FRect`, because
 * that is the form both the overlap test and the narrowphase want and this way
 * the conversion happens once, in a setter, instead of on every one of the tens
 * of thousands of tests a frame. The setters take the rectangle form a game
 * already has.
 */
typedef struct akgl_CollisionShape {
    uint8_t kind;           /**< One of the `AKGL_COLLISION_SHAPE_*` values. #AKGL_COLLISION_SHAPE_NONE means this actor does not collide, and is what a zeroed shape is. */
    uint32_t flags;         /**< Bitwise OR of the `AKGL_COLLISION_FLAG_*` values. */
    uint32_t layermask;     /**< Which layers this shape is *on*. What other shapes test against. */
    uint32_t collidemask;   /**< Which layers this shape *responds to*. Asymmetric on purpose: a player can block against an NPC while the NPC ignores the player. */
    float32_t ox;           /**< Centre offset from the actor's `x`, in map pixels. */
    float32_t oy;           /**< Centre offset from the actor's `y`. Positive is down, matching screen space. */
    float32_t oz;           /**< Centre offset from the actor's `z`. */
    float32_t hx;           /**< Half-extent along x. The radius, for a circle or a capsule. */
    float32_t hy;           /**< Half-extent along y. Ignored for a circle. */
    float32_t hz;           /**< Half-extent along z. Never 0 after a setter has run; see the extrusion note on this file. */
} akgl_CollisionShape;

/**
 * @brief Build an axis-aligned box from the frame-relative rectangle a game has.
 *
 * @p body is an offset and a size measured from the actor's position -- the same
 * form a game already writes a hitbox in, inset into its sprite frame because
 * sprite art does not reach the edges of its cell. It is converted here to the
 * centre-and-half-extent form everything downstream uses.
 *
 * The shape is zeroed first, so every field not named by the arguments ends at a
 * known value: no flags, and the default masks of #AKGL_COLLISION_LAYER_ACTOR on
 * and #AKGL_COLLISION_LAYER_STATIC responded to. That default is chosen so an
 * actor given a shape and nothing else collides with the map and with nothing
 * else -- a town full of NPCs does not start shoving itself around because
 * somebody gave the townsfolk hitboxes.
 *
 * @param dest  Receives the shape. Required.
 * @param body  Offset and size in map pixels, relative to the actor's position.
 *              Required. Both `w` and `h` must be positive.
 * @param depth Half-extent along z. Pass 0 for a 2D game and the extrusion is
 *              chosen for you; see the note on this file for why that matters.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p dest or @p body is `NULL`.
 * @throws AKERR_VALUE If `body->w` or `body->h` is not positive, or @p depth is
 *         negative.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_collision_shape_box(akgl_CollisionShape *dest, SDL_FRect *body, float32_t depth);

/**
 * @brief Build a circle, extruded along z into a cylinder.
 *
 * A cylinder and not a sphere: a sphere's caps would round away from the plane
 * and let a shape slip past a corner in a way a 2D game never expects.
 *
 * @param dest   Receives the shape. Required.
 * @param ox     Centre offset from the actor's `x`, in map pixels.
 * @param oy     Centre offset from the actor's `y`.
 * @param radius Radius in map pixels. Must be positive.
 * @param depth  Half-extent along z, or 0 to have it chosen.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p dest is `NULL`.
 * @throws AKERR_VALUE If @p radius is not positive, or @p depth is negative.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_collision_shape_circle(akgl_CollisionShape *dest, float32_t ox, float32_t oy, float32_t radius, float32_t depth);

/**
 * @brief Build a capsule: a box with semicircular caps on one axis.
 *
 * Useful for a character that should slide off a corner rather than catch on it,
 * which a box does and a capsule does not.
 *
 * @param dest  Receives the shape. Required.
 * @param body  Offset and size in map pixels, as for akgl_collision_shape_box.
 *              Required.
 * @param axis  #AKGL_COLLISION_SHAPE_CAPSULE_X or #AKGL_COLLISION_SHAPE_CAPSULE_Y.
 * @param depth Half-extent along z, or 0 to have it chosen.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p dest or @p body is `NULL`.
 * @throws AKERR_VALUE If either side is not positive, @p depth is negative, @p
 *         axis is neither capsule kind, or the capped axis is not the longer one
 *         -- a capsule whose caps are wider than it is long is a circle written
 *         confusingly, and is refused rather than silently reinterpreted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_collision_shape_capsule(akgl_CollisionShape *dest, SDL_FRect *body, uint8_t axis, float32_t depth);

/**
 * @brief The planar bounds a shape occupies with its owner at a given position.
 *
 * This is what the broad phase indexes on and what a cheap overlap test uses
 * before anything more expensive runs. `z` is not represented: the extrusion
 * exists to keep the narrowphase honest, not to be searched.
 *
 * @param shape The shape. Required.
 * @param x     The owner's `x`.
 * @param y     The owner's `y`.
 * @param dest  Receives the bounds in map pixels. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p shape or @p dest is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_collision_shape_bounds(akgl_CollisionShape *shape, float32_t x, float32_t y, SDL_FRect *dest);

/**
 * @brief Whether two shapes are allowed to interact, in the given direction.
 *
 * Asymmetric, and that is the point: @p self responds to @p other when @p
 * other's `layermask` intersects @p self's `collidemask`. The reverse is a
 * separate question with its own answer, which is how a player blocks against a
 * pushable crate while the crate ignores everything.
 *
 * A shape of kind #AKGL_COLLISION_SHAPE_NONE, or one carrying
 * #AKGL_COLLISION_FLAG_DISABLED, interacts with nothing in either direction.
 *
 * @param self  The shape doing the responding. Required.
 * @param other The shape being responded to. Required.
 * @param dest  Receives whether the pair is worth testing. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If any argument is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_collision_shape_interacts(akgl_CollisionShape *self, akgl_CollisionShape *other, bool *dest);

#endif
