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

#include "testnet.h"
#include "testdata.h"
#include <librecast/mtree.h>
#include <librecast/net.h>
#include <librecast/sync.h>
#include <errno.h>
#include <pthread.h>

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

#define TIMEOUT_SECONDS 2

enum {
	TID_SEND,
	TID_RECV
};

static sem_t sem_recv;

struct pkg_s {
	void *data;
	size_t len;
	uint8_t *hash;
	mtree_t tree;
};

void *thread_recv(void *arg)
{
	struct pkg_s *pkg = (struct pkg_s *)arg;
	lc_ctx_t *lctx = lc_ctx_new();
	pthread_cleanup_push((void (*)(void *))lc_ctx_free, lctx);
	lc_recvtree(lctx, pkg->hash, &pkg->tree, NULL, NULL, 0);
	pthread_cleanup_pop(1); /* lc_ctx_free */
	sem_post(&sem_recv);
	return NULL;
}

void *thread_send(void *arg)
{
	struct pkg_s *pkg = (struct pkg_s *)arg;
	lc_ctx_t *lctx = lc_ctx_new();
	pthread_cleanup_push((void (*)(void *))lc_ctx_free, lctx);
	lc_sendtree(lctx, pkg->hash, &pkg->tree, NULL, NULL, NET_LOOPBACK);
	pthread_cleanup_pop(1); /* lc_ctx_free */
	return NULL;
}
#endif

int main(void)
{
	char name[] = "lc_sendtree()/lc_recvtree()";
#if HAVE_RQ_OTI
	uint8_t *src, *dst;
	struct pkg_s pkg_send = {0}, pkg_recv = {0};
	pthread_t tid[2];
	struct timespec timeout = {0};

	test_name(name);
	test_require_net(TEST_NET_BASIC);

	/* allocate some memory to work with */
	src = malloc(TEST_SIZE);
	test_assert(src != NULL, "allocate source");
	if (!src) return TEST_FAIL;
	dst = malloc(TEST_SIZE);
	memset(dst, 0, TEST_SIZE);
	test_assert(dst != NULL, "allocate destination");
	if (!dst) return TEST_FAIL;

	test_assert(!test_random_bytes(src, TEST_SIZE), "randomize src");

	/* find hash of source */
	mtree_init(&pkg_send.tree, TEST_SIZE);
	mtree_build(&pkg_send.tree, src, NULL);

	/* sync */

	/* start send thread */
	pkg_send.data = src;
	pkg_send.len = TEST_SIZE;
	pkg_send.hash = pkg_send.tree.tree;
	pthread_create(&tid[TID_SEND], NULL, thread_send, &pkg_send);

	/* start recv thread */
	sem_init(&sem_recv, 0, 0);
	pkg_recv.data = dst;
	pkg_recv.len = TEST_SIZE;
	pkg_recv.hash = pkg_send.tree.tree;
	pthread_create(&tid[TID_RECV], NULL, thread_recv, &pkg_recv);

	/* handle timeout */
	clock_gettime(CLOCK_REALTIME, &timeout);
	timeout.tv_sec += TIMEOUT_SECONDS;
	int ret;
	if ((ret = sem_timedwait(&sem_recv, &timeout)) == -1 && errno == ETIMEDOUT) {
		for (int i = 0; i < 2; i++) pthread_cancel(tid[i]);
	}
	pthread_cancel(tid[TID_SEND]);
	test_assert(ret == 0, "timeout waiting for recv thread");
	sem_destroy(&sem_recv);

	/* stop threads */
	for (int i = 0; i < 2; i++) pthread_join(tid[i], NULL);

	/* check what we received */
	test_assert(mtree_verify(&pkg_recv.tree) == 0, "verify received tree");

	test_assert(!memcmp(pkg_recv.tree.tree, pkg_send.tree.tree, pkg_send.tree.nodes * HASHSIZE),
				"trees match");

	test_log("tree->nodes = %zu\n", pkg_recv.tree.nodes);
	test_log("tree->len = %zu\n", pkg_recv.tree.len);
	test_log("tree->chunks = %zu\n", pkg_recv.tree.chunks);

	/* clean up */
	mtree_free(&pkg_send.tree);
	mtree_free(&pkg_recv.tree);
	free(dst);
	free(src);

	return test_status;
#else
	return test_skip(name);
#endif /* HAVE_RQ_OTI */
}
