#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>

/*
 *  Memory management.
 */

#ifdef  MALLOC_DEBUG

DEBUG_MESSAGE(no_such_ptr_in_hash,"%s(): no pointer 0x%08X in hash.\n")
DEBUG_MESSAGE(memb_overrun,"%s(): OVERRUN in block 0x%08X (leak: %u).\n")


/*
 * Sets the tag to attach to memory blocks.
 */
void    V_GLOBAL
memSetOpaque(unsigned opaque)
{
	GetThreadData()->mem_opaque = opaque;
}

/*
 * Installs pseudo-unique sentinel
 */
#define	BLOCK_SENTINEL(entry)	((u32_t)(entry)->data)

V_LOCAL	void
libvInstallBlockSentinel(mem_list_entry_p entry)
{
	entry->sentinel_ptr = (u32_p)((u8_p)entry->data + entry->size);
	*entry->sentinel_ptr = ((u32_t)(entry)->data);
}

/*
 * Validates sentinels on this and previous blocks
 */
V_LOCAL	vboolean
libvCheckBlockSentinel(mem_list_entry_p entry)
{
	CALLER_NAME(libvCheckBlockSentinel)
	if(!entry)
		return false;
	if(*entry->sentinel_ptr != BLOCK_SENTINEL(entry))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,memb_overrun,caller_name,entry->data,entry->size);
#endif	/* CHECKED_BUILD */
		return false;
	}
	return true;
}

/*
 * Allocates new mem_list_entry_t struct. Assumes global
 * data is already locked.
 */
V_LOCAL mem_list_entry_p
libvAllocEntry(size_t data_size)
{
	CALLER_NAME(libvAllocEntry)
	size_t cksize;
	mem_list_entry_t *entry;
	global_data_t *gd;
	cksize = sizeof(mem_list_entry_t) + data_size + sizeof(u32_t);
	entry = (mem_list_entry_p)archMemAlloc(cksize);
	if(!entry)
	{
#ifdef  CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(): archMemAlloc(%u) failed.\n",caller_name,cksize);
#endif  /* CHECKED_BUILD */
		return NULL;
	}
	gd = libvLockedGlobal();
	entry->next = NULL;
	entry->size = data_size;
	entry->opaque = GetThreadData()->mem_opaque;
	entry->data = entry + 1;
	libvInstallBlockSentinel(entry);
	gd->hash_ref_count++;
	return entry;
}

V_LOCAL mem_list_entry_p
libvFindPrevPointer(mem_list_entry_p head,void *ptr)
{
	while(head->next)
	{
		if(head->next->data == ptr)
			return head;
		head = head->next;
	}
	return NULL;
}

V_LOCAL void libvFreeMemHashes(global_data_p gd);

/*
 * Deallocates mem_list_entry_t struct. Assumes global data is locked.
 */
V_LOCAL void
libvFree(mem_list_entry_p head,void *entry_data)
{
	CALLER_NAME(libvFree)
	mem_list_entry_t *entry;
	global_data_p gd = libvLockedGlobal();
	head = libvFindPrevPointer(head,entry_data);
	if(!head)
	{
#ifdef  CHECKED_BUILD
		conlPrintf(LOG_ERROR,no_such_ptr_in_hash,caller_name,entry_data);
#endif  /* CHECKED_BUILD */
		return;
	}
	entry = head->next;
	if(!libvCheckBlockSentinel(entry))
		return;
	/*
	 * Passed; remove from list and destroy.
	 */
	head->next = entry->next;
	gd->hash_ref_count--;
	if(!gd->hash_ref_count)
		libvFreeMemHashes(gd);
	/*
	 * Erase first dword of data block.
	 */
	MARK_OBJECT(entry->data,LIBV_INVALID_MAGIC);
	archMemFree(entry);
	return;
}

/*
 * Reallocates new mem_list_entry_t struct. Assumes that
 * global data is already locked.
 */
