/**
 * @file graphics.h
 * @brief Declares the graphics backend: where DRAW, BOX, CIRCLE and PAINT land.
 *
 * The core library is free of SDL and builds with no libakgl present, so the
 * graphics verbs cannot call akgl_draw_* directly. They call through a record of
 * function pointers instead -- the same shape as akbasic_TextSink, and the same
 * shape libakgl itself uses for akgl_RenderBackend and akgl_PhysicsBackend.
 *
 * akbasic_graphics_init_akgl() lives in the separate akbasic_akgl target and
 * draws through whatever renderer the host game already initialized. A host that
 * renders some other way supplies its own record and never links libakgl at all.
 *
 * A runtime with no graphics backend is the normal case -- the standalone driver
 * has none -- so every graphics verb refuses with AKBASIC_ERR_DEVICE rather than
 * dereferencing a NULL vtable.
 */

#ifndef _AKBASIC_GRAPHICS_H_
#define _AKBASIC_GRAPHICS_H_

#include <akerror.h>

#include <akbasic/types.h>

/**
 * @brief The coordinate space assumed when the device will not say how big it is.
 *
 * A coordinate reaching the backend is a *device pixel*: `DRAW 1, 799, 599` lands
 * at the bottom-right of an 800x600 window, and every pixel of that window is
 * addressable from BASIC. The interpreter still does not own the renderer -- it
 * asks, through akbasic_GraphicsBackend::size(), and the backend that does own
 * one answers.
 *
 * These constants are what is assumed when nobody answers: a backend that leaves
 * `size` NULL, or reports a nonsense size, gets BASIC 7.0's 320x200 hi-res
 * screen. That is the right fallback rather than a guess, because it is the space
 * a C128 listing was written for.
 */
#define AKBASIC_GRAPHICS_WIDTH		320
#define AKBASIC_GRAPHICS_HEIGHT		200

/** @brief How many COLOR sources there are. BASIC 7.0 numbers them 0 through 6. */
#define AKBASIC_COLOR_SOURCES		7

/**
 * @brief An RGBA color, in the shape SDL_Color has without requiring SDL.
 *
 * BASIC never names a color this way -- COLOR takes a source and a 1-16 palette
 * index -- so this is what the palette table in src/graphics_tables.c converts
 * to, and what the backend receives.
 */
typedef struct
{
    uint8_t r;
    uint8_t g;
    uint8_t b;
    uint8_t a;
} akbasic_Color;

/**
 * @brief The graphics verbs' own state, which lives on the runtime.
 *
 * DRAW, BOX, CIRCLE and PAINT name a *color source*, not a color: COLOR binds a
 * source to a palette index and the drawing verbs reference the source. That
 * indirection is BASIC 7.0's, not an invention here, and it is why `source` is a
 * table of palette indices rather than a single current color.
 */
typedef struct
{
    int mode;				/* GRAPHIC mode; 0 is text, and refuses to draw	*/
    int source[AKBASIC_COLOR_SOURCES];	/* palette index bound to each COLOR source	*/
    double x;				/* pixel cursor: where LOCATE put it, or where	*/
    double y;				/*   the last DRAW ended			*/
    bool scaling;			/* SCALE on					*/
    int linewidth;			/* WIDTH: 1 or 2 pixels per drawn line		*/
    double xmax;			/* user-coordinate maxima SCALE maps from	*/
    double ymax;
    int devwidth;			/* what the device last said it is, in pixels;	*/
    int devheight;			/*   the AKBASIC_GRAPHICS_* fallback until then	*/
} akbasic_GraphicsState;

/**
 * @brief Where the graphics verbs draw.
 *
 * Coordinates are `double` rather than an integer pixel address because SCALE
 * makes them fractional: a program running at a 1023x1023 logical scale on a
 * 320x200 screen produces non-integer pixel positions, and rounding at each verb
 * rather than once at the backend accumulates visible drift along a polyline.
 *
 * There is deliberately no circle entry point. BASIC 7.0's CIRCLE takes two
 * radii, a start and end angle, a rotation and a degree increment -- it is a
 * polygon by definition -- so it is built from `line` calls in the verb handler.
 * See TODO.md section 5.
 */
