#include <SDL3/SDL.h>
#include "character.h"
#include "actor.h"
#include "heap.h"
#include "registry.h"
#include "iterator.h"

SDL_Window *window;
SDL_Renderer *renderer;

ErrorContext *test_character_initialize()
{
    PREPARE_ERROR(errctx);
    character *testchar = NULL;
    ATTEMPT {
	CATCH(errctx, heap_next_character(&testchar));
	CATCH(errctx, character_initialize(testchar, "testchar"));
	FAIL_ZERO_BREAK(
	    errctx,
	    SDL_GetPointerProperty(REGISTRY_CHARACTER, "testchar", NULL),
	    ERR_KEY,
	    "Character was not placed in the registry");
	FAIL_NONZERO_BREAK(
	    errctx,
	    strcmp((char *)&testchar->name, "testchar"),
	    ERR_VALUE,
	    "Character was not named properly ('testchar' vs '%s')",
	    (char *)&testchar->name);
	FAIL_ZERO_BREAK(
	    errctx,
	    testchar->state_sprites,
	    ERR_NULLPOINTER,
	    "Character state sprites map was not initialized");
    } CLEANUP {
	IGNORE(heap_release_character(testchar));
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_character_sprite_mgmt()
{
    character *testchar = NULL;
    sprite *testsprite = NULL;
    sprite *testsprite2 = NULL;
    sprite *comparesprite = NULL;
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, heap_next_character(&testchar));
	CATCH(errctx, sprite_load_json("assets/testsprite.json"));
	testsprite = SDL_GetPointerProperty(REGISTRY_SPRITE, "testsprite", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testsprite,
	    ERR_KEY,
	    "Sprite loaded from json but not in registry");
	CATCH(errctx, sprite_load_json("assets/testsprite2.json"));
	testsprite2 = SDL_GetPointerProperty(REGISTRY_SPRITE, "testsprite2", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testsprite2,
	    ERR_KEY,
	    "Sprite 2 loaded from json but not in registry");
	CATCH(errctx, testchar->sprite_add(testchar, testsprite, ACTOR_STATE_ALIVE));
	CATCH(errctx, testchar->sprite_add(testchar, testsprite2, ACTOR_STATE_DEAD));
	CATCH(errctx, testchar->sprite_get(testchar, (ACTOR_STATE_ALIVE), &comparesprite));
	FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite), ERR_VALUE, "Wrong sprite for state ACTOR_STATE_ALIVE | ACTOR_STATE_FACE_LEFT");
	CATCH(errctx, testchar->sprite_get(testchar, ACTOR_STATE_DEAD, &comparesprite));
	FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite2), ERR_VALUE, "Wrong sprite for state ACTOR_STATE_DEAD");
    } CLEANUP {
	IGNORE(heap_release_sprite(testsprite));
	IGNORE(heap_release_sprite(testsprite2));
	IGNORE(heap_release_character(testchar));
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_character_iterate_state_sprites()
{
    character *testchar = NULL;
    sprite *testsprite = NULL;
    sprite *testsprite2 = NULL;
    iterator opflags = {.flags = ITERATOR_OP_RELEASE, .layerid = 0};
    
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, heap_next_character(&testchar));
	CATCH(errctx, sprite_load_json("assets/testsprite.json"));
	testsprite = SDL_GetPointerProperty(REGISTRY_SPRITE, "testsprite", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testsprite,
	    ERR_KEY,
	    "Sprite loaded from json but not in registry");
	CATCH(errctx, sprite_load_json("assets/testsprite2.json"));
	testsprite2 = SDL_GetPointerProperty(REGISTRY_SPRITE, "testsprite2", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testsprite2,
	    ERR_KEY,
	    "Sprite 2 loaded from json but not in registry");
	CATCH(errctx, testchar->sprite_add(testchar, testsprite, ACTOR_STATE_ALIVE));
	CATCH(errctx, testchar->sprite_add(testchar, testsprite2, ACTOR_STATE_DEAD));

	SDL_EnumerateProperties(testchar->state_sprites, &character_state_sprites_iterate, &opflags);

	// This is called by heap_release_character so we should assume that our property map is being
	// deleted soon after this. So we don't care if the sprites have been deleted from the sprite_states.
	// We just want to know they've been released.
	FAIL_NONZERO_BREAK(
	    errctx,
	    (testsprite->refcount > 1),
	    ERR_VALUE,
	    "heap_release_sprite not called for testsprite from iterator");
	FAIL_NONZERO_BREAK(
	    errctx,
	    (testsprite2->refcount > 1),
	    ERR_VALUE,
	    "heap_release_sprite not called for testsprite from iterator");
    } CLEANUP {
	IGNORE(heap_release_sprite(testsprite));
	IGNORE(heap_release_sprite(testsprite2));
	IGNORE(heap_release_character(testchar));	
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_character_load_json()
{
    character *testcharacter = NULL;
    sprite *testsprite = NULL;
    sprite *testsprite2 = NULL;
    sprite *comparesprite = NULL;
    int tsrc = 0;
    int tsrc2 = 0;
    
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, heap_next_character(&testcharacter));
	CATCH(errctx, sprite_load_json("assets/testsprite.json"));
	testsprite = SDL_GetPointerProperty(REGISTRY_SPRITE, "testsprite", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testsprite,
	    ERR_KEY,
	    "Sprite loaded from json but not in registry");
	CATCH(errctx, sprite_load_json("assets/testsprite2.json"));
	testsprite2 = SDL_GetPointerProperty(REGISTRY_SPRITE, "testsprite2", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testsprite2,
	    ERR_KEY,
	    "Sprite 2 loaded from json but not in registry");
	
	CATCH(errctx, character_load_json("assets/testcharacter.json"));
	testcharacter = SDL_GetPointerProperty(REGISTRY_CHARACTER, "testcharacter", NULL);
	FAIL_ZERO_BREAK(
	    errctx,
	    testcharacter,
	    ERR_KEY,
	    "Character loaded from json but not in registry");
	CATCH(errctx, testcharacter->sprite_get(testcharacter, (ACTOR_STATE_ALIVE | ACTOR_STATE_FACE_LEFT), &comparesprite));
	FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite), ERR_VALUE, "Wrong sprite for state ACTOR_STATE_ALIVE");
	CATCH(errctx, testcharacter->sprite_get(testcharacter, ACTOR_STATE_DEAD, &comparesprite));
	FAIL_ZERO_BREAK(errctx, (comparesprite == testsprite2), ERR_VALUE, "Wrong sprite for state ACTOR_STATE_DEAD");

	FAIL_ZERO_BREAK(errctx, (testcharacter->vx != 0.200000003), ERR_VALUE, "Wrong X velocity for test character");
	FAIL_ZERO_BREAK(errctx, (testcharacter->vy != 0.200000003), ERR_VALUE, "Wrong Y velocity for test character");

	// Release our handles on the sprites so the character's heap_release can reduce them to 0
	CATCH(errctx, heap_release_sprite(testsprite));
	CATCH(errctx, heap_release_sprite(testsprite2));
	tsrc = testsprite->refcount;
	tsrc2 = testsprite2->refcount;
	CATCH(errctx, heap_release_character(testcharacter));
	FAIL_ZERO_BREAK(
	    errctx,
	    ((testsprite->refcount < tsrc) || (testsprite2->refcount < tsrc2)),
	    ERR_VALUE,
	    "character did not reduce reference count of its child sprites when released");
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    SUCCEED_RETURN(errctx);
}

int main(void)
{
    PREPARE_ERROR(errctx);
    ATTEMPT {
	SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
    
	if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
	    FAIL_BREAK(errctx, ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
	}
    
	if (!SDL_CreateWindowAndRenderer("net/aklabs/libsdl3game/test_character", 640, 480, SDL_WINDOW_HIDDEN, &window, &renderer)) {
	    FAIL_BREAK(errctx, ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
	}

	CATCH(errctx, heap_init());
	CATCH(errctx, registry_init());
	CATCH(errctx, test_character_initialize());
	CATCH(errctx, test_character_sprite_mgmt());
	CATCH(errctx, test_character_iterate_state_sprites());
	CATCH(errctx, test_character_load_json());
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);
    return 0;
}
