//
//  RingBuffer.hpp
//  SwitchboardSDK
//
//  Created by Balázs Kiss on 2022. 02. 21..
//  Copyright © 2022. Synervoz Inc. All rights reserved.
//

#pragma once

#include <atomic>
#include <audio/Nonblocking.hpp>

namespace switchboard::audio {

/**
 * RingBuffer class.
 * @brief Class providing ring buffer functionality. Supports single-threaded and multi-threaded (single producer, single consumer) use cases.
 */
template <typename T>
class RingBuffer {
public:
    /**
     * @brief Creates a RingBuffer instance. Allocates the underlying buffer, not suitable for use in the real-time thread.
     *
     * @param numberOfItems The number of items that the ring buffer can hold.
     */
    explicit RingBuffer(unsigned int numberOfItems);

    /**
     * @brief Creates a RingBuffer instance without allocating. Suitable for resizing a ring buffer in the real-time thread.
     * @details Does not delete the buffer on destruction. Make sure to handle deletion.
     *
     * @param numberOfItems The number of items that the ring buffer can hold.
     * @param buffer The underlying buffer the ring buffer is operating on.
     */
    RingBuffer(unsigned int numberOfItems, T* buffer);

    /**
     * @brief RingBuffer destructor.
     */
    ~RingBuffer();

    /**
     * @brief Clears all data in the ring buffer.
     */
    void clear() noexcept SWITCHBOARD_AUDIO_NONBLOCKING;

    /**
     * @brief Returns the maximum number of items that can be added to the ring buffer.
     *
     * @returns The number of items that can be produced.
     */
    [[nodiscard]] unsigned int getAvailableItemsToProduce() const noexcept SWITCHBOARD_AUDIO_NONBLOCKING;

    /**
     * @brief Adds items to the ring buffer.
     *
     * @param sourceBuffer The buffer where the items are copied from.
     * @param numberOfItems The number of items that should be added to the ring buffer.
     */
    void produce(const T* sourceBuffer, unsigned int numberOfItems) noexcept SWITCHBOARD_AUDIO_NONBLOCKING;

    /**
     * @brief Returns the maximum number of items that can be read from the ring buffer.
     *
     * @returns The number items that can be consumed.
     */
    [[nodiscard]] unsigned int getAvailableItemsToConsume() const noexcept SWITCHBOARD_AUDIO_NONBLOCKING;

    /**
     * @brief Reads items from the ring buffer and updates the read pointer.
     *
     * @param destBuffer [inout] The buffer where the read items will be copied to.
     * @param numberOfItems The number of items that should be consumed.
     */
    void consume(T* destBuffer, unsigned int numberOfItems) noexcept SWITCHBOARD_AUDIO_NONBLOCKING;

    /**
     * @brief Reads items from the ring buffer without updating the read pointer.
     *
     * @param destBuffer [inout] The buffer where the read items will be copied to.
     * @param numberOfItems The number of items that should be consumed.
     */
    void read(T* destBuffer, unsigned int numberOfItems) noexcept SWITCHBOARD_AUDIO_NONBLOCKING;

    /**
     * @brief Discards items from the ring buffer. Steps the read pointer without reading the items.
     *
     * @param numberOfItems The number of items that should be discarded.
     */
    void discard(unsigned int numberOfItems) noexcept SWITCHBOARD_AUDIO_NONBLOCKING;

private:
    unsigned int maxSize;
    unsigned int size;
    T* buffer;
    bool ownsBuffer;
    std::atomic<unsigned int> readPointerIndex;
    std::atomic<unsigned int> writePointerIndex;

    T* getWritePointer() noexcept SWITCHBOARD_AUDIO_NONBLOCKING;
    T* getReadPointer() noexcept SWITCHBOARD_AUDIO_NONBLOCKING;
};

}
