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

/*
 * Value types
 */
typedef enum
{
	reg_type_int    = 0,
	reg_type_float  = 1,
	reg_type_string	= 2,
	reg_type_binary = 3,
	reg_type_callback,	/* the only data which can't be saved in file */
	reg_type_max
}	reg_type_t;

typedef struct libv_var_s
{
	struct		libv_var_s *next;
	char		*key;
	reg_type_t	type;
	union
	{
		s32_t	v_int;
		vec_t	v_float;
		char	*v_string;
		union
		{
			u32_t	len;
			void	*data;
		}	v_binary;
		void	(V_CALL *v_callback)(void *arg);
	}	value;
} libv_var_t, *libv_var_p;

typedef struct vmap_c
{
	libv_object_t	base;
	u32_t			hashsize;
	libv_var_p		*hash;
}	vmap_t, *vmap_p;

V_LOCAL	char *vmap_object = "hashtable";
#define	VMAP_MAGIC	0x48415348	/* HASH */
#define	DEFAULT_VMAP_HASHSIZE	512
#define VMAP_HASHFN(map,x)	((x) & (map->hashsize-1))

/*
 * This is example only, needs further improvement !
 */
V_LOCAL u32_t _hashcode_for_key_(char *str)
{
	char *ptr=str;
	u32_t retval = 0;
	while(*ptr)
	{
		retval += *ptr;
		ptr++;
	}
	return retval;
}

/*
 * Allocates new list entry and stores *value in it.
 * Last parameter used only for strings and binaries.
 */
V_LOCAL libv_var_p
vmapAllocEntry(char *key,reg_type_t type,void_p value,u32_t value_size)
{
	libv_var_p result;
	size_t len, key_bytes, value_bytes;
	key_bytes = INT_ALIGN(1+strlen(key));
	if(type==reg_type_string || type==reg_type_binary)
		value_bytes = INT_ALIGN(value_size);
	else
		value_bytes = 0; /* value will be stored in union */
	len = key_bytes + value_bytes + sizeof(libv_var_t);
	result = (libv_var_p)memAlloc(len);
	result->next = NULL;
	result->type = type;
	result->key = (char *)(result + 1);
	strcpy(result->key,key);
	switch(type)
	{
		case reg_type_int:
			result->value.v_int = *((s32_t*)value);
			break;
		case reg_type_float:
			result->value.v_float = *((vec_t*)value);
			break;
		case reg_type_string:
			result->value.v_string = result->key + key_bytes;
			memCopy(result->value.v_string,value,value_size);
			break;
		case reg_type_binary:
			result->value.v_binary.len = value_size;
			if(value_size > 0)
			{
				result->value.v_binary.data = result->key + key_bytes;
				memCopy(result->value.v_binary.data,value,value_size);
			}
			else
				result->value.v_binary.data = NULL;
			break;
		case reg_type_callback:
			*((void**)(&result->value.v_callback)) = value;
			break;
		default:
			break;
	}
	return result;
}

V_LOCAL libv_var_p
vmapGetEntry(vmap_p map,char *key)
{
	libv_var_p var;
	var = map->hash[VMAP_HASHFN(map,_hashcode_for_key_(key))];
	while(var && strcmp(var->key, key))
		var = var->next;
	return var;
}

/*
 * Input:
 *	map - hash to operate with;
 *	key - key for entry (must be separate argument!);
 *	entry - variable to insert (can be NULL).
 *	overwrite - if key already exists, overwrite it.
 * Returns 'true' if key (was) already in map.
 */