V_LOCAL mem_list_entry_p
libvReallocEntry(mem_list_entry_p head,void *entry_data,size_t data_size)
{
	CALLER_NAME(libvReallocEntry)
	u32_t cksize;
	mem_list_entry_p entry;
	head = libvFindPrevPointer(head,entry_data);
	if(!head)
	{
#ifdef  CHECKED_BUILD
		conlPrintf(LOG_ERROR,no_such_ptr_in_hash,caller_name,entry_data);
#endif  /* CHECKED_BUILD */
		return NULL;
	}
	entry = head->next;
	/* check sentinels of this and prev blocks */
	if(!libvCheckBlockSentinel(entry))
		return NULL;
	/* passed; remove from list */
	head->next = entry->next;
	/* archMemReallocate memory, but do _not_ touch hash reference counter */
	cksize = sizeof(mem_list_entry_t) + data_size + sizeof(u32_t);
	entry = (mem_list_entry_p)archMemRealloc(entry,cksize);
	if(!entry)
	{
#ifdef  CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s(): archMemRealloc(%u) failed.\n",
			caller_name,cksize);
#endif  /* CHECKED_BUILD */
		return NULL;
	}
	entry->next = NULL;
	entry->size = data_size;
	/* entry->opaque remains unchanged */
	entry->data = entry + 1;
	libvInstallBlockSentinel(entry);
	return entry;
}

/*
 * All hash - support routines assume global data is locked.
 */

V_LOCAL global_data_p libvAllocateMemHashes(global_data_p);

V_LOCAL mem_list_entry_p V_CALL
libvGetMemHash(global_data_p gd)
{
	return gd->memory_hash;
}

V_LOCAL mem_list_entry_p V_CALL
libvGetObjHash(global_data_p gd)
{
	return gd->object_hash;
}

V_LOCAL mem_list_entry_p V_CALL
libvGetMemHashStub(global_data_p gd)
{
	return libvAllocateMemHashes(gd)->memory_hash;
}

V_LOCAL mem_list_entry_p V_CALL
libvGetObjHashStub(global_data_p gd)
{
	return libvAllocateMemHashes(gd)->object_hash;
}

mem_list_entry_p (V_CALL *libvGetMemoryHash)(global_data_p) = libvGetMemHashStub;
mem_list_entry_p (V_CALL *libvGetObjectHash)(global_data_p) = libvGetObjHashStub;

/*
 * It is Ok to leave it unprotected.
 */
V_LOCAL u32_t mem_zhashtable[4][256];

#define	ZHSEED		373

V_LOCAL u32_t
libvZHashPointer(void_p ptr,u32_t mask)
{
	u8_p key = (u8_p)&ptr;
	u32_t val = 4;
	val ^= mem_zhashtable[0][key[0]];
	val ^= mem_zhashtable[1][key[1]];
	val ^= mem_zhashtable[2][key[2]];
	val ^= mem_zhashtable[3][key[3]];
#ifdef	CONFIG_ARCH_64BIT
	val ^= malloc_zhashtab[0][key[4]];
	val ^= malloc_zhashtab[1][key[5]];
	val ^= malloc_zhashtab[2][key[6]];
	val ^= malloc_zhashtab[3][key[7]];
#endif	/* CONFIG_ARCH_64BIT */
	return val & mask;
}

#define	MEMORY_HASHPROC(ptr)	libvZHashPointer(ptr,MEMORY_HASH_LIMIT)
#define	OBJECT_HASHPROC(ptr)	libvZHashPointer(ptr,OBJECT_HASH_LIMIT)

V_LOCAL global_data_p
libvAllocateMemHashes(global_data_p gd)
{
	vsize_t mhlen = (MEMORY_HASH_LIMIT + 1 ) * sizeof(mem_list_entry_t);
	vsize_t ohlen = (OBJECT_HASH_LIMIT + 1) * sizeof(mem_list_entry_t);
	gd->memory_hash = (mem_list_entry_p)archMemAlloc(mhlen);
	gd->object_hash = (mem_list_entry_p)archMemAlloc(ohlen);
	if(!gd->memory_hash || !gd->object_hash)
	{
		libvError("Failed to initialize memory management subsystem.");
		assert(0);
	}
	memZero(gd->memory_hash,mhlen);
	memZero(gd->object_hash,ohlen);
	if(!mem_zhashtable[0][0])
	{
		u32_t i, j;
		ran_state_t rs;
		ranInitState(&rs,ZHSEED);
		for(i = 0;i < 4;i++)
			for(j = 0; j < 256;j++)
				mem_zhashtable[i][j] = (u32_t)ranGenerateNext(&rs);
	}
	libvGetMemoryHash = libvGetMemHash;
	libvGetObjectHash = libvGetObjHash;
	return gd;
}

