/// @file ring_buffer.h
/// @brief
/// @copyright (c) 2014-2021 This file is distributed under the MIT License.
/// See LICENSE.md for details.

#pragma once

#define DECLARE_FIXED_SIZE_RING_BUFFER(type, name, length, init)                     \
    typedef struct fs_rb_##name##_t {                                                \
        unsigned size, read, write;                                                  \
        type buffer[length];                                                         \
    } fs_rb_##name##_t;                                                              \
    static inline void fs_rb_##name##_init(fs_rb_##name##_t *rb)                     \
    {                                                                                \
        rb->size = length;                                                           \
        rb->read = rb->write = 0;                                                    \
        char *dst            = (char *)rb->buffer;                                   \
        long num             = sizeof(type) * length;                                \
        while (num--) *dst++ = (char)(init & 0xFF);                                  \
    }                                                                                \
    static inline unsigned fs_rb_##name##_step(fs_rb_##name##_t *rb, unsigned index) \
    {                                                                                \
        return (index == (rb->size - 1)) ? 0 : index + 1;                            \
    }                                                                                \
    static inline void fs_rb_##name##_push_front(fs_rb_##name##_t *rb, type item)    \
    {                                                                                \
        if (fs_rb_##name##_step(rb, rb->write) == rb->read)                          \
            rb->read = fs_rb_##name##_step(rb, rb->read);                            \
        rb->buffer[rb->write] = item;                                                \
        rb->write             = fs_rb_##name##_step(rb, rb->write);                  \
    }                                                                                \
    static inline type fs_rb_##name##_empty(fs_rb_##name##_t *rb)                    \
    {                                                                                \
        return rb->write == rb->read;                                                \
    }                                                                                \
    static inline type fs_rb_##name##_pop_back(fs_rb_##name##_t *rb)                 \
    {                                                                                \
        type item = init;                                                            \
        if (!fs_rb_##name##_empty(rb)) {                                             \
            item     = rb->buffer[rb->read];                                         \
            rb->read = fs_rb_##name##_step(rb, rb->read);                            \
        }                                                                            \
        return item;                                                                 \
    }                                                                                \
    static inline type fs_rb_##name##_get(fs_rb_##name##_t *rb, unsigned index)      \
    {                                                                                \
        if (index < rb->size)                                                        \
            return rb->buffer[index];                                                \
        return init;                                                                 \
    }                                                                                \
    static inline type fs_rb_##name##_back(fs_rb_##name##_t *rb)                     \
    {                                                                                \
        if (!fs_rb_##name##_empty(rb))                                               \
            return rb->buffer[rb->read];                                             \
        return init;                                                                 \
    }                                                                                \
    static inline type fs_rb_##name##_front(fs_rb_##name##_t *rb)                    \
    {                                                                                \
        if (!fs_rb_##name##_empty(rb)) {                                             \
            if (rb->write > 0)                                                       \
                return rb->buffer[rb->write - 1];                                    \
            return rb->buffer[rb->size - 1];                                         \
        }                                                                            \
        return init;                                                                 \
    }

#ifdef __KERNEL__

#define DECLARE_RING_BUFFER(type, name, init)                                             \
    typedef struct rb_##name##_t {                                                        \
        const unsigned size;                                                              \
        unsigned read, write;                                                             \
        type *buffer;                                                                     \
    } rb_##name##_t;                                                                      \
    static inline rb_##name##_t alloc_rb_##name(unsigned len)                             \
    {                                                                                     \
        rb_##name##_t rb = { len, 0U, 0U, len > 0 ? kmalloc(sizeof(type) * len) : NULL }; \
        memset(rb.buffer, init, sizeof(type) * len);                                      \
        return rb;                                                                        \
    }                                                                                     \
    static inline void free_rb_##name(rb_##name##_t *rb)                                  \
    {                                                                                     \
        kfree(rb->buffer);                                                                \
    }                                                                                     \
    static inline unsigned step_rb_##name(rb_##name##_t *rb, unsigned index)              \
    {                                                                                     \
        return (index == (rb->size - 1)) ? 0 : index + 1;                                 \
    }                                                                                     \
    static inline void push_rb_##name(rb_##name##_t *rb, type item)                       \
    {                                                                                     \
        if (step_rb_##name(rb, rb->write) == rb->read)                                    \
            rb->read = step_rb_##name(rb, rb->read);                                      \
        rb->buffer[rb->write] = item;                                                     \
        rb->write             = step_rb_##name(rb, rb->write);                            \
    }                                                                                     \
    static inline void pop_rb_##name(rb_##name##_t *rb, type *item)                       \
    {                                                                                     \
        *item = init;                                                                     \
        if (rb->write != rb->read) {                                                      \
            *item    = rb->buffer[rb->read];                                              \
            rb->read = step_rb_##name(rb, rb->read);                                      \
        }                                                                                 \
    }                                                                                     \
    static inline void get_rb_##name(rb_##name##_t *rb, unsigned index, type *item)       \
    {                                                                                     \
        if (index < rb->size)                                                             \
            *item = rb->buffer[index];                                                    \
    }

#else

#define DECLARE_RING_BUFFER(type, name)                                                  \
    typedef struct rb_##name##_t {                                                       \
        const unsigned size;                                                             \
        unsigned read, write;                                                            \
        type *buffer;                                                                    \
    } rb_##name##_t;                                                                     \
    static inline rb_##name##_t alloc_rb_##name(unsigned len)                            \
    {                                                                                    \
        rb_##name##_t rb = { len, 0U, 0U, len > 0 ? malloc(sizeof(type) * len) : NULL }; \
        memset(rb.buffer, 0, sizeof(type) * len);                                        \
        return rb;                                                                       \
    }                                                                                    \
    static inline void free_rb_##name(rb_##name##_t *rb)                                 \
    {                                                                                    \
        free(rb->buffer);                                                                \
    }                                                                                    \
    static inline unsigned step_rb_##name(rb_##name##_t *rb, unsigned index)             \
    {                                                                                    \
        return (index == (rb->size - 1)) ? 0 : index + 1;                                \
    }                                                                                    \
    static inline void push_rb_##name(rb_##name##_t *rb, type item)                      \
    {                                                                                    \
        if (step_rb_##name(rb, rb->write) == rb->read)                                   \
            rb->read = step_rb_##name(rb, rb->read);                                     \
        rb->buffer[rb->write] = item;                                                    \
        rb->write             = step_rb_##name(rb, rb->write);                           \
    }                                                                                    \
    static inline void pop_rb_##name(rb_##name##_t *rb, type *item)                      \
    {                                                                                    \
        if (rb->write != rb->read) {                                                     \
            *item    = rb->buffer[rb->read];                                             \
            rb->read = step_rb_##name(rb, rb->read);                                     \
        }                                                                                \
    }                                                                                    \
    static inline void get_rb_##name(rb_##name##_t *rb, unsigned index, type *item)      \
    {                                                                                    \
        if (index < rb->size)                                                            \
            *item = rb->buffer[index];                                                   \
    }
#endif