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

/*
 *  Part 1: memory management.
 */

#ifdef  MALLOC_DEBUG

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

/*
 *  List - based memory routines
 */
V_LOCAL mem_list_entry_p
libvAllocEntry(size_t data_size)
{
	size_t cksize;
	mem_list_entry_t *entry;
	cksize=sizeof(mem_list_entry_t) + data_size;
	entry=(mem_list_entry_p)malloc(cksize);
	if(!entry)
	{
#ifdef  CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"libvAllocEntry(): malloc(%u) failed.\n",data_size);
#endif  /* CHECKED_BUILD */
		return NULL;
	}
	entry->next=NULL;
	entry->size=data_size;
	entry->opaque=GetThreadData()->mem_opaque;
	entry->data=entry+1;
	return entry;
}

V_LOCAL vboolean
libvPointerInList(mem_list_entry_t *head,void *ptr)
{
	while(head->next)
	{
		/* Skip first (head) node */
		head=head->next;
		if(head->data == ptr)
			return true;
	}
	return false;
}

#ifdef  CHECKED_BUILD
void    libv_null_pointer()
{
}
#endif  /* CHECKED_BUILD */

void    V_CALL libvRref(void);
void    V_CALL libvHashUnref(void);

V_LOCAL void
libvFree(mem_list_entry_t *head,void *entry_data)
{
	mem_list_entry_t *entry;
	while(head->next)
	{
		if(head->next->data == entry_data)
		{
			entry=head->next;
			head->next=entry->next;
			/* Note: we must libvHashUnref() here */
			libvHashUnref();
			/*
			 * Erase first dword of data block.
			 */
			MARK_OBJECT(entry->data,LIBV_INVALID_MAGIC);
			free(entry);
			return;
		}
		head=head->next;
	}
#ifdef  CHECKED_BUILD
	conlPrintf(LOG_ERROR,
		"libvFree(): no pointer 0x%0000000X in hash.\n",entry_data);
	if(entry_data == NULL)
		libv_null_pointer();
#endif  /* CHECKED_BUILD */
	return;
}

/*
 *  Memory management support routines
 */
V_LOCAL global_data_p AllocateHashes(void);

V_LOCAL mem_list_entry_p V_CALL LockMemHash(void)
{
	/* Note: we leave global data locked */
	return libvLockGlobal()->memory_hash;
}

V_LOCAL mem_list_entry_p V_CALL LockObjHash(void)
{
	/* Note: we leave global data locked */
	return libvLockGlobal()->object_hash;
}

V_LOCAL mem_list_entry_p V_CALL LockMemHashStub(void)
{
	/* Note: we leave global data locked */
	return AllocateHashes()->memory_hash;
}

V_LOCAL mem_list_entry_p V_CALL LockObjHashStub(void)
{
	/* Note: we leave global data locked */
	return AllocateHashes()->object_hash;
}

mem_list_entry_p (V_CALL *libvLockMemoryHash)(void) = LockMemHashStub;
mem_list_entry_p (V_CALL *libvLockObjectHash)(void) = LockObjHashStub;

V_LOCAL global_data_p AllocateHashes(void)
{
	global_data_t *gd;
	gd=libvLockGlobal();
	gd->memory_hash=(mem_list_entry_p)
		calloc(MEMORY_HASH_LIMIT+1,sizeof(mem_list_entry_t));
	gd->object_hash=(mem_list_entry_p)
		calloc(OBJECT_HASH_LIMIT+1,sizeof(mem_list_entry_t));
	libvLockMemoryHash = LockMemHash;
	libvLockObjectHash = LockObjHash;
	gd->hash_ref_count=0;
	/* Note: we leave global data locked */
	return gd;
}

/*  Note: we assume that global data is locked ! */
void    V_CALL libvHashRef(void)
{
	global_data_t *gd = libvLockedGlobal();
	gd->hash_ref_count++;
}