V_LOCAL void
libvFreeMemHashes(global_data_p gd)
{
	if(gd->memory_hash != NULL)
	{
		archMemFree(gd->memory_hash);
		gd->memory_hash = NULL;
	}
	if(gd->object_hash != NULL)
	{
		archMemFree(gd->object_hash);
		gd->object_hash = NULL;
	}
	libvGetMemoryHash = libvGetMemHashStub;
	libvGetObjectHash = libvGetObjHashStub;
}

/*
 * Hash - based memory routines
 */

void_p  V_GLOBAL
memAlloc(size_t size)
{
	mem_list_entry_t *head, *entry;
	global_data_p gd;
	if(!size)
		return NULL;
	gd = libvLockGlobal();
	entry = libvAllocEntry(size);
	if(!entry)
	{
		libvUnlockGlobal();
		return NULL;
	}
	head = libvGetMemoryHash(gd);
	head += MEMORY_HASHPROC(entry->data);
	entry->next = head->next;
	head->next = entry;
	libvUnlockGlobal();
	return entry->data;
}

void_p  V_GLOBAL
memAllocZ(size_t size)
{
	void_p block = memAlloc(size);
	if(block)
		memZero(block,size);
	return block;
}

vboolean    V_GLOBAL
memIsPointer(void *ptr)
{
	vboolean result;
	mem_list_entry_t *head;
	head = libvGetMemoryHash(libvLockGlobal()) + MEMORY_HASHPROC(ptr);
	result = libvFindPrevPointer(head,ptr) ? true : false;
	libvUnlockGlobal();
	return result;
}

void    V_GLOBAL
memFree(void *ptr)
{
	mem_list_entry_t *head;
	head = libvGetMemoryHash(libvLockGlobal()) + MEMORY_HASHPROC(ptr);
	libvFree(head,ptr);
	libvUnlockGlobal();
}

/*
 * Note: opaque remains unchanged.
 */
void_p  V_GLOBAL
memRealloc(void *ptr,size_t newsize)
{
	mem_list_entry_p hash, head, entry;
	if(!newsize)
	{
		memFree(ptr);
		return NULL;
	}
	if(!ptr)
		return memAlloc(newsize);
	hash = libvGetMemoryHash(libvLockGlobal());
	head = hash + MEMORY_HASHPROC(ptr);
	/* Reallocate memory */
	entry = libvReallocEntry(head,ptr,newsize);
	if(!entry)
	{
		libvUnlockGlobal();
		return NULL;
	}
	/* Add entry to hash */
	head = hash + MEMORY_HASHPROC(entry->data);
	entry->next = head->next;
	head->next = entry;
	libvUnlockGlobal();
	return entry->data;
}

void    V_GLOBAL
memStats(u32_t *bytes_ret,u32_t *blocks_ret,u32_t *handles_ret)
{
	int i;
	unsigned pc=0,oc=0,mc=0;
	mem_list_entry_t *head;
	global_data_t *gd = libvLockGlobal();
	if(gd->object_hash != NULL)
	{
		for(i = 0;i <= OBJECT_HASH_LIMIT;i++)
		{
			head = (gd->object_hash + i)->next;
			while(head)
			{
				mc += head->size;
				oc++;
				head = head->next;
			}
		}
	}
	if(gd->memory_hash != NULL)
	{
		for(i = 0;i <= MEMORY_HASH_LIMIT;i++)
		{
			head=(gd->memory_hash + i)->next;
			while(head)
			{
				mc += head->size;
				pc++;
				head = head->next;
			}
		}
	}
	if(bytes_ret)
		*bytes_ret = mc;
	if(blocks_ret)
		*blocks_ret = pc;
	if(handles_ret)
		*handles_ret = oc;
	libvUnlockGlobal();
}

vboolean	V_GLOBAL
memHeapGood(void)
{
	int i;
	vboolean result = true;
	mem_list_entry_p head;
	global_data_t *gd = libvLockGlobal();
	if(gd->object_hash != NULL)
	{
		for(i = 0;i <= OBJECT_HASH_LIMIT;i++)
		{
			head = (gd->object_hash + i)->next;
			for(;head;head = head->next)
				if(!libvCheckBlockSentinel(head))
					result = false; /* don't break */
		}
	}
	if(gd->memory_hash != NULL)
	{
		for(i = 0;i <= MEMORY_HASH_LIMIT;i++)
		{
			head = (gd->memory_hash + i)->next;
			for(;head;head = head->next)
				if(!libvCheckBlockSentinel(head))
					result = false; /* don't break */
		}
	}
	libvUnlockGlobal();
	return result;
}

