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

/*
 * Good alternative to current 'registry' tree ...
 */
 
typedef struct libv_var_s
{
	struct		libv_var_s *next;
	char		*key;
	hash_type_t	type;
	union
	{
		s32_t	v_int;
		vec_t	v_float;
		char	*v_string;
		struct
		{
			u32_t	len;
			void	*data;
		}	v_binary;
		void	(V_CALL *v_callback)(void *arg);
	}	value;
} libv_var_t, *libv_var_p;

typedef struct hash_s
{
	libv_object_t	base;
	u32_t			hashsize;
	libv_var_p		*hash;
}	hash_t, *hash_p;

V_LOCAL	char *hash_object = "hashtable";
#define	VMAP_MAGIC	0x48415348	/* HASH */
#define	DEFAULT_VMAP_PWR		9
#define	DEFAULT_VMAP_HASHSIZE	(1 << DEFAULT_VMAP_PWR)
#define VMAP_HASHFN(map,x)	((x) & (map->hashsize-1))
#define	GET_VMAP(handle,caller) \
	CHECK_AND_GET_OBJECT(hash_t,handle,caller,hash_object,VMAP_MAGIC)

/*
 * FIXME: needs further improvement
 */
V_LOCAL u32_t
_hashcode_for_key_(char *str)
{
	char *ptr=str;
	u32_t retval = 0;
	while(*ptr)
	{
		retval += *((unsigned char*)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
hashAllocEntry(char *key,hash_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==hash_type_string || type==hash_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)memAllocZ(len);
	if(!result)
		return NULL;
	result->next = NULL;
	result->type = type;
	result->key = (char *)(result + 1);
	strcpy(result->key,key);
	switch(type)
	{
		case hash_type_int:
			result->value.v_int = *((s32_t*)value);
			break;
		case hash_type_float:
			result->value.v_float = *((vec_t*)value);
			break;
		case hash_type_string:
			result->value.v_string = result->key + key_bytes;
			memCopy(result->value.v_string,value,value_size);
			break;
		case hash_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 hash_type_callback:
			*((void**)(&result->value.v_callback)) = value;
			break;
		default:
			break;
	}
	return result;
}

V_LOCAL libv_var_p
hashGetEntry(hash_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, or just inserted.
 */
V_LOCAL vboolean
hashInsertEntry(hash_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);
				break;
			}
			else
				return true;
		}
	}
	/* Insert entry to the start of list */
	if(entry)
	{
		entry->next = map->hash[pos];
		map->hash[pos] = entry;
		return true;
	}
	return exist;
}

