/***************************************************************************

    restrack.c

    Core MAME resource tracking.

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.

***************************************************************************/

#include "restrack.h"
#include "pool.h"
#include "timer.h"
#include "state.h"



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

typedef enum _memory_block_overlap memory_block_overlap;
enum _memory_block_overlap
{
	OVERLAP_NONE,
	OVERLAP_PARTIAL,
	OVERLAP_FULL
};



/***************************************************************************
    GLOBAL VARIABLES
***************************************************************************/

/* pool list */
static object_pool *pools[64];

/* resource tracking */
int resource_tracking_tag = 0;



/***************************************************************************
    PROTOTYPES
***************************************************************************/

static void astring_destructor(void *object, size_t size);
static void bitmap_destructor(void *object, size_t size);
static memory_block_overlap pool_contains_block(object_pool *pool, void *ptr, size_t size, void **found_block, size_t *found_block_size);



/***************************************************************************
    CORE IMPLEMENTATION
***************************************************************************/

/*-------------------------------------------------
    malloc_or_die_file_line - allocate memory or die
    trying
-------------------------------------------------*/

void *malloc_or_die_file_line(size_t size, const char *file, int line)
{
	void *result;

	/* fail on attempted allocations of 0 */
	if (size == 0)
		fatalerror("Attempted to malloc zero bytes (%s:%d)", file, line);

	/* allocate and return if we succeeded */
#ifdef MALLOC_DEBUG
	result = malloc_file_line(size, file, line);
#else
	result = malloc(size);
#endif
	if (result != NULL)
	{
#ifdef MAME_DEBUG
		rand_memory(result, size);
#endif
		return result;
	}

	/* otherwise, die horribly */
	fatalerror("Failed to allocate %d bytes (%s:%d)", (int)size, file, line);
}


/*-------------------------------------------------
    init_resource_tracking - initialize the
    resource tracking system
-------------------------------------------------*/

void init_resource_tracking(void)
{
	resource_tracking_tag = 0;
}


/*-------------------------------------------------
    exit_resource_tracking - tear down the
    resource tracking system
-------------------------------------------------*/

void exit_resource_tracking(void)
{
	while (resource_tracking_tag != 0)
		end_resource_tracking();
}


/*-------------------------------------------------
    memory_error - report a memory error
-------------------------------------------------*/

static void memory_error(const char *message)
{
	fatalerror("%s", message);
}


/*-------------------------------------------------
    begin_resource_tracking - start tracking
    resources
-------------------------------------------------*/

void begin_resource_tracking(void)
{
	object_pool *new_pool;

	/* sanity check */
	assert_always(resource_tracking_tag < ARRAY_LENGTH(pools), "Too many memory pools");

	/* create a new pool */
	new_pool = pool_alloc(memory_error);
	if (!new_pool)
		fatalerror("Failed to allocate new memory pool");
	pools[resource_tracking_tag] = new_pool;

	/* add resource types */
	pool_type_register(new_pool, OBJTYPE_ASTRING, "String", astring_destructor);
	pool_type_register(new_pool, OBJTYPE_BITMAP, "Bitmap", bitmap_destructor);
	pool_type_register(new_pool, OBJTYPE_TIMER, "Timer", timer_destructor);
	pool_type_register(new_pool, OBJTYPE_STATEREG, "Save State Registration", state_destructor);

	/* increment the tag counter */
	resource_tracking_tag++;
}


/*-------------------------------------------------
    end_resource_tracking - stop tracking
    resources
-------------------------------------------------*/

void end_resource_tracking(void)
{
	/* decrement the tag counter */
	resource_tracking_tag--;

	/* free the memory pool */
	pool_free(pools[resource_tracking_tag]);
	pools[resource_tracking_tag] = NULL;
}


/*-------------------------------------------------
    current_pool - identifies the current memory
    pool
-------------------------------------------------*/

static object_pool *current_pool(void)
{
	object_pool *pool;
	assert_always((resource_tracking_tag > 0) && (resource_tracking_tag <= ARRAY_LENGTH(pools)), "Invalid resource_tracking_tag");
	pool = pools[resource_tracking_tag - 1];
	assert_always(pool != NULL, "current_pool() is NULL");
	return pool;
}


/*-------------------------------------------------
    restrack_register_object - registers an
    object with the current pool
-------------------------------------------------*/

void *restrack_register_object(object_type type, void *ptr, size_t size, const char *file, int line)
{
	return pool_object_add_file_line(current_pool(), type, ptr, size, file, line);
}


/*-------------------------------------------------
    auto_malloc_file_line - allocate auto-
    freeing memory
-------------------------------------------------*/

void *auto_malloc_file_line(size_t size, const char *file, int line)
{
	return pool_malloc_file_line(current_pool(), size, file, line);
}


/*-------------------------------------------------
    auto_realloc_file_line - reallocate auto-
    freeing memory
-------------------------------------------------*/

