/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	This program is distributed in the hope that it will be 
	useful, but WITHOUT ANY WARRANTY; without even the 
	implied warranty of MERCHANTABILITY or FITNESS FOR A 
	PARTICULAR PURPOSE.

**********************************************************************/

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

#include "svg2gb_hash.h"

#ifndef ASSERT

#ifdef _WIN32
#include <windows.h>
#define ASSERT(exp) if(!(exp)) DebugBreak();
#else
#include <assert.h>
#define ASSERT assert
#endif

#endif /* end of #ifdef ASSERT */ 

GB_HASH_CHAIN_POOL *gb_add_hash_chain_pool(GB_HASH_TABLE *h);

GB_HASH_TABLE *gb_make_hash_table(
	int table_size, 
	int pool_block_size, 
	int (*hash_func)(void *key),
	BOOL (*key_equals)(void *key_lhs, void *key_rhs),
	BOOL (*value_equals)(void *value_lhs, void *value_rhs)
	){
	GB_HASH_TABLE *tbl;
	
	ASSERT(table_size);
	ASSERT(pool_block_size);
	ASSERT(hash_func);
	ASSERT(key_equals);
	
	tbl = (GB_HASH_TABLE *)malloc(sizeof(GB_HASH_TABLE));
	tbl->data = (GB_HASH_CHAIN**)calloc(sizeof(GB_HASH_CHAIN*), table_size);
	tbl->table_size = table_size;
	tbl->pool = 0;
	tbl->pool_block_size = pool_block_size;
	tbl->hash_func = hash_func;
	tbl->key_equals = key_equals;
	tbl->value_equals = value_equals;
	return tbl;
}

void gb_free_hash_pool(GB_HASH_CHAIN_POOL *pool){
	if(pool){
		gb_free_hash_pool(pool->next);
		free(pool);
	}
}

void gb_free_hash_table(GB_HASH_TABLE *tbl){
	gb_free_hash_pool(tbl->pool);
	free(tbl->data);
	free(tbl);
}

GB_HASH_CHAIN *gb_alloc_hash_chain(GB_HASH_TABLE *h){
	GB_HASH_CHAIN *ret=0;
	GB_HASH_CHAIN_POOL *pool;
	
	for(pool = h->pool; pool; pool=pool->next){
		if(pool->avail)
			break;
	}
	if(!pool)
		pool = gb_add_hash_chain_pool(h);
	ret = pool->avail;
	pool->avail = pool->avail->next;
	ret->next = 0;
	return ret;
}


GB_HASH_CHAIN_POOL *gb_add_hash_chain_pool(GB_HASH_TABLE *h){
	GB_HASH_CHAIN_POOL *pool;
	GB_HASH_CHAIN *c;
	int i;
	pool = (GB_HASH_CHAIN_POOL *)calloc(sizeof(*pool),1);
	pool->buff = calloc(sizeof(*c), h->pool_block_size);
	
	for(i=0,c=pool->buff; i<h->pool_block_size-1; ++i,++c){
		c->next=c+1;
	}
	pool->avail=pool->buff;
	
	pool->next = h->pool;
	h->pool=pool;
	return pool;
}

void gb_hash_insert_multi(GB_HASH_TABLE *tbl, void *key, void *value){
	int hash;
	GB_HASH_CHAIN *c;

	hash = tbl->hash_func(key);
	c = gb_alloc_hash_chain(tbl);
	c->data.key = key;
	c->data.value = value;
	c->next = tbl->data[hash];
	tbl->data[hash] = c;
}

void gb_hash_insert(GB_HASH_TABLE *tbl, void *key, void *value){
	int hash;
	GB_HASH_CHAIN *c;
	hash = tbl->hash_func(key);
	for(c=tbl->data[hash]; c; c=c->next){
		if(tbl->key_equals(c->data.key, key)){
			c->data.key = key;
			c->data.value = value;
			return;
		}
	}
	
	c = gb_alloc_hash_chain(tbl);
	c->data.key = key;
	c->data.value = value;
	c->next = tbl->data[hash];
	tbl->data[hash] = c;
}

GB_MULTI_RESULT *gb_make_multi_result()
{
	GB_MULTI_RESULT *ret;
	ret = (GB_MULTI_RESULT*)calloc(1,sizeof(GB_MULTI_RESULT));
	return ret;
}

void gb_reset_multi_result(GB_MULTI_RESULT *result)
{
	int i;
	for(i=0; i<result->count; ++i){
		result->data[i] = 0;
	}
	result->count = 0;
}

void gb_free_multi_result(GB_MULTI_RESULT *result){
	if(result->data){
		free(result->data);
	}
	free(result);
}

void gb_multi_result_add_data(GB_MULTI_RESULT *ret, void *value){
	if(ret->data_buff_count <= ret->count){
		int new_buff_count;
		new_buff_count = ret->data_buff_count;
		if(new_buff_count==0){
			new_buff_count = 8;
		}
		else{
			new_buff_count *= 2;
		}
		ret->data = realloc(ret->data, sizeof(void*)*new_buff_count);
		ret->data_buff_count = new_buff_count;
	}
	ret->data[ret->count++] = value;
}

void gb_hash_find_multi(GB_MULTI_RESULT *ret, GB_HASH_TABLE *tbl, void *key){
	GB_HASH_CHAIN *c;
	for(c = tbl->data[tbl->hash_func(key)]; c; c=c->next){
		if(tbl->key_equals(c->data.key, key)){
			gb_multi_result_add_data(ret, c->data.value);
		}
	}
}

void *gb_hash_find(GB_HASH_TABLE *tbl, void *key){
GB_HASH_CHAIN *c;
	for(c = tbl->data[tbl->hash_func(key)]; c; c=c->next){
		if(tbl->key_equals(c->data.key, key)){
			return c->data.value;
		}
	}
	return 0;
}

void gb_hash_erase(GB_HASH_TABLE *tbl, void *key, void *value){
int h;
GB_HASH_CHAIN *c;
GB_HASH_CHAIN *prev=0;
	
	h = tbl->hash_func(key);
	for(c = tbl->data[h]; c; prev=c, c=c->next){
		if(tbl->key_equals(c->data.key, key)){
			if(value){
				ASSERT(tbl->value_equals);
				if(!tbl->value_equals(c->data.value, value))
					continue;
			}
			
			if(prev)
				prev->next = c->next;
			else
				tbl->data[h] = c->next;
			c->next = tbl->pool->avail;
			tbl->pool->avail = c;
			return ;
		}
	}
}
