/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2022 Claudio Calvelli <clc@librecast.net> */

#include "testnet.h"
#include <librecast/types.h>
#include <librecast/net.h>
#include <librecast_pvt.h>
#include <pthread.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
#ifdef HAVE_ENDIAN_H
#include <endian.h>
#elif HAVE_SYS_ENDIAN_H
#include <sys/endian.h>
#endif

#define WAITS 1
#define NUM_MESSAGES 12

static sem_t sem;
static char channel_name[] = "0000-0043";
static unsigned int msg_seen = 0;
static const unsigned int msg_mask = (1U << NUM_MESSAGES) - 1;

static void *recv_thread(void *arg)
{
	unsigned int fail_it = (1U << 3) | (1U << 6);
	lc_ctx_t *lctx;
	lc_socket_t *sock_data, *sock_nack;
	lc_channel_t *chan_data, *chan_nack;

	(void)arg;

	/* create a channel to receive the messages */
	lctx = lc_ctx_new();

	sock_data = lc_socket_new(lctx);
	chan_data = lc_channel_new(lctx, channel_name);
	chan_nack = lc_channel_sidehash(chan_data, (void *)SIDE_CHANNEL_NACK, strlen(SIDE_CHANNEL_NACK));
	sock_nack = lc_socket_new(lctx);
	lc_socket_loop(sock_nack, 1);
	lc_channel_bind(sock_data, chan_data);
	lc_channel_bind(sock_nack, chan_nack);
	lc_channel_join(chan_data);

	/* tell main() we have started */
	msg_seen = 0;
	sem_post(&sem);
	while ((msg_seen & msg_mask) != msg_mask) {
		int num;
		lc_message_t message = {
		    .data = (void *)&num,
		    .len  = sizeof(num),
		};
		lc_msg_recv(sock_data, &message);
		/* is it the message we expected? */
		if (message.seq >= 1 && message.seq <= NUM_MESSAGES && message.timestamp == (uint64_t)message.seq << 10) {
			unsigned int bit = 1U << (message.seq - 1);
			if (fail_it & bit) {
				/* pretend that we didn't receive this and send a NACK instead */
				uint64_t miss = htobe64(message.seq);
				fail_it &= ~bit;
				lc_socket_send(sock_nack, &miss, sizeof(miss), 0);
			}
			else msg_seen |= bit;
		}
		lc_msg_free(&message);
	}
	/* tell main() that all is done here */
	sem_post(&sem);
	lc_channel_free(chan_nack);
	lc_socket_close(sock_nack);
	lc_channel_free(chan_data);
	lc_socket_close(sock_data);
	lc_ctx_free(lctx);
	return NULL;
}

int main(void)
{
	lc_ctx_t * lctx;
	lc_socket_t * sock;
	lc_channel_t * chan;
	lc_message_t message = {0};
	pthread_t rthread;
	pthread_attr_t attr;
	struct timespec ts;

	test_name("NACK 1: retransmission of packet received but discarded");
	test_require_net(TEST_NET_BASIC);

	/* create a channel and enable the logging with the standard NACK thread */
	lctx = lc_ctx_new();
	sock = lc_socket_new(lctx);
	chan = lc_channel_new(lctx, channel_name);
	lc_socket_loop(sock, 1);
	lc_channel_bind(sock, chan);
	sem_init(&sem, 0, 0);
	test_assert(lc_channel_nack_handler(chan, 10) >= 0, "Could not set NACK hread");

	/* get ready... */
	pthread_attr_init(&attr);
	pthread_create(&rthread, &attr, recv_thread, NULL);
	pthread_attr_destroy(&attr);

	/* make sure the recv thread has started */
	sem_wait(&sem);
	/* now go and send a number of messages */
	for (int msg_sent = 1; msg_sent <= NUM_MESSAGES; msg_sent++) {
		message.timestamp = (uint64_t)msg_sent << 10;
		message.seq = msg_sent;
		message.len = sizeof(msg_sent);
		message.data = &msg_sent;
		message.op = LC_OP_DATA;
		lc_msg_send(chan, &message);
	}

	/* wait for the receive thread to have received everything */
	clock_gettime(CLOCK_REALTIME, &ts);
	ts.tv_sec += WAITS;
	sem_timedwait(&sem, &ts);
	pthread_cancel(rthread);
	pthread_join(rthread, NULL);

	/* check we've received all messages */
	for (int msg_num = 0; msg_num < NUM_MESSAGES; msg_num++) {
		char description[64];
		snprintf(description, sizeof(description), "Message #%d not received from NACK thread", msg_num);
		test_assert(msg_seen & (1U << msg_num), description);
	}

	lc_channel_free(chan);
	lc_socket_close(sock);
	lc_ctx_free(lctx);
	sem_destroy(&sem);
	return test_status;
}