V_LOCAL	u32_t	V_CALL
hash_destroy(vhandle_t handle)
{
	u32_t i;
	libv_var_p head, entry;
	hash_p map = (hash_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
hashCreateEx(u32_t hashsize)
{
	hash_p map;
	if(!IS_POWER_TWO(hashsize))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"hashCreateEx(%u): hashsize must be power of 2.\n",hashsize);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	ALLOC_OBJECT(map,hash_t,hashsize * sizeof(libv_var_p),VMAP_MAGIC);
	map->hashsize = hashsize;
	map->hash = (libv_var_p*)(map + 1);
	map->base.destroy = hash_destroy;
	return (vhandle_t)map;
}

vhandle_t	V_GLOBAL
hashCreate(void)
{
	return hashCreateEx(DEFAULT_VMAP_HASHSIZE);
}

vboolean V_GLOBAL
hashGetKeyType(vhandle_t handle,char *key,hash_type_t *type_ret)
{
	libv_var_p entry;
	hash_p map = GET_VMAP(handle,"hashGetKeyType");
	if(!map)
		return false;
	entry = hashGetEntry(map,key);
	if(!entry)
		return false;
	if(type_ret)
		*type_ret = entry->type;
	return true;
}

vboolean V_GLOBAL
hashRemoveKey(vhandle_t handle,char *key)
{
	libv_var_p entry;
	hash_p map = GET_VMAP(handle,"hashRemoveKey");
	if(!map)
		return false;
	entry = hashGetEntry(map,key);
	if(!entry)
		return false;
	hashInsertEntry(map,key,NULL,true);
	return true;
}

vboolean V_GLOBAL
hashSetInt(vhandle_t handle,char *key,s32_t value)
{
	libv_var_p entry;
	hash_p map = GET_VMAP(handle,"hashSetInt");
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetInt(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	entry = hashAllocEntry(key,hash_type_int,&value,sizeof(s32_t));
	if(!entry)
		return false;
	hashInsertEntry(map,key,entry,true);
	return true;
}

vboolean V_GLOBAL
hashSetFloat(vhandle_t handle,char *key,vec_t value)
{
	libv_var_p entry;
	hash_p map = GET_VMAP(handle,"hashSetFloat");
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetFloat(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	entry = hashAllocEntry(key,hash_type_float,&value,sizeof(vec_t));
	if(!entry)
		return false;
	hashInsertEntry(map,key,entry,true);
	return true;
}

vboolean V_GLOBAL
hashSetString(vhandle_t handle,char *key,char *value)
{
	libv_var_p entry;
	hash_p map = GET_VMAP(handle,"hashSetString");
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetString(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(value)
	{
		entry = hashAllocEntry(key,hash_type_string,value,1+strlen(value));
		if(!entry)
			return false;
	}
	else
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetString(): value must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	hashInsertEntry(map,key,entry,true);
	return true;
}

vboolean V_GLOBAL
hashSetBinary(vhandle_t handle,char *key,void *value,u32_t len)
{
	libv_var_p entry;
	hash_p map = GET_VMAP(handle,"hashSetBinary");
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetBinary(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(value)
	{
#ifdef	CHECKED_BUILD
		if(!len)
			conlPuts(LOG_WARNING,"hashSetBinary(): zero-length binary value.\n");
#endif	/* CHECKED_BUILD */
		entry = hashAllocEntry(key,hash_type_binary,value,len);
		if(!entry)
			return false;
	}
	else
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetBinary(): value must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	hashInsertEntry(map,key,entry,true);
	return true;
}

vboolean V_GLOBAL
hashSetCallback(vhandle_t handle,char *key,void (V_CALL *callback)(void *arg))
{
	void *addr;
	libv_var_p entry;
	hash_p map = GET_VMAP(handle,"hashSetCallback");
	if(!map)
		return false;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetCallback(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	addr = *((void**)&callback);
	if(addr)
	{
		entry = hashAllocEntry(key,hash_type_callback,addr,sizeof(void*));
		if(!entry)
			return false;
	}
	else
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashSetCallback(): value must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	hashInsertEntry(map,key,entry,true);
	return true;
}

/* -------------------------------------------------------------------- */

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

/*
 * Returns 'def' if 'key' not found / not key of string.
 */
char_p V_GLOBAL
hashStringForKey(vhandle_t handle,char *key,char *def)
{
	libv_var_p var;
	hash_p map = GET_VMAP(handle,"hashStringForKey");
	if(!map)
		return def;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashStringForKey(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return def;
	}
	var = hashGetEntry(map,key);
	if(var != NULL && var->type == hash_type_string)
		return var->value.v_string;
	else
		return def;
}

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

/*
 * Returns 'def' if 'key' not found / not key of int.
 */
s32_t	V_GLOBAL
hashIntForKey(vhandle_t handle,char *key,s32_t def)
{
	s32_t result;
	return hashGetInt(handle,key,&result) ? result : def;
}

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

/*
 * Returns 'def' if 'key' not found / not key of float.
 */
vec_t	V_GLOBAL
hashFloatForKey(vhandle_t handle,char *key,vec_t def)
{
	vec_t result;
	return hashGetFloat(handle,key,&result) ? result : def;
}

/*
 * Returns 'def' if 'key' not found / not key of binary.
 */
void_p	V_GLOBAL
hashBinaryForKey(vhandle_t handle,char *key,void_p def,u32_t *len_ret)
{
	libv_var_p var;
	hash_p map = GET_VMAP(handle,"hashBinaryForKey");
	if(!map)
		return def;
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"hashBinaryForKey(): key must be non-NULL.\n");
#endif	/* CHECKED_BUILD */
		return def;
	}
	var = hashGetEntry(map,key);
	if(var==NULL || var->type!=hash_type_binary)
		return def;
	if(len_ret)
		*len_ret = var->value.v_binary.len;
	return var->value.v_binary.data;
}

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

void V_GLOBAL
hashDestroy(vhandle_t handle)
{
	libvCloseHandle(handle,"hashDestroy",hash_object,VMAP_MAGIC);
}

V_LOCAL void
hashSaveEntry(vhandle_t out,libv_var_t *entry)
{
	u8_t *ptr,*endptr;
	switch(entry->type)
	{
		case hash_type_int:
			filePrintf(out,"int \"%s\" %i\n",entry->key,entry->value.v_int);
			break;
		case hash_type_float:
			filePrintf(out,"vec_t \"%s\" %g\n",entry->key,entry->value.v_float);
			break;
		case hash_type_string:
			filePrintf(out,"string \"%s\" \"%s\"\n",entry->key,entry->value.v_string);
			break;
		case hash_type_binary:
			filePrintf(out,"binary \"%s\" %u ",entry->key,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);
			filePrintf(out,"\n");
			break;
		default:
			break;
	}
}

V_LOCAL vboolean
hashLoadEntry(vhandle_t dest,
	u8_stack_p key_storage,
	u8_stack_p value_storage,
	vhandle_t scanner,
	vboolean *normal_eof)
{
	char *key, *value;
	char type[64];
	if(!lexNext(scanner))
	{
		*normal_eof = true;
		return false;
	}
	lexCopyLastToken(scanner,type,64);
	*normal_eof = false;
	u8sResize(key_storage,0);
	key = lexNext(scanner);
	if(!key)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"hashLoadEntry(line %u): expected key, got EOF.\n",
			lexLastLine(scanner));
#endif	/* CHECKED_BUILD */
		return false;
	}
	u8sPushBuffer(key_storage,(u8_p)key,strlen(key)+1);
	key = (char*)u8sGetStorage(key_storage);
	if(!strcmp(type,"int"))
	{
		s32_t res;
		if(!lexNextInteger(scanner,&res))
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"hashLoadEntry(line %u): "
				"expected int value for key '%s', got '%s'.\n",
				lexLastLine(scanner),key,strSafe(lexLast(scanner)));
#endif	/* CHECKED_BUILD */
			return false;
		}
		hashSetInt(dest,key,res);
	} else if(!strcmp(type,"vec_t"))
	{
		vec_t res;
		if(!lexNextFloat(scanner,&res))
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"hashLoadEntry(line %u): "
				"expected float value for key '%s', got '%s'.\n",
				lexLastLine(scanner),key,strSafe(lexLast(scanner)));
#endif	/* CHECKED_BUILD */
			return false;
		}
		hashSetFloat(dest,key,res);
	} else if(!strcmp(type,"string"))
	{
		value = lexNext(scanner);
		if(!value)
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"hashLoadEntry(line %u): "
				"expected string value for key '%s', got EOF.\n",
				lexLastLine(scanner),key);
