/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2023 Brett Sheffield <bacs@librecast.net> */

#include "test.h"
#include "testdata.h"
#include <librecast/mdex.h>
#include <librecast.h>
#include <librecast/crypto.h>

int main(void)
{
	unsigned char hash[HASHSIZE];
	char hex[HEXLEN];
	mdex_t *mdex;
	mdex_entry_t entry = {0};
	const size_t entries = 64;
	char *path;
	mdex_filemeta_t file = {0};
	size_t z;
	int rc;

	test_name("Channel Indexing - mdex_*()");

	path = strdup("/path/to/file/that/will/be/reused");
	file.name = path;

	/* create an mdex and fill it will entries */
	mdex = mdex_init(entries);
	for (z = 0; z < entries + 1; z++) {
		hash_generic(hash, HASHSIZE, (unsigned char *)&z, sizeof z);
		hash_bin2hex(hex, sizeof hex, hash, sizeof hash);
		fprintf(stderr, "%zu: %s\n", z, hex);
		/* entry */
		entry.type = ((char)z & 1) + 1; /* odd => MDEX_FILE, even => MDEX_PTR */
		entry.ptr.size = z;
		switch (entry.type) {
			case MDEX_PTR:
				entry.ptr.data = (void *)0x1234;
				break;
			case MDEX_FILE:
				entry.file.off = z;
				entry.file.file = file;
				break;
		}
		errno = 0;
		rc = mdex_get(mdex, hash, HASHSIZE, NULL);
		test_assert(rc == -1, "entry %zu not found before adding", z);
		test_assert(errno == ENOENT, "entry %zu errno == ENOENT", z);
		rc = mdex_put(mdex, hash, HASHSIZE, &entry);
		test_assert(rc == 0, "entry %zu added", z);
		rc = mdex_get(mdex, hash, HASHSIZE, NULL);
		test_assert(rc == 0, "entry %zu found after adding", z);

	}
	for (z = 0; z < entries; z++) {
		/* check each entry */
		hash_generic(hash, HASHSIZE, (unsigned char *)&z, sizeof z);
		hash_bin2hex(hex, sizeof hex, hash, sizeof hash);
		fprintf(stderr, "%zu: %s\n", z, hex);
		rc = mdex_get(mdex, hash, HASHSIZE, &entry);
		test_assert(rc == 0, "entry %zu found", z);
		test_assert(entry.type == ((char)z & 1) + 1, "entry type %i", entry.type);
		test_assert(entry.ptr.size == z, "entry size set");
		switch (entry.type) {
			case MDEX_PTR:
				test_assert(entry.ptr.data == (void *)0x1234, "entry (PTR) data ptr set");
				break;
			case MDEX_FILE:
				test_assert(entry.file.off == z, "entry (FILE) off set");
				test_assert(!strcmp(entry.file.file.name, path),
						"entry file name set");
				break;
		}

	}
	/* delete entry, check it was removed */
#define deleteme 11
	z = deleteme;
	hash_generic(hash, HASHSIZE, (unsigned char *)&z, sizeof z);
	rc = mdex_del(mdex, hash, HASHSIZE);
	test_assert(rc == 0, "%zu: mdex_del returned %i", z, rc);
	rc = mdex_get(mdex, hash, HASHSIZE, NULL);
	test_assert(rc == -1, "entry %zu not found after deleting", z);
	test_assert(errno == ENOENT, "entry %zu errno == ENOENT", z);

	/* check other entries are still reachable */
	for (z = 0; z < entries; z++) {
		if (z == deleteme) continue;
		/* check each entry */
		hash_generic(hash, HASHSIZE, (unsigned char *)&z, sizeof z);
		rc = mdex_get(mdex, hash, HASHSIZE, NULL);
		test_assert(rc == 0, "entry %zu found", z);
	}

	/* delete root node */
	z = 0;
	hash_generic(hash, HASHSIZE, (unsigned char *)&z, sizeof z);
	rc = mdex_del(mdex, hash, HASHSIZE);
	test_assert(rc == 0, "%zu: mdex_del returned %i", z, rc);
	rc = mdex_get(mdex, hash, HASHSIZE, NULL);
	test_assert(rc == -1, "entry %zu not found after deleting", z);
	test_assert(errno == ENOENT, "entry %zu errno == ENOENT", z);

	/* check other entries are still reachable */
	for (z = 1; z < entries; z++) {
		if (z == deleteme) continue;
		/* check each entry */
		hash_generic(hash, HASHSIZE, (unsigned char *)&z, sizeof z);
		rc = mdex_get(mdex, hash, HASHSIZE, NULL);
		test_assert(rc == 0, "entry %zu found (after root deletion)", z);
	}

	/* add a new entry, reusing deleted entry from stack */
	mdex_entry_t reentry = {0};
	z = entries + 1;
	entry.type = CHAR_MAX;
	hash_generic(hash, HASHSIZE, (unsigned char *)&z, sizeof z);
	rc = mdex_put(mdex, hash, HASHSIZE, &entry);
	test_assert(rc == 0, "entry %zu added", z);
	rc = mdex_get(mdex, hash, HASHSIZE, &reentry);
	test_assert(rc == 0, "entry %zu found after adding", z);

	/* update previously added entry */
	entry.type = 42;
	rc = mdex_put(mdex, hash, HASHSIZE, &entry);
	test_assert(rc == 0, "entry %zu added", z);
	rc = mdex_get(mdex, hash, HASHSIZE, &reentry);
	test_assert(rc == 0, "entry %zu found after adding", z);
	test_assert(reentry.type == 42, "entry was updated %i == %i",
			(int)entry.type, (int)reentry.type);

	mdex_free(mdex);
	return test_status;
}
