#define UNHANDLED_ERROR_TERMINATION_BEHAVIOR \
    handle_unhandled_error(errctx);

#include "error.h"

#define UNHANDLED_ERROR_EXIT 0
#define UNHANDLED_ERROR_SET  1

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

int UNHANDLED_ERROR_BEHAVIOR;
ErrorContext *unhandled_error_context;

void handle_unhandled_error_noexit(ErrorContext *errctx)
{
    if ( errctx == NULL ) {
	return;
    }
    if ( UNHANDLED_ERROR_BEHAVIOR == UNHANDLED_ERROR_EXIT ) {
	exit(errctx->status);
    }
    if ( UNHANDLED_ERROR_BEHAVIOR == UNHANDLED_ERROR_SET ) {
	unhandled_error_context = errctx;
	errctx->refcount += 1;
	return;
    }
}

int actor_updated;
ErrorContext *actor_update_noop(actor *obj)
{
    PREPARE_ERROR(errctx);
    actor_updated = 1;
    SUCCEED_RETURN(errctx);
}

// Currently the renderer assumes there is a global variable named `renderer`
int actor_rendered;
ErrorContext *actor_render_noop(actor *obj, SDL_Renderer *r)
{
    PREPARE_ERROR(errctx);
    actor_rendered = 1;
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_registry_actor_iterator_nullpointers(void)
{
    PREPARE_ERROR(errctx);
    
    ErrorUnhandledErrorHandler defaulthandler = error_handler_unhandled_error;

    error_handler_unhandled_error = handle_unhandled_error_noexit;
    ATTEMPT {
	UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_SET;
	DETECT(unhandled_error_context, registry_iterate_actor(NULL, REGISTRY_ACTOR, ""));
    } CLEANUP {
	UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT;
    } PROCESS(unhandled_error_context) {
    } HANDLE(unhandled_error_context, ERR_NULLPOINTER) {
	printf("Handled\n");
    } FINISH(unhandled_error_context, true);
    IGNORE(heap_release_error(unhandled_error_context));
    error_handler_unhandled_error = defaulthandler;
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_registry_actor_iterator_missingactor(void)
{
    PREPARE_ERROR(errctx);
    ErrorUnhandledErrorHandler defaulthandler = error_handler_unhandled_error;

    iterator iter = {
	.layerid = 0,
	.flags = 0
    };
    error_handler_unhandled_error = handle_unhandled_error_noexit;
    ATTEMPT {
	UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_SET;
	DETECT(
	    unhandled_error_context,
	    registry_iterate_actor(
		&iter,
		REGISTRY_ACTOR,
		"Actor who doesn't exist")
	    );
    } CLEANUP {
	UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT;
    } PROCESS(unhandled_error_context) {
    } HANDLE(unhandled_error_context, ERR_KEY) {
	printf("Handled\n");
    } FINISH(unhandled_error_context, true);
    IGNORE(heap_release_error(unhandled_error_context));
    error_handler_unhandled_error = defaulthandler;
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_registry_actor_iterator_updaterender(void)
{
    actor *testactor;
    iterator iter = {
	.layerid = 0,
	.flags = ITERATOR_OP_UPDATE | ITERATOR_OP_RENDER
    };
    ErrorUnhandledErrorHandler defaulthandler = error_handler_unhandled_error;

    PREPARE_ERROR(errctx);
    error_handler_unhandled_error = handle_unhandled_error_noexit;
    ATTEMPT {
	UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_SET;
	CATCH(unhandled_error_context, heap_next_actor(&testactor));
	CATCH(unhandled_error_context, actor_initialize(testactor, "test"));

	testactor->layer = 0;
	testactor->updatefunc = &actor_update_noop;
	testactor->renderfunc = &actor_render_noop;

	DETECT(
	    unhandled_error_context,
	    registry_iterate_actor(
		&iter,
		REGISTRY_ACTOR,
		"test")
	    );

	FAIL_ZERO_BREAK(
	    unhandled_error_context,
	    actor_updated,
	    ERR_BEHAVIOR,
	    "actor->updatefunc not called by the iterator"
	    );
	FAIL_ZERO_BREAK(
	    unhandled_error_context,
	    actor_rendered,
	    ERR_BEHAVIOR,
	    "actor->renderfunc not called by the iterator"
	    );
    } CLEANUP {
	UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT;
	IGNORE(heap_release_actor(testactor));
    } PROCESS(unhandled_error_context) {
    } FINISH(unhandled_error_context, true);

    IGNORE(heap_release_error(unhandled_error_context));
    error_handler_unhandled_error = defaulthandler;
    
    SUCCEED_RETURN(errctx);
}

ErrorContext *test_actor_set_character(void)
{
    actor *testactor = NULL;
    character *testchar = NULL;
    
    PREPARE_ERROR(errctx);

    ATTEMPT {
	CATCH(errctx, actor_set_character(NULL, "test"));
    } CLEANUP {
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
        printf("Handled\n");
    } FINISH(errctx, true);

    
    ATTEMPT {
	CATCH(errctx, heap_next_actor(&testactor));

	CATCH(errctx, actor_initialize(testactor, "test"));
	testactor->layer = 0;
	testactor->updatefunc = &actor_update_noop;
	testactor->renderfunc = &actor_render_noop;
	
	CATCH(errctx, actor_set_character(testactor, "test"));
    } CLEANUP {
      IGNORE(heap_release_actor(testactor));
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_NULLPOINTER) {
        printf("Handled\n");
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, heap_next_actor(&testactor));
	CATCH(errctx, heap_next_character(&testchar));

	CATCH(errctx, actor_initialize(testactor, "test"));
	testactor->layer = 0;
	testactor->updatefunc = &actor_update_noop;
	testactor->renderfunc = &actor_render_noop;
	
	CATCH(errctx, character_initialize(testchar, "test"));
	
	CATCH(errctx, actor_set_character(testactor, "test"));
    } CLEANUP {
      IGNORE(heap_release_actor(testactor));
      IGNORE(heap_release_character(testchar));
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    SUCCEED_RETURN(errctx);
}

ErrorContext *test_actor_manage_children(void)
{
    actor *parent = NULL;
    actor *child = NULL;
    string *tmpstring = NULL;
    int i = 0;
    int j = 0;
    PREPARE_ERROR(errctx);

    ATTEMPT {
	CATCH(errctx, heap_init());
	CATCH(errctx, heap_next_string(&tmpstring));
	CATCH(errctx, heap_next_actor(&parent));
	CATCH(errctx, actor_initialize(parent, "parent"));
	for ( i = 0 ; i < ACTOR_MAX_CHILDREN; i++ ) {
	    sprintf((char *)&tmpstring->data, "child %d", i);
	    CATCH(errctx, heap_next_actor(&child));
	    CATCH(errctx, actor_initialize(child, (char *)&tmpstring->data));
	    CATCH(errctx, parent->addchild(parent, child));
	    // Release our claim on the actor so the parent can own the child and kill it
	    CATCH(errctx, heap_release_actor(child));
	}
    } CLEANUP {
	IGNORE(heap_release_string(tmpstring));
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    ATTEMPT {
	// Doesn't matter which child we use for this test
	child = parent->children[1];
	CATCH(errctx, parent->addchild(parent, child));
    } CLEANUP {
	if ( errctx == NULL ) {
	    FAIL(errctx, ERR_BEHAVIOR, "addchild does not throw ERR_RELATIONSHIP when child already has a parent");
	}
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_RELATIONSHIP) {
	// Expected behavior
	SDL_Log("addchild throws ERR_RELATIONSHIP when child already has a parent");
    } FINISH(errctx, true);
    
    ATTEMPT {
	CATCH(errctx, heap_next_actor(&child));
	CATCH(errctx, parent->addchild(parent, child));
    } CLEANUP {
	if ( errctx == NULL ) {
	    FAIL(errctx, ERR_BEHAVIOR, "addchild does not throw ERR_OUTOFBOUNDS when all children already set");
	}
    } PROCESS(errctx) {
    } HANDLE(errctx, ERR_OUTOFBOUNDS) {
	// Expected behavior
	SDL_Log("addchild throws ERR_OUTOFBOUNDS when all children already set");
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, heap_release_actor(parent));
	// All actor objects on the heap should be empty now
	for ( i = 0; i < MAX_HEAP_ACTOR; i++) {
	    FAIL_NONZERO_BREAK(errctx, HEAP_ACTOR[i].refcount, ERR_VALUE, "Actor not properly cleared");
	    FAIL_NONZERO_BREAK(errctx, HEAP_ACTOR[i].parent, ERR_VALUE, "Actor not properly cleared");
	    for ( j = 0 ; j < ACTOR_MAX_CHILDREN; j++) {
		if ( HEAP_ACTOR[i].children[j] != NULL ) {
		    FAIL(errctx, ERR_VALUE, "Actor not properly cleared");
		    goto _test_actor_addchild_heaprelease_cleanup;
		}
	    }
	}
	for ( j = 0; j < ACTOR_MAX_CHILDREN; j++) {
	    sprintf((char *)&tmpstring->data, "child %d", i);
	    FAIL_NONZERO_BREAK(
		errctx,
		SDL_GetPointerProperty(REGISTRY_ACTOR, (char *)&tmpstring->data, NULL),
		ERR_KEY,
		"Child %s was not removed from the registry",
		(char *)&tmpstring->data);
	}
_test_actor_addchild_heaprelease_cleanup:
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);

    ATTEMPT {
	CATCH(errctx, heap_next_actor(&parent));
	CATCH(errctx, actor_initialize(parent, "parent"));
	CATCH(errctx, heap_next_actor(&child));
	CATCH(errctx, actor_initialize(child, "child"));
	// Don't release our claim on the child. The child should not be reclaimed.
	CATCH(errctx, heap_release_actor(parent));
	FAIL_NONZERO_BREAK(errctx, child->parent, ERR_VALUE, "Child still references released parent");
	FAIL_ZERO_BREAK(errctx, child->refcount, ERR_VALUE, "Child prematurely released");
	FAIL_NONZERO_BREAK(errctx, strcmp(child->name, "child"), ERR_VALUE, "Child had identity erased");
	FAIL_ZERO_BREAK(
	    errctx,
	    (child == SDL_GetPointerProperty(REGISTRY_ACTOR, child->name, NULL)),
	    ERR_KEY,
	    "Child prematurely removed from the registry");
	// Now we can release the child
	CATCH(errctx, heap_release_actor(child));
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH(errctx, true);
    
    SUCCEED_RETURN(errctx);
}

int main(void)
{
    actor_updated = 0;
    actor_rendered = 0;
    UNHANDLED_ERROR_BEHAVIOR = UNHANDLED_ERROR_EXIT;
    PREPARE_ERROR(errctx);
    ATTEMPT {
	CATCH(errctx, registry_init_actor());
	CATCH(errctx, registry_init_sprite());
	CATCH(errctx, registry_init_spritesheet());
	CATCH(errctx, registry_init_character());

	CATCH(errctx, test_registry_actor_iterator_nullpointers());
	CATCH(errctx, test_registry_actor_iterator_missingactor());
	CATCH(errctx, test_registry_actor_iterator_updaterender());
	CATCH(errctx, test_actor_set_character());
	CATCH(errctx, test_actor_manage_children());
    } CLEANUP {
    } PROCESS(errctx) {
    } FINISH_NORETURN(errctx);

    return 0;
}
