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

/*
 * Last update: 05.jul.2005
 */
 
typedef struct libv_var_s
{
	struct		libv_var_s *next;
	char		*key;
	hash_type_t	type;
	union
	{
		s32_t	v_int;
		void_p	v_pointer;
		vec_t	v_real;
		char_p	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			hashmask;
	libv_var_p		*hash;
}	hash_t, *hash_p;

STATIC_STRING(hash_object,"hashtable")

#define	HASH_MAGIC				0x48415348	/* HASH */

#define	DEFAULT_HASH_PWR		10	/* 1024 entries */

#define	DEFAULT_HASH_SIZE		(1 << DEFAULT_HASH_PWR)

#define HASH_HASHFN(map,str)	APHash(str,(map)->hashmask)

#define	GET_HASH(handle) \
	CHECK_AND_GET_OBJECT(hash_t,handle,caller_name,hash_object,HASH_MAGIC)

DEBUG_MESSAGE(vmap_wrong_key_name,"\"key\" must be non-empty string")

#define	VALIDATE_KEY(key) \
	if(!(key) || !*(key)) \
	{ \
		ERROR_GENERIC(vmap_wrong_key_name); \
		return false; \
	}

/*
 * APHash()
 *
 * Author: Arash Partow - 2002
 * URL: http://www.partow.net
*/
V_LOCAL u32_t
APHash(char* str,u32_t mask)
{
	u32_t i, hash = 0;
	for(i = 0; *str; str++, i++)
	{
		hash ^= (i & 1) ? (~((hash << 11) ^ (*str) ^ (hash >> 5))) :
			((hash <<  7) ^ (*str) ^ (hash >> 3));
	}
	return (hash & mask);
}

/*
 * Allocates new list entry for both key and value.
 * 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 = value_size ? INT_ALIGN(value_size) : 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_real:
			result->value.v_real = *((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_pointer:
			result->value.v_pointer = *((void_p*)value);
			break;
		case hash_type_callback:
			*((void**)(&result->value.v_callback)) = value;
			break;
		default:
			memFree(result);
			return NULL;
	}
	return result;
}

V_LOCAL char_p
hashTypeName(hash_type_t type)
{
	STATIC_STRING(int_name,"integer")
	STATIC_STRING(real_name,"real")
	STATIC_STRING(string_name,"string")
	STATIC_STRING(binary_name,"binary")
	STATIC_STRING(pointer_name,"pointer")
	STATIC_STRING(callback_name,"callback")
	STATIC_STRING(unknown_name,"unknown")
	switch(type)
	{
		case hash_type_int: return int_name;
		case hash_type_real: return real_name;
		case hash_type_string: return string_name;
		case hash_type_binary: return binary_name;
		case hash_type_pointer: return pointer_name;
		case hash_type_callback: return callback_name;
		default: break;
	}
	return unknown_name;
}

V_LOCAL libv_var_p
hashGetEntry(hash_p map,char *key)
{
	libv_var_p var;
	var = map->hash[HASH_HASHFN(map,key)];
	while(var && strcmp(var->key, key))
		var = var->next;
	return var;
}

/*
 * Input:
 *	map - hash to operate with;
 *	entry - variable to insert / overwrite.
 */
V_LOCAL void
hashInsertEntry(hash_t *map,libv_var_t *entry)
{
	CALLER_NAME(hashInsertEntry)
	u32_t pos;
	libv_var_p var, prev = NULL;
	pos = HASH_HASHFN(map,entry->key);
	var = map->hash[pos];
	/* Check for presence */
	while(var && strcmp(var->key,entry->key))
	{
		prev = var;
		var = var->next;
	}
	if(var)
	{
#ifdef	CHECKED_BUILD
		if(entry->type != var->type)
		{
			conlPrintf(LOG_WARNING,
				"%s(): key \"%s\" of type \"%s\" will be overriden"
				" by the same of type \"%s\".\n",caller_name,entry->key,
				hashTypeName(var->type),hashTypeName(entry->type));
		}
#endif	/* CHECKED_BUILD */
		/* Delete entry */
		if(prev)
			prev->next = var->next;
		else
			map->hash[pos] = var->next;
		memFree(var);
	}
	/* Insert entry at the start of list */
	entry->next = map->hash[pos];
	map->hash[pos] = entry;
}

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;
	UNREF_EXIT(map);
	for(i = 0;i <= map->hashmask; i++)
	{
		head = map->hash[i];
		while(head)
		{
			entry = head;
			head = head->next;
			memFree(entry);
		}
	}
	memFreeObject(map);
	return 0;
}

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