void    V_GLOBAL
memPrintObjStats(void)
{
	int i;
	mem_list_entry_t *head;
	global_data_t *gd = libvLockGlobal();
	if(gd->object_hash != NULL)
	{
		for(i = 0;i <= OBJECT_HASH_LIMIT;i++)
		{
			head=(gd->object_hash + i)->next;
			while(head)
			{
#ifdef  CHECKED_BUILD
				char *magic=(char*)head->data;
				conlPrintf(LOG_TRACE,"Object '%c%c%c%c' (%X): %u bytes (opaque %u)\n",
					magic[3],magic[2],magic[1],magic[0],
					head->data,
					head->size,
					head->opaque);
#else   /* !CHECKED_BUILD */
				conlPrintf(LOG_TRACE,"Object %X: %u bytes (opaque %u)\n",
					head->data,
					head->size,
					head->opaque);
#endif  /* !CHECKED_BUILD */
				head=head->next;
			}
		}
	}
	libvUnlockGlobal();
}

void    V_GLOBAL
memPrintMemStats(void)
{
	int i;
	mem_list_entry_t *head;
	global_data_t *gd = libvLockGlobal();
	if(gd->memory_hash != NULL)
	{
		for(i = 0;i <= MEMORY_HASH_LIMIT;i++)
		{
			head = (gd->memory_hash + i)->next;
			while(head)
			{
				conlPrintf(LOG_TRACE,"Block %X: %u bytes (opaque %u)\n",
					head->data,
					head->size,
					head->opaque);
				head=head->next;
			}
		}
	}
	libvUnlockGlobal();
}

/*
 * Part 2: object management.
 */
vhandle_t V_CALL
memAllocObject(size_t size)
{
	mem_list_entry_t *head, *entry;
	global_data_p gd;
	if(!size)
		return LIBV_INVALID_HANDLE;
	gd = libvLockGlobal();
	entry = libvAllocEntry(size);
	if(!entry)
	{
		libvUnlockGlobal();
		return LIBV_INVALID_HANDLE;
	}
	head = libvGetObjectHash(gd);
	head += OBJECT_HASHPROC(entry->data);
	entry->next = head->next;
	head->next = entry;
	memZero(entry->data,size);
	libvUnlockGlobal();
	return (vhandle_t)entry->data;
}

/*
 *  Returns 'true' if 'ptr' was allocated by memAllocObject().
 */
vboolean    V_CALL
memIsObject(vhandle_t ptr)
{
	vboolean result;
	mem_list_entry_t *head;
	head = libvGetObjectHash(libvLockGlobal()) + OBJECT_HASHPROC(ptr);
	result = libvFindPrevPointer(head,(void_p)ptr) ? true : false;
	libvUnlockGlobal();
	return result;
}

/*
 *  Frees memory block previosly allocated by memAllocObject().
 *  Should be used only in destructors.
 */
void    V_CALL
memFreeObject(vhandle_t ptr)
{
	mem_list_entry_t *head;
	head = libvGetObjectHash(libvLockGlobal()) + OBJECT_HASHPROC(ptr);
	libvFree(head,ptr);
	libvUnlockGlobal();
}

#else   /* !MALLOC_DEBUG */

/*
 *  Note: changing 32-bit integers is an atomic operation,
 *  so it's safe _not_ to move counters into protected section.
 */
V_LOCAL u32_t   pointer_count = 0;
V_LOCAL u32_t   handle_count = 0;

void_p  V_GLOBAL
memAlloc(size_t size)
{
	void *ptr = archMemAlloc(size);
	if(ptr != NULL)
		pointer_count++;
	return ptr;
}

vhandle_t V_CALL
memAllocObject(size_t size)
{
	void *ptr = archMemAlloc(size);
	if(ptr! = NULL)
		handle_count++;
	return ptr ? (vhandle_t)ptr : LIBV_INVALID_HANDLE;
}

vboolean    V_GLOBAL
memIsPointer(void *ptr)
{
	return (ptr != NULL) ? true : false;
}

vboolean    V_CALL
memIsObject(vhandle_t ptr)
{
	return (ptr != LIBV_INVALID_HANDLE) ? true : false;
}

void    V_GLOBAL
memFree(void *ptr)
{
	archMemFree(ptr);
	pointer_count--;
}

void    V_CALL
memFreeObject(vhandle_t ptr)
{
	archMemFree((void_p)ptr);
	handle_count--;
}