/*  Note: we assume that global data is locked ! */
void    V_CALL libvHashUnref(void)
{
	global_data_t *gd = libvLockedGlobal();
	if(--gd->hash_ref_count <= 0)
	{
		if(gd->memory_hash != NULL)
		{
			free(gd->memory_hash);
			gd->memory_hash=NULL;
		}
		if(gd->object_hash != NULL)
		{
			free(gd->object_hash);
			gd->object_hash=NULL;
		}
		libvLockMemoryHash = LockMemHashStub;
		libvLockObjectHash = LockObjHashStub;
	}
}

/*
 *  Hash - based memory routines
 */

void_p  V_GLOBAL
memAlloc(size_t size)
{
	mem_list_entry_t *head, *entry;
	if(!size)
		return NULL;
	entry = libvAllocEntry(size);
	if(!entry)
		return NULL;
	head = libvLockMemoryHash() + MEMORY_HASHPROC(entry->data);
	while(head->next)
		head=head->next;
	head->next=entry;
	/* new node inserted, so increase hash reference count */
	libvHashRef();
	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=libvLockMemoryHash() + MEMORY_HASHPROC(ptr);
	result=libvPointerInList(head,ptr);
	libvUnlockGlobal();
	return result;
}

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

/*
 *  Note: tag remains unchanged !
 */
void_p  V_GLOBAL
memRealloc(void *ptr,size_t newsize)
{
	mem_list_entry_t *hash, *head, *entry;
	if(!newsize)
	{
		memFree(ptr);
		return NULL;
	}
	if(!ptr)
		return memAlloc(newsize);
	/* Find entry */
	hash=libvLockMemoryHash();
	head=hash + MEMORY_HASHPROC(ptr);
	while(head->next)
	{
		if(head->next->data == ptr)
			break;
		head=head->next;
	}
	entry=head->next;
	if(!entry)
	{
#ifdef  CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"memRealloc(): no pointer 0x%0000000X in hash.\n",ptr);
#endif  /* CHECKED_BUILD */
		libvUnlockGlobal();
		return ptr;
	}
	/* Remove entry from hash */
	head->next=entry->next;
	/* Reallocate memory */
	entry=(mem_list_entry_t*)realloc(entry,sizeof(mem_list_entry_t)+newsize);
	/* Note: we won't change 'opaque' */
	entry->next=NULL;
	entry->size=newsize;
	entry->data=entry+1;
	/* Add entry to hash */
	head=hash + MEMORY_HASHPROC(entry->data);
	while(head->next)
		head=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();
}

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;
	entry=libvAllocEntry(size);
	if(entry == NULL)
		return LIBV_INVALID_HANDLE;
	head = libvLockObjectHash() + OBJECT_HASHPROC(entry->data);
	while(head->next)
		head = head->next;
	head->next = entry;
	/*
	 * New node inserted, so increase hash reference count
	 */
	libvHashRef();
	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 = libvLockObjectHash() + OBJECT_HASHPROC(ptr);
	result = libvPointerInList(head,(void_p)ptr);
	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 = libvLockObjectHash() + 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=malloc(size);
	if(ptr!=NULL)
		pointer_count++;
	return ptr;
}

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

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)
{
	free(ptr);
	pointer_count--;
}

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

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

void    V_GLOBAL
memStats(u32_t *bytes_ret,u32_t *blocks_ret,u32_t *handles_ret)
{
	if(bytes_ret)
		*bytes_ret=alloc_count;
	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 must 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 - 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 above function; if caller wasn't allocated
 * any resources, it can use this as destructor.
 */
u32_t   V_CALL
libv_default_destructor(vhandle_t handle)
{
	libv_object_t *self=(libv_object_t *)handle;
	self->ref_count--;
	if(!self->ref_count)
	{
		memFreeObject(self);
		return 0;
	}
	return self->ref_count;
}

#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 procedure 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: we say it '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);
}