typedef struct akbasic_GraphicsBackend
{
    void *self;

    /**
     * How big the drawing surface is, in pixels.
     *
     * **Optional, and the only optional entry point in the record.** It was
     * added after the rest, so a host that filled one of these in by hand
     * before it existed leaves it NULL and keeps the 320x200 fallback rather
     * than crashing; and a backend drawing somewhere with no meaningful size
     * has an honest way to say so. Everything else here is required.
     *
     * Called before each verb draws rather than once at attach, because a
     * window is resizable and a script that runs for an hour should not be
     * drawing to the size the window was when it started.
     */
    akerr_ErrorContext AKERR_NOIGNORE *(*size)(struct akbasic_GraphicsBackend *self, int *width, int *height);

    /** Plot one pixel. */
    akerr_ErrorContext AKERR_NOIGNORE *(*point)(struct akbasic_GraphicsBackend *self, double x, double y, akbasic_Color color);
    /** Draw a line between two points. */
    akerr_ErrorContext AKERR_NOIGNORE *(*line)(struct akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color);
    /** Outline an axis-aligned rectangle. A rotated BOX becomes four `line` calls. */
    akerr_ErrorContext AKERR_NOIGNORE *(*rect)(struct akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color);
    /** Fill an axis-aligned rectangle. */
    akerr_ErrorContext AKERR_NOIGNORE *(*filled_rect)(struct akbasic_GraphicsBackend *self, double x1, double y1, double x2, double y2, akbasic_Color color);
    /**
     * Flood-fill the region containing (x, y).
     *
     * May report AKERR_OUTOFBOUNDS having filled only part of the region: the
     * akgl implementation walks spans from a fixed stack and gives up rather
     * than growing it. PAINT reports that to the program instead of leaving a
     * half-painted screen unexplained.
     */
    akerr_ErrorContext AKERR_NOIGNORE *(*paint)(struct akbasic_GraphicsBackend *self, int x, int y, akbasic_Color color);
    /** Clear the whole drawing surface to one color. */
    akerr_ErrorContext AKERR_NOIGNORE *(*clear)(struct akbasic_GraphicsBackend *self, akbasic_Color color);

    /**
     * Save a rectangular region and yield an opaque handle to it.
     *
     * SSHAPE stores the handle in a BASIC string, not the pixels -- see TODO.md
     * section 5 for what that deviation costs.
     */
    akerr_ErrorContext AKERR_NOIGNORE *(*save_shape)(struct akbasic_GraphicsBackend *self, int x1, int y1, int x2, int y2, int *handle);
    /** Blit a saved region back at (x, y). */
    akerr_ErrorContext AKERR_NOIGNORE *(*paste_shape)(struct akbasic_GraphicsBackend *self, int handle, double x, double y);
    /** Release every saved region. NEW and CLR call this; nothing else reclaims a slot. */
    akerr_ErrorContext AKERR_NOIGNORE *(*free_shapes)(struct akbasic_GraphicsBackend *self);
} akbasic_GraphicsBackend;

/**
 * @brief Convert a BASIC color index to the RGBA the backend draws with.
 *
 * BASIC 7.0 numbers its sixteen colors from 1, not 0; index 17 is the "current"
 * color on a real C128 and has no meaning here.
 *
 * @param index Palette index, 1 through 16.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When `index` is outside 1..16.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_palette(int index, akbasic_Color *dest);

/**
 * @brief Reset the graphics state to its power-on values.
 *
 * Text mode, the default color-source bindings, the pixel cursor at the origin
 * and scaling off. GRAPHIC CLR and NEW both come back through here.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_state_init(akbasic_GraphicsState *obj);

/**
 * @brief Resolve a COLOR source to the RGBA the backend draws with.
 * @param obj Graphics state to read the binding from.
 * @param source COLOR source number, 0 through 6.
 * @param dest Output destination populated by the function.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj` or `dest` is NULL.
 * @throws AKBASIC_ERR_BOUNDS When `source` is outside 0..6.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_source_color(akbasic_GraphicsState *obj, int source, akbasic_Color *dest);

#endif // _AKBASIC_GRAPHICS_H_