void_p  V_GLOBAL
memRealloc(void *ptr,size_t newsize)
{
	return archMemRealloc(ptr,newsize);
}

void    V_GLOBAL
memStats(u32_t *bytes_ret,u32_t *blocks_ret,u32_t *handles_ret)
{
	if(bytes_ret)
		*bytes_ret = 0;
	if(blocks_ret)
		*blocks_ret = pointer_count;
	if(handles_ret)
		*handles_ret = handle_count;
	libvUnlockGlobal();
}

#endif  /* !MALLOC_DEBUG */

void    V_GLOBAL
memPrintStats(void)
{
	u32_t pc,oc,mc;
	memStats(&mc,&pc,&oc);
#ifdef  MALLOC_DEBUG
	conPrintf("memStats(): %u bytes in %d blocks "
		"and %u handles.\n",mc,pc,oc);
#else   /* !MALLOC_DEBUG */
	conPrintf("memStats(): %d blocks, %u handles.\n",pc,oc);
#endif  /* !MALLOC_DEBUG */
}

/*
 * Here (at default destructor) we can only decrement reference
 * counter and self-destroy if it reaches zero. Thus, for all objects
 * which perform dynamic memory/resource allocation, own object-specific
 * destructors have to be defined.
 */
u32_t   V_CALL
libv_destruct_and_complain(vhandle_t handle)
{
	/*
	 * Note: we assume 'handle' is valid, because it was tested
	 * for validity in wrapper routine libvCloseHandle().
	 */
	libv_object_t *self = (libv_object_t *)handle;
	self->ref_count--;
	if(!self->ref_count)
	{
#ifdef  CHECKED_BUILD
		conlPuts(LOG_WARNING,"libv_object_destructor(): default "
			"destructor called; broken LibV code ?.\n");
#endif  /* CHECKED_BUILD */
		memFreeObject(self);
		return 0;
	}
	return self->ref_count;
}

/*
 * Quiet version of the above function. 
 * This routine can be used as a destructor if there was no dynamic
 * memory allocation.
 */
u32_t   V_CALL
libv_default_destructor(vhandle_t handle)
{
	libv_object_t *self = (libv_object_t *)handle;
	if(--self->ref_count)
		return self->ref_count;
	memFreeObject(self);
	return 0;
}

#ifdef  CHECKED_BUILD

void_p  V_CALL
libvIsHandle(vhandle_t handle,char *caller,char *object,
	u32_t magic)
{
	if(!memIsObject(handle))
	{
		if(handle != LIBV_INVALID_HANDLE)
			conlPrintf(LOG_ERROR,
				"%s(): descriptor 0x%08X not owned by object manager.\n",
				caller,handle);
		else
			conlPrintf(LOG_ERROR,"%s(): passed LIBV_INVALID_HANDLE "
				"(failure of construction ?).\n",caller,handle);
		return LIBV_INVALID_HANDLE;
	}
	if(CHECK_MAGIC(handle,magic))
	{
		conlPrintf(LOG_ERROR,"%s(): %s damaged: bad magic 0x%08X.\n",
			caller,object ? object : "object");
		return LIBV_INVALID_HANDLE;
	}
	return handle;
}

/*
 * Note: this routine only calls object - specific destructor.
 * It's up to object's destructor to manipulate with reference
 * counter.
 */
u32_t   V_CALL
libvCloseHandle(vhandle_t handle,char *caller,char *object,
	u32_t magic)
{
	libv_object_t *obj;
	obj = (libv_object_t*)libvIsHandle(handle,caller,object,magic);
	if(obj == LIBV_INVALID_HANDLE)
		return 0; /* Invalid object: say _not exist_. */
	/* Call destructor (later must derefernce the object) */
	return obj->destroy(obj);
}

#endif  /* CHECKED_BUILD */

/*
 *  Quiet version of libvCloseHandle()
 */
u32_t   V_CALL
__libvCloseHandle(vhandle_t handle)
{
	libv_object_t *obj;
	if(!memIsObject(handle))
	{
#ifdef  CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"libvCloseHandle(): invalid descriptor %X.\n",handle);
#endif  /* CHECKED_BUILD */
		obj = LIBV_INVALID_HANDLE;
	}
	else
		obj = (libv_object_t*)handle;
	if(obj == LIBV_INVALID_HANDLE)
		return 0;
	/* Call destructor (later must derefernce the object) */
	return obj->destroy(obj);
}