#endif	/* CHECKED_BUILD */
			return false;
		}
		hashSetString(dest,key,value);
	} else if(!strcmp(type,"binary"))
	{
		s32_t i, len;
		u32_t res;
		if(!lexNextInteger(scanner,&len))
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"hashLoadEntry(line %u): "
				"expected len of binary value for key '%s', got '%s'.\n",
				lexLastLine(scanner),key,strSafe(lexLast(scanner)));
#endif	/* CHECKED_BUILD */
			return false;
		}
		u8sResize(value_storage,0);
		for(i=0;i<len;i++)
		{
			value = lexNext(scanner);
			if(!value)
			{
#ifdef	CHECKED_BUILD
				conlPrintf(LOG_ERROR,"hashLoadEntry(line %u): "
					"expected %d-th byte of binary for key '%s', got EOF.\n",
					lexLastLine(scanner),i,key);
#endif	/* CHECKED_BUILD */
				return false;
			}
			if(!strToHex(value,&res) || res>255)
			{
#ifdef	CHECKED_BUILD
				conlPrintf(LOG_ERROR,"hashLoadEntry(line %u): "
					"expected %d-th byte of binary for key '%s', got '%s'.\n",
					lexLastLine(scanner),i,key,value);
#endif	/* CHECKED_BUILD */
				return false;
			}
			u8sPush(value_storage,(u8_t)res);
		}
		if(u8sCount(value_storage))
			hashSetBinary(dest,key,
				u8sGetStorage(value_storage),u8sCount(value_storage));
		else
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,"hashLoadEntry(line %u): "
				"empty binary for key '%s'.\n",
				lexLastLine(scanner),key);
