/* Copyright 2018 Romain "Artefact2" Dal Maso <artefact2@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "gumble.h"

#if CCH_HASHTABLE_STATS
#include <stdio.h>
#endif

void cch_hashtable_init(cch_hashtable_t* ht, unsigned char num_entries_log2, size_t member_size) {
	size_t num_entries = (1ULL << num_entries_log2);
	ht->mask = num_entries - 1;
	ht->entry_size = member_size + sizeof(cch_hash_t);
	ht->data = malloc(num_entries * ht->entry_size);
	assert(ht->data);
	memset(ht->data, 0, num_entries * ht->entry_size); /* XXX: collisions on zero? */

#if CCH_HASHTABLE_STATS
	ht->gets = ht->sets = ht->hits = ht->overwrites = ht->collisions = 0;
#endif
}

void cch_hashtable_free(cch_hashtable_t* ht) {
	free(ht->data);

#if CCH_HASHTABLE_STATS
	fprintf(stderr, "==========\nhashtable at %p:\n%llu entries, %lu bytes each, %llu MiB total size\n%u gets, %u sets, %.2f%% full, %.2f%% hit rate, %.2f%% collision rate, %.2f%% overwrite rate\n==========\n",
			ht,
			ht->mask + 1,
			ht->entry_size,
			(ht->entry_size * (ht->mask + 1)) >> 20,
			ht->gets,
			ht->sets,
			100.f * (ht->sets - ht->overwrites) / (ht->mask + 1),
			100.f * ht->hits / ht->gets,
			100.f * ht->collisions / ht->gets,
			100.f * ht->overwrites / ht->sets
		);
#endif
}

const void* cch_hashtable_get(cch_hashtable_t* ht, cch_hash_t k) {
	const void* data = ht->data + ht->entry_size * (k & ht->mask);
	cch_hash_t h = *(cch_hash_t*)data;

	if(h != k) {
#if CCH_HASHTABLE_STATS
		if(h) ++ht->collisions;
		++ht->gets;
#endif
		return 0; /* Type 2 collision */
	}

#if CCH_HASHTABLE_STATS
	++ht->hits;
	++ht->gets;
#endif

	return data + sizeof(cch_hash_t);
}

void cch_hashtable_set(cch_hashtable_t* ht, cch_hash_t k, const void* v) {
	void* data = ht->data + ht->entry_size * (k & ht->mask);

#if CCH_HASHTABLE_STATS
	if(*(cch_hash_t*)data) ++ht->overwrites;
	++ht->sets;
#endif

	*(cch_hash_t*)data = k;
	memcpy(data + sizeof(cch_hash_t), v, ht->entry_size - sizeof(cch_hash_t));
}
