//#include <autoconf.h>
#include "tcm_heap.h"

#include <string.h>    // memset()

#include <osdep_service.h>

//#define _DEBUG

#if CONFIG_USE_TCM_HEAP
#define FREE_FILL_CODE     0xDEAD
#define ALLOC_FILL_CODE    0xBEEF

#define ROUND_UP2(x, pad) (((x) + ((pad) - 1)) & ~((pad) - 1))

//static
struct Heap g_tcm_heap;

#if defined (__ICCARM__)
#pragma location=".tcm.heap"
#else
__attribute__((section(".tcm.heap")))
#endif
HEAP_DEFINE_BUF(tcm_heap, TCM_HEAP_SIZE);
//unsigned char tcm_heap[TCM_HEAP_SIZE];

static int g_heap_inited = 0;
static	_lock	tcm_lock;

extern void vPortSetExtFree( void (*free)( void *p ), uint32_t upper, uint32_t lower );

void tcm_heap_init(void)
{
	//#ifdef _DEBUG
	//memset(memory, FREE_FILL_CODE, size);
	//#endif

	//ASSERT2(((int)memory % alignof(heap_buf_t)) == 0,
	//"memory buffer is unaligned, please use the HEAP_DEFINE_BUF() macro to declare heap buffers!\n");
	
	/* Initialize heap with a single big chunk */
	g_tcm_heap.FreeList = (MemChunk *)&tcm_heap;
	g_tcm_heap.FreeList->next = NULL;
//	g_tcm_heap.FreeList->size = sizeof(tcm_heap);
	g_tcm_heap.FreeList->size = tcm_heap_size; // ((0x20000000 - (u32)&tcm_heap - 520 + sizeof(heap_buf_t) - 1)/sizeof(heap_buf_t))*sizeof(heap_buf_t);
	
	g_heap_inited = 1;
	rtw_spinlock_init(&tcm_lock);
	
#if PLATFORM_FREERTOS	
	// let RTOS know how to free memory if using as task stack
	vPortSetExtFree(tcm_heap_free, 0x20000000, 0x1fff0000);
#endif
}

void tcm_heap_dump(void)
{
	if(!g_heap_inited) tcm_heap_init();
#if CONFIG_DEBUG_LOG > 1
	MemChunk *chunk, *prev;
	struct Heap* h = &g_tcm_heap;
	int count = 0;
	int free_mem;

	DBG_8195A("TCM Free Heap Memory List:\n");
	for (chunk = h->FreeList; chunk; chunk = chunk->next) {
		DBG_8195A(" [%d]=%p, %d\n", ++count, chunk, chunk->size);
	}

/*
	for (prev = (MemChunk *)&h->FreeList, chunk = h->FreeList;
		chunk;
		prev = chunk, chunk = chunk->next)
	{
		DBG_8195A(" [%d]=%p, %d\n", ++count, chunk, chunk->size);
	}
*/
#endif
}

static void *tcm_heap_allocmem(int size)
{
	MemChunk *chunk, *prev;
	struct Heap* h = &g_tcm_heap;
	_irqL 	irqL;

	rtw_enter_critical(&tcm_lock, &irqL);
	
	if(!g_heap_inited) tcm_heap_init();

	/* Round size up to the allocation granularity */
	size = ROUND_UP2(size, sizeof(MemChunk));

	/* Handle allocations of 0 bytes */
	if (!size)
		size = sizeof(MemChunk);

	/* Walk on the free list looking for any chunk big enough to
	 * fit the requested block size.
	 */
	for (prev = (MemChunk *)&h->FreeList, chunk = h->FreeList;
		chunk;
		prev = chunk, chunk = chunk->next)
	{
		if (chunk->size >= size)
		{
			if (chunk->size == size)
			{
				/* Just remove this chunk from the free list */
				prev->next = chunk->next;
			}
			else
			{
				/* Allocate from the END of an existing chunk */
				chunk->size -= size;
				chunk = (MemChunk *)((uint8_t *)chunk + chunk->size);
			}
#ifdef _DEBUG
			memset(chunk, ALLOC_FILL_CODE, size);
#endif

			rtw_exit_critical(&tcm_lock, &irqL);
			DBG_TCM_HEAP_INFO("tcm_alloc:%p[%d]\n", chunk, size);
			return (void *)chunk;
		}
	}
	rtw_exit_critical(&tcm_lock, &irqL);
	DBG_TCM_HEAP_WARN("tcm_alloc(%d) - freeSpace(%d)!\n", size, tcm_heap_freeSpace());
	return NULL; /* fail */
}