#endif	/* CHECKED_BUILD */
			return false;
		}
	}
	else
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"hashLoadEntry(line %u): unknown type '%s' of key '%s'.\n",
			lexLastLine(scanner),type,key);
#endif	/* CHECKED_BUILD */
		return false;
	}
	return true;
}

vboolean	V_GLOBAL
hashLoadFromStream(vhandle_t dest_map,vhandle_t stream)
{
	vboolean ok;
	u8_stack_t key_storage = TYPED_STACK_EMPTY_INITIALIZER;
	u8_stack_t value_storage = TYPED_STACK_EMPTY_INITIALIZER;
	vhandle_t scanner = lexParseStream(stream,NULL);
	if(scanner == LIBV_INVALID_HANDLE)
		return false;
	while(hashLoadEntry(dest_map,&key_storage,&value_storage,scanner,&ok));
	u8sClear(&key_storage);
	u8sClear(&value_storage);
	lexDestroy(scanner);
	return ok;
}

void	V_GLOBAL
hashSaveToStream(vhandle_t handle,vhandle_t outstream)
{
	u32_t i;
	libv_var_p var;
	hash_p map = GET_VMAP(handle,"hashSaveToStream");
	for(i=0;i<map->hashsize;i++)
	{
		for(var = map->hash[i];var;var=var->next)
			hashSaveEntry(outstream,var);
	}
}

void	V_GLOBAL
hashSave(vhandle_t handle,char *filename)
{
	vhandle_t out = fileOpenWrite(filename);
	if(out)
	{
		hashSaveToStream(handle,out);
		fileClose(out);
	}
}

vhandle_t	V_GLOBAL
hashLoad(char *filename)
{
	vhandle_t map;
	vhandle_t infile = fileOpenRead(filename);
	if(infile == LIBV_INVALID_HANDLE)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"hashLoad(): unable to open '%s'.\n",filename);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	map = hashCreate();
	if(!hashLoadFromStream(map,infile))
	{
		hashDestroy(map);
		map = LIBV_INVALID_HANDLE;
	}
	fileClose(infile);
	return map;
}

#if 0

void hash_usage(hash_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);
	}
}	

void hash_dump(vhandle_t handle)
{
	vhandle_t con = fileOpenWrite("CON");
	hashSaveToStream(handle,con);
	fileClose(con);
}

void V_CALL proc1(void_p arg)
{
	printf("proc1: %s\n",(char*)arg);
}

void V_CALL proc2(void_p arg)
{
	printf("proc2: %s\n",(char*)arg);
}

int main()
{
	hash_p map;
	
	logStartConsoleLog();
	logUpperLevel(LOG_TRACE);
	
	map = hashCreate();
	
	/*
	hashSetString(map,"k1","1");
	hashSetString(map,"k2","2");
	hashSetString(map,"k3","3");
	hashSetInt(map,"int_key 1",15);
	hashSetInt(map,"int_key 2",30);
	hashSetFloat(map,"float_key 1",15.1);
	hashSetInt(map,"float_key 2",30.2);
	*/

	hashSetBinary(map,"bin_key 1","123456",6);
	hashSetBinary(map,"bin_key 2","234567",3);
	
	hashSetCallback(map,"proc1",proc1);
	hashSetCallback(map,"proc2",proc2);

    hashCall(map,"proc1","hello");
    hashCall(map,"proc2","hello");
    
	/*
	hashRemoveKey(map,"k1");
    */
    
	hash_dump(map);
    
    hashSave(map,"test");
	hashDestroy(map);
	map = hashLoad("test");
	puts("--------------------------");
	hash_dump(map);
	
	hashDestroy(map);
	
	memPrintStats();	
	
	return 0;
}

#endif /* 0 */

