/**
 * @file frontend.h
 * @brief The standalone SDL frontend: a host, not part of the interpreter.
 *
 * Everything else under include/akbasic deliberately owns no window, no renderer
 * and no event loop -- goal 3 forbids it, because a game embedding this
 * interpreter already has all three. This file is the other side of that line.
 * It is the *host* the standalone `basic` executable needs, ported from the Go
 * reference's main.go and basicruntime_graphics.go, and it is built into its own
 * target so the separation is visible in the build graph rather than only in a
 * comment: `akbasic` is the interpreter, `akbasic_akgl` is the adaptors, and
 * `akbasic_frontend` is the one thing here that creates a window.
 *
 * A game does not link this. It creates its own window and calls the four
 * initializers in akbasic/akgl.h against it.
 */

#ifndef _AKBASIC_FRONTEND_H_
#define _AKBASIC_FRONTEND_H_

#include <stdio.h>

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

#include <akerror.h>

#include <akgl/renderer.h>

#include <akbasic/akgl.h>
#include <akbasic/runtime.h>
#include <akbasic/sink.h>

/**
 * @brief Interpreter steps run between two frames.
 *
 * The reason there is a number here at all is TODO.md section 1.6: the library
 * steps, it does not run, precisely so a host can bound it. An unbounded run
 * would starve the event pump, and a window that does not answer its close
 * button is a window the desktop kills.
 *
 * 256 rather than 1 because a frame costs far more than a step and a tight
 * program should not be paced at 60 lines per second. It is a pacing knob and
 * nothing else -- no golden output depends on it, since a bounded run reproduces
 * an unbounded one exactly.
 */
#define AKBASIC_FRONTEND_STEPS_PER_FRAME	256

/** @brief Window size the reference opens, from deps/basicinterpret/main.go. */
#define AKBASIC_FRONTEND_WIDTH			800
/** @brief Window height the reference opens. */
#define AKBASIC_FRONTEND_HEIGHT			600
/** @brief Font size the reference opens its font at. */
#define AKBASIC_FRONTEND_FONT_SIZE		16

/**
 * @brief Everything the standalone program owns.
 *
 * All of it by value: the frontend allocates nothing, and a driver puts one of
 * these in static storage next to its runtime for the same reason the runtime is
 * static -- neither belongs on a default stack.
 */
typedef struct akbasic_AkglFrontend
{
    SDL_Window *window;
    akgl_RenderBackend *renderer;
    TTF_Font *font;
    int width;
    int height;

    /*
     * Three sinks, because output goes two places. The tee is what the runtime
     * is given; the other two are its halves. TODO.md section 3 item 5: the
     * second write belongs out here, never inside the interpreter.
     */
    akbasic_AkglSink akglstate;
    akbasic_TextSink akglsink;
    akbasic_StdioSink stdiostate;
    akbasic_TextSink stdiosink;
    akbasic_TeeSink teestate;
    akbasic_TextSink sink;

    akbasic_GraphicsBackend graphics;
    akbasic_AkglGraphics graphicsstate;
    akbasic_AudioBackend audio;
    akbasic_InputBackend input;
    akbasic_SpriteBackend sprites;
    akbasic_AkglSprites spritesstate;
    akbasic_UiBackend ui;
    akbasic_AkglUi uistate;

    /** False once the window has been closed; the drive loop stops on it. */
    bool running;
    /** True when this frontend called SDL_Init and owes it an SDL_Quit. */
    bool ownssdl;
    /** True when an audio device opened. A machine with none still runs. */
    bool audioready;
} akbasic_AkglFrontend;

/**
 * @brief Bring up SDL, a window, a renderer, a font and all four adaptors.
 *
 * Reproduces the reference's main.go: an 800x600 window titled "BASIC" and the
 * Commodore font at 16 points. Audio is best-effort -- a machine with no sound
 * device still runs BASIC, it just refuses SOUND and PLAY with
 * AKBASIC_ERR_DEVICE, which is the same answer a stdio build gives.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param title Window title.
 * @param w Window width in pixels.
 * @param h Window height in pixels.
 * @param fontpath TTF to open for the text layer.
 * @param fontsize Point size to open it at.
 * @param mirror Where output is mirrored in addition to the window; NULL selects stdout.
 * @param in Where the runtime reads its program, or NULL to read typed lines from the window.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When `obj`, `title` or `fontpath` is NULL.
 * @throws AKGL_ERR_SDL When SDL, the window, the renderer or the font refuses to open.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_frontend_akgl_init(akbasic_AkglFrontend *obj, const char *title, int w, int h, const char *fontpath, int fontsize, FILE *mirror, FILE *in);

/**
 * @brief Give a runtime this frontend's sink and its three devices.
 *
 * Initializes @p rt as well, because a runtime has to be told its sink at
 * initialization and there is nothing useful a caller could do between the two
 * calls.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param rt The runtime to wire up.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When either argument is NULL.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_frontend_akgl_attach(akbasic_AkglFrontend *obj, akbasic_Runtime *rt);

/**
 * @brief One frame: pump events, clear, draw the text layer, present.
 *
 * Deliberately shaped as an akbasic_AkglPump so the sink's line editor can be
 * handed this exact function -- the editor waits for a typed line by borrowing
 * frames from here, which is how INPUT works without anybody blocking.
 *
 * The graphics verbs draw straight to the renderer and are *not* redrawn from a
 * display list, so this does not clear the target: a DRAW from three frames ago
 * has to still be on screen. The text layer is drawn over whatever is there,
 * which is also what a C128 does with its text and bitmap planes.
 *
 * @param self The frontend, as a void * so this matches akbasic_AkglPump.
 * @param running Set false when the window has been closed.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When either argument is NULL.
 * @throws AKGL_ERR_SDL When the renderer refuses to present.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_frontend_akgl_pump(void *self, bool *running);

/**
 * @brief Run the interpreter to completion, a bounded number of steps per frame.
 *
 * This is the standalone program's whole loop. It ends when the script sets
 * AKBASIC_MODE_QUIT or the window is closed, and either is a clean exit -- a
 * closed window is a user quitting, not a failure.
 *
 * @param obj Object to initialize, inspect, or modify.
 * @param rt The runtime to drive.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER When either argument is NULL.
 * @throws AKERR_* Whatever the script raised and nothing handled.
 */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_frontend_akgl_drive(akbasic_AkglFrontend *obj, akbasic_Runtime *rt);

/**
 * @brief Close the font, the renderer, the window and SDL, in that order.
 *
 * Returns nothing and refuses nothing: this runs on the way out, including on
 * the way out of a failure, and there is no caller left to tell.
 *
 * @param obj Object to initialize, inspect, or modify.
 */
void akbasic_frontend_akgl_shutdown(akbasic_AkglFrontend *obj);

#endif // _AKBASIC_FRONTEND_H_