static void tcm_heap_freemem(void *mem, int size)
{
	MemChunk *prev;
	//ASSERT(mem);
	struct Heap* h = &g_tcm_heap;
	_irqL 	irqL;

	rtw_enter_critical(&tcm_lock, &irqL);	
	
//	if(!g_heap_inited)	tcm_heap_init();

#ifdef _DEBUG
	memset(mem, FREE_FILL_CODE, size);
#endif

	/* Round size up to the allocation granularity */
	size = ROUND_UP2(size, sizeof(MemChunk));

	/* Handle allocations of 0 bytes */
	if (!size)
		size = sizeof(MemChunk);

	/* Special cases: first chunk in the free list or memory completely full */
	//ASSERT((uint8_t*)mem != (uint8_t*)h->FreeList);
	if (((uint8_t *)mem) < ((uint8_t *)h->FreeList) || !h->FreeList)
	{
		/* Insert memory block before the current free list head */
		prev = (MemChunk *)mem;
		prev->next = h->FreeList;
		prev->size = size;
		h->FreeList = prev;
	}
	else /* Normal case: not the first chunk in the free list */
	{
		/*
		 * Walk on the free list. Stop at the insertion point (when mem
		 * is between prev and prev->next)
		 */
		prev = h->FreeList;
		while (prev->next < (MemChunk *)mem && prev->next)
			prev = prev->next;

		/* Make sure mem is not *within* prev */
		//ASSERT((uint8_t*)mem >= (uint8_t*)prev + prev->size);

		/* Should it be merged with previous block? */
		if (((uint8_t *)prev) + prev->size == ((uint8_t *)mem))
		{
			/* Yes */
			prev->size += size;
		}
		else /* not merged with previous chunk */
		{
			MemChunk *curr = (MemChunk*)mem;

			/* insert it after the previous node
			 * and move the 'prev' pointer forward
			 * for the following operations
			 */
			curr->next = prev->next;
			curr->size = size;
			prev->next = curr;

			/* Adjust for the following test */
			prev = curr;
		}
	}

	/* Also merge with next chunk? */
	if (((uint8_t *)prev) + prev->size == ((uint8_t *)prev->next))
	{
		prev->size += prev->next->size;
		prev->next = prev->next->next;

		/* There should be only one merge opportunity, becuase we always merge on free */
		//ASSERT((uint8_t*)prev + prev->size != (uint8_t*)prev->next);
	}
	
	rtw_exit_critical(&tcm_lock, &irqL);	
	DBG_TCM_HEAP_INFO("tcm_free:%p[%d]\n", mem, size);
}

int tcm_heap_freeSpace(void)
{
	int free_mem = 0;
	struct Heap* h = &g_tcm_heap;
	_irqL 	irqL;
	MemChunk *chunk;

	rtw_enter_critical(&tcm_lock, &irqL);
	
	if(!g_heap_inited)	tcm_heap_init();
	
	for (chunk = h->FreeList; chunk; chunk = chunk->next)
		free_mem += chunk->size;

	rtw_exit_critical(&tcm_lock, &irqL);
	return free_mem;
}


/**
 * Standard malloc interface
 */
void *tcm_heap_malloc(int size)
{
	int *mem;

	size += sizeof(int);
	if ((mem = (int*)tcm_heap_allocmem(size))){
		*mem++ = size;
	}

	return mem;
}

/**
 * Standard calloc interface
 */
void *tcm_heap_calloc(int size)
{
	void *mem;

	if ((mem = tcm_heap_malloc(size)))
		memset(mem, 0, size);

	return mem;
}

/**
 * Free a block of memory, determining its size automatically.
 *
 * \param h    Heap from which the block was allocated.
 * \param mem  Pointer to a block of memory previously allocated with
 *             either heap_malloc() or heap_calloc().
 *
 * \note If \a mem is a NULL pointer, no operation is performed.
 *
 * \note Freeing the same memory block twice has undefined behavior.
 *
 * \note This function works like the ANSI C free().
 */
void tcm_heap_free(void *mem)
{
	int *_mem = (int *)mem;

	if (_mem)
	{
		--_mem;
		tcm_heap_freemem(_mem, *_mem);
	}
}

#if 0
//----------- Tests -------------
static void alloc_test(int size, int test_len)
{
	//Simple test
	uint8_t *a[100];
	int i, j;
	
	for (i = 0; i < test_len; i++)
	{
		a[i] = tcm_heap_allocmem(size);
		//ASSERT(a[i]);
		for (j = 0; j < size; j++)
			a[i][j] = i;
	}

	//ASSERT(heap_freeSpace(&h) == HEAP_SIZE - test_len * ROUND_UP2(size, sizeof(MemChunk)));

	for (i = 0; i < test_len; i++)
	{
		for (j = 0; j < size; j++)
		{
			DBG_8195A("a[%d][%d] = %d\n", i, j, a[i][j]);
			//ASSERT(a[i][j] == i);
		}
		tcm_heap_freemem(a[i], size);
	}
	//ASSERT(heap_freeSpace(&h) == HEAP_SIZE);
}

#define ALLOC_SIZE 256
#define ALLOC_SIZE2 1024
#define TEST_LEN 20
#define TEST_LEN2 10
#define HEAP_SIZE 59*1024
int tcm_heap_testRun(void)
{
	alloc_test(ALLOC_SIZE, TEST_LEN);
	alloc_test(ALLOC_SIZE2, TEST_LEN2);
	/* Try to allocate the whole heap */
	uint8_t *b = tcm_heap_allocmem(HEAP_SIZE);
	int i, j;
	//ASSERT(b);
	//ASSERT(heap_freeSpace(&h) == 0);

	//ASSERT(!heap_allocmem(&h, HEAP_SIZE));

	for (j = 0; j < HEAP_SIZE; j++)
		b[j] = j;
	
	for (j = 0; j < HEAP_SIZE; j++)
	{
		DBG_8195A("b[%d] = %d\n", j, j);
		//ASSERT(b[j] == (j & 0xff));
	}
	tcm_heap_freemem(b, HEAP_SIZE);
	//ASSERT(heap_freeSpace(&h) == HEAP_SIZE);

	return 0;
}
#endif // tests

#endif