/*
 * Copyright (C) 2022 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see LICENSE file in root directory
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#ifndef QUEUE_H
#define QUEUE_H

#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <pthread.h>

#ifdef __cplusplus
extern "C" {
#endif

extern int pthread_cond_clockwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
				  clockid_t clockid,
				  const struct timespec *abstime);

struct thread_wait_queue {
	pthread_cond_t thread_wait_cv;
	pthread_mutex_t thread_wait_lock;
};

#define DECLARE_WAIT_QUEUE(name)                                               \
	struct thread_wait_queue name = {                                      \
		.thread_wait_cv = PTHREAD_COND_INITIALIZER,                    \
		.thread_wait_lock = PTHREAD_MUTEX_INITIALIZER,                 \
	}

#define thread_wait_no_event(queue)                                            \
	do {                                                                   \
		int __mret __attribute__((unused));                            \
                                                                               \
		__mret = pthread_mutex_lock(&(queue)->thread_wait_lock);       \
		assert(__mret == 0);                                           \
		__mret = pthread_cond_wait(&(queue)->thread_wait_cv,           \
					   &(queue)->thread_wait_lock);        \
		assert(__mret == 0);                                           \
		__mret = pthread_mutex_unlock(&(queue)->thread_wait_lock);     \
		assert(__mret == 0);                                           \
	} while (0)

/*
 * Timed wait on event, reltime is the relative time to wait.
 * NOTE: Writes result into caller-supplied variable `ret` (must be int).
 */
#define thread_timedwait_no_event(queue, reltime)                              \
	do {                                                                   \
		struct timespec __ts;                                          \
		int __mret __attribute__((unused));                            \
                                                                               \
		__mret = pthread_mutex_lock(&(queue)->thread_wait_lock);       \
		assert(__mret == 0);                                           \
		clock_gettime(CLOCK_MONOTONIC, &__ts);                         \
		__ts.tv_sec += (reltime)->tv_sec;                              \
		__ts.tv_nsec += (reltime)->tv_nsec;                            \
		if (__ts.tv_nsec >= 1000000000) {                              \
			__ts.tv_sec += __ts.tv_nsec / 1000000000;              \
			__ts.tv_nsec = __ts.tv_nsec % 1000000000;              \
		}                                                              \
		ret = -pthread_cond_clockwait(&(queue)->thread_wait_cv,        \
					      &(queue)->thread_wait_lock,      \
					      CLOCK_MONOTONIC, &__ts);         \
		assert(ret == 0 || ret == -ETIMEDOUT);                         \
		__mret = -pthread_mutex_unlock(&(queue)->thread_wait_lock);    \
		assert(__mret == 0);                                           \
	} while (0)

#define thread_wait_event(queue, condition)                                    \
	while (!(condition)) {                                                 \
		thread_wait_no_event(queue);                                   \
	}

/*
 * NOTE: Caller must have `int ret = 0;` in scope before using this macro.
 */
#define thread_timedwait_event(queue, condition, reltime)                      \
	while (!(condition) && !ret) {                                         \
		thread_timedwait_no_event(queue, reltime);                     \
	}

static inline bool thread_queue_sleeper(struct thread_wait_queue *queue)
{
	int ret __attribute__((unused));
	ret = pthread_mutex_trylock(&queue->thread_wait_lock);
	if (ret == 0)
		return true;
	assert(ret == EBUSY);

	ret = pthread_mutex_unlock(&(queue)->thread_wait_lock);
	assert(ret == 0);
	return false;
}

#define thread_wake(queue)                                                     \
	do {                                                                   \
		int __cret __attribute__((unused));                            \
		__cret = pthread_cond_signal(&(queue)->thread_wait_cv);        \
		assert(__cret == 0);                                           \
	} while (0);

#define thread_wake_all(queue)                                                 \
	do {                                                                   \
		int __cret __attribute__((unused));                            \
		__cret = pthread_cond_broadcast(&(queue)->thread_wait_cv);     \
		assert(__cret == 0);                                           \
	} while (0);

#ifdef __cplusplus
}
#endif

#endif /* QUEUE_H */
