#include "testnet.h"
#include <librecast/net.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#define WAITS 1

static sem_t sem;
volatile ssize_t byt_recv, byt_sent;
static char channame[] = "0000-0019";
static char data[] = "black lives matter";

void *testthread(void *arg)
{
	lc_ctx_t *lctx;
	lc_socket_t *sock;
	lc_channel_t *chan;
	lc_message_t msg;
	char buf[BUFSIZ];

	lc_msg_init(&msg);
	msg.data = buf;
	msg.len = BUFSIZ;

	lctx = lc_ctx_new();
	test_assert(lctx != NULL, "lc_ctx_new()");
	sock = lc_socket_new(lctx);
	test_assert(sock != NULL, "lc_socket_new()");
	chan = lc_channel_new(lctx, channame);
	test_assert(chan != NULL, "lc_channel_new()");

	test_assert(lc_channel_bind(sock, chan) == 0, "lc_channel_bind()");
	test_assert(lc_channel_join(chan) == 0, "lc_channel_join()");

	sem_post(&sem); /* tell send thread we're ready */
	byt_recv = lc_msg_recv(sock, &msg);

	test_log("recv %zi bytes\n", byt_recv);

	test_assert(msg.op == LC_OP_PING, "opcode matches");
	test_expectn(data, msg.data, msg.len); /* got our data back */

	lc_msg_free(&msg);
	lc_ctx_free(lctx);

	sem_post(&sem); /* tell send thread we're done */

	return arg;
}

int main(void)
{
	lc_ctx_t *lctx;
	lc_socket_t *sock;
	lc_channel_t *chan;
	lc_message_t msg;
	pthread_t thread;
	struct timespec ts;
	unsigned op;
	int rc;

	test_name("lc_msg_send() / lc_msg_recv() - blocking network recv");
	test_require_net(TEST_NET_BASIC);

	/* fire up test thread */
	rc = sem_init(&sem, 0, 0);
	test_assert(rc == 0, "sem_init returned %i", rc);
	if (rc) return test_status;
	rc = pthread_create(&thread, NULL, &testthread, NULL);
	test_assert(rc == 0, "pthread_create()");
	if (rc) goto err_sem_destroy;
	rc = sem_wait(&sem); /* recv thread is ready */
	test_assert(rc == 0, "sem_wait()");
	if (rc) goto err_pthread_cancel;

	/* Librecast Context, Socket + Channel */
	lctx = lc_ctx_new();
	test_assert(lctx != NULL, "lc_ctx_new()");
	if (!lctx) goto err_ctx_free;
	sock = lc_socket_new(lctx);
	test_assert(sock != NULL, "lc_socket_new()");
	if (!sock) goto err_ctx_free;
	chan = lc_channel_new(lctx, channame);
	test_assert(chan != NULL, "lc_channel_new()");
	if (!chan) goto err_ctx_free;
	rc = lc_socket_loop(sock, 1); /* talking to ourselves, set loopback */
	test_assert(rc == 0, "lc_socket_loop");
	if (rc) goto err_ctx_free;
	rc = lc_channel_bind(sock, chan);
	test_assert(rc == 0, "lc_channel_bind");
	if (rc) goto err_ctx_free;

	/* send msg with PING opcode */
	op = LC_OP_PING;
	lc_msg_init_data(&msg, &data, strlen(data) + 1, NULL, NULL);
	lc_msg_set(&msg, LC_ATTR_OPCODE, &op);
	byt_sent = lc_msg_send(chan, &msg);
	test_assert(byt_sent > 0, "lc_msg_send() returned %zi", byt_sent);
	if (byt_sent == -1) goto err_msg_free;
	lc_msg_free(&msg); /* clear struct before recv */

	/* wait for recv thread */
	rc = clock_gettime(CLOCK_REALTIME, &ts);
	test_assert(rc == 0, "clock_gettime()");
	if (rc) goto err_msg_free;
	ts.tv_sec += WAITS;
	rc = sem_timedwait(&sem, &ts);
	test_assert(rc == 0, "timeout");

	test_assert(byt_sent == byt_recv, "bytes sent (%zi) == bytes received (%zi)", byt_sent, byt_recv);

	/* clean up */
err_msg_free:
	lc_msg_free(&msg);
err_ctx_free:
	lc_ctx_free(lctx);
err_pthread_cancel:
	pthread_cancel(thread);
	pthread_join(thread, NULL);
err_sem_destroy:
	sem_destroy(&sem);

	return test_status;
}
