/* dict.c -- hash-table based dictionaries ($Revision: 1.1.1.1 $) */

#include "es.h"
#include "gc.h"

#define	INIT_DICT_SIZE	2
#define	REMAIN(n)	(((n) * 2) / 3)
#define	GROW(n)		((n) * 2)

/*
 * hashing
 */

/* strhash2 -- the (probably too slow) haahr hash function */
static unsigned long strhash2(const char *str1, const char *str2) {

#define	ADVANCE() { \
		if ((c = *s++) == '\0') { \
			if (str2 == NULL) \
				break; \
			else { \
				s = (unsigned char *) str2; \
				str2 = NULL; \
				if ((c = *s++) == '\0') \
					break; \
			} \
		} \
	}

	int c;
	unsigned long n = 0;
	unsigned char *s = (unsigned char *) str1;
	assert(str1 != NULL);
	while (1) {
		ADVANCE();
		n += (c << 17) ^ (c << 11) ^ (c << 5) ^ (c >> 1);
		ADVANCE();
		n ^= (c << 14) + (c << 7) + (c << 4) + c;
		ADVANCE();
		n ^= (~c << 11) | ((c << 3) ^ (c >> 1));
		ADVANCE();
		n -= (c << 16) | (c << 9) | (c << 2) | (c & 3);
	}
	return n;
}

/* strhash -- hash a single string */
static unsigned long strhash(const char *str) {
	return strhash2(str, NULL);
}


/*
 * data structures and garbage collection
 */

DefineTag(Dict, static);

typedef struct {
	char *name;
	void *value;
} Assoc;

struct Dict {
	int size, remain;
	Assoc table[1];		/* variable length */
};


static Dict *mkdict0(int size) {
	size_t len = offsetof(Dict, table[size]);
	Dict *dict = gcalloc(len, &DictTag);
	memzero(dict, len);
	dict->size = size;
	dict->remain = REMAIN(size);
	return dict;
}

static void *DictCopy(void *op) {
	Dict *dict = op;
	size_t len = offsetof(Dict, table[dict->size]);
	void *np = gcalloc(len, &DictTag);
	memcpy(np, op, len);
	return np;
}

static size_t DictScan(void *p) {
	Dict *dict = p;
	int i;
	for (i = 0; i < dict->size; i++) {
		Assoc *ap = &dict->table[i];
		ap->name  = forward(ap->name);
		ap->value = forward(ap->value);
	}
	return offsetof(Dict, table[dict->size]);
}


/*
 * private operations
 */

static char DEAD[] = "DEAD";

static Assoc *get(Dict *dict, const char *name) {
	Assoc *ap;
	unsigned long n = strhash(name), mask = dict->size - 1;
	for (; (ap = &dict->table[n & mask])->name != NULL; n++)
		if (ap->name != DEAD && streq(name, ap->name))
			return ap;
	return NULL;
}

static Dict *put(Dict *dict, char *name, void *value) {
	unsigned long n, mask;
	Assoc *ap;
	assert(get(dict, name) == NULL);
	assert(value != NULL);

	if (dict->remain <= 1) {
		Dict *new;
		Ref(Dict *, old, dict);
		Ref(char *, np, name);
		Ref(void *, vp, value);
		new = mkdict0(GROW(old->size));
		dictforall(old, (void (*)(void *, char *, void *)) put, new);
		dict = new;
		name = np;
		value = vp;
		RefEnd3(vp, np, old);
	}

	n = strhash(name);
	mask = dict->size - 1;
	for (; (ap = &dict->table[n & mask])->name != DEAD; n++)
		if (ap->name == NULL) {
			--dict->remain;
			break;
		}

	ap->name = name;
	ap->value = value;
	return dict;
}

static void rm(Dict *dict, Assoc *ap) {
	unsigned long n, mask;
	assert(dict->table <= ap && ap < &dict->table[dict->size]);

	ap->name = DEAD;
	ap->value = NULL;
	n = ap - dict->table;
	mask = dict->size - 1;
	for (n++; (ap = &dict->table[n & mask])->name == DEAD; n++)
		;
	if (ap->name != NULL)
		return;
	for (n--; (ap = &dict->table[n & mask])->name == DEAD; n--) {
		ap->name = NULL;
		++dict->remain;
	}
}



/*
 * exported functions
 */

extern Dict *mkdict(void) {
	return mkdict0(INIT_DICT_SIZE);
}

extern void *dictget(Dict *dict, const char *name) {
	Assoc *ap = get(dict, name);
	if (ap == NULL)
		return NULL;
	return ap->value;
}

extern Dict *dictput(Dict *dict, char *name, void *value) {
	Assoc *ap = get(dict, name);
	if (value != NULL)
		if (ap == NULL)
			dict = put(dict, name, value);
		else
			ap->value = value;
	else if (ap != NULL)
		rm(dict, ap);
	return dict;
}

extern void dictforall(Dict *dp, void (*proc)(void *, char *, void *), void *arg) {
	int i;
	Ref(Dict *, dict, dp);
	Ref(void *, argp, arg);
	for (i = 0; i < dict->size; i++) {
		Assoc *ap = &dict->table[i];
		if (ap->name != NULL && ap->name != DEAD)
			(*proc)(argp, ap->name, ap->value);
	}
	RefEnd2(argp, dict);
}

/* dictget2 -- look up the catenation of two names (such a hack!) */
extern void *dictget2(Dict *dict, const char *name1, const char *name2) {
	Assoc *ap;
	unsigned long n = strhash2(name1, name2), mask = dict->size - 1;
	for (; (ap = &dict->table[n & mask])->name != NULL; n++)
		if (ap->name != DEAD && streq2(ap->name, name1, name2))
			return ap->value;
	return NULL;
}