V_LOCAL vboolean
vmapInsertEntry(vmap_t *map,char *key,libv_var_t *entry,vboolean overwrite)
{
	vboolean exist = false;
	libv_var_p head, var, prev;
	u32_t pos = VMAP_HASHFN(map,_hashcode_for_key_(key));
	/* Check either given key already present or not */
	head = map->hash[pos];
	for(prev=NULL,var=head;var;prev=var,var=var->next)
	{
		if(!strcmp(var->key,key))
		{
			exist = true;
			if(overwrite)
			{
				/* Delete entry */
				if(prev)
					prev->next = var->next;
				else
					map->hash[pos] = var->next;
				memFree(var);
			}
			else
				return true;
		}
	}
	/* Insert entry to the start of list */
	if(entry)
	{
		entry->next = map->hash[pos];
		map->hash[pos] = entry;
	}
	return exist;
}

V_LOCAL	u32_t	V_CALL
vmap_destroy(vhandle_t handle)
{
	u32_t i;
	libv_var_p head, entry;
	vmap_p map = (vmap_p)handle;
	if(--map->base.ref_count)
		return map->base.ref_count;
	for(i=0;i<map->hashsize;i++)
	{
		head = map->hash[i];
		while(head)
		{
			entry = head;
			head = head->next;
			memFree(entry);
		}
	}
	memFreeObject(map);
	return 0;
}

/*
 * Creates empty map
 */
vhandle_t	V_GLOBAL
vmapCreateEx(u32_t hashsize)
{
	vmap_p map;
	if(!IS_POWER_TWO(hashsize))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vmapCreateEx(%u): hashsize must be power of 2.\n",hashsize);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	ALLOC_OBJECT(map,vmap_t,hashsize * sizeof(libv_var_p),VMAP_MAGIC);
	map->hashsize = hashsize;
	map->hash = (libv_var_p*)(map + 1);
	map->base.destroy = vmap_destroy;
	return (vhandle_t)map;
}

vhandle_t	V_GLOBAL
vmapCreate(void)
{
	return vmapCreateEx(DEFAULT_VMAP_HASHSIZE);
}

/*
 * vmapSetXXX return 'false' if key was overwritten,
 * or error occured.
 * FIXME: wrong design !
 */
vboolean V_GLOBAL
vmapSetInt(vhandle_t handle,char *key,s32_t value)
{
	libv_var_p entry = NULL;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapSetInt",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapSetInt(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	entry = vmapAllocEntry(key,reg_type_int,&value,sizeof(s32_t));
	return !vmapInsertEntry(map,key,entry,true);
}

vboolean V_GLOBAL
vmapSetFloat(vhandle_t handle,char *key,vec_t value)
{
	libv_var_p entry = NULL;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapSetFloat",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapSetFloat(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	entry = vmapAllocEntry(key,reg_type_float,&value,sizeof(vec_t));
	return !vmapInsertEntry(map,key,entry,true);
}

vboolean V_GLOBAL
vmapSetString(vhandle_t handle,char *key,char *value)
{
	libv_var_p entry = NULL;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapSetString",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapSetString(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(value)
		entry = vmapAllocEntry(key,reg_type_string,value,1+strlen(value));
	return !vmapInsertEntry(map,key,entry,true);
}

vboolean V_GLOBAL
vmapSetBinary(vhandle_t handle,char *key,void *value,u32_t len)
{
	libv_var_p entry = NULL;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapSetBinary",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapSetBinary(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(value)
	{
#ifdef	CHECKED_BUILD
		if(!len)
			conlPuts(LOG_WARNING,"vmapSetBinary(): zero-length binary value.\n");
#endif	/* CHECKED_BUILD */
		entry = vmapAllocEntry(key,reg_type_binary,value,len);
	}
	return !vmapInsertEntry(map,key,entry,true);
}

vboolean V_GLOBAL
vmapSetCallback(vhandle_t handle,char *key,void (V_CALL *callback)(void *arg))
{
	void *addr;
	libv_var_p entry = NULL;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapSetCallback",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapSetCallback(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	addr = *((void**)&callback);
	if(addr)
		entry = vmapAllocEntry(key,reg_type_callback,addr,sizeof(void*));
	return !vmapInsertEntry(map,key,entry,true);
}

/*
 * Returns NULL if 'key' not found / not key of string.
 */
