/**
 * @file text.h
 * @brief Loading fonts and drawing or measuring strings with them.
 *
 * Fonts are `TTF_Font *` handles kept in the #AKGL_REGISTRY_FONT property
 * registry under a caller-chosen name; there is no akgl font type wrapping them.
 * SDL_ttf must be initialized (akgl_game_init does it) before any of this.
 *
 * The two measure functions do not touch the renderer, so they are usable
 * before -- or entirely without -- a window. Drawing is immediate mode: each
 * akgl_text_rendertextat() call rasterizes, uploads, blits, and throws the
 * texture away, which is fine for a HUD line and wrong for a large body of
 * static text redrawn every frame.
 */

#ifndef _AKGL_TEXT_H_
#define _AKGL_TEXT_H_

#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <akerror.h>

/**
 * @brief Open a TrueType font at one size and publish it in the font registry.
 *
 * A size is baked into the handle, so the same file at two sizes is two calls
 * under two names. Nothing releases these: the handles live until the process
 * ends.
 *
 * @param name     Registry key to publish the font under. Required. An existing
 *                 entry with the same name is replaced, and the font it
 *                 displaced is closed through akgl_text_unloadfont -- but only
 *                 after the new one has opened, so a failed load leaves the
 *                 caller with the font they already had.
 * @param filepath Path to a `.ttf`/`.otf` file. Required. Used verbatim -- not
 *                 resolved against `SDL_GetBasePath()`.
 * @param size     Point size to rasterize at. Passed straight to SDL_ttf, which
 *                 rejects anything that is not positive.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p name or @p filepath is `NULL`.
 * @throws AKGL_ERR_SDL If the font cannot be opened -- missing, unreadable, not
 *         a font, or a @p size SDL_ttf refuses. The message carries
 *         `SDL_GetError()`.
 * @throws AKERR_KEY If the font cannot be written into #AKGL_REGISTRY_FONT --
 *         in practice, because akgl_registry_init has not run.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_loadfont(char *name, char *filepath, int size);
/**
 * @brief Close a loaded font and take it out of the font registry.
 *
 * The other half of akgl_text_loadfont, and for a while the half that did not
 * exist: a font could be opened and published but never handed back, so a game
 * that changed fonts between scenes had no way to reclaim the one it had
 * finished with. A `TTF_Font` is about ten kilobytes once FreeType's own
 * structures are counted.
 *
 * The registry entry is cleared before the font is closed, so a font is never
 * reachable through #AKGL_REGISTRY_FONT after it has gone back to SDL_ttf.
 *
 * @param name Registry key the font was published under. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p name is `NULL`.
 * @throws AKERR_KEY If no font is registered under @p name -- including the
 *         case where it was already unloaded, which makes a double unload an
 *         error rather than a double close.
 *
 * @warning Anything still holding the `TTF_Font *` -- a caller that fetched it
 *          from the registry earlier, a pending akgl_text_rendertextat -- is
 *          left with a dangling pointer. Fonts are not reference counted.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name);
/**
 * @brief Close every font in #AKGL_REGISTRY_FONT and destroy the registry.
 *
 * A font, once loaded, was reachable only by name -- so a game that exited
 * without unloading each one by hand left them open, and `SDL_Quit` destroying
 * the property registry took the last reference to them with it. That is
 * bounded by how many fonts a game loads rather than unbounded, but there was
 * no way to do anything about it at all.
 *
 * Call this during shutdown, **before** `TTF_Quit` or `SDL_Quit`. Afterwards
 * #AKGL_REGISTRY_FONT is 0 and akgl_text_loadfont needs
 * akgl_registry_init_font() again before it can register anything.
 *
 * @return `NULL`. Closing a font reports nothing, and an uninitialized registry
 *         is success rather than an error -- shutdown paths run after partial
 *         startups.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadallfonts(void);
/**
 * @brief Rasterize a string and blit it at a screen position, in one call.
 *
 * Renders blended (anti-aliased, alpha-blended) through SDL_ttf, uploads the
 * result to a texture, draws it through the global `akgl_renderer`, and destroys both
 * the texture and the surface before returning. The text is drawn at its natural
 * size -- @p x and @p y are the top-left corner, not a centre.
 *
 * Coordinates are screen coordinates, not world ones: this does not go through
 * the camera, so a HUD stays put while the world scrolls under it.
 *
 * @param font       Font to render with, from akgl_text_loadfont. Required.
 * @param text       UTF-8 text. Required. May contain newlines, which break
 *                   lines on either path.
 * @param color      Text colour, including alpha.
 * @param wraplength Wrap width in pixels. Greater than 0 wraps on word
 *                   boundaries at that width; 0 or less draws a single line and
 *                   breaks only on newlines in @p text.
 * @param x          Left edge of the text, in screen pixels.
 * @param y          Top edge of the text, in screen pixels.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p font or @p text is `NULL`; if the global
 *         `akgl_renderer`, its `sdl_renderer`, or its `draw_texture` is `NULL` --
 *         that last one is the state a backend is in between being allocated
 *         and being run through akgl_render_2d_bind(); if SDL_ttf cannot
 *         rasterize the string; or if the surface cannot be uploaded as a
 *         texture. The last two carry `SDL_GetError()` and are a reused status
 *         rather than a pointer problem.
 * @throws AKERR_* Whatever the backend's `draw_texture` raises.
 *
 * @note On a failure after rasterizing -- the texture upload, or the draw -- the
 *       surface and texture are not destroyed, because the error returns before
 *       the cleanup. Repeated failures leak.
 * @note The empty string is **refused**, not drawn as nothing: SDL_ttf reports
 *       "Text has zero width" and this passes that on as `AKERR_NULLPOINTER`.
 *       akgl_text_measure() accepts it, so the two disagree. A caller drawing a
 *       line of text that may be empty has to check for it. Stale: the two agree
 *       as of 0.5.0 -- an empty string returns success without rasterizing. See
 *       TODO.md, "Found while rewriting the Doxygen comments", and issue #63.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *text, SDL_Color color, int wraplength, int x, int y);
/**
 * @brief Report the size, in pixels, that @p text would occupy on one line.
 *
 * Nothing is drawn and no renderer is required. A caller building a character
 * grid measures one cell with this -- the advance width of a single glyph in a
 * monospaced font -- and derives the rest of the grid from it.
 *
 * @param font Font to measure with, from akgl_text_loadfont. Required.
 * @param text UTF-8 text to measure. Required. The empty string is legal and
 *             measures 0 wide by one line high.
 * @param w    Receives the width in pixels. Required.
 * @param h    Receives the height in pixels -- one line, whatever @p text
 *             contains, since this form does not wrap. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p font, @p text, @p w, or @p h is `NULL`.
 * @throws AKGL_ERR_SDL If SDL_ttf cannot measure the string -- a corrupt font,
 *         or text that is not valid UTF-8. The message carries `SDL_GetError()`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text, int *w, int *h);
/**
 * @brief Report the size, in pixels, that @p text would occupy when wrapped.
 *
 * The companion to akgl_text_measure() for the wrapping case, matching the
 * @p wraplength argument akgl_text_rendertextat() already takes: a string
 * longer than @p wraplength reports the height of every line it breaks onto.
 * A @p wraplength of zero wraps only on newlines in @p text.
 *
 * @param font       Font to measure with, from akgl_text_loadfont. Required.
 * @param text       UTF-8 text to measure. Required.
 * @param wraplength Wrap width in pixels. 0 wraps on newlines only. Negative is
 *                   refused rather than passed through: SDL_ttf reads a negative
 *                   width as a very large unsigned one and silently stops
 *                   wrapping, which would return a measurement that is wrong
 *                   rather than an error.
 * @param w          Receives the width in pixels: the longest line, not
 *                   @p wraplength. Required.
 * @param h          Receives the height in pixels, covering every line the text
 *                   wraps onto. Required.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p font, @p text, @p w, or @p h is `NULL`.
 * @throws AKERR_OUTOFBOUNDS If @p wraplength is negative.
 * @throws AKGL_ERR_SDL If SDL_ttf cannot measure the string -- a corrupt font,
 *         or text that is not valid UTF-8. The message carries `SDL_GetError()`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure_wrapped(TTF_Font *font, char *text, int wraplength, int *w, int *h);

#endif // _AKGL_TEXT_H_