void *auto_realloc_file_line(void *ptr, size_t size, const char *file, int line)
{
	object_pool *pool = current_pool();
	if (ptr != NULL)
	{
		int tag = resource_tracking_tag;
		for (; tag > 0; tag--)
		{
			pool = pools[tag - 1];
			if (pool_object_exists(pool, OBJTYPE_MEMORY, ptr))
				break;
		}
		assert_always(tag > 0, "Failed to find alloc in pool");
	}

	return pool_realloc_file_line(pool, ptr, size, file, line);
}


/*-------------------------------------------------
    auto_strdup_file_line - allocate auto-freeing
    string
-------------------------------------------------*/

char *auto_strdup_file_line(const char *str, const char *file, int line)
{
	return pool_strdup_file_line(current_pool(), str, file, line);
}


/*-------------------------------------------------
    auto_strdup_allow_null_file_line - allocate
    auto-freeing string if str is null
-------------------------------------------------*/

char *auto_strdup_allow_null_file_line(const char *str, const char *file, int line)
{
	return (str != NULL) ? auto_strdup_file_line(str, file, line) : NULL;
}


/*-------------------------------------------------
    auto_astring_alloc_file_line - allocate
    auto-freeing astring
-------------------------------------------------*/

astring *auto_astring_alloc_file_line(const char *file, int line)
{
	return restrack_register_object(OBJTYPE_ASTRING, astring_alloc(), 0, file, line);
}


/*-------------------------------------------------
    auto_bitmap_alloc_file_line - allocate
    auto-freeing bitmap
-------------------------------------------------*/

bitmap_t *auto_bitmap_alloc_file_line(int width, int height, bitmap_format format, const char *file, int line)
{
	return restrack_register_object(OBJTYPE_BITMAP, bitmap_alloc(width, height, format), width * height, file, line);
}


/*-------------------------------------------------
    validate_auto_malloc_memory - validate that a
    block of memory has been allocated by auto_malloc()
-------------------------------------------------*/

void validate_auto_malloc_memory(void *memory, size_t memory_size)
{
	memory_block_overlap overlap = OVERLAP_NONE;
	void *block_base = NULL;
	size_t block_size = 0;
	int i;

	/* scan all pools for an overlapping block */
	for (i = 0; overlap == OVERLAP_NONE && i < resource_tracking_tag; i++)
		overlap = pool_contains_block(pools[i], memory, memory_size, &block_base, &block_size);

	/* fatal error if not a full overlap */
	switch (overlap)
	{
		case OVERLAP_NONE:
			fatalerror("Memory block [0x%p-0x%p] not found", memory, (UINT8 *)memory + memory_size - 1);
			break;

		case OVERLAP_PARTIAL:
			fatalerror("Memory block [0x%p-0x%p] partially overlaps with allocated block [0x%p-0x%p]", memory, (UINT8 *)memory + memory_size - 1, block_base, (UINT8 *)block_base + block_size - 1);
			break;

		case OVERLAP_FULL:
			/* expected outcome */
			break;
	}
}


/*-------------------------------------------------
    astring_destructor - destructor for astring
    objects
-------------------------------------------------*/

static void astring_destructor(void *object, size_t size)
{
	astring_free(object);
}


/*-------------------------------------------------
    bitmap_destructor - destructor for bitmap
    objects
-------------------------------------------------*/

static void bitmap_destructor(void *object, size_t size)
{
	bitmap_free(object);
}


/*-------------------------------------------------
    pool_contains_block - determines if a pool
    contains a memory block
-------------------------------------------------*/

static memory_block_overlap pool_contains_block(object_pool *pool, void *ptr, size_t size, void **found_block, size_t *found_block_size)
{
	memory_block_overlap overlap = OVERLAP_NONE;
	object_pool_iterator *iter;
	UINT8 *ptrstart = ptr;
	UINT8 *ptrend = ptrstart + size - 1;
	void *blockptr = NULL;
	size_t blocksize = 0;

	/* iterate over memory objects in the pool */
	for (iter = pool_iterate_begin(pool, OBJTYPE_MEMORY); iter != NULL && pool_iterate_next(iter, &blockptr, &blocksize,  NULL); )
	{
		int startwithin = (ptrstart >= (UINT8 *)blockptr && ptrstart < (UINT8 *)blockptr + blocksize);
		int endwithin = (ptrend >= (UINT8 *)blockptr && ptrend < (UINT8 *)blockptr + blocksize);

		/* if the start of the incoming pointer lies within the block... */
		if (startwithin || endwithin)
		{
			overlap = (startwithin && endwithin) ? OVERLAP_FULL : OVERLAP_PARTIAL;
			break;
		}
	}
	pool_iterate_end(iter);

	/* store the results */
	if (overlap != OVERLAP_NONE)
	{
		if (found_block != NULL)
			*found_block = blockptr;
		if (found_block_size != NULL)
			*found_block_size = blocksize;
	}
	return overlap;
}
