#include "test/sleep.h"

/*
 * Simple templated message queue implementation that relies on only mutexes for
 * synchronization (which reduces portability issues).  Given the following
 * setup:
 *
 *   typedef struct mq_msg_s mq_msg_t;
 *   struct mq_msg_s {
 *           mq_msg(mq_msg_t) link;
 *           [message data]
 *   };
 *   mq_gen(, mq_, mq_t, mq_msg_t, link)
 *
 * The API is as follows:
 *
 *   bool mq_init(mq_t *mq);
 *   void mq_fini(mq_t *mq);
 *   unsigned mq_count(mq_t *mq);
 *   mq_msg_t *mq_tryget(mq_t *mq);
 *   mq_msg_t *mq_get(mq_t *mq);
 *   void mq_put(mq_t *mq, mq_msg_t *msg);
 *
 * The message queue linkage embedded in each message is to be treated as
 * externally opaque (no need to initialize or clean up externally).  mq_fini()
 * does not perform any cleanup of messages, since it knows nothing of their
 * payloads.
 */
#define mq_msg(a_mq_msg_type) ql_elm(a_mq_msg_type)

#define mq_gen(a_attr, a_prefix, a_mq_type, a_mq_msg_type, a_field)            \
	typedef struct {                                                       \
		mtx_t lock;                                                    \
		ql_head(a_mq_msg_type) msgs;                                   \
		unsigned count;                                                \
	} a_mq_type;                                                           \
	a_attr bool a_prefix##init(a_mq_type *mq) {                            \
		if (mtx_init(&mq->lock)) {                                     \
			return true;                                           \
		}                                                              \
		ql_new(&mq->msgs);                                             \
		mq->count = 0;                                                 \
		return false;                                                  \
	}                                                                      \
	a_attr void a_prefix##fini(a_mq_type *mq) {                            \
		mtx_fini(&mq->lock);                                           \
	}                                                                      \
	a_attr unsigned a_prefix##count(a_mq_type *mq) {                       \
		unsigned count;                                                \
                                                                               \
		mtx_lock(&mq->lock);                                           \
		count = mq->count;                                             \
		mtx_unlock(&mq->lock);                                         \
		return count;                                                  \
	}                                                                      \
	a_attr a_mq_msg_type *a_prefix##tryget(a_mq_type *mq) {                \
		a_mq_msg_type *msg;                                            \
                                                                               \
		mtx_lock(&mq->lock);                                           \
		msg = ql_first(&mq->msgs);                                     \
		if (msg != NULL) {                                             \
			ql_head_remove(&mq->msgs, a_mq_msg_type, a_field);     \
			mq->count--;                                           \
		}                                                              \
		mtx_unlock(&mq->lock);                                         \
		return msg;                                                    \
	}                                                                      \
	a_attr a_mq_msg_type *a_prefix##get(a_mq_type *mq) {                   \
		a_mq_msg_type *msg;                                            \
		unsigned       ns;                                             \
                                                                               \
		msg = a_prefix##tryget(mq);                                    \
		if (msg != NULL) {                                             \
			return msg;                                            \
		}                                                              \
                                                                               \
		ns = 1;                                                        \
		while (true) {                                                 \
			sleep_ns(ns);                                          \
			msg = a_prefix##tryget(mq);                            \
			if (msg != NULL) {                                     \
				return msg;                                    \
			}                                                      \
			if (ns < 1000 * 1000 * 1000) {                         \
				/* Double sleep time, up to max 1 second. */   \
				ns <<= 1;                                      \
				if (ns > 1000 * 1000 * 1000) {                 \
					ns = 1000 * 1000 * 1000;               \
				}                                              \
			}                                                      \
		}                                                              \
	}                                                                      \
	a_attr void a_prefix##put(a_mq_type *mq, a_mq_msg_type *msg) {         \
		mtx_lock(&mq->lock);                                           \
		ql_elm_new(msg, a_field);                                      \
		ql_tail_insert(&mq->msgs, msg, a_field);                       \
		mq->count++;                                                   \
		mtx_unlock(&mq->lock);                                         \
	}
