/*****************************************************************************\
 *  Copyright (C) 2012 CEA/DAM/DIF
 *
 *  This file is part of Slurm, a resource management program.
 *  For details, see <https://slurm.schedmd.com/>.
 *  Please also read the included file: DISCLAIMER.
 *
 *  Slurm is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  In addition, as a special exception, the copyright holders give permission
 *  to link the code of portions of this program with the OpenSSL library under
 *  certain conditions as described in each individual source file, and
 *  distribute linked combinations including the two. You must obey the GNU
 *  General Public License in all respects for all of the code used other than
 *  OpenSSL. If you modify file(s) with this exception, you may extend this
 *  exception to your version of the file(s), but you are not obligated to do
 *  so. If you do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source files in
 *  the program, then also delete it here.
 *
 *  Slurm 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.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with Slurm; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
\*****************************************************************************/
#include <check.h>
#include <stdio.h>
#include <stdlib.h>

#include "src/common/xhash.h"
#include "src/common/xmalloc.h"

/* FIXME: how to check memory leaks with valgrind ? (to check if xhash_free
 * does free all structures correctly). */

/*****************************************************************************
 * DEFINITIONS
 *****************************************************************************/

typedef struct hashable_st {
	char id[255];
	uint32_t idn;
} hashable_t;

void hashable_identify(void* voiditem, const char** key, uint32_t* key_len)
{
	hashable_t* item = (hashable_t*)voiditem;
	*key = item->id;
	*key_len = strlen(item->id);
}

/*****************************************************************************
 * FIXTURE                                                                   *
 *****************************************************************************/

xhash_t* g_ht = NULL;
hashable_t g_hashables[200];
uint32_t g_hashableslen = sizeof(g_hashables)/sizeof(g_hashables[0]);

static void setup(void)
{
	int i;
	g_ht = xhash_init(hashable_identify, NULL);
	if (!g_ht) return; /* fatal error, will be detected by test cases */
	for (i = 0; i < g_hashableslen; ++i) {
		snprintf(g_hashables[i].id, sizeof(g_hashables[i].id), "%d", i);
		g_hashables[i].idn = i;
		/* it is an error if xhash_add returns null but it will be
		 * detected by test cases */
		if (!xhash_add(g_ht, g_hashables + i)) return;
	}
}

static void teardown(void)
{
	xhash_free(g_ht);
}

/*****************************************************************************
 * UNIT TESTS                                                                *
 ****************************************************************************/

START_TEST(test_init_free)
{
	xhash_t* ht = NULL;

	mark_point();

	/* invalid case */
	ht = xhash_init(NULL, NULL);
	ck_assert_msg(ht == NULL, "allocated table without identifying function");

	/* alloc and free */
	ht = xhash_init(hashable_identify, NULL);
	ck_assert_msg(ht != NULL, "hash table was not allocated");
	xhash_free(ht);
}
END_TEST

START_TEST(test_add)
{
	xhash_t* ht = NULL;
	hashable_t a[4] = {{"0", 0}, {"1", 1}, {"2", 2}, {"3", 3}};
	int i, len = sizeof(a)/sizeof(a[0]);
	char buffer[255];
	ht = xhash_init(hashable_identify, NULL);
	ck_assert_msg(xhash_add(NULL, a) == NULL, "invalid cases not null");
	ck_assert_msg(xhash_add(ht, NULL) == NULL, "invalid cases not null");
	ck_assert_msg(xhash_add(ht, a)   != NULL, "xhash_add failed");
	ck_assert_msg(xhash_add(ht, a+1) != NULL, "xhash_add failed");
	ck_assert_msg(xhash_add(ht, a+2) != NULL, "xhash_add failed");
	ck_assert_msg(xhash_add(ht, a+3) != NULL, "xhash_add failed");
	for (i = 0; i < len; ++i) {
		snprintf(buffer, sizeof(buffer), "%d", i);
		ck_assert_msg(xhash_get_str(ht, buffer) == (a + i),
				"bad hashable item returned");
	}
	xhash_free(ht);
}
END_TEST

START_TEST(test_find)
{
	xhash_t* ht = g_ht;
	char buffer[255];
	int i;

	/* test bad match */
	ck_assert_msg(xhash_get_str(ht, "bad") == NULL  , "invalid case not null");
	ck_assert_msg(xhash_get_str(ht, "-1") == NULL   , "invalid case not null");
	ck_assert_msg(xhash_get_str(ht, "10000") == NULL, "invalid case not null");

	/* test all good indexes */
	for (i = 0; i < g_hashableslen; ++i) {
		snprintf(buffer, sizeof(buffer), "%d", i);
		ck_assert_msg(xhash_get_str(ht, buffer) == (g_hashables + i),
				"bad hashable item returned");
	}
}
END_TEST