/*
 * Creates empty hash
 */
vhandle_t	V_GLOBAL
hashCreateEx(u32_t hashtab_size)
{
	CALLER_NAME(hashCreateEx)
	hash_p map;
	if(!hashtab_size || !IS_POWER_TWO(hashtab_size))
	{
		ERROR_GENERIC("hash table size must be power of 2");
		return LIBV_INVALID_HANDLE;
	}
	ALLOC_OBJECT(map,hash_t,hashtab_size * sizeof(libv_var_p),HASH_MAGIC);
	map->hashmask = hashtab_size - 1;
	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_HASH_SIZE);
}

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

/*
 * Returns true if entry for 'key' was in hash (and successfully removed).
 */
vboolean V_GLOBAL
hashRemoveKey(vhandle_t handle,char *key)
{
	CALLER_NAME(hashRemoveKey)
	u32_t pos;
	libv_var_p var, prev = NULL;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	pos = HASH_HASHFN(map,key);
	var = map->hash[pos];
	while(var && strcmp(var->key,key))
	{
		prev = var;
		var = var->next;
	}
	if(!var)
	{
		WARNING_GENERIC_3ARG("key \"",key,"\" not found");
		return false;
	}
	/* Delete entry */
	if(prev)
		prev->next = var->next;
	else
		map->hash[pos] = var->next;
	memFree(var);
	return true;
}

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

vboolean V_GLOBAL
hashSetInt(vhandle_t handle,char *key,s32_t value)
{
	CALLER_NAME(hashSetInt)
	libv_var_p entry;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	entry = hashAllocEntry(key,hash_type_int,&value,sizeof(s32_t));
	if(!entry)
		return false;
	hashInsertEntry(map,entry);
	return true;
}

vboolean V_GLOBAL
hashSetPointer(vhandle_t handle,char *key,void_p value)
{
	CALLER_NAME(hashSetPointer)
	libv_var_p entry;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	entry = hashAllocEntry(key,hash_type_pointer,&value,sizeof(void_p));
	if(!entry)
		return false;
	hashInsertEntry(map,entry);
	return true;
}

vboolean V_GLOBAL
hashSetReal(vhandle_t handle,char *key,vec_t value)
{
	CALLER_NAME(hashSetReal)
	libv_var_p entry;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	entry = hashAllocEntry(key,hash_type_real,&value,sizeof(vec_t));
	if(!entry)
		return false;
	hashInsertEntry(map,entry);
	return true;
}

vboolean V_GLOBAL
hashSetString(vhandle_t handle,char *key,char *value)
{
	CALLER_NAME(hashSetString)
	libv_var_p entry;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	if(!value)
	{
		ERROR_GENERIC("string may be empty but must not be NULL");
		return false;
	}
	entry = hashAllocEntry(key,hash_type_string,value,1 + strlen(value));
	if(!entry)
		return false;
	hashInsertEntry(map,entry);
	return true;
}

vboolean V_GLOBAL
hashSetBinary(vhandle_t handle,char *key,void *value,u32_t len)
{
	CALLER_NAME(hashSetBinary)
	libv_var_p entry;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	if(!len)
	{
		ERROR_GENERIC("zero-length binary");
		return false;
	}
	entry = hashAllocEntry(key,hash_type_binary,value,len);
	hashInsertEntry(map,entry);
	return true;
}

vboolean V_GLOBAL
hashSetCallback(vhandle_t handle,char *key,void (V_CALL *callback)(void *arg))
{
	CALLER_NAME(hashSetCallback)
	void *addr;
	libv_var_p entry;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	addr = *((void**)&callback);
	if(!addr)
	{
		ERROR_GENERIC("start address must not be NULL");
		return false;
	}
	entry = hashAllocEntry(key,hash_type_callback,addr,sizeof(void*));
	if(!entry)
		return false;
	hashInsertEntry(map,entry);
	return true;
}

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

/*
 * Returns 'false' if 'key' not found or 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 or not key of string.
 */
