/*
 * ringbuffer.h
 *
 *  Created on: May 31, 2025
 *      Author: Attila Body
 */

#pragma once

#include <cstddef>
#include <cstdint>

namespace f4ll {

class iringbuffer
{
public:
    using size_type = size_t;

    /// @brief Copies data to the ring buffer (without committing it)
    /// @param data Pointer to the data to copy
    /// @param len Length of the data to copy
    /// @retval Length of the data copied (differs when not all data can fit)
    virtual size_type put(uint8_t const *data, size_type len) = 0;

    /// @brief Commits the data already placed into the buffer and notifies the
    /// consumer about it's availability
    virtual void commit() = 0;

    /// @brief Waits until all the data from the ring buffer gets consumed.
    // void flush();

    /// @brief Gets a pointer to the next chunk of committed data in the buffer
    /// without registering the consumption.
    ///        The caller should also call report_consumption using the returned
    ///        chunk length after it finished processing the data.
    /// @param[out] data Receives a pointer to the first byte of the available
    /// data in the buffer
    /// @param[out] len Receives the length of the chunk available in the buffer.
    /// Will not exceed len_requested.
    /// @retval true if the buffer has more available data, false otherwise.
    virtual bool get_chunk(uint8_t const *&data, size_type &len) const = 0;

    /// @brief Marks the chunk returned by ringbuffer_GetChunk as available.
    /// @param consumed The length of the chunk as returned by
    /// ringbuffer_GetChunk(..., len)
    virtual void consumed(size_type len) = 0;

    /// @brief Gets a pointer to the next free chunk in the buffer
    /// @retval Pointer to the beginning of the next free buffer chmemory area
    /// @param[out] len Receives the length of the returned buffer area.
    virtual uint8_t *get_free_chunk(size_type &len) = 0;

    /// @brief Registers the data written in the free buffer chunk.
    ///        IMPORTANT: Do not call put() after start modifying the buffer
    ///        returned by get_free_chunk() before registering the written data by
    ///        calling supplied()
    /// @param len The length of the data written in the buffer
    virtual bool produced(size_type len) = 0;

    /// @brief Returns the number of uncommited bytes in the ring buffer.
    virtual size_type uncommited() const = 0;

    /// @brief Returns the number of commited bytes in the ring buffer.
    virtual size_type commited() const = 0;

    /// @brief Discards the uncommited data in the ring buffer.
    virtual void discard() = 0;

    /// @brief Returns the size of the internal buffer.
    ///        One byte in the buffer is always reserved as separator
    virtual size_type size() const = 0;

    /// @brief Returns the size of the used place in the buffer
    //        (including non-commited data)
    virtual size_type used() const = 0;

    /// @brief Returns the free storage capacity of the buffer
    virtual size_type unused() const = 0;
};

//
class ringbuffer_ext : public iringbuffer
{
public:
    ringbuffer_ext(uint8_t *bptr, size_type bsize);

    size_type put(uint8_t const *data, size_type len) override;
    void commit() override;

    bool get_chunk(uint8_t const *&data, size_type &len) const override;
    void consumed(size_type len) override;

    uint8_t *get_free_chunk(size_type &len) override;
    bool produced(size_type len) override;

    size_type uncommited() const override;
    size_type commited() const override;
    void discard() override;
    size_type size() const override;
    size_type used() const override;
    size_type unused() const override;

private:
    uint8_t *m_buffer;           //!< Data bufer
    size_type const m_bsize;     //!< Data buffer size
    size_type m_head = 0;        //!< Write position
    size_type m_head_shadow = 0; //!< Shadowed write position for collecting data
                                 //!< before committing it
    size_type m_tail = 0;        //!< Read position

    size_type marker_diff(size_type m1, size_type m2) const;
    size_type max_chunk_len() const;
};

//
template <iringbuffer::size_type SZ> class ringbuffer : public ringbuffer_ext
{
public:
    ringbuffer();

private:
    uint8_t m_buffer[SZ];
};

//
template <iringbuffer::size_type SZ>
ringbuffer<SZ>::ringbuffer()
    : ringbuffer_ext(m_buffer, SZ)
{
}

} // namespace f1ll
