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

#include <librecast_pvt.h>
#include <librecast/q.h>

int q_init(q_t *q)
{
	astor(&q->ridx, 0);
	astor(&q->widx, 0);
	if (sem_init(&q->wlock, 0, UINT16_MAX)) return -1;
	return sem_init(&q->rlock, 0, 0);
}

int q_free(q_t *q)
{
	return (sem_destroy(&q->wlock) | sem_destroy(&q->rlock));
}

int q_trypush(q_t *q, void *(*f)(void *), void *restrict arg)
{
	uint16_t widx;
	if (!q) return (errno = EINVAL), -1;
	if (sem_trywait(&q->wlock) == -1) return -1;
	widx = (uint16_t)(fetch_add(&q->widx, 1) & 0xffff);
	q->job[widx].f = f;
	q->job[widx].arg = arg;
	return sem_post(&q->rlock);
}

int q_push(q_t *q, void *(*f)(void *), void *restrict arg)
{
	uint16_t widx;
	if (!q) return (errno = EINVAL), -1;
	if (sem_wait(&q->wlock) == -1) return -1;
	widx = (uint16_t)(fetch_add(&q->widx, 1) & 0xffff);
	q->job[widx].f = f;
	q->job[widx].arg = arg;
	return sem_post(&q->rlock);
}

int q_trywait(q_t *q, q_job_t *job)
{
	uint16_t ridx;
	if (sem_trywait(&q->rlock) == -1) return -1;
	ridx = (uint16_t)(fetch_add(&q->ridx, 1) & 0xffff);
	job->f = q->job[ridx].f;
	job->arg = q->job[ridx].arg;
	return sem_post(&q->wlock);
}

int q_wait(q_t *q, q_job_t *job)
{
	uint16_t ridx;
	if (sem_wait(&q->rlock) == -1) return -1;
	ridx = (uint16_t)(fetch_add(&q->ridx, 1) & 0xffff);
	job->f = q->job[ridx].f;
	job->arg = q->job[ridx].arg;
	return sem_post(&q->wlock);
}

void *q_job_seek(void *arg)
{
	q_t *q = (q_t *)arg;
	q_job_t job = {0};
	while (1) {
		pthread_testcancel();
		if (q_wait(q, &job) == -1) continue;
		job.f(job.arg);
	}
	return arg;
}

int q_search(q_t *q, void *(*f)(void *), void *restrict arg)
{
	/* No locks, so readers may read while we're searching, and writers may write.
	 * If something is read while we search, we will still return a positive
	 * match for that item, even though it is no longer in the queue.
	 * If a writer writes we will not notice that write.
	 * If this is a problem, an external lock will be required. */
	uint16_t ridx = aload(&q->ridx);
	int semval;
	if (sem_getvalue(&q->wlock, &semval) == -1) return -1;
	for (int i = 0; i < UINT16_MAX - semval; i++, ridx++) {
		if (q->job->f == f && q->job->arg == arg)
			return 1;
	}
	return 0;
}

int q_pool_create(pthread_t tid[], int nthreads, void *(*f)(void *), void *restrict arg)
{
	for (int z = 0; nthreads; z++) {
		if (pthread_create(&tid[z], NULL, f, arg)) break;
		nthreads--;
	}
	return nthreads;
}

int q_pool_destroy(pthread_t tid[], int nthreads)
{
	for (int z = 0; nthreads; z++) {
		if (pthread_cancel(tid[z]) || pthread_join(tid[z], NULL))
			break;
		nthreads--;
	}
	return nthreads;
}
