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

#include <stdlib.h>
#include <akerror.h>

#include <akgl/game.h>
#include <akgl/sprite.h>
#include <akgl/heap.h>
#include <akgl/registry.h>
#include <akgl/staticstring.h>
#include <akgl/iterator.h>
#include <akgl/error.h>

akgl_Actor akgl_heap_actors[AKGL_MAX_HEAP_ACTOR];
akgl_Sprite akgl_heap_sprites[AKGL_MAX_HEAP_SPRITE];
akgl_SpriteSheet akgl_heap_spritesheets[AKGL_MAX_HEAP_SPRITESHEET];
akgl_Character akgl_heap_characters[AKGL_MAX_HEAP_CHARACTER];
akgl_String akgl_heap_strings[AKGL_MAX_HEAP_STRING];
akgl_CollisionProxy akgl_heap_collision_proxies[AKGL_MAX_HEAP_COLLISION_PROXY];
akgl_CollisionCell akgl_heap_collision_cells[AKGL_MAX_HEAP_COLLISION_CELL];
akgl_BspNode akgl_heap_bspnodes[AKGL_MAX_HEAP_BSPNODE];

akerr_ErrorContext *akgl_heap_init(void)
{
    PREPARE_ERROR(errctx);
    int i = 0;
    PASS(errctx, akgl_heap_init_actor());
    for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++) {
	memset(&akgl_heap_sprites[i], 0x00, sizeof(akgl_Sprite));
    }
    for ( i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++) {
	memset(&akgl_heap_spritesheets[i], 0x00, sizeof(akgl_SpriteSheet));
    }
    for ( i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++) {
	memset(&akgl_heap_characters[i], 0x00, sizeof(akgl_Character));
    }
    for ( i = 0; i < AKGL_MAX_HEAP_STRING; i++) {
	memset(&akgl_heap_strings[i], 0x00, sizeof(akgl_String));
    }
    for ( i = 0; i < AKGL_MAX_HEAP_COLLISION_PROXY; i++) {
	memset(&akgl_heap_collision_proxies[i], 0x00, sizeof(akgl_CollisionProxy));
	akgl_heap_collision_proxies[i].first = -1;
    }
    PASS(errctx, akgl_heap_init_collision_cells());
    PASS(errctx, akgl_heap_init_bspnodes());
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_init_actor(void)
{
    PREPARE_ERROR(errctx);
    for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++) {
	memset(&akgl_heap_actors[i], 0x00, sizeof(akgl_Actor));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_next_actor(akgl_Actor **dest)
{
    PREPARE_ERROR(errctx);
    for (int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
	if ( akgl_heap_actors[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_actors[i];
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused actor on the heap");
}

akerr_ErrorContext *akgl_heap_next_sprite(akgl_Sprite **dest)
{
    PREPARE_ERROR(errctx);
    for (int i = 0; i < AKGL_MAX_HEAP_SPRITE; i++ ) {
	if ( akgl_heap_sprites[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_sprites[i];
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused sprite on the heap");
}

akerr_ErrorContext *akgl_heap_next_spritesheet(akgl_SpriteSheet **dest)
{
    PREPARE_ERROR(errctx);
    for (int i = 0; i < AKGL_MAX_HEAP_SPRITESHEET; i++ ) {
	if ( akgl_heap_spritesheets[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_spritesheets[i];
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused spritesheet on the heap");
}

akerr_ErrorContext *akgl_heap_next_character(akgl_Character **dest)
{
    PREPARE_ERROR(errctx);
    for (int i = 0; i < AKGL_MAX_HEAP_CHARACTER; i++ ) {
	if ( akgl_heap_characters[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_characters[i];
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused character on the heap");
}

akerr_ErrorContext *akgl_heap_next_string(akgl_String **dest)
{
    PREPARE_ERROR(errctx);
    for (int i = 0; i < AKGL_MAX_HEAP_STRING; i++ ) {
	if ( akgl_heap_strings[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_strings[i];
	akgl_heap_strings[i].refcount += 1;
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused string on the heap");
}

akerr_ErrorContext *akgl_heap_release_actor(akgl_Actor *ptr)
{
    int i = 0;
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL actor reference");
    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	for ( i = 0; i < AKGL_ACTOR_MAX_CHILDREN; i++ ) {
	    if ( ptr->children[i] != NULL ) {
		PASS(errctx, akgl_heap_release_actor(ptr->children[i]));
	    }
	}
	/*
	 * The proxy goes with the actor. It holds a borrowed `owner` pointer
	 * into the slot about to be zeroed, so leaving it registered would give
	 * the broad phase a proxy whose owner reads as a free actor -- and the
	 * next contact against it would report a collision with nothing.
	 */
	if ( ptr->proxy != NULL ) {
	    PASS(errctx, akgl_heap_release_collision_proxy(ptr->proxy));
	    ptr->proxy = NULL;
	}
	SDL_ClearProperty(AKGL_REGISTRY_ACTOR, (char *)&ptr->name);
	memset(ptr, 0x00, sizeof(akgl_Actor));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_release_character(akgl_Character *ptr)
{
    PREPARE_ERROR(errctx);
    akgl_Iterator opflags;
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL character reference");
    AKGL_BITMASK_CLEAR(opflags.flags);

    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	// Give back every sprite reference akgl_character_sprite_add took, and
	// the property set holding the map, before the slot is zeroed. Without
	// this a game that loads and releases characters level by level
	// exhausts the sprite pool and leaks one SDL_PropertiesID per
	// character. akgl_character_state_sprites_iterate exists for exactly
	// this walk and simply was never called from here.
	if ( ptr->state_sprites != 0 ) {
	    AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_RELEASE);
	    SDL_EnumerateProperties(
		ptr->state_sprites,
		&akgl_character_state_sprites_iterate,
		(void *)&opflags
		);
	    SDL_DestroyProperties(ptr->state_sprites);
	    ptr->state_sprites = 0;
	}
	SDL_ClearProperty(AKGL_REGISTRY_CHARACTER, (char *)&ptr->name);
	memset(ptr, 0x00, sizeof(akgl_Character));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_release_sprite(akgl_Sprite *ptr)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "Received NULL sprite reference");
    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	SDL_ClearProperty(AKGL_REGISTRY_SPRITE, (char *)&ptr->name);
	memset(ptr, 0x00, sizeof(akgl_Sprite));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_release_spritesheet(akgl_SpriteSheet *ptr)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "Received NULL spritesheet reference");
    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	// TODO : If we go threaded, make sure this is only happening on the main thread
	SDL_ClearProperty(AKGL_REGISTRY_SPRITESHEET, (char *)&ptr->name);
	if ( ptr-> texture != NULL )
	    SDL_DestroyTexture(ptr->texture);
	ptr->texture = NULL;
	memset(ptr, 0x00, sizeof(akgl_SpriteSheet));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_release_string(akgl_String *ptr)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "Received NULL string reference");
    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	memset(&ptr->data, 0x00, AKGL_MAX_STRING_LENGTH);
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_next_collision_proxy(akgl_CollisionProxy **dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination reference");
    for (int i = 0; i < AKGL_MAX_HEAP_COLLISION_PROXY; i++ ) {
	if ( akgl_heap_collision_proxies[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_collision_proxies[i];
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused collision proxy on the heap");
}

akerr_ErrorContext *akgl_heap_release_collision_proxy(akgl_CollisionProxy *ptr)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL collision proxy reference");
    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	memset(ptr, 0x00, sizeof(akgl_CollisionProxy));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_next_collision_cell(akgl_CollisionCell **dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination reference");
    for (int i = 0; i < AKGL_MAX_HEAP_COLLISION_CELL; i++ ) {
	if ( akgl_heap_collision_cells[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_collision_cells[i];
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused collision cell entry on the heap");
}

akerr_ErrorContext *akgl_heap_release_collision_cell(akgl_CollisionCell *ptr)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL collision cell reference");
    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	memset(ptr, 0x00, sizeof(akgl_CollisionCell));
	ptr->next = -1;
	ptr->prev = -1;
	ptr->ownernext = -1;
	ptr->cell = -1;
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_init_collision_cells(void)
{
    PREPARE_ERROR(errctx);
    for ( int i = 0; i < AKGL_MAX_HEAP_COLLISION_CELL; i++ ) {
	memset(&akgl_heap_collision_cells[i], 0x00, sizeof(akgl_CollisionCell));
	akgl_heap_collision_cells[i].next = -1;
	akgl_heap_collision_cells[i].prev = -1;
	akgl_heap_collision_cells[i].ownernext = -1;
	akgl_heap_collision_cells[i].cell = -1;
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_next_bspnode(akgl_BspNode **dest)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination reference");
    for (int i = 0; i < AKGL_MAX_HEAP_BSPNODE; i++ ) {
	if ( akgl_heap_bspnodes[i].refcount != 0 ) {
	    continue;
	}
	*dest = &akgl_heap_bspnodes[i];
	SUCCEED_RETURN(errctx);
    }
    FAIL_RETURN(errctx, AKGL_ERR_HEAP, "Unable to find unused BSP node on the heap");
}

akerr_ErrorContext *akgl_heap_release_bspnode(akgl_BspNode *ptr)
{
    PREPARE_ERROR(errctx);
    FAIL_ZERO_RETURN(errctx, ptr, AKERR_NULLPOINTER, "NULL BSP node reference");
    if ( ptr->refcount > 0 ) {
	ptr->refcount -= 1;
    }
    if ( ptr->refcount == 0 ) {
	memset(ptr, 0x00, sizeof(akgl_BspNode));
    }
    SUCCEED_RETURN(errctx);
}

akerr_ErrorContext *akgl_heap_init_bspnodes(void)
{
    PREPARE_ERROR(errctx);
    for ( int i = 0; i < AKGL_MAX_HEAP_BSPNODE; i++ ) {
	memset(&akgl_heap_bspnodes[i], 0x00, sizeof(akgl_BspNode));
    }
    SUCCEED_RETURN(errctx);
}
