/**
 * @file renderer.c
 * @brief Implements the renderer subsystem.
 */

#include <SDL3/SDL.h>

#include <akgl/renderer.h>
#include <akgl/staticstring.h>
#include <akgl/registry.h>
#include <akgl/heap.h>
#include <akgl/game.h>

#include <akerror.h>
#include <akstdlib.h>

akerr_ErrorContext *akgl_render_2d_init(akgl_RenderBackend *self)
{
    akgl_String *width = NULL;
    akgl_String *height = NULL;
    int screenwidth;
    int screenheight;
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");

    // The two pooled strings come back in CLEANUP. Released after both parses,
    // as they used to be, a non-numeric game.screenwidth returned past them and
    // leaked two of the pool's 256 entries.
    ATTEMPT {
	CATCH(errctx, akgl_get_property("game.screenwidth", &width, "0"));
	CATCH(errctx, akgl_get_property("game.screenheight", &height, "0"));
	CATCH(errctx, aksl_atoi(width->data, &screenwidth));
	CATCH(errctx, aksl_atoi(height->data, &screenheight));
	SDL_Log("Initializing screen (%sx%s = %dx%d)", width->data, height->data, screenwidth, screenheight);
    } CLEANUP {
	if ( width != NULL ) {
	    IGNORE(akgl_heap_release_string(width));
	    width = NULL;
	}
	if ( height != NULL ) {
	    IGNORE(akgl_heap_release_string(height));
	    height = NULL;
	}
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    FAIL_ZERO_RETURN(
	errctx,
	SDL_CreateWindowAndRenderer(akgl_game.uri, screenwidth, screenheight, 0, &akgl_window, &self->sdl_renderer),
	AKGL_ERR_SDL,
	"Couldn't create window/renderer: %s",
	SDL_GetError());

    akgl_camera->x = 0;
    akgl_camera->y = 0;
    akgl_camera->w = screenwidth;
    akgl_camera->h = screenheight;

    PASS(errctx, akgl_render_2d_bind(self));
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_render_2d_bind(akgl_RenderBackend *self)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");

    // Deliberately does not touch self->sdl_renderer: a host that owns its own
    // window has already put one there, and this is the only way it gets a
    // usable backend without libakgl creating a second window.
    self->shutdown = &akgl_render_2d_shutdown;
    self->frame_start = &akgl_render_2d_frame_start;
    self->frame_end = &akgl_render_2d_frame_end;
    self->draw_texture = &akgl_render_2d_draw_texture;
    self->draw_mesh = &akgl_render_2d_draw_mesh;
    self->draw_world = &akgl_render_2d_draw_world;
    SUCCEED_RETURN(errctx);
}


akerr_ErrorContext *akgl_render_2d_shutdown(akgl_RenderBackend *self)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_render_2d_frame_start(akgl_RenderBackend *self)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
    FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
    SDL_SetRenderDrawColor(self->sdl_renderer, 0, 0, 0, 255);
    SDL_RenderClear(self->sdl_renderer);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_render_2d_frame_end(akgl_RenderBackend *self)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
    FAIL_ZERO_RETURN(errctx, self->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend");
    SDL_RenderPresent(self->sdl_renderer);
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_render_2d_draw_texture(akgl_RenderBackend *self, SDL_Texture *texture, SDL_FRect *src, SDL_FRect *dest, double angle, SDL_FPoint *center, SDL_FlipMode flip)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");
    FAIL_ZERO_RETURN(errctx, texture, AKERR_NULLPOINTER, "texture");
    //FAIL_ZERO_RETURN(errctx, src, AKERR_NULLPOINTER, "src");
    //FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "dest");

    if ( angle != 0 ) {
	FAIL_ZERO_RETURN(errctx, center, AKERR_NULLPOINTER, "center");
	FAIL_ZERO_RETURN(
	    errctx,
	    SDL_RenderTextureRotated(self->sdl_renderer, texture, src, dest, angle, center, flip),
	    AKERR_NULLPOINTER, "%s", SDL_GetError()
	    );
    } else {
	FAIL_ZERO_RETURN(
	    errctx,
	    SDL_RenderTexture(self->sdl_renderer, texture, src, dest),
	    AKERR_NULLPOINTER, "%s", SDL_GetError()
	    );
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_render_2d_draw_mesh(akgl_RenderBackend *self)
{
    PREPARE_ERROR(errctx);
    FAIL_RETURN(errctx, AKERR_API, "Not implemented");
}

akerr_ErrorContext *akgl_render_2d_draw_world(akgl_RenderBackend *self, akgl_Iterator *opflags)
{
    PREPARE_ERROR(errctx);
    akgl_Iterator defflags;
    akgl_Actor *actor = NULL;

    FAIL_ZERO_RETURN(errctx, self, AKERR_NULLPOINTER, "self");

    if ( opflags == NULL ) {
	opflags = &defflags;
	PASS(errctx, aksl_memset((void *)opflags, 0x00, sizeof(akgl_Iterator)));
    }
    for ( int i = 0; i < AKGL_TILEMAP_MAX_LAYERS ; i++ ) {
	if ( i < akgl_gamemap->numlayers ) {
	    PASS(errctx, akgl_tilemap_draw(akgl_gamemap, akgl_camera, i));
	}
	for ( int j = 0; j < AKGL_MAX_HEAP_ACTOR ; j++ ) {
	    actor = &akgl_heap_actors[j];
	    if ( actor->refcount == 0 ) {
		continue;
	    }
	    if ( actor->layer != i ) {
		continue;
	    }
	    PASS(errctx, actor->renderfunc(actor));
	}
    }

    SUCCEED_RETURN(errctx);
}
