/**
 * @file controller.h
 * @brief Input binding: SDL events in, actor state changes out.
 *
 * A control map ties one actor to one keyboard and one gamepad, and holds up to
 * #AKGL_MAX_CONTROLS bindings. A binding says "when event X arrives from device
 * Y carrying button or key Z, call this handler". Eight maps means up to eight
 * locally controlled players, each on its own device.
 *
 * The host pumps SDL events into akgl_controller_handle_event(), which scans the
 * maps in order and stops at the first binding that claims the event -- so a key
 * bound in two maps only fires once, in the lower-numbered one.
 *
 * Alongside that, and independent of it, every key press is pushed into a small
 * ring buffer that akgl_controller_poll_key() and akgl_controller_poll_keystroke()
 * drain. That serves a caller that wants "is there a key waiting" without owning
 * an event loop, and it sees keys whether or not a control map also claimed them.
 *
 * The two pollers read the same buffer and differ in what they hand back. A game
 * asking "was the up arrow pressed" wants a keycode and nothing else, which is
 * akgl_controller_poll_key(). A line editor asking "what did the user type" needs
 * the modifier state and the character SDL composed -- there is no `"` keycode,
 * and no keycode at all for a dead key or an IME commit -- which is
 * akgl_controller_poll_keystroke().
 */

#ifndef _AKGL_CONTROLLER_H_
#define _AKGL_CONTROLLER_H_

#include <SDL3/SDL.h>
#include <akerror.h>
#include <akgl/types.h>
// The binding handlers below take an akgl_Actor *, so this header cannot be
// included before akgl/actor.h without one. akgl_Actor is a typedef of a named
// struct, and repeating a typedef is not C99, so it is included rather than
// forward-declared. actor.h reaches only types.h and character.h, so this does
// not close a cycle.
#include <akgl/actor.h>

/** @brief How many control maps exist -- effectively the local player limit. */
#define AKGL_MAX_CONTROL_MAPS 8
/** @brief Bindings per control map. The default map installed by akgl_controller_default uses 8 of them. */
#define AKGL_MAX_CONTROLS     32

/**
 * @brief Keystrokes akgl_controller_handle_event() will hold for a poller.
 *
 * The Commodore keyboard buffer this serves held ten. Thirty-two is enough that
 * a program which polls once per frame never loses a key to a fast typist, and
 * small enough that the buffer stays a fixed-size object in the library's data
 * segment.
 */
#define AKGL_CONTROLLER_KEY_BUFFER 32

/**
 * @brief Bytes of composed text one buffered keystroke can carry, NUL included.
 *
 * A UTF-8 code point is at most four bytes, so this holds one character and its
 * terminator with room to spare. An input event carrying more than fits -- an
 * IME committing a whole word at once -- is truncated on a code point boundary
 * rather than split through the middle of one.
 */
#define AKGL_CONTROLLER_KEYSTROKE_TEXT 8

/** @brief One buffered keystroke: which key, which modifiers, and what it typed. */
typedef struct {
    SDL_Keycode key;                            /**< The keycode, or 0 for an entry that carries only composed text -- an IME commit, or a character finished by a dead key. */
    SDL_Keymod mod;                             /**< Modifier state when the key went down. 0 on a text-only entry, whose text already reflects them. */
    char text[AKGL_CONTROLLER_KEYSTROKE_TEXT];  /**< What the keystroke composed to, UTF-8. The empty string for a key that produces no character, such as an arrow or a bare modifier. */
} akgl_Keystroke;

/** @brief Maps one SDL input to pressed and released callbacks. */
typedef struct {
    uint32_t event_on;          /**< SDL event type that fires `handler_on`, e.g. `SDL_EVENT_KEY_DOWN`. */
    uint32_t event_off;         /**< SDL event type that fires `handler_off`, e.g. `SDL_EVENT_KEY_UP`. */
    uint8_t button;             /**< Gamepad button (`SDL_GamepadButton`) this binding is for. Only consulted for gamepad events. */
    SDL_Keycode key;            /**< Keycode this binding is for. Only consulted for keyboard events. */
    uint8_t axis;               /**< Analogue axis. Declared but not yet consulted by akgl_controller_handle_event. */
    uint8_t axis_range_min;     /**< Low end of the axis range that counts as "on". Not yet consulted. */
    uint8_t axis_range_max;     /**< High end of that range. Not yet consulted. */
    akerr_ErrorContext AKERR_NOIGNORE *(*handler_on)(akgl_Actor *obj, SDL_Event *event);     /**< Called with the map's target on `event_on`. Required if `event_on` can fire. */
    akerr_ErrorContext AKERR_NOIGNORE *(*handler_off)(akgl_Actor *obj, SDL_Event *event);    /**< Called with the map's target on `event_off`. */
} akgl_Control;