char_p V_GLOBAL
hashStringForKey(vhandle_t handle,char *key,char *def)
{
	CALLER_NAME(hashStringForKey)
	libv_var_p var;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return def;
	VALIDATE_KEY(key);
	var = hashGetEntry(map,key);
	if(var)
	{
		if(var->type == hash_type_string)
			return var->value.v_string;
		else
		{
			ERROR_GENERIC_3ARG("key \"",key,
				"\" does not associated with string");
			return def;
		}
	}
	else
		return def;
}

/*
 * Returns 'false' if 'key' not found or not key of int.
 */
vboolean	V_GLOBAL
hashGetInt(vhandle_t handle,char *key,s32_t *dest)
{
	CALLER_NAME(hashGetInt)
	libv_var_p var;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	var = hashGetEntry(map,key);
	if(!var)
		return false;
	if(var->type == hash_type_int)
	{
		if(dest)
			*dest = var->value.v_int;
		return true;
	}
	ERROR_GENERIC_3ARG("key \"",key,"\" does not associated with integer");
	return false;
}

/*
 * Returns 'def' if 'key' not found or 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 or not key of real.
 */
vboolean	V_GLOBAL
hashGetReal(vhandle_t handle,char *key,vec_t *dest)
{
	CALLER_NAME(hashGetReal)
	libv_var_p var;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	var = hashGetEntry(map,key);
	if(!var)
		return false;
	if(var->type == hash_type_real)
	{
		if(dest)
			*dest = var->value.v_real;
		return true;
	}
	ERROR_GENERIC_3ARG("key \"",key,"\" does not associated with real");
	return false;
}

/*
 * Returns 'def' if 'key' not found or not key of real.
 */
vec_t	V_GLOBAL
hashRealForKey(vhandle_t handle,char *key,vec_t def)
{
	vec_t result;
	return hashGetReal(handle,key,&result) ? result : def;
}

/*
 * Returns 'def' if 'key' not found or not key of binary.
 */
void_p	V_GLOBAL
hashBinaryForKey(vhandle_t handle,char *key,void_p def,u32_t *len_ret)
{
	CALLER_NAME(hashBinaryForKey)
	libv_var_p var;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return def;
	VALIDATE_KEY(key);
	var = hashGetEntry(map,key);
	if(!var)
		return def;
	if(var->type == hash_type_binary)
	{
		if(len_ret)
			*len_ret = var->value.v_binary.len;
		return var->value.v_binary.data;
	}
	ERROR_GENERIC_3ARG("key \"",key,"\" does not associated with binary");
	return def;
}

/*
 * Returns 'false' if 'key' not found or not key of callback.
 */
vboolean	V_GLOBAL
hashCall(vhandle_t handle,char *key,void *arg)
{
	CALLER_NAME(hashCall)
	libv_var_p var;
	hash_p map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	VALIDATE_KEY(key);
	var = hashGetEntry(map,key);
	if(!var)
		return false;
	if(var->type == hash_type_callback)
	{
		(*(var->value.v_callback))(arg);
		return true;
	}
	ERROR_GENERIC_3ARG("key \"",key,"\" does not associated with callback");
	return false;
}

void V_GLOBAL
hashDestroy(vhandle_t handle)
{
	CALLER_NAME(hashDestroy)
	libvCloseHandle(handle,caller_name,hash_object,HASH_MAGIC);
}

V_LOCAL vboolean
hashSaveEntry(vhandle_t out,libv_var_t *entry)
{
	CALLER_NAME(hashSaveEntry)
	vboolean res;
	u8_t *ptr,*endptr;
	u32_t count, nl = 0;
	switch(entry->type)
	{
		case hash_type_int:
			return filePrintf(out,"int \"%s\" %i\n",
				entry->key,entry->value.v_int);
		case hash_type_real:
			return filePrintf(out,"vec_t \"%s\" %g\n",
				entry->key,entry->value.v_real);
		case hash_type_string:
			return filePrintf(out,"string \"%s\" \"%s\"\n",
				entry->key,entry->value.v_string);
		case hash_type_binary:
			res = filePrintf(out,"binary \"%s\" %u\n\t",
				entry->key,entry->value.v_binary.len);
			ptr = (u8_t*)entry->value.v_binary.data;
			endptr = ptr + entry->value.v_binary.len;
			for(count = 0;ptr < endptr;ptr++)
			{
				if(nl)
					res = res && filePrintf(out,"\t");
				nl = ++count == 16;
				if(count == 9)
					res = res && filePrintf(out,"    ");
				res = res && filePrintf(out,"%02X%s",*ptr,nl ? "\n" : " ");
				if(nl)
					count = 0;
			}
			if(!nl)
				res = res && filePrintf(out,"\n");
			return res;
		default:
			ERROR_GENERIC_3ARG("unable to save value of type \"",
				hashTypeName(entry->type),"\"");
			break;
	}
	return false;
}

