/* radare2 - BSD 3 Clause License - crowell, pancake 2016 */

#include "ht.h"
#include "sdb.h"

// Sizes of the ht.
const int ht_primes_sizes[] = {
#if GROWABLE
	3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131,
	163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
	1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861,
	5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023,
	25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523,
	108631, 130363, 156437, 187751, 225307, 270371, 324449,
	389357, 467237, 560689, 672827, 807403, 968897, 1162687,
	1395263, 1674319, 2009191, 2411033, 2893249, 3471899,
	4166287, 4999559, 5999471, 7199369
#else
	1024,
#endif
};


// Create a new hashtable and return a pointer to it.
// size - number of buckets in the hashtable
// hashfunction - the function that does the hashing, must not be null.
// comparator - the function to check if values are equal, if NULL, just checks
// == (for storing ints).
// keydup - function to duplicate to key (eg strdup), if NULL just does strup.
// valdup - same as keydup, but for values but if NULL just assign
// pair_free - function for freeing a keyvaluepair - if NULL just does free.
// calcsize - function to calculate the size of a value. if NULL, just stores 0.
static SdbHash* internal_ht_new(ut32 size, HashFunction hashfunction,
				 ListComparator comparator, DupKey keydup,
				 DupValue valdup, HtKvFreeFunc pair_free,
				 CalcSize calcsizeK, CalcSize calcsizeV) {
	SdbHash* ht = calloc (1, sizeof (*ht));
	if (!ht) {
		return NULL;
	}
	ht->size = size;
	ht->count = 0;
	ht->prime_idx = 0;
	ht->load_factor = 1;
	ht->hashfn = hashfunction;
	ht->cmp = (ListComparator)strcmp;
	ht->dupkey = keydup? keydup: (DupKey)strdup;
	ht->dupvalue = valdup? valdup: NULL; 
	ht->table = calloc (ht->size, sizeof (SdbList*));
	ht->calcsizeK = calcsizeK? calcsizeK: (CalcSize)strlen;
	ht->calcsizeV = calcsizeV? calcsizeV: NULL;
	ht->freefn = pair_free;
	ht->deleted = ls_newf (free);
#if INSERTORDER
	ht->list = ls_newf (NULL);
#endif
	// Because we use calloc, each listptr will be NULL until used */
	return ht;
}

bool ht_delete_internal(SdbHash* ht, const char* key, ut32* hash) {
	HtKv* kv;
	SdbListIter* iter;
	ut32 computed_hash = hash ? *hash : ht->hashfn (key);
#if USE_KEYLEN
	ut32 key_len = ht->calcsizeK ((void *)key);
#endif
	ut32 bucket = computed_hash % ht->size;
#if INSERTORDER
	ls_foreach (ht->list, iter, kv) {
#if USE_KEYLEN
		if (key_len != kv->key_len) {
			continue;
		}
#endif
		if (key == kv->key || !ht->cmp (key, kv->key)) {
			ls_delete (ht->list, iter);
			ht->count--;
			break;
		}
	}
#endif
	SdbList* list = ht->table[bucket];
	ls_foreach (list, iter, kv) {
#if USE_KEYLEN
		if (key_len != kv->key_len) {
			continue;
		}
#endif
		if (key == kv->key || !ht->cmp (key, kv->key)) {
#if EXCHANGE
			ls_split_iter (list, iter);
			ls_append (ht->deleted, iter);
			if (list->free) {
				list->free (iter->data);
			}
			iter->data = NULL;
#else
			ls_delete (list, iter);
#endif
			ht->count--;
			return true;
		}
	}
	return false;
}

SdbHash* ht_new(DupValue valdup, HtKvFreeFunc pair_free, CalcSize calcsizeV) {
	return internal_ht_new (ht_primes_sizes[0], (HashFunction)sdb_hash, 
	  			(ListComparator)strcmp, (DupKey)strdup,
				valdup, pair_free, (CalcSize)strlen, calcsizeV);
}

void ht_free(SdbHash* ht) {
	if (ht) {
		ut32 i;
		for (i = 0; i < ht->size; i++) {
			ls_free (ht->table[i]);
		}
		free (ht->table);
		ls_free (ht->deleted);
#if INSERTORDER
		ls_free (ht->list);
#endif
		free (ht);
	}
}

void ht_free_deleted(SdbHash* ht) {
	if (!ls_empty (ht->deleted)) {
		ls_free (ht->deleted);
		ht->deleted = ls_newf (free);
	}
}