/* returns the number of item deleted from the hash table */
static int test_delete_helper()
{
	xhash_t* ht = g_ht;
	int ret = 0;
	int i;
	char buffer[255];
	for (i = 0; i < g_hashableslen; ++i) {
		snprintf(buffer, sizeof(buffer), "%d", i);
		if (xhash_get_str(ht, buffer) != (g_hashables + i)) {
			++ret;
		}
	}
	return ret;
}

START_TEST(test_delete)
{
	xhash_t* ht = g_ht;
	int result;
	char buffer[255];

	/* invalid cases */
	xhash_delete_str(NULL, "1");
	ck_assert_msg(xhash_get_str(ht, "1") != NULL, "invalid case null");
	/* Deleting non-existent item should do nothing. */
	xhash_delete(ht, NULL, 0);
	ck_assert_msg(xhash_count(ht) == g_hashableslen,
			"invalid delete has been done");
	result = test_delete_helper();
	ck_assert_msg(result == 0,
			"no item should have been deleted, but %d were deleted",
			result);

	/* test correct deletion */
	xhash_delete_str(ht, "10");
	ck_assert_msg(xhash_get_str(ht, "10") == NULL, "item not deleted");
	ck_assert_msg(xhash_count(ht) == (g_hashableslen-1), "bad count");
	/* left edge */
	xhash_delete_str(ht, "0");
	ck_assert_msg(xhash_get_str(ht, "0") == NULL, "item not deleted");
	ck_assert_msg(xhash_count(ht) == (g_hashableslen-2), "bad count");
	/* right edge */
	snprintf(buffer, sizeof(buffer), "%u", (g_hashableslen-2));
	xhash_delete_str(ht, buffer);
	ck_assert_msg(xhash_get_str(ht, "0") == NULL, "item not deleted");
	ck_assert_msg(xhash_count(ht) == (g_hashableslen-3), "bad count");

	result = test_delete_helper();
	ck_assert_msg(result == 3, "bad number of items were deleted: %d",
			result);
}
END_TEST

START_TEST(test_count)
{
	xhash_t* ht = g_ht;
	hashable_t a[4] = {{"0", 0}, {"1", 1}, {"2", 2}, {"3", 3}};
	ck_assert_msg(xhash_count(ht) == g_hashableslen,
		"invalid count (fixture table)");
	ht = xhash_init(hashable_identify, NULL);
	xhash_add(ht, a);
	xhash_add(ht, a+1);
	xhash_add(ht, a+2);
	xhash_add(ht, a+3);
	ck_assert_msg(xhash_count(ht) == 4, "invalid count (fresh table)");
	xhash_free(ht);
}
END_TEST

static void test_walk_helper_callback(void* item, void* arg)
{
	hashable_t* hashable = (hashable_t*)item;
	hashable->idn = UINT32_MAX;
}

START_TEST(test_walk)
{
	xhash_t* ht = g_ht;
	int i;
	xhash_walk(ht, test_walk_helper_callback, NULL);
	for (i = 0; i < g_hashableslen; ++i) {
		ck_assert_msg(g_hashables[i].idn == UINT32_MAX,
				"hashable item was not walked over");
	}
}
END_TEST

/*****************************************************************************
 * TEST SUITE                                                                *
 ****************************************************************************/

Suite *xhash_suite(void)
{
	Suite *s = suite_create("xhash");
	TCase *tc_core = tcase_create("Core");
	tcase_add_checked_fixture(tc_core, setup, teardown);
	tcase_add_test(tc_core, test_init_free);
	tcase_add_test(tc_core, test_add);
	tcase_add_test(tc_core, test_find);
	tcase_add_test(tc_core, test_delete);
	tcase_add_test(tc_core, test_count);
	tcase_add_test(tc_core, test_walk);
	suite_add_tcase(s, tc_core);
	return s;
}

/*****************************************************************************
 * TEST RUNNER                                                               *
 ****************************************************************************/

int main(void)
{
    int number_failed;
    SRunner *sr = srunner_create(xhash_suite());

    srunner_run_all(sr, CK_NORMAL);
    number_failed = srunner_ntests_failed(sr);
    srunner_free(sr);

    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