V_LOCAL vboolean
hashLoadEntry(vhandle_t dest,
	u8_stack_p key_storage,
	u8_stack_p value_storage,
	vhandle_t scanner,
	vboolean *normal_eof)
{
	CALLER_NAME(hashLoadEntry)
	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)
	{
		PARSE_ERROR(scanner,"expected key");
		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))
		{
			PARSE_ERROR(scanner,"expected integer");
			return false;
		}
		hashSetInt(dest,key,res);
	} else if(!strcmp(type,"vec_t"))
	{
		vec_t res;
		if(!lexNextFloat(scanner,&res))
		{
			PARSE_ERROR(scanner,"expected real");
			return false;
		}
		hashSetReal(dest,key,res);
	} else if(!strcmp(type,"string"))
	{
		value = lexNext(scanner);
		if(!value)
		{
			PARSE_ERROR(scanner,"expected string");
			return false;
		}
		hashSetString(dest,key,value);
	} else if(!strcmp(type,"binary"))
	{
		s32_t i, len;
		u32_t res;
		if(!lexNextInteger(scanner,&len))
		{
			PARSE_ERROR(scanner,"expected len of binary value");
			return false;
		}
		u8sResize(value_storage,0);
		for(i=0;i<len;i++)
		{
			value = lexNext(scanner);
			if(!value)
			{
				PARSE_ERROR(scanner,"expected next byte");
				return false;
			}
			if(!strToHex(value,&res) || res>255)
			{
				PARSE_ERROR(scanner,"expected next byte");
				return false;
			}
			u8sPush(value_storage,(u8_t)res);
		}
		if(u8sCount(value_storage))
			hashSetBinary(dest,key,
				u8sGetStorage(value_storage),u8sCount(value_storage));
		else
		{
			PARSE_ERROR(scanner,"empty binary");
			return false;
		}
	}
	else
	{
		PARSE_ERROR(scanner,"unknown type");
		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;
}

vboolean	V_GLOBAL
hashSaveToStream(vhandle_t handle,vhandle_t outstream)
{
	CALLER_NAME(hashSaveToStream)
	vboolean res = true;
	u32_t i;
	libv_var_p var;
	hash_p map;
	if(outstream == LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	map = GET_HASH(handle);
	if(map == LIBV_INVALID_HANDLE)
		return false;
	for(i=0;i <= map->hashmask; i++)
	{
		for(var = map->hash[i];var;var = var->next)
			res = res && hashSaveEntry(outstream,var);
	}
	return res;
}

vboolean	V_GLOBAL
hashSave(vhandle_t handle,char *filename)
{
	vhandle_t out;
	out = fileOpenSafeOverwrite(filename);
	if(out != LIBV_INVALID_HANDLE)
	{
		vboolean res = hashSaveToStream(handle,out);
		if(!res)
			fileCancelSafeOverwrite(out);
		else
			fileClose(out);
		return res;
	}
	return false;
}

vhandle_t	V_GLOBAL
hashLoad(char *filename)
{
	CALLER_NAME(hashLoad)
	vhandle_t map;
	vhandle_t infile;
	if(!filename || !*filename)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	infile = fileOpenRead(filename);
	if(infile == LIBV_INVALID_HANDLE)
		return LIBV_INVALID_HANDLE;
	map = hashCreate();
	if(!hashLoadFromStream(map,infile))
	{
		hashDestroy(map);
		map = LIBV_INVALID_HANDLE;
	}
	fileClose(infile);
	return map;
}

#ifdef	HASH_DEBUG

/*
 * Test stuff
 */

void hash_usage(hash_p map)
{
	u32_t i, total, count, min = MAXLONG, max = 0;
	libv_var_p head;
	for(i = total = 0;i <= map->hashmask;i++)
	{
		for(head = map->hash[i], count = 0;head;head = head->next)
			count++;
		if(count > max)
			max = count;
		if(count < min)
			min = count;
		total += count;
	}
	printf("table size %u, %u entries, min %u, max %u, diff %u\n",
		map->hashmask + 1, total, min, max, max - min);
}	

#endif /* HASH_DEBUG */