// Increases the size of the hashtable by 2.
#if GROWABLE
static void internal_ht_grow(SdbHash* ht) {
	SdbHash* ht2;
	SdbHash swap;
	HtKv* kv;
	SdbListIter* iter;
	ut32 i, sz = ht_primes_sizes[ht->prime_idx];
	ht2 = internal_ht_new (sz, ht->hashfn, ht->cmp, ht->dupkey,
			ht->dupvalue, (HtKvFreeFunc)ht->freefn, ht->calcsize);
	ht2->prime_idx = ht->prime_idx;
	for (i = 0; i < ht->size; i++) {
		ls_foreach (ht->table[i], iter, kv) {
			(void)ht_insert (ht2, kv->key, kv->value);
		}
	}
	// And now swap the internals.
	swap = *ht;
	*ht = *ht2;
	*ht2 = swap;
	ht_free (ht2);
}
#endif

static bool internal_ht_insert_kv(SdbHash *ht, HtKv *kv, bool update) {
	bool found;
	if (!ht || !kv) {
		return false;
	}
	ut32 bucket, hash = ht->hashfn (kv->key);
	if (update) {
		(void)ht_delete_internal (ht, kv->key, &hash);
	} else {
		(void)ht_find (ht, kv->key, &found);
	}
	if (update || !found) {
		bucket = hash % ht->size;
		if (!ht->table[bucket]) {
			ht->table[bucket] = ls_newf ((SdbListFree)ht->freefn);
		}
		ls_prepend (ht->table[bucket], kv);
#if INSERTORDER
		ls_append (ht->list, kv);
#endif
		ht->count++;
#if GROWABLE
		// Check if we need to grow the table.
		if (ht->count >= ht->load_factor * ht_primes_sizes[ht->prime_idx]) {
			ht->prime_idx++;
			internal_ht_grow (ht);
		}
#endif
		return true;
	}
	return false;
}

static bool internal_ht_insert(SdbHash* ht, bool update, const char* key,
				void* value) {
	if (!ht || !key || !value) {
		return false;
	}
	HtKv* kv = calloc (1, sizeof (HtKv));
	if (kv) {
		kv->key = ht->dupkey ((void *)key);
		if (ht->dupvalue) {
			kv->value = ht->dupvalue ((void *)value);
		} else {
			kv->value = (void *)value;
		}
		kv->key_len = ht->calcsizeK ((void *)kv->key);
		if (ht->calcsizeV) {
			kv->value_len = ht->calcsizeV ((void *)kv->value);
		} else {
			kv->value_len = 0;
		}
		if (!internal_ht_insert_kv (ht, kv, update)) {
			if (ht->freefn) {
				ht->freefn (kv);
			}
			return false;
		}
		return true;
	}
	return false;
}
bool ht_insert_kv(SdbHash *ht, HtKv *kv, bool update) {
	return internal_ht_insert_kv (ht, kv, update);
}
// Inserts the key value pair key, value into the hashtable.
// Doesn't allow for "update" of the value.
bool ht_insert(SdbHash* ht, const char* key, void* value) {
	return internal_ht_insert (ht, false, key, value);
}

// Inserts the key value pair key, value into the hashtable.
// Does allow for "update" of the value.
bool ht_update(SdbHash* ht, const char* key, void* value) {
	return internal_ht_insert (ht, true, key, value);
}

// Returns the corresponding SdbKv entry from the key.
// If `found` is not NULL, it will be set to true if the entry was found, false
// otherwise.
HtKv* ht_find_kv(SdbHash* ht, const char* key, bool* found) {
	ut32 hash, bucket;
	SdbListIter* iter;
	HtKv* kv;
#if USE_KEYLEN
	ut32 key_len = ht->calcsizeK ((void *)key);
#endif
	hash = ht->hashfn (key);
	bucket = hash % ht->size;
	ls_foreach (ht->table[bucket], iter, kv) {
#if USE_KEYLEN
		if (key_len != kv->key_len) {
			continue;
		}
#endif
		bool match = !ht->cmp (key, kv->key);
		if (match) {
			if (found) {
				*found = true;
			}
			return kv;
		}
	}
	if (found) {
		*found = false;
	}
	return NULL;
}

// Looks up the corresponding value from the key.
// If `found` is not NULL, it will be set to true if the entry was found, false
// otherwise.
void* ht_find(SdbHash* ht, const char* key, bool* found) {
	bool _found = false;
	if (!found) {
		found = &_found;
	}
	HtKv* kv = ht_find_kv (ht, key, found);
	return (kv && *found)? kv->value : NULL;
}

// Deletes a entry from the hash table from the key, if the pair exists.
bool ht_delete(SdbHash* ht, const char* key) {
	return ht_delete_internal (ht, key, NULL);
}

void ht_foreach(SdbHash *ht, HtForeachCallback cb, void *user) {
	ut32 i = 0;
	HtKv *kv;
	SdbListIter *iter;
	for (i = 0; i < ht->size; i++) {
		ls_foreach (ht->table[i], iter, kv) {
			if (!kv || !kv->key || !kv->value) {
				continue;
			}
			if (!cb (user, kv->key, kv->value)) {
				return;
			}
		}
	}
}
