/**
 * @file draw.h
 * @brief Declares the public draw API.
 *
 * Immediate-mode plotting against whichever renderer the caller hands in. This
 * is the shape a BASIC-style graphics vocabulary needs -- DRAW, BOX, CIRCLE,
 * PAINT, SSHAPE and GSHAPE all say "put this on the screen now" rather than
 * "add this to the scene" -- and it sits alongside the actor and tilemap
 * rendering rather than replacing it.
 *
 * Every entry point takes its color as an argument instead of reading a
 * current-color global. A caller that has a notion of a current color (a BASIC
 * COLOR statement, say) already owns that state and does not need the library
 * to keep a second copy that can disagree with it. The renderer's own draw
 * color is saved and restored around each call, so drawing a line never changes
 * what the next SDL_RenderClear() paints.
 */

#ifndef _AKGL_DRAW_H_
#define _AKGL_DRAW_H_

#include <SDL3/SDL.h>

#include <akerror.h>

#include <akgl/renderer.h>
#include <akgl/types.h>

/**
 * @brief Spans akgl_draw_flood_fill() will hold while walking a region.
 *
 * The fill keeps a fixed stack of horizontal runs still to be examined rather
 * than recursing per pixel. A region complicated enough to need more than this
 * many pending runs at once reports AKERR_OUTOFBOUNDS instead of overflowing;
 * an ordinary convex or moderately concave shape needs a few dozen.
 */
#define AKGL_DRAW_MAX_FLOOD_SPANS 4096

/**
 * @brief Triangles each corner of akgl_draw_filled_rounded_rect() is built from.
 *
 * A quarter circle approximated by this many segments is visually round at any
 * radius a UI panel plausibly uses; the vertex arrays are fixed at this size,
 * so it is a compile-time cost, not a per-call one.
 */
#define AKGL_DRAW_ROUNDED_RECT_SEGMENTS 16

/**
 * @brief Most vertices akgl_draw_arc() will spend on one arc.
 *
 * An arc is a triangle strip between its inner and outer edge, two vertices
 * per step, so half of these are positions along the curve. The step count
 * scales with the swept angle and is clamped to fit this array -- a full
 * circle gets all of them, a sliver gets a few -- rather than allocating.
 */
#define AKGL_DRAW_ARC_MAX_POINTS 64

