/* 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>

int runtest_size(size_t TEST_SIZE)
{
	net_tree_t *nettree;
	unsigned char treehash[HASHSIZE] = {0};
	unsigned char *testdata;
	unsigned char *hash;
	uint8_t *ptr;
	mdex_t *mdex;
	mtree_t mtree = {0};
	mtree_t newtree = {0};
	mdex_entry_t entry = {0};
	size_t chunksz, hashsz, min, max, parts, sz;
	size_t off = 0;
	size_t len = TEST_SIZE;
	int rc;

	testdata = malloc(TEST_SIZE);
	assert(testdata);
	test_random_bytes(testdata, TEST_SIZE);
	mdex = mdex_init(0);
	rc = mtree_init(&mtree, TEST_SIZE);
	test_assert(rc == 0, "mtree_init() returned %i", rc);
	rc = mtree_build(&mtree, testdata, NULL);
	test_assert(rc == 0, "mtree_build() returned %i", rc);
	rc = mdex_add(mdex, testdata, TEST_SIZE, NULL, 0);
	test_assert(rc == 0, "mdex_add() returned %i", rc);
	hashsz = HASHSIZE * mtree.nodes;

	/* test root hash of mtree + all chunks are in index */
	rc = mtree_init(&newtree, TEST_SIZE);
	ptr = newtree.tree;
	test_assert(rc == 0, "mtree_init(newtree) returned %i", rc);

	/* check all parts of tree are in mdex */
	sz = sizeof (net_tree_t) + hashsz;
	parts = howmany(sz, MTREE_CHUNKSIZE);
	fprintf(stderr, "tree has %zu parts\n", parts);
	for (size_t z = 0; z < parts; z++) {
		mdex_tree_hash(treehash, HASHSIZE, &mtree, z);
		fprintf(stderr, "%zu: ", z);
		hash_hex_debug(stderr, treehash, HASHSIZE);
		rc = mdex_get(mdex, treehash, HASHSIZE, &entry);
		test_assert(rc == 0, "root hash found");
		if (rc != 0) goto exit_0;
		fprintf(stderr, "sizeof(struct net_tree_s)=%zu\n", sizeof(struct net_tree_s));
		off = (z) ? 0 : offsetof(net_tree_t, tree); /* skip to tree on first part */
		fprintf(stderr, "%zu: off = %zu\n", z, off);
		fprintf(stderr, "copying %zu bytes of tree\n", entry.ptr.size - off);
		memcpy(ptr, (uint8_t *)entry.ptr.data + off, entry.ptr.size - off);
		ptr += entry.ptr.size - off;
		chunksz = MIN(sz, MTREE_CHUNKSIZE);
		test_assert(entry.type == (MDEX_PTR | MDEX_OTI), "root entry type set, %i", entry.type);
		test_assert(entry.ptr.size == chunksz, "root entry size set, sz=%zu, expected %zu",
				entry.ptr.size, chunksz);
		nettree = (net_tree_t *)entry.ptr.data;
		if (!z) test_assert(be64toh(nettree->size) == mtree.len, "tree size set");
		sz -= MTREE_CHUNKSIZE;
	}

	/* test reassembled tree */
	test_assert(newtree.len == mtree.len, "len = %zu", newtree.len);
	test_assert(newtree.base == mtree.base, "base = %zu", newtree.base);
	test_assert(newtree.chunks == mtree.chunks, "chunks = %zu", newtree.chunks);
	test_assert(newtree.nodes == mtree.nodes, "nodes = %zu", newtree.nodes);
	test_assert(mtree_verify(&newtree) == 0, "reassembled tree is valid");
	test_assert(!memcmp(newtree.tree, mtree.tree, hashsz), "reassembled tree matches");

	min = mtree_subtree_data_min(mtree.base, 0);
	max = min + mtree.chunks - 1;
	off = 0;
	for (size_t z = min; z <= max; z++) {
		hash = mtree_nnode(&mtree, z);
		fprintf(stderr, "%03zu: ", z);
		hash_hex_debug(stderr, hash, HASHSIZE);
		assert(hash);
		rc = mdex_get(mdex, hash, HASHSIZE, &entry);
		test_assert(rc == 0, "hash %zu found", z);
		test_assert(entry.ptr.size == MIN(MTREE_CHUNKSIZE, len), "size %zu matches", z);
		test_assert(entry.ptr.data == testdata + off, "entry %zu matches", z);
		off += MTREE_CHUNKSIZE; len -= MTREE_CHUNKSIZE;
	}
exit_0:
	mtree_free(&newtree);
	mtree_free(&mtree);
	mdex_free(mdex);
	free(testdata);
	return test_status;
}

int main(void)
{
	test_name("mdex_add()");
	runtest_size(1024 * 31 + 5);   /* single node */
	runtest_size(1024 * 100 + 5);
	runtest_size(1024 * 1024 + 5); /* larger tree with padding chunks */
	runtest_size(1024 * 1024 * 32 + 5); /* multi-part tree */
	return test_status;
}
