/* 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/mtree.h>
#include <librecast/sync.h>

#define TEST_SIZE 1024 * 1024 + 5
/* nice powers of two hide bugs - ensure test size is odd */
static_assert(TEST_SIZE % 2, "TEST_SIZE must be an odd number");

int main(void)
{
	struct stat sb;
	char src[] = "0000-0054.src.tmp.XXXXXX";
	unsigned char alias[HASHSIZE];
	unsigned char *hash;
	unsigned char *testdata;
	mdex_t *mdex;
	mtree_t mtree = {0};
	net_tree_t *nettree;
	mdex_entry_t entry = {0};
	size_t min, max;
	size_t off = 0;
	size_t sz = 0;
	size_t len = TEST_SIZE;
	int fds, rc;

	test_name("mdex_addfile()");

	/* create test file */
	fds = test_data_file(src, TEST_SIZE, TEST_TMP | TEST_RND);
	test_assert(fds != -1, "test_data_file()");
	if (fds == -1) goto exit_0;
	test_assert(test_data_size(src, TEST_SIZE) == 0, "source exists and is correct size");
	test_assert(stat(src, &sb) != -1, "stat source file");

	mdex = mdex_init(0);
	rc = mtree_init(&mtree, TEST_SIZE);
	test_assert(rc == 0, "mtree_init() returned %i", rc);

	/* map file, build tree */
	testdata = lc_mmapfile(src, &sz, PROT_READ, MAP_PRIVATE, 0, &sb);
	rc = mtree_build(&mtree, testdata, NULL);

	rc = mdex_addfile(mdex, src, NULL, MDEX_ALIAS);
	test_assert(rc == 0, "mdex_addfile() returned %i", rc);

	/* test root hash of mtree + all chunks are in index */
	mdex_tree_hash_sb(alias, sizeof alias, &mtree, 0, &sb, src);
	test_log("fetching root hash\n");
	hash_hex_debug(stderr, alias, HASHSIZE);
	rc = mdex_get(mdex, alias, HASHSIZE, &entry);
	test_assert(rc == 0, "root hash found");
	test_assert((entry.type & (MDEX_PTR | MDEX_OTI)), "root entry type set");
	nettree = (net_tree_t *)entry.ptr.data;
	nettree->namesz = ntohs(nettree->namesz);
	sz = sizeof (net_tree_t) + HASHSIZE * mtree.nodes + nettree->namesz;
	test_assert(entry.ptr.size == sz, "root entry size set, sz=%zu", entry.ptr.size);
	test_assert(!memcmp(nettree->tree + nettree->namesz, mtree.tree, HASHSIZE * mtree.nodes),
			"mtree matches sz=%zu", entry.ptr.size);

	/* test hash of filename returns root hash via alias */
	memset(&entry, 0, sizeof entry);
	rc = mdex_getalias(mdex, src, &entry);
	test_assert(rc == 0, "mdex_getalias() returned %i", rc);

	min = mtree_subtree_data_min(mtree.base, 0);
	max = min + mtree.chunks;
	for (size_t z = min; z < max; z++) {
		hash = mtree_nnode(&mtree, z);
		hash_hex_debug(stderr, hash, HASHSIZE);
		assert(hash);
		rc = mdex_get(mdex, hash, HASHSIZE, &entry);
		test_assert(rc == 0, "hash found");
		test_assert((entry.type & MDEX_FILE), "entry type set");
		size_t expected = MIN(MTREE_CHUNKSIZE, len);
		test_assert(entry.file.size == expected,
				"%03zu: size %zu == %zu matches", z, entry.file.size, expected);
		test_assert(entry.file.off == off, "offset matches");
		test_assert(entry.file.file.ref == 1, "file refcount set");
		test_assert(!strcmp(entry.file.file.name, src), "filename set");
		off += MTREE_CHUNKSIZE; len -= MTREE_CHUNKSIZE;
	}

	mtree_free(&mtree);
	mdex_free(mdex);
	close(fds);
	unlink(src);
exit_0:
	return test_status;
}