/**
 * @brief Paint an 8x8 grey checkerboard over a region, the way an image editor shows transparency.
 *
 * A diagnostic backdrop rather than a general primitive -- `charviewer` uses it
 * so a sprite's transparent pixels are visible instead of blending into black.
 *
 * Until 0.5.0 this was the one function in the library outside the error
 * protocol: it returned `void`, drew through the *global* `akgl_renderer`
 * without checking it, and left the renderer's draw colour changed. It now
 * takes a backend like every other entry point here and restores the colour it
 * found, which is also what makes it testable without a world.
 *
 * @param self The backend to draw through. Required, along with its
 *             `sdl_renderer`.
 * @param w    Width of the region to cover, in pixels, starting at x = 0. Zero
 *             or negative paints nothing and is not reported.
 * @param h    Height of the region, starting at y = 0. Both round up to whole
 *             8px cells, so a 12-pixel height paints 16.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
 * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or a cell
 *         cannot be filled.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_background(akgl_RenderBackend *self, int w, int h);

/**
 * @brief Plot a single pixel.
 * @param self  The backend to draw through. Required, along with its
 *              `sdl_renderer`.
 * @param x     Horizontal position in render-target pixels. Outside the target
 *              is clipped by SDL, not reported.
 * @param y     Vertical position.
 * @param color Colour to plot in, including alpha. Blending follows the
 *              renderer's current blend mode, which this does not change.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
 * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the plot
 *         itself fails. The message carries `SDL_GetError()`.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_point(akgl_RenderBackend *self, float32_t x, float32_t y, SDL_Color color);

/**
 * @brief Draw a line between two points, endpoints included.
 * @param self  The backend to draw through. Required, along with its
 *              `sdl_renderer`.
 * @param x1    Horizontal position of the first endpoint.
 * @param y1    Vertical position of the first endpoint.
 * @param x2    Horizontal position of the second endpoint.
 * @param y2    Vertical position of the second endpoint. Coincident endpoints
 *              draw a single pixel rather than nothing.
 * @param color Colour to draw in, including alpha.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
 * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the line
 *         cannot be drawn.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_line(akgl_RenderBackend *self, float32_t x1, float32_t y1, float32_t x2, float32_t y2, SDL_Color color);

/**
 * @brief Draw the outline of a rectangle.
 * @param self  The backend to draw through. Required, along with its
 *              `sdl_renderer`.
 * @param rect  The rectangle, in render-target pixels. Required. A zero or
 *              negative width or height draws nothing and is not reported.
 * @param color Colour to draw in, including alpha.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p rect is
 *         `NULL`.
 * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the
 *         outline cannot be drawn.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color);

/**
 * @brief Fill a rectangle, outline included.
 * @param self  The backend to draw through. Required, along with its
 *              `sdl_renderer`.
 * @param rect  The rectangle, in render-target pixels. Required. A zero or
 *              negative width or height fills nothing and is not reported.
 * @param color Colour to fill with, including alpha.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p rect is
 *         `NULL`.
 * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if the fill
 *         cannot be drawn.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rect(akgl_RenderBackend *self, SDL_FRect *rect, SDL_Color color);

/**
 * @brief Draw the outline of a circle.
 *
 * SDL3 has no circle primitive, so this plots one with the midpoint circle
 * algorithm -- integer arithmetic, eight-way symmetry, one pass per octant. A
 * radius of zero draws the center pixel and nothing else.
 *
 * @param self   The backend to draw through. Required, along with its
 *               `sdl_renderer`.
 * @param x      Horizontal position of the centre. Rounded to the nearest whole
 *               pixel -- the algorithm is integer-only.
 * @param y      Vertical position of the centre, rounded the same way.
 * @param radius Radius in pixels, rounded to the nearest whole pixel. Must not
 *               be negative; 0 draws the centre pixel alone.
 * @param color  Colour to draw in, including alpha. The outline is one pixel
 *               wide and is not anti-aliased.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
 * @throws AKERR_OUTOFBOUNDS If @p radius is negative. The message reports it.
 * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if any of
 *         the plotting passes fails. Failures inside the loop are recorded and
 *         reported once at the end rather than aborting mid-circle, so a partial
 *         arc may already be on the target.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_circle(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, SDL_Color color);

/**
 * @brief Flood the connected region containing one pixel with a color.
 *
 * SDL3 has no flood fill either, and unlike the shape primitives it cannot be
 * done on the GPU side: the region is defined by what is already on the screen.
 * This reads the render target back, walks the region on the CPU with a
 * bounded span stack, and blits the result over the area it touched.
 *
 * Filling a region that already holds @p color is a no-op rather than an error.
 * A seed outside the render target reports AKERR_OUTOFBOUNDS.
 *
 * The region is four-connected -- it spreads up, down, left and right, not
 * diagonally -- and its boundary is any pixel whose colour differs from the
 * seed's, exactly. There is no tolerance, so an anti-aliased edge stops the fill
 * at its first blended pixel and leaves a fringe.
 *
 * @param self  The backend to draw through. Required, along with its
 *              `sdl_renderer`.
 * @param x     Horizontal position of the seed pixel, in render-target pixels.
 *              Must be inside the target.
 * @param y     Vertical position of the seed pixel.
 * @param color Colour to fill with. Written over the region rather than blended,
 *              since the pixels going back are the ones just read out.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
 * @throws AKERR_OUTOFBOUNDS If the seed is outside the render target -- the
 *         message reports both the seed and the target size -- or if the region
 *         needs more than #AKGL_DRAW_MAX_FLOOD_SPANS pending spans, in which
 *         case **it is left partially filled**.
 * @throws AKGL_ERR_SDL If the render target cannot be read back, converted,
 *         uploaded, or blitted.
 *
 * @warning Not reentrant, and not safe from two threads: the span stack is a
 *          single file-scope array. Nothing that touches an `SDL_Renderer` is
 *          thread-safe either way.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_flood_fill(akgl_RenderBackend *self, int x, int y, SDL_Color color);

/**
 * @brief Read a rectangle of the render target into a surface.
 *
 * The save half of SSHAPE/GSHAPE. When `*dest` is `NULL` the function allocates
 * the surface and the caller owns it from then on -- release it with
 * SDL_DestroySurface(). When `*dest` already points at a surface of exactly
 * @p src's dimensions the pixels are copied into it instead, so a caller
 * saving the same region repeatedly does not churn allocations.
 *
 * @param self The backend to read from. Required, along with its `sdl_renderer`.
 * @param src  Rectangle of the render target to read, in pixels. Required. It
 *             must fit *entirely* inside the target: SDL would otherwise clip
 *             the read and hand back a smaller surface than was asked for, which
 *             a caller pasting it back would not notice.
 * @param dest Address of the destination surface. Required, and `*dest` must be
 *             initialized -- `NULL` to have one allocated (the caller then owns
 *             it and frees it with `SDL_DestroySurface`), or an existing surface
 *             of exactly @p src's dimensions to reuse.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, @p src, or @p dest
 *         is `NULL`.
 * @throws AKERR_OUTOFBOUNDS If @p src has no area, if it does not fit inside the
 *         render target, or if a supplied `*dest` is a different size from
 *         @p src. Each message reports both sets of dimensions.
 * @throws AKGL_ERR_SDL If the target size cannot be queried, the pixels cannot
 *         be read, or the copy into a supplied `*dest` fails.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_copy_region(akgl_RenderBackend *self, SDL_Rect *src, SDL_Surface **dest);

/**
 * @brief Draw a saved surface back onto the render target.
 *
 * The restore half of SSHAPE/GSHAPE, taking what akgl_draw_copy_region()
 * produced. The surface is not consumed and may be pasted as many times as the
 * caller likes.
 *
 * @param self The backend to draw through. Required, along with its
 *             `sdl_renderer`.
 * @param src  The surface to paste. Required. Drawn at its own size -- there is
 *             no scaling -- and *replacing* what is on the target rather than
 *             blending with it, which is GSHAPE's default behaviour and means a
 *             saved region's transparent pixels come back as transparent rather
 *             than letting the background show through.
 * @param x    Horizontal position of the paste's left edge.
 * @param y    Vertical position of its top edge. Parts falling outside the
 *             target are clipped by SDL, not reported.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p src is
 *         `NULL`.
 * @throws AKGL_ERR_SDL If the surface cannot be uploaded as a texture, its blend
 *         mode cannot be set, or the draw fails.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_paste_region(akgl_RenderBackend *self, SDL_Surface *src, float32_t x, float32_t y);

/**
 * @brief Fill a rectangle whose corners are rounded off with quarter circles.
 *
 * The body is three axis-aligned fills and the corners are triangle fans
 * through `SDL_RenderGeometry` -- #AKGL_DRAW_ROUNDED_RECT_SEGMENTS triangles
 * each -- so nothing here is anti-aliased, matching every other primitive in
 * this header.
 *
 * @param self   The backend to draw through. Required, along with its
 *               `sdl_renderer`.
 * @param rect   The rectangle, in render-target pixels. Required. A zero or
 *               negative width or height fills nothing and is not reported.
 * @param radius Corner radius in pixels. Zero or negative falls through to
 *               akgl_draw_filled_rect() -- a square corner is not an error.
 *               Larger than half the shorter side is clamped to it, which at
 *               the limit turns a square into a circle rather than folding the
 *               corners over each other.
 * @param color  Colour to fill with, including alpha.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self, `self->sdl_renderer`, or @p rect is
 *         `NULL`.
 * @throws AKGL_ERR_SDL If the draw colour cannot be read or set, or if a band
 *         or corner cannot be drawn. A failure partway leaves the shape
 *         partially drawn.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_filled_rounded_rect(akgl_RenderBackend *self, SDL_FRect *rect, float32_t radius, SDL_Color color);

/**
 * @brief Stroke a circular arc of a given thickness between two angles.
 *
 * Angles are in degrees, 0 pointing along +x (three o'clock) and increasing
 * clockwise on screen -- the same convention as the rotation argument the
 * render backend's `draw_texture` takes. The stroke runs from the arc's outer
 * edge at @p radius inward by @p thickness, drawn as one triangle strip, not
 * anti-aliased.
 *
 * @param self      The backend to draw through. Required, along with its
 *                  `sdl_renderer`.
 * @param x         Horizontal position of the arc's centre.
 * @param y         Vertical position of the arc's centre.
 * @param radius    Outer radius in pixels. Must be positive.
 * @param start_deg Angle the arc starts at.
 * @param end_deg   Angle the arc ends at. At or below @p start_deg nothing is
 *                  drawn and nothing is reported, matching the zero-area
 *                  rectangle fills above. Neither angle is normalized, so an
 *                  arc crossing three o'clock is 350 to 370, not 350 to 10; a
 *                  sweep beyond 360 degrees is clamped to one full turn.
 * @param thickness Stroke width in pixels, measured inward from @p radius.
 *                  Must be positive; at or above @p radius the arc becomes a
 *                  filled wedge to the centre.
 * @param color     Colour to stroke in, including alpha.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
 * @throws AKERR_OUTOFBOUNDS If @p radius or @p thickness is not positive. The
 *         message reports the offending value.
 * @throws AKGL_ERR_SDL If the strip cannot be drawn.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_arc(akgl_RenderBackend *self, float32_t x, float32_t y, float32_t radius, float32_t start_deg, float32_t end_deg, float32_t thickness, SDL_Color color);

/**
 * @brief Restrict every subsequent draw to a rectangle of the render target.
 *
 * Everything drawn through @p self after this -- primitives here, textures,
 * text -- is clipped to @p rect until the restriction is cleared. This is the
 * only entry point in this header where a `NULL` rectangle is legal rather
 * than an error: it means "clear the restriction", matching what
 * `SDL_SetRenderClipRect` does with it, and there is no other value that
 * could.
 *
 * The clip is renderer state, not saved and restored around anything: a
 * caller that sets it owns putting it back, the way a `CLEANUP` block puts
 * back a draw colour. Leaving it set clips the next frame's world too.
 *
 * @param self The backend to clip. Required, along with its `sdl_renderer`.
 * @param rect Rectangle to clip to, in render-target pixels -- integer, since
 *             a clip boundary is a pixel boundary. `NULL` clears the clip.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p self or `self->sdl_renderer` is `NULL`.
 * @throws AKGL_ERR_SDL If the clip cannot be set or cleared.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_draw_set_clip(akgl_RenderBackend *self, SDL_Rect *rect);

#endif //_AKGL_DRAW_H_
