/**
 * @file docs_screenshot.c
 * @brief Bring libakgl up against an offscreen target, call one documented frame, save a PNG.
 *
 * The drawing, sprite and tilemap chapters describe what a call puts on the
 * screen, and a sentence is a poor way to describe a picture. This is what turns
 * the listing in a chapter into the image beside it: `tools/docs_screenshots.sh`
 * extracts the listing from the markdown, compiles it against this file, and
 * runs the result -- so the figure is generated from the exact code the reader
 * is looking at rather than from a copy that can drift away from it.
 *
 * **It is a host**, and deliberately a small one: `akgl_game_init()` opens a
 * real window, owns the frame loop and does not stop until the game quits, none
 * of which a build step wants. What this does instead is the shortest path to
 * pixels -- the dummy video driver, a software renderer, one frame, read the
 * target back, write it out. `tests/sprite.c` and `tests/draw.c` already set
 * that pattern.
 *
 * The frame itself is `docs_frame()`, supplied by the chapter's block through
 * `tests/docs_preludes/akglframe.pre`. It is declared in `docs_prelude.h` so the
 * host and the prelude cannot disagree about the signature.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>

#include <akerror.h>

#include <docs_prelude.h>

/** @brief Set in HANDLE_DEFAULT and read after FINISH; see the note in main(). */
static int failed = 0;

static void usage(void)
{
    fprintf(stderr,
	    "usage: akgl_docs_screenshot <output.png> [width] [height]\n"
	    "\n"
	    "  Calls docs_frame() against an offscreen renderer of the given size\n"
	    "  (default 320x240) and writes what it drew as a PNG.\n");
}

/**
 * @brief Everything between SDL being up and the pixels being on disk.
 *
 * Split out so the ATTEMPT in main() has one thing to CATCH and the SDL teardown
 * has one place to happen.
 */
static akerr_ErrorContext AKERR_NOIGNORE *draw_frame(const char *outpath, int w, int h)
{
    PREPARE_ERROR(errctx);
    SDL_Surface *shot = NULL;

    FAIL_ZERO_RETURN(errctx, outpath, AKERR_NULLPOINTER, "Received null output path");

    PASS(errctx, akgl_error_init());
    akgl_renderer = &akgl_default_renderer;

    FAIL_ZERO_RETURN(errctx, SDL_Init(SDL_INIT_VIDEO), AKGL_ERR_SDL,
		     "Couldn't initialize SDL: %s", SDL_GetError());
    FAIL_ZERO_RETURN(
	errctx,
	SDL_CreateWindowAndRenderer(
	    "net/aklabs/libakgl/docs_screenshot", w, h, 0,
	    &akgl_window, &akgl_renderer->sdl_renderer),
	AKGL_ERR_SDL,
	"Couldn't create window/renderer: %s", SDL_GetError());

    PASS(errctx, akgl_render_2d_bind(akgl_renderer));
    PASS(errctx, akgl_heap_init());
    PASS(errctx, akgl_registry_init());

    akgl_camera = &akgl_default_camera;
    akgl_camera->x = 0.0f;
    akgl_camera->y = 0.0f;
    akgl_camera->w = (float32_t)w;
    akgl_camera->h = (float32_t)h;

    /*
     * Opaque black, so a figure has a defined background rather than whatever
     * the driver left in the buffer. Clearing is the host's prerogative -- the
     * library's draw calls put things on the target and do not own what is
     * underneath them -- and this is the host doing it.
     */
    FAIL_ZERO_RETURN(errctx, SDL_SetRenderDrawColor(akgl_renderer->sdl_renderer, 0, 0, 0, 0xff),
		     AKGL_ERR_SDL, "%s", SDL_GetError());
    FAIL_ZERO_RETURN(errctx, SDL_RenderClear(akgl_renderer->sdl_renderer),
		     AKGL_ERR_SDL, "%s", SDL_GetError());

    PASS(errctx, docs_frame());

    shot = SDL_RenderReadPixels(akgl_renderer->sdl_renderer, NULL);
    FAIL_ZERO_RETURN(errctx, (shot != NULL), AKGL_ERR_SDL,
		     "Couldn't read the target back: %s", SDL_GetError());
    if ( !IMG_SavePNG(shot, outpath) ) {
	SDL_DestroySurface(shot);
	FAIL_RETURN(errctx, AKGL_ERR_SDL, "Couldn't write %s: %s", outpath, SDL_GetError());
    }
    SDL_DestroySurface(shot);
    SUCCEED_RETURN(errctx);
}

int main(int argc, char **argv)
{
    PREPARE_ERROR(errctx);
    int w = 320;
    int h = 240;

    if ( argc < 2 ) {
	usage();
	return 2;
    }
    if ( argc > 2 ) {
	w = atoi(argv[2]);
    }
    if ( argc > 3 ) {
	h = atoi(argv[3]);
    }
    if ( w <= 0 || h <= 0 ) {
	usage();
	return 2;
    }

    /*
     * Set here rather than left to the environment, so the tool answers the same
     * on a developer's desktop as it does in CI. A figure regenerated over a
     * real GPU could differ by a pixel of antialiasing and turn every rebuild
     * into a binary diff.
     */
    SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "dummy");
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
    SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");

    ATTEMPT {
	CATCH(errctx, draw_frame(argv[1], w, h));
    } CLEANUP {
	if ( akgl_window != NULL ) {
	    SDL_DestroyWindow(akgl_window);
	    akgl_window = NULL;
	}
	SDL_Quit();
    } PROCESS(errctx) {
    } HANDLE_DEFAULT(errctx) {
	LOG_ERROR_WITH_MESSAGE(errctx, "could not draw the figure");
	/*
	 * A flag rather than a `return`: FINISH ends with RELEASE_ERROR, and
	 * leaving a HANDLE block early means the context never goes back to
	 * AKERR_ARRAY_ERROR -- one leaked slot per call, and AGENTS.md documents
	 * where that ends up.
	 */
	failed = 1;
	/*
	 * FINISH_NORETURN rather than FINISH: FINISH expands a
	 * `return __err_context` that an int-returning function cannot compile,
	 * even where the branch is unreachable.
	 */
    } FINISH_NORETURN(errctx);

    return failed;
}
