#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>

#include "gpklib/dict.h"


#define D	if(1)

#define NOTHING_REMOVED (-1)

static void db_init(struct dict_bucket *b)
{
	b->k = b->v = b->c = NULL;
}

struct dict *dict_constr()
{
	int i;
	struct dict *o = (struct dict *)malloc(sizeof(struct dict));
	o->n_inclusive = 0;
	o->up = NULL;
	for(i=0; i<DICT_BUCKETS; i++)
		db_init(&o->b[i]);
	return o;
}


static int n_incl(struct dict *d)
{
	int i;
	int ni = 0;
	for(i=0; i<DICT_BUCKETS; i++)	{
		if(d->b[i].k)
			ni++;
		if(d->d[i])
			ni += n_incl(d->d[i]);
	}
	return ni;
}


static void db_destr(struct dict_bucket *b)
{
	if(b->k)	{
		free(b->k);
		b->k = NULL;
	}
	if(b->v)	{
		free(b->v);
		b->v = NULL;
	}
	if(b->c)	{
		free(b->c);
		b->c = NULL;
	}
}

static int destr_guts(struct dict *d)
{
	int i;
	int ni = 0;
	for(i=0; i<DICT_BUCKETS; i++)	{
		if(d->b[i].k) {
			ni++;
			db_destr(&d->b[i]);
		}
		if(d->d[i]) {
			ni += destr_guts(d->d[i]);
			d->d[i] = 0;
		}
		D printf("ni=%d, n_incl=%d\n", ni, d->n_inclusive);
	}
	assert(ni == d->n_inclusive);
	d->n_inclusive = 0;
	free(d);
	return ni;
}


void dict_destr(struct dict *d)
{
	destr_guts(d);
}


long dict_hash(const char *k)
{
	long h = 3331, o = 17;
	int i;
	for(i=0; k[i]; i++) {
		h = (h%13117 + 3*k[i] + o%197) * (k[i] + h%15477 + 13);
		o = o ^ h;
	}
	return o;
}

static const struct dict_bucket *get_guts(const struct dict *d, const char *k, long h)
{
	int b = h % DICT_BUCKETS;
	if(d->b[b].k && strcmp(d->b[b].k, k)==0)
		return &d->b[b];
	else if(d->d[b])
		return get_guts(d->d[b], k, h>>DICT_BITS);
	else
		return NULL;
}


const struct dict_bucket *dict_getb(const struct dict *d, const char *k)
{
	return get_guts(d, k, dict_hash(k));
}

const char *dict_getv(const struct dict *d, const char *k, const char *dfl)
{
	const struct dict_bucket *o = dict_getb(d, k);
	return o ? o->v : dfl;
}


static int remove_guts(struct dict *d, const char *key, long h)
{
	int b = h % DICT_BUCKETS;
	assert(d->n_inclusive >= 0);
	D assert(d->n_inclusive == n_incl(d));
	if(d->b[b].k && strcmp(d->b[b].k, key)==0) {
		db_destr(&d->b[b]);
		assert(d->n_inclusive > 0);
		--d->n_inclusive;
		return d->n_inclusive;
	}
	else if(d->d[b]) {
		int ni;
		D printf("DBG: b=%d\n", b);
		ni = remove_guts(d->d[b], key, h>>DICT_BITS);
		D printf("DBG: ni=%d d->n_in=%d n_incl=%d\n", ni, d->n_inclusive, n_incl(d));
		if(ni == 0) {
			dict_destr(d->d[b]);
			d->d[b] = NULL;
			assert(d->n_inclusive > 0);
			d->n_inclusive --;
		}
		else if(ni > 0) {
			assert(d->n_inclusive > 0);
			d->n_inclusive --;
		}

		D printf("DBG: rem: ni=%d d->n_in=%d n_incl=%d\n", ni, d->n_inclusive, n_incl(d));
		D assert(d->n_inclusive == n_incl(d));
		return ni==NOTHING_REMOVED ? NOTHING_REMOVED : d->n_inclusive;
	}
	return -1;
}

void dict_remove(struct dict *d, const char *key)
{
	remove_guts(d, key, dict_hash(key));
}


const struct dict_bucket *dict_indexb(const struct dict *d, int i)
{
	int j;
	D printf("DBG: i=%d n_incl=%d\n", i, d->n_inclusive);
	if(i<0 || i>=d->n_inclusive)
		return NULL;
	for(j=0; j<DICT_BUCKETS; j++)
		if(d->b[j].k) {
			if(i == 0) {
				return &d->b[j];
			}
			i--;
		}
	for(j=0; j<DICT_BUCKETS && i>=0; j++)
		if(d->d[j]) {
			int tmp = i - d->d[j]->n_inclusive;
			if(tmp < 0)
				return dict_indexb(d->d[j], i);
			i = tmp;
		}
	return NULL;
}


const char *dict_indexv(const struct dict *d, int i, const char *dfl)
{
	const struct dict_bucket *o = dict_indexb(d, i);
	return o ? o->v : dfl;
}


static void db_set(struct dict_bucket *b, const char *k, const char *v, const char *c)
{
	if(k) {	/* k==0 signals that b->k is not changing and does not need to be replaced. */
		if(b->k)
			free(b->k);
		b->k = strdup(k);
	}
	if(b->v)
		free(b->v);
	b->v = v ? strdup(v) : NULL;

	if(b->c)
		free(b->c);
	b->c = c ? strdup(c) : NULL;
}