char_p V_GLOBAL
vmapStringForKey(vhandle_t handle,char *key)
{
	libv_var_p var;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapStringForKey",vmap_object,VMAP_MAGIC);
	if(!map)
		return NULL;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapStringForKey(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	var = vmapGetEntry(map,key);
	if(var != NULL && var->type == reg_type_string)
		return var->value.v_string;
	else
		return NULL;
}

/*
 * Returns 'false' if 'key' not found / not key of string.
 */
vboolean V_GLOBAL
vmapGetString(vmap_p map,char *key,char *dest,u32_t dest_len)
{
	char *ptr = vmapStringForKey(map,key);
	if(ptr == NULL)
		return false;
	/* Let strCopy() check for other args */
	strCopy(dest,ptr,dest_len);
	return true;
}

/*
 * Returns 'false' if 'key' not found / not key of int.
 */
vboolean	V_GLOBAL
vmapGetInt(vhandle_t handle,char *key,s32_t *dest)
{
	libv_var_p var;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapGetInt",vmap_object,VMAP_MAGIC);
	if(map == NULL)
		return false;
	if(key == NULL)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapGetInt(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	var = vmapGetEntry(map,key);
	if(var==NULL || var->type!=reg_type_int)
		return false;
	if(dest)
		*dest = var->value.v_int;
	return true;
}

/*
 * Returns -1 if 'key' not found / not key of int.
 */
s32_t	V_GLOBAL
vmapIntForKey(vmap_p map,char *key)
{
	s32_t result;
	return vmapGetInt(map,key,&result) ? result : -1;
}

/*
 * Returns 'def' if 'key' not found / not key of int.
 */
s32_t	V_GLOBAL
vmapIntForKeyEx(vmap_p map,char *key,s32_t def)
{
	s32_t result;
	return vmapGetInt(map,key,&result) ? result : def;
}

/*
 * Returns 'false' if 'key' not found / not key of float.
 */
vboolean	V_GLOBAL
vmapGetFloat(vhandle_t handle,char *key,vec_t *dest)
{
	libv_var_p var;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapGetFloat",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapGetFloat(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	var = vmapGetEntry(map,key);
	if(var==NULL || var->type!=reg_type_float)
		return false;
	if(dest)
		*dest = var->value.v_float;
	return true;
}

/*
 * Returns 0 if 'key' not found / not key of float.
 */
vec_t	V_GLOBAL
vmapFloatForKey(vmap_p map,char *key)
{
	vec_t result;
	return vmapGetFloat(map,key,&result) ? result : 0;
}

/*
 * Returns 'def' if 'key' not found / not key of float.
 */
vec_t	V_GLOBAL
vmapFloatForKeyEx(vmap_p map,char *key,vec_t def)
{
	vec_t result;
	return vmapGetFloat(map,key,&result) ? result : def;
}

/*
 * Returns 'false' if 'key' not found / not key of binary.
 */
vboolean	V_GLOBAL
vmapGetBinaryLen(vhandle_t handle,char *key,u32_t *len_ret)
{
	libv_var_p var;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapGetBinaryLen",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapGetBinaryLen(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	var = vmapGetEntry(map,key);
	if(var==NULL || var->type!=reg_type_binary)
		return false;
	if(len_ret)
		*len_ret = var->value.v_binary.len;
	return true;
}

/*
 * Returns 'false' if 'key' not found / not key of binary.
 */
vboolean	V_GLOBAL
vmapGetBinary(vhandle_t handle,char *key,void *dest,u32_t dest_len)
{
	u32_t src_len;
	libv_var_p var;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapGetBinary",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapGetBinary(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	var = vmapGetEntry(map,key);
	if(var==NULL || var->type!=reg_type_binary)
		return false;
	src_len = var->value.v_binary.len;
	if(dest_len > src_len)
		dest_len = src_len;
	if(dest)
		memCopy(dest,var->value.v_binary.data,dest_len);
	return true;
}

/*
 * Returns NULL if 'key' not found / not key of binary.
 */
void_p	V_GLOBAL
vmapBinaryForKey(vhandle_t handle,char *key)
{
	libv_var_p var;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapBinaryForKey",vmap_object,VMAP_MAGIC);
	if(!map)
		return NULL;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapBinaryForKey(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	var = vmapGetEntry(map,key);
	if(var==NULL || var->type!=reg_type_binary)
		return NULL;
	return var->value.v_binary.data;
}

/*
 * Returns 'false' if 'key' not found / not key of callback.
 */
vboolean	V_GLOBAL
vmapCall(vhandle_t handle,char *key,void *arg)
{
	libv_var_p var;
	vmap_p map =
		(vmap_p)libvIsHandle(handle,"vmapCall",vmap_object,VMAP_MAGIC);
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vmapCall(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	var = vmapGetEntry(map,key);
	if(var==NULL || var->type!=reg_type_callback)
		return false;
	var->value.v_callback(arg);
	return true;
}

void V_GLOBAL
vmapDestroy(vhandle_t handle)
{
	libvCloseHandle(handle,"vmapDestroy",vmap_object,VMAP_MAGIC);
}

const char *type_token[] = {"int", "float", "string", "binary"};

V_LOCAL void
vmapSaveEntry(vhandle_t out,libv_var_t *entry)
{
	u8_t *ptr,*endptr;
	if(entry->type > reg_type_binary)
		return;
	filePrintf(out,"\"%s\"\t%s\t",entry->key,type_token[entry->type]);
	switch(entry->type)
	{
		case reg_type_int:
			filePrintf(out,"%i\n",entry->value.v_int);
			break;
		case reg_type_float:
			filePrintf(out,"%g\n",entry->value.v_float);
			break;
		case reg_type_string:
			filePrintf(out,"\"%s\"\n",entry->value.v_string);
			break;
		case reg_type_binary:
			filePrintf(out,"%u\t",entry->value.v_binary.len);
			ptr=(u8_t*)entry->value.v_binary.data;
			endptr=ptr + entry->value.v_binary.len;
			for(;ptr<endptr;ptr++)
				filePrintf(out,"%02X ",ptr);
			break;
		default:
			break;
	}
}

void	V_GLOBAL
vmapSaveToStream(vmap_p map,vhandle_t outstream)
{
	u32_t i;
	libv_var_p var;
	for(i=0;i<map->hashsize;i++)
	{
		for(var = map->hash[i];var;var=var->next)
			vmapSaveEntry(outstream,var);
	}
}

void	V_GLOBAL
vmapSave(vmap_p map,char *filename)
{
	vhandle_t out = fileOpenWrite(filename);
	if(out)
	{
		vmapSaveToStream(map,out);
		fileClose(out);
	}
}

/*
DEBUG
*/

void __vmap_hash_usage(vmap_p map)
{
	u32_t i, count;
	libv_var_p head;
	for(i=0;i<map->hashsize;i++)
	{
		head=map->hash[i];
		count=0;
		while(head)
		{
			head=head->next;
			count++;
		}
		printf("entry %u \tused %u times\n",i,count);
	}
}	

int main()
{
	vmap_p map;
	
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);
	
	map = vmapCreate();
	
	printf("%d ",(int)vmapSetString(map,"k1","1"));
	printf("%d ",(int)vmapSetString(map,"k2","2"));
	printf("%d ",(int)vmapSetString(map,"k3","3"));
	
	printf("%d ",(int)vmapSetString(map,"k2","5"));
	printf("%d ",(int)vmapSetString(map,"k1","4"));
	
	memPrintStats();	

	printf("%s \n",vmapStringForKey(map,"k1"));
	printf("%s \n",vmapStringForKey(map,"k2"));
	printf("%s \n",vmapStringForKey(map,"k3"));
	
    vmapSave(map,"test");
	vmapDestroy(map);
	
	memPrintStats();	
	
	return 0;
}