/** @brief Groups input bindings for one actor and its input devices. */
typedef struct {
    akgl_Actor *target;                         /**< The actor these bindings drive. A `NULL` target makes the whole map inert, which is how an unused slot is spelled. */
    uint16_t nextMap;                           /**< Number of bindings in use; the index akgl_controller_pushmap writes to next. */
    akgl_Control controls[AKGL_MAX_CONTROLS];   /**< The bindings, scanned in order. */
    SDL_KeyboardID kbid;                        /**< Keyboard this map listens to, or 0 for any. A non-zero id ignores every other keyboard, which is what keeps two players on two keyboards apart. */
    SDL_JoystickID jsid;                        /**< Gamepad this map listens to, matched the same way. */
    SDL_MouseID mouseid;                        /**< Mouse this map listens to. Declared but not yet consulted. */
    SDL_PenID penid;                            /**< Pen this map listens to. Declared but not yet consulted. */
} akgl_ControlMap;

/** @brief Every control map. Zeroed by akgl_game_init; index it by the same id the functions below take. */
extern akgl_ControlMap akgl_controlmaps[AKGL_MAX_CONTROL_MAPS];

/**
 * @brief Log every attached keyboard and its SDL id.
 *
 * A diagnostic, not a query: it writes to the SDL log rather than returning
 * anything. Its use is finding the `kbid` to hand akgl_controller_default when
 * more than one keyboard is attached.
 *
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If SDL cannot enumerate keyboards. The message
 *         carries `SDL_GetError()`; note that "no keyboards attached" is
 *         reported by SDL as an empty list, not as a failure.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void);

/**
 * @brief Dispatch one SDL event to whichever control map binds it.
 *
 * The entry point for the whole subsystem -- call it for every event the host
 * pumps. A key press is recorded in the poll buffer first, whether or not any
 * map wants it, and an `SDL_EVENT_TEXT_INPUT` is attached to the press it
 * belongs to; then the maps are scanned in index order and, within a map,
 * bindings in the order they were pushed. The **first** binding whose event type
 * and device id and button/key all match wins, and the scan stops there.
 *
 * An event nothing binds is not an error: it returns success having done
 * nothing, which is what lets a host pass every event through unconditionally.
 *
 * @param appstate Passed through from SDL's callback. Required -- but only as a
 *                 non-`NULL` token: nothing here reads it. Pass any non-`NULL`
 *                 pointer if your program has no app state.
 * @param event    The event to dispatch. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`.
 * @throws AKERR_* Whatever the matched binding's handler raises.
 *
 * @warning A matched binding's handler pointer is not checked, so a control
 *          pushed with a `NULL` `handler_on` or `handler_off` crashes when its
 *          event arrives.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_event(void *appstate, SDL_Event *event);

/**
 * @brief Set the movement and facing bits on the "player" actor for a D-pad or arrow press.
 *
 * A whole-application shortcut rather than a control-map binding: it looks the
 * actor up by the literal name `"player"` in #AKGL_REGISTRY_ACTOR instead of
 * being told which actor to act on, so it only ever drives one. The
 * akgl_actor_cmhf_* handlers are the general form.
 *
 * Facing follows movement unless the actor sets `movement_controls_face`, which
 * is how an actor that aims independently of the direction it walks opts out.
 *
 * @param appstate Passed through from SDL. Required as a non-`NULL` token only;
 *                 never read.
 * @param event    The button or key event. Required. Both the gamepad and
 *                 keyboard unions are read on every call, so the arm that does
 *                 not correspond to the event type is read as garbage -- which
 *                 works only because no real button and keycode pair collides.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is
 *         no actor registered under the name "player".
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_down(void *appstate, SDL_Event *event);
/**
 * @brief Clear the movement bit on the "player" actor for a D-pad or arrow release, and reset its animation.
 *
 * The counterpart to akgl_controller_handle_button_down. It clears only the movement
 * bit, leaving the facing bits alone so the actor keeps looking the way it was
 * walking, and rewinds `curSpriteFrameId` to 0 so the next step starts from the
 * first frame of the walk cycle rather than wherever it stopped.
 *
 * @param appstate Passed through from SDL. Required as a non-`NULL` token only.
 * @param event    The button or key release event. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`, or if there is
 *         no actor registered under the name "player".
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_button_up(void *appstate, SDL_Event *event);
/**
 * @brief Open a gamepad that has just been plugged in, and log its mapping.
 *
 * SDL delivers no button events from an unopened gamepad, so a device that
 * appears mid-session has to be opened here the way akgl_controller_open_gamepads
 * opens the ones present at startup. A gamepad SDL has already opened is logged
 * and left alone.
 *
 * The mapping is logged because a controller with no entry in the database
 * produces no button events at all, and that is otherwise indistinguishable
 * from a broken binding.
 *
 * @param appstate Passed through from SDL. Required as a non-`NULL` token only.
 * @param event    The SDL_EVENT_GAMEPAD_ADDED event. Required. The joystick id
 *                 is read out of the `gbutton` arm rather than `gdevice`, which
 *                 works because the two share their `which` field.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`.
 *
 * @note A failed `SDL_OpenGamepad` is logged rather than reported: this returns
 *       success and the device stays silent.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_added(void *appstate, SDL_Event *event);
/**
 * @brief Close a gamepad that has been unplugged.
 *
 * An unplugged device SDL still has open is a leaked handle, so this closes it.
 * A removal for a device that was never opened is logged and otherwise ignored.
 * Any control map still holding that `jsid` simply stops matching -- the map is
 * not torn down.
 *
 * @param appstate Passed through from SDL. Required as a non-`NULL` token only.
 * @param event    The SDL_EVENT_GAMEPAD_REMOVED event. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p appstate or @p event is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_handle_removed(void *appstate, SDL_Event *event);

/**
 * @brief Append a binding to a control map.
 *
 * The binding is copied, so the caller's `akgl_Control` can be a stack local
 * reused across pushes -- which is exactly what akgl_controller_default does.
 * Bindings can only be appended; there is no remove, and no way to reset a map
 * short of zeroing it in ::akgl_controlmaps directly.
 *
 * @param controlmapid Which map to append to, 0 through
 *                     #AKGL_MAX_CONTROL_MAPS - 1.
 * @param control      The binding to copy in. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p control is `NULL`.
 * @throws AKERR_OUTOFBOUNDS If @p controlmapid is at or above
 *         #AKGL_MAX_CONTROL_MAPS, or if the map already holds
 *         #AKGL_MAX_CONTROLS bindings.
 *
 * @warning A **negative** @p controlmapid is not rejected -- only the upper
 *          bound is checked -- and indexes before the start of
 *          ::akgl_controlmaps. TODO.md, "Defects".
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akgl_Control *control);

/**
 * @brief Bind an actor to the arrow keys and the D-pad, in one call.
 *
 * Points the map at the named actor and pushes eight bindings: the four arrow
 * keys and the four D-pad directions, each wired to the matching
 * `akgl_actor_cmhf_*_on`/`_off` pair. It is the "just give me something that
 * works" path -- a game wanting different keys builds its own bindings with
 * akgl_controller_pushmap.
 *
 * @param controlmapid Which map to configure, 0 through
 *                     #AKGL_MAX_CONTROL_MAPS - 1.
 * @param actorname    Registry name of the actor to drive. Required in practice,
 *                     though a `NULL` is reported as "not found" rather than as
 *                     a null pointer.
 * @param kbid         SDL keyboard id to listen to, or **0 for any keyboard**,
 *                     which is what a single-player game wants. Do not pass
 *                     `SDL_GetKeyboards()[0]`: that reports what is attached,
 *                     while the id an event carries is chosen by the video
 *                     backend, and the two need not agree. See
 *                     akgl_controller_list_keyboards.
 * @param jsid         SDL gamepad id to listen to, or 0 for any gamepad,
 *                     matched the same way.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_OUTOFBOUNDS If @p controlmapid is at or above
 *         #AKGL_MAX_CONTROL_MAPS, or if the map cannot hold eight more bindings.
 * @throws AKGL_ERR_REGISTRY If @p actorname is not in #AKGL_REGISTRY_ACTOR --
 *         usually because the actor has not been created yet.
 *
 * @warning A negative @p controlmapid is not rejected. See
 *          akgl_controller_pushmap.
 * @note It appends rather than replaces, so calling it twice on the same map
 *       leaves sixteen bindings and the first eight are the ones that fire.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid);

/**
 * @brief Open every gamepad currently attached.
 *
 * SDL will not deliver button events from a gamepad nobody has opened, so this
 * runs once at startup -- akgl_game_init calls it. Devices plugged in later are
 * SDL_EVENT_GAMEPAD_ADDED events, handled elsewhere.
 *
 * No gamepads attached is success, not an error.
 *
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If SDL reports gamepads present but cannot
 *         enumerate them, or if one of them cannot be opened. The message
 *         carries `SDL_GetError()`.
 *
 * @note The enumeration array is only freed on the success path, so a failure
 *       part-way through leaks it.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_open_gamepads(void);

/**
 * @brief Take the oldest waiting keystroke, if there is one.
 *
 * The rest of this header is built around the host pumping SDL events into
 * akgl_controller_handle_event(), which suits a game loop and does not suit an
 * embedded interpreter asking "is there a key waiting, yes or no" without
 * owning the event loop itself. Every key press that reaches
 * akgl_controller_handle_event() is recorded in a fixed ring buffer first,
 * whether or not a control map claims it, and this drains that buffer one
 * keystroke per call.
 *
 * When no key is waiting the call still succeeds: @p available is set to
 * `false` and @p keycode to 0. The caller polls, it does not block.
 *
 * A full buffer drops the *newest* keystroke rather than the oldest, so what
 * was typed first is what is read first. This runs on whichever thread pumps
 * events; it is not synchronized.
 *
 * Entries that carry only composed text and no keycode are discarded on the way
 * past rather than reported as keycode 0: this form is for a caller that acts on
 * keys, and there is no key to report. A caller that wants those characters uses
 * akgl_controller_poll_keystroke() instead.
 *
 * @param keycode   Receives the SDL keycode, or 0 when nothing was waiting.
 *                  Required -- the return value is the error context.
 * @param available Receives `true` when a keystroke was taken, `false` when the
 *                  buffer was empty. Required. Check this rather than testing
 *                  @p keycode against 0.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p keycode or @p available is `NULL`. Both are
 *         required; there is no "I only want to know whether one is waiting"
 *         form.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *available);

/**
 * @brief Take the oldest waiting keystroke whole: key, modifiers and text.
 *
 * akgl_controller_poll_key() with nothing thrown away. A line editor needs all
 * three: the keycode to recognise Backspace and Return, the modifier state to
 * tell Ctrl-C from a `c`, and the composed text because a keycode cannot express
 * what a shifted key produces on the user's own layout. `"`, `!`, `(`, `)`, `:`
 * and `;` are all unreachable from a keycode alone, and so is every lower-case
 * letter.
 *
 * The text is what SDL composed, taken from the `SDL_EVENT_TEXT_INPUT` that
 * follows the key press -- the only correct way to get a character out of SDL,
 * and what makes a keyboard layout, a compose key and a dead key work. **SDL
 * only sends those events while text input is started**, so a host that wants
 * the `text` field populated calls `SDL_StartTextInput()` on its window first.
 * Without it, `key` and `mod` still arrive and `text` is always empty.
 *
 * The two pollers drain the same buffer, so a keystroke taken by one is not
 * waiting for the other.
 *
 * @param dest      Receives the keystroke. Required. Written only when a
 *                  keystroke was waiting; zeroed otherwise, so `key` is 0 and
 *                  `text` is the empty string.
 * @param available Receives `true` when a keystroke was taken, `false` when the
 *                  buffer was empty. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p dest or @p available is `NULL`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_keystroke(akgl_Keystroke *dest, bool *available);

/**
 * @brief Discard every keystroke waiting in the buffer.
 *
 * For a caller that has been ignoring input and does not want a backlog acted
 * on the moment it starts polling again.
 *
 * This empties only the polling buffer. Keys already dispatched to control maps
 * have had their effect and cannot be taken back.
 *
 * @return `NULL`. There is no failure path -- it resets two counters.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_flush_keys(void);
#endif // _AKGL_CONTROLLER_H_