static int add_guts(struct dict *d, const char *k, const char *v, const char *c, long h)
{
	int b = h % DICT_BUCKETS;
	int n_added = 0;
	assert(k);
	assert(d->n_inclusive >= 0);
	D assert(d->n_inclusive == n_incl(d));
	if(d->b[b].k && strcmp(d->b[b].k, k)==0) {
		db_set(&d->b[b], NULL, v, c);
		assert(d->n_inclusive >= 1);
	}
	else if(d->b[b].k) {
		if(! d->d[b])
			d->d[b] = dict_constr();
		n_added = add_guts(d->d[b], k, v, c, h>>DICT_BITS);
	}
	else if(d->d[b]) {
		n_added = add_guts(d->d[b], k, v, c, h>>DICT_BITS);
		}
	else {
		db_set(&d->b[b], k, v, c);
		n_added = 1;
	}
	d->n_inclusive += n_added;
	D assert(d->n_inclusive == n_incl(d));
	return n_added;
}


void dict_add(struct dict *d, const char *k, const char *v, const char *c)
{
	add_guts(d, k, v, c, dict_hash(k));
}



static void copy_guts(struct dict *o, const struct dict *d)
{
	int i;
	for(i=0; i<DICT_BUCKETS; i++) {
		if(d->b[i].k)
			dict_add(o, d->b[i].k, d->b[i].v, d->b[i].c);
		if(d->d[i])
			copy_guts(o, d->d[i]);
	}
}


struct dict *dict_copy(const struct dict *d)
{
	struct dict *o = dict_constr();
	copy_guts(o, d);
	return o;
}


void test()
{
	int i;
	struct dict *d = dict_constr();
	dict_destr(d);
	printf("PASS1\n");

	d = dict_constr();
	dict_add(d, "key", "val", 0);
	dict_destr(d);
	printf("PASS2\n");

	d = dict_constr();
	dict_add(d, "key", "val", 0);
	assert(dict_getv(d, "key", (const char *)0));
	assert(strcmp(dict_getv(d, "key", "NG"), "val")==0);
	dict_add(d, "k", "v", 0);
	assert(dict_getv(d, "key", (const char *)0));
	assert(dict_getv(d, "k", (const char *)0));
	assert(d->n_inclusive == 2);
	dict_add(d, "k", "v2", 0);
	assert(d->n_inclusive == 2);
	dict_add(d, "key", "val", 0);
	assert(d->n_inclusive == 2);
	printf("INFO: %s\n", dict_getv(d, "k", "NG"));
	assert(strcmp(dict_getv(d, "k", "NG"), "v2")==0);
	dict_destr(d);
	printf("PASS3\n");

	d = dict_constr();
	assert(dict_getv(d, "key", (const char *)0) == 0);
	dict_add(d, "k", "v", 0);
	dict_remove(d, "k");
	assert(d->n_inclusive == 0);
	assert(dict_getv(d, "k", (const char *)0) == 0);
	dict_destr(d);
	printf("PASS4\n");

	d = dict_constr();
	dict_remove(d, "k");
	assert(d->n_inclusive == 0);
	dict_destr(d);
	printf("PASS4.1\n");

	d = dict_constr();
	for(i=1; i<200; i++) {
		char buf[100];
		sprintf(buf, "%d", i);
		dict_add(d, buf, buf, 0);
	}
	dict_destr(d);
	printf("PASS4.5\n");

	d = dict_constr();
	for(i=1; i<200; i++) {
		char buf[100];
		sprintf(buf, "%d", i);
		dict_add(d, buf, buf, 0);
	}
	for(i=1; i<200; i++) {
		char buf[100];
		sprintf(buf, "%d", i);
		dict_remove(d, buf);
	}
	dict_destr(d);
	printf("PASS4.7\n");

	d = dict_constr();
	for(i=1; i<2000; i++) {
		char buf[100];
		sprintf(buf, "%d", i);
		dict_add(d, buf, buf, 0);
	}
	printf("INFO: %s\n", dict_getv(d, "1017", "NG"));
	assert(strcmp(dict_getv(d, "1017", "NG"), "1017")==0);
	printf("PASS5\n");
	for(i=1; i<2000; i++) {
		char buf[100];
		int tmp = ((i+1717)*(i%97))%2000;
		if(tmp == 1017)
			tmp = 13;
		sprintf(buf, "%d", tmp);
		dict_remove(d, buf);
		tmp = (tmp + 13*i*(i%51))%2000;
		if(tmp == 1017)
			tmp = 13;
		sprintf(buf, "%d", tmp);
		dict_add(d, buf, "replaced", 0);
	}
	assert(strcmp(dict_getv(d, "1017", "NG"), "1017")==0);
	dict_destr(d);
	printf("PASS6\n");

	d = dict_constr();
	{
	struct dict *c;
	int has[2000];
	for(i=0; i<2000; i++) {
		char buf[100];
		sprintf(buf, "%d", i);
		dict_add(d, buf, buf, 0);
		has[i] = 0;
	}
	c = dict_copy(d);
	for(i=0; i<2000; i++) {
		const char * x = dict_indexv(d, i, "NG");
		assert(x && strcmp(x, "NG")!=0);
		has[atoi(x)]++;
	}
	for(i=0; i<2000; i++)
		assert(has[i]==1);
	dict_destr(d);
	for(i=0; i<2000; i++)
		has[i] = 0;
	for(i=0; i<2000; i++) {
		const char * x = dict_indexv(c, i, "NG");
		assert(x && strcmp(x, "NG")!=0);
		has[atoi(x)]++;
	}
	for(i=0; i<2000; i++)
		assert(has[i]==1);
	dict_destr(c);
	}
	printf("OK\n");
}


#ifdef TEST
int main(int argc, char **argv)
{
	test();
}
#endif
