#ifndef _buffer_h__
#define _buffer_h__

/** Buffer **
 * 
 * Wrapper around a heap-allocated array with cursors for reading/writing sequential data.
 * 
 * This Buffer is NOT circular.
 * 
 * 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.
 * 
 * Cursors are kept at the start and end of the data, i.e.: size == end - start
 * 
 * Putting data means appending data at the end, incrementing the end cursor.
 *
 * Getting data means incrementing the start cursor, or setting it back to 0 if there is
 * no remaining data to be read, i.e. if start == end.
 * 
 * To use a Buffer on the heap:
 * 
 *   Buffer *buf = buffer_new();
 *   buffer_grow(buf, CAPACITY);
 *   ...
 *   buffer_delete(buf);
 * 
 * To use a Buffer on the stack:
 * 
 *   Buffer buf;
 *   buffer_init(&buf);
 *   buffer_grow(&buf, CAPACITY);
 *   ...
 *   buffer_clear(&buf);
 * 
 */

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif


/**
 * Default Buffer capacity (bytes)
 */
#define BUFFER_DEFAULT_CAPACITY 64

/**
 * Wrapper around a heap-allocated array with cursors for reading/writing sequential data.
 * 
 * A Buffer created with buffer_new() must be deleted later with buffer_delete().
 * 
 * A Buffer declared on the stack must be initialized with buffer_init() and must be
 * cleared later with buffer_clear().
 * 
 * A statically declared Buffer does not need to be initialized, but it must be cleared
 * later with buffer_clear().
 * 
 * Struct members:
 * 
 *   array:    heap-allocated char array
 *   capacity: length of array
 *   start:    start index of data
 *   end:      end index of data, exclusive
 */
typedef struct Buffer {

    char *array;
    size_t capacity;
    size_t start;
    size_t end;

} Buffer;

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

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

/**
 * Delete a Buffer previously created with buffer_new().
 */
void buffer_delete(Buffer *buf);

/**
 * Initialize a Buffer declared on the stack. It must be cleared later with buffer_clear().
 * 
 * After initialization, the Buffer is empty, i.e. both capacity and size are zero.
 * Use buffer_grow() to increase capacity.
 */
void buffer_init(Buffer *buf);

/**
 * Clear a Buffer, i.e. set capacity and size to zero, and free the underlying array.
 * 
 * This function is intended to be called when the Buffer is no longer needed, since it
 * frees the internal array.
 * 
 * The Buffer itself is not freed can be re-used immediately. It is not necessary to call
 * buffer_init() after buffer_clear(), though you will need to call buffer_grow() to
 * allocate a new internal array.
 * 
 * To simply reset a Buffer to zero, it is more efficient to call buffer_reset() instead,
 * as it will keep the existing array.
 * 
 * It is not necessary to call buffer_clear() before buffer_delete().
 */
void buffer_clear(Buffer *buf);

/**
 * Return the size of a Buffer, i.e. end - start
 */
#define buffer_size(buf) ((buf)->end - (buf)->start)

/**
 * Return the number of available bytes between the end cursor and the end of the array
 * itself. This represents the maximum number of bytes that can be put into the Buffer
 * before overflow.
 */
#define buffer_available(buf) ((buf)->capacity - (buf)->end)

/**
 * Reset the Buffer to an empty state, i.e. start = end = 0, meaning size == 0.
 * The capacity is retained.
 * 
 * To set the capacity to 0 and free the internal array, use buffer_clear() instead.
 */
#define buffer_reset(buf) { (buf)->start = (buf)->end = 0; }

/**
 * Set the capacity of a Buffer to n bytes. The start and end cursors do not change.
 * The internal array is (re-)allocated accordingly.
 * 
 * If n == 0, BUFFER_DEFAULT_CAPACITY is used instead.
 */
void buffer_grow(Buffer *buf, size_t n);

/**
 * Ensure that a Buffer has at least n bytes available for putting new data, i.e. after
 * calling this function, buffer_available() >= n.
 */
void buffer_reserve(Buffer *buf, size_t n);

/**
 * Put (copy) n bytes from src to the end of this Buffer. The end cursor is incremented by
 * n bytes.
 * 
 * IMPORTANT: No capacity check is performed! Overflow IS possible! Use buffer_reserve()
 * to ensure there is enough available room at the end of the array.
 */
void buffer_put(Buffer *buf, const void *src, size_t n);

/**
 * Put (copy) a single byte to the end of this Buffer. The end cursor is incremented by 1.
 *
 * IMPORTANT: No capacity check is performed! Overflow IS possible! Use buffer_reserve()
 * to ensure there is enough available room at the end of the array.
 */
#define buffer_put_char(buf, c) (buf)->array[(buf)->end++] = (c);

/**
 * Get (copy) at most n bytes from the start of the Buffer to dst. The start cursor is
 * incremented by the number of bytes copied, preparing the next get.
 * 
 * Return: the number of bytes copied, <= n
 */
size_t buffer_get(Buffer *buf, void *dst, size_t n);

/**
 * Get (copy) at most 1 byte from the start of the Buffer to dst. The start cursor is
 * incremented by 1 if the Buffer isn't empty, preparing the next get.
 */
#define buffer_get_char(buf) ((buf)->array[(buf)->start++])

/**
 * Skip at most n bytes from the start of the Buffer. The start cursor is incremented
 * by at most n bytes, but no copy is performed. This is equivalent to buffer_get()
 * without the need for a destination.
 */
void buffer_skip(Buffer *buf, size_t n);

/**
 * Move the data section to the beginning of the array, e.g.
 * 
 *     [    ..........      ]
 *          ^         ^
 *         start     end
 * 
 * becomes:
 * 
 *     [..........          ]
 *      ^         ^
 *     start     end
 * 
 * resulting in more available room for putting new data into the Buffer.
 * 
 * This is useful after getting partial data, where unread data remains in the Buffer.
 *
 */
void buffer_pack(Buffer *buf);


#ifdef __cplusplus
}
#endif

#endif
