#ifndef _queue_h__
#define _queue_h__

/** Queue **
 * 
 * Simple FIFO using a dynamically allocated circular array.
 * 
 * This is NOT a priority queue. Use a Heap for that (see the `heap` module).
 * 
 * This structure distinguishes between capacity, i.e. the length of its underlying array,
 * and size, i.e. the used portion of capacity. It's always true that size < capacity.
 *
 * Because of the circular array implementation, size is never equal to capacity, and
 * it is possible that end < start.
 *
 * To use a Queue on the heap:
 * 
 *   Queue *q = queue_new();
 *   queue_grow(q, CAPACITY);  // optional
 *   ...
 *   queue_delete(q);
 * 
 * To use a Queue on the stack:
 * 
 *   Queue q;
 *   queue_init(&q);
 *   queue_grow(&q, CAPACITY);  // optional
 *   ...
 *   queue_clear(&q);
 * 
 */

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif


/**
 * Default Queue capacity (maximum size)
 */
#define QUEUE_DEFAULT_CAPACITY 8

/**
 * Simple Queue (FIFO) using a dynamically allocated circular array.
 *
 * Struct members:
 * 
 *   elements: queue-allocated array of elements (each element is a pointer)
 *   capacity: length of array
 *   start:    start index of data
 *   end:      end index of data, exclusive (possible that end < start)
 */
typedef struct Queue {

    void **elements;
    size_t capacity;
    size_t start;
    size_t end;

} Queue;

/**
 * Declare an empty Queue with given name. This Queue is already initialized.
 * Make sure to call queue_clear() when it is no longer needed.
 */ 
#define QUEUE(name) \
    Queue name = { .elements = NULL, .capacity = 0, .start = 0, .end = 0 };

/**
 * Create a new Queue on the heap. It must be deleted later with queue_delete().
 * 
 * The newly created Queue is empty, i.e. both capacity and size are zero.
 * Use queue_grow() to increase capacity.
 * 
 * Return: pointer to the newly allocated Queue
 */
Queue *queue_new();

/**
 * Delete a Queue previously created with queue_new().
 */
void queue_delete(Queue *q);

/**
 * Initialize a Queue declared on the stack. It must be cleared later with queue_clear().
 * 
 * After initialization, the Queue is empty, i.e. both capacity and size are zero.
 * Use queue_grow() to increase capacity.
 */
void queue_init(Queue *q);

/**
 * Clear a Queue, i.e. set capacity and size to zero, and free the underlying array.
 * 
 * This function is intended to be called when the Queue is no longer needed, since it
 * frees the internal array.
 * 
 * The Queue itself is not freed can be re-used immediately. It is not necessary to call
 * queue_init() after queue_clear().
 * 
 * To remove all elements from the Queue, it is more efficient to call queue_remove_all()
 * instead, as it will keep the existing array.
 * 
 * It is not necessary to call queue_clear() before queue_delete().
 */
void queue_clear(Queue *q);

/**
 * Set the capacity of a Queue to n bytes.
 * The internal array is (re-)allocated accordingly.
 * 
 * If n == 0, QUEUE_DEFAULT_CAPACITY is used instead.
 */
void queue_grow(Queue *q, size_t n);

/**
 * Return the size of a Queue.
 */
#define queue_size(q)  ( (q)->capacity ? ((q)->end + (q)->capacity - (q)->start) % (q)->capacity : 0 )

/**
 * Append an element to the end of the Queue. If the Queue is full, it grows automatically.
 * 
 * Complexity: Amortized O(1)
 */
void queue_add(Queue *q, void *element);

/**
 * Append an element to the front of the Queue. If the Queue is full, it grows automatically.
 * 
 * Complexity: Amortized O(1)
 */
void queue_add_front(Queue *q, void *element);

/**
 * Return the first element of the Queue, or NULL if the Queue is empty.
 * The Queue is not modified.
 * 
 * Complexity: O(1)
 */
#define queue_peek(q)  ( (q)->start != (q)->end ? (q)->elements[(q)->start] : NULL )

/**
 * Remove the first element from the Queue and return it, or return NULL if the Queue is empty.
 * 
 * Complexity: O(1)
 */
void *queue_remove(Queue *q);

/**
 * Remove all elements from the Queue. Capacity is retained.
 * 
 * Complexity: O(1)
 */
#define queue_remove_all(q)  { (q)->start = (q)->end = 0; }


#ifdef __cplusplus
}
#endif

#endif
