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

#include "test.h"
#include <librecast_pvt.h>
#include <librecast/net.h>
#include <semaphore.h>

#define JOBS 42

static void *godot(void *arg)
{
	test_log("%s", __func__);
	sem_post((sem_t *)arg);
	return NULL;
}

int main(void)
{
	lc_ctx_t *lctx;
	sem_t semlock;
	struct timespec timeout;
	int nthreads = 1;
	int rc;

	test_name("lc_ctx_qpool_init() / lc_ctx_qpool_resize() / lc_ctx_qpool_free()");

	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) return test_status;

	/* create a single thread and queue and free it */
	rc = lc_ctx_qpool_init(lctx, nthreads);
	if (!test_assert(rc == 0, "lc_ctx_qpool_init()")) goto err_ctx_free;
	test_assert(lctx->nthreads == nthreads, "nthreads (%i == %i)", lctx->nthreads, nthreads);
	test_assert(lctx->q != NULL, "context queue allocated");
	rc = lc_ctx_qpool_free(lctx);
	if (!test_assert(rc == 0, "lc_ctx_qpool_free()")) goto err_ctx_free;

	/* create a single thread and queue */
	rc = lc_ctx_qpool_init(lctx, nthreads);
	if (!test_assert(rc == 0, "lc_ctx_qpool_init()")) goto err_ctx_free;

	/* call lc_ctx_qpool_resize() with unchanged nthreads (NOOP) */
	rc = lc_ctx_qpool_resize(lctx, nthreads);
	if (!test_assert(rc == 0, "lc_ctx_qpool_resize() (no resize)")) goto err_ctx_free;
	test_assert(lctx->nthreads == nthreads, "nthreads (%i == %i)", lctx->nthreads, nthreads);

	/* double the number of threads a few times */
	for (nthreads = 2; nthreads <= 16; nthreads <<= 1) {
		test_log("resizing qpool => %i\n", nthreads);
		rc = lc_ctx_qpool_resize(lctx, nthreads);
		if (!test_assert(rc == 0, "lc_ctx_qpool_resize() => %i", nthreads)) goto err_ctx_free;
		test_assert(lctx->nthreads == nthreads, "nthreads (%i == %i)", lctx->nthreads, nthreads);
		test_assert(lctx->q != NULL, "context queue allocated");
	}

	/* now decrease threads */
	nthreads = 7;
	test_log("decreasing pool to %i\n", nthreads);
	rc = lc_ctx_qpool_resize(lctx, nthreads);
	if (!test_assert(rc == 0, "lc_ctx_qpool_resize() => %i", nthreads)) goto err_ctx_free;
	test_assert(lctx->nthreads == nthreads, "nthreads (%i == %i)", lctx->nthreads, nthreads);
	test_assert(lctx->q != NULL, "context queue allocated");

	/* push some jobs onto the queue */
	sem_init(&semlock, 0, 0);
	for (int i = 0; i < JOBS; i++) q_push(lc_ctx_q(lctx), &godot, &semlock);
	clock_gettime(CLOCK_REALTIME, &timeout);
	timeout.tv_sec++;
	for (int i = 0; i < JOBS; i++) {
		test_assert(sem_timedwait(&semlock, &timeout) == 0, "timeout waiting for Godot [%i]", i);
	}
	sem_destroy(&semlock);

	/* free the qpool */
	rc = lc_ctx_qpool_free(lctx);
	if (!test_assert(rc == 0, "lc_ctx_qpool_free()")) goto err_ctx_free;

	/* ensure queue and threads are freed by call to lc_ctx_free() */
	rc = lc_ctx_qpool_init(lctx, nthreads);
	if (!test_assert(rc == 0, "lc_ctx_qpool_init()")) goto err_ctx_free;
err_ctx_free:
	lc_ctx_free(lctx);
	return test_status;
}
