/**
 * @file out_queue.h
 *
 * Generic output queue. Users need to define:
 * - `OUT_QUEUE_PREFIX` : the prefix (must end with an '_')
 * - `OUT_QUEUE_SIZE`   : max length of the queue
 * - `OUT_QUEUE_MAXTIME`: maximum time in advance.
 *
 * Copyright (c) 2018-2019 CERN (home.cern)
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#ifndef OUT_QUEUE_PREFIX
#error "OUT_QUEUE_PREFIX not defined"
#endif

#define OUT_QUEUE_CONCAT2(x,y) x##y
#define OUT_QUEUE_CONCAT(x,y) OUT_QUEUE_CONCAT2(x,y)
#define OUT_QUEUE_NAME(x) OUT_QUEUE_CONCAT(OUT_QUEUE_PREFIX,x)
#define OUT_QUEUE_STRUCT OUT_QUEUE_NAME(out_queue)

struct OUT_QUEUE_STRUCT {
        struct wrtd_event events[OUT_QUEUE_SIZE];
        int head, tail, count;

        /* Last timestamp value written to output config.  */
        uint32_t last_programmed_sec;
        uint32_t last_programmed_ns;
};

/**
 * Initializes an empty pulse queue
 */
static void OUT_QUEUE_NAME(out_queue_init)(struct OUT_QUEUE_STRUCT *p)
{
        p->head = 0;
        p->tail = 0;
        p->count = 0;

        p->last_programmed_sec = 0;
        p->last_programmed_ns = 0;
}


/**
 * Requests a new entry in a pulse queue. Returns pointer to the new
 * entry or NULL if the queue is full.
 */
static struct wrtd_event *OUT_QUEUE_NAME(out_queue_push)(struct OUT_QUEUE_STRUCT *p)
{
        struct wrtd_event *ev;

        if (p->count == OUT_QUEUE_SIZE)
                return NULL;

        ev = &p->events[p->head];
        p->count++;
        p->head++;

        if (p->head == OUT_QUEUE_SIZE)
                p->head = 0;

        return ev;
}


/**
 * Returns non-0 if pulse queue p contains any pulses.
 */
static inline int OUT_QUEUE_NAME(out_queue_empty)(struct OUT_QUEUE_STRUCT *p)
{
        return (p->count == 0);
}


/**
 * Returns the oldest entry in the pulse queue (or NULL if empty).
 */
static struct wrtd_event *OUT_QUEUE_NAME(out_queue_front)(struct OUT_QUEUE_STRUCT *p)
{
        if (!p->count)
                return NULL;
        return &p->events[p->tail];
}


/**
 * Releases the oldest entry from the pulse queue.
 */
static void OUT_QUEUE_NAME(out_queue_pop)(struct OUT_QUEUE_STRUCT *p)
{
        p->tail++;

        if(p->tail == OUT_QUEUE_SIZE)
                p->tail = 0;
        p->count--;
}

/**
 * Checks if the timestamp of the last programmed pulse is lost because
 * of timeout
 */
static int OUT_QUEUE_NAME(out_queue_check_timeout) (struct OUT_QUEUE_STRUCT *q,
                                                    struct wrtd_tstamp *now)
{
        int delta;

        if(q->last_programmed_sec > now->seconds + OUT_QUEUE_MAXTIME) {
                pr_error("Enqueued event very far in the future. Dropping.");
                return 0;
        }

        /* Current time exceeds FD setpoint? */

        delta = now->seconds - q->last_programmed_sec;
        if (delta != 0)
                return delta > 0;

        delta = now->ns - q->last_programmed_ns;
        return (delta > 0);
}

#undef OUT_QUEUE_STRUCT
#undef OUT_QUEUE_NAME
#undef OUT_QUEUE_CONCAT
#undef OUT_QUEUE_CONCAT2
