// SPDX-License-Identifier: LGPL-2.1

/*
 * Copyright (C) 2009, Steven Rostedt <srostedt@redhat.com>
 * Copyright (C) 2018 VMware Inc, Steven Rostedt <rostedt@goodmis.org>
 */

/**
 *  @file    libkshark-hash.c
 *  @brief   Hash table of integer Id numbers.
 */

// C
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

// KernelShark
#include "libkshark.h"

/**
 * @brief: quick_hash - A quick (non secured) hash alogirthm
 * @param val: The value to perform the hash on
 * @param bits: The size in bits you need to return
 *
 * This is a quick hashing function adapted from Donald E. Knuth's 32
 * bit multiplicative hash. See The Art of Computer Programming (TAOCP).
 * Multiplication by the Prime number, closest to the golden ratio of
 * 2^32.
 *
 * "bits" is used to max the result for use cases that require
 * a power of 2 return value that is less than 32 bits. Any value
 * of "bits" greater than 31 (or zero), will simply return the full hash
 * on "val".
 */
static inline uint32_t quick_hash(uint32_t val, unsigned int bits)
{
	val *= UINT32_C(2654435761);

	if (!bits || bits > 31)
		return val;

	return val & ((1 << bits) - 1);
}

static size_t hash_size(struct kshark_hash_id *hash)
{
	return (1 << hash->n_bits);
}

/**
 * Create new hash table of Ids.
 */
struct kshark_hash_id *kshark_hash_id_alloc(size_t n_bits)
{
	struct kshark_hash_id *hash;
	size_t size;

	hash = calloc(1, sizeof(*hash));
	if (!hash)
		goto fail;

	hash->n_bits = n_bits;
	hash->count = 0;

	size = hash_size(hash);
	hash->hash = calloc(size, sizeof(*hash->hash));
	if (!hash->hash)
		goto fail;

	return hash;

 fail:
	fprintf(stderr, "Failed to allocate memory for hash table.\n");
	kshark_hash_id_free(hash);
	return NULL;
}

/** Free the hash table of Ids. */
void kshark_hash_id_free(struct kshark_hash_id *hash)
{
	if (!hash)
		return;

	if (hash->hash) {
		kshark_hash_id_clear(hash);
		free(hash->hash);
	}

	free(hash);
}

/**
 * @brief Check if an Id with a given value exists in this hash table.
 */
bool kshark_hash_id_find(struct kshark_hash_id *hash, int id)
{
	uint32_t key = quick_hash(id, hash->n_bits);
	struct kshark_hash_id_item *item;

	for (item = hash->hash[key]; item; item = item->next)
		if (item->id == id)
			break;

	return !!(unsigned long) item;
}

/**
 * @brief Add Id to the hash table.
 *
 * @param hash: The hash table to add to.
 * @param id: The Id number to be added.
 *
 * @returns Zero if the Id already exists in the table, one if the Id has been
 *	    added and negative errno code on failure.
 */
int kshark_hash_id_add(struct kshark_hash_id *hash, int id)
{
	uint32_t key = quick_hash(id, hash->n_bits);
	struct kshark_hash_id_item *item;

	if (kshark_hash_id_find(hash, id))
		return 0;

	item = calloc(1, sizeof(*item));
	if (!item) {
		fprintf(stderr,
			"Failed to allocate memory for hash table item.\n");
		return -ENOMEM;
	}

	item->id = id;
	item->next = hash->hash[key];
	hash->hash[key] = item;
	hash->count++;

	return 1;
}

/**
 * @brief Remove Id from the hash table.
 */
void kshark_hash_id_remove(struct kshark_hash_id *hash, int id)
{
	struct kshark_hash_id_item *item, **next;
	int key = quick_hash(id, hash->n_bits);

	next = &hash->hash[key];
	while (*next) {
		if ((*next)->id == id)
			break;
		next = &(*next)->next;
	}

	if (!*next)
		return;

	assert(hash->count);

	hash->count--;
	item = *next;
	*next = item->next;

	free(item);
}

/** Remove (free) all Ids (items) from this hash table. */
void kshark_hash_id_clear(struct kshark_hash_id *hash)
{
	struct kshark_hash_id_item *item, *next;
	size_t size;
	int i;

	if (!hash || ! hash->hash)
		return;

	size = hash_size(hash);
	for (i = 0; i < size; i++) {
		next = hash->hash[i];
		if (!next)
			continue;

		hash->hash[i] = NULL;
		while (next) {
			item = next;
			next = item->next;
			free(item);
		}
	}

	hash->count = 0;
}

static int compare_ids(const void* a, const void* b)
{
	int arg1 = *(const int*)a;
	int arg2 = *(const int*)b;

	if (arg1 < arg2)
		return -1;

	if (arg1 > arg2)
		return 1;

	return 0;
}

/**
 * @brief Get a sorted array containing all Ids of this hash table.
 */
int *kshark_hash_ids(struct kshark_hash_id *hash)
{
	struct kshark_hash_id_item *item;
	size_t size = hash_size(hash);
	int count = 0, i;
	int *ids;

	if (!hash->count)
		return NULL;

	ids = calloc(hash->count, sizeof(*ids));
	if (!ids) {
		fprintf(stderr,
			"Failed to allocate memory for Id array.\n");
		return NULL;
	}

	for (i = 0; i < size; i++) {
		item = hash->hash[i];
		while (item) {
			ids[count++] = item->id;
			item = item->next;
		}
	}

	qsort(ids, hash->count, sizeof(*ids), compare_ids);

	return ids;
}
