/*
{{copyright}} 
*/

/*
{{version}} 
*/

/*
{{license}} 
*/

#pragma once

#include <array>
#include <cstdint>
#include <optional>

namespace sdi_toolBox::generic
{
//--------------------------------------------------------------
template<class T, size_t BUFFER_SIZE>
class CircularBuffer final
{
  public:
  CircularBuffer()                                           = default;     // Constructor
  ~CircularBuffer()                                          = default;     // Destructor
  CircularBuffer(const CircularBuffer &other)                = delete;      // Copy constructor
  CircularBuffer(CircularBuffer &&other) noexcept            = delete;      // Move constructor
  CircularBuffer &operator=(const CircularBuffer &other)     = delete;      // Copy assignment
  CircularBuffer &operator=(CircularBuffer &&other) noexcept = delete;      // Move assignment

  bool             push(const T &item);     // Add an element to the buffer
  std::optional<T> pop();                   // Retrieve an element from the buffer
  bool             isEmpty() const;         // Check if the buffer is empty
  bool             isFull() const;          // Check if the buffer is full
  size_t           size() const;            // Get the current number of elements

  protected:
  std::array<T, BUFFER_SIZE> m_buffer;
  volatile size_t            m_head = 0;     // Index for the next write (push)
  volatile size_t            m_tail = 0;     // Index for the next read (pop)
  volatile size_t            m_size = 0;     // Current number of elements stored
};
//--------------------------------------------------------------

//--------------------------------------------------------------
/* Add an element to the buffer */
template<class T, size_t BUFFER_SIZE>
inline bool CircularBuffer<T, BUFFER_SIZE>::push(const T &item)
{
  if (isFull())
    return false;     // Buffer is full

  // Write the item at the current head position
  m_buffer[m_head] = item;

  // Move head pointer, wrapping around using modulo
  m_head = (m_head + 1) % BUFFER_SIZE;

  // Increase the current size of the data in the buffer
  m_size = m_size + 1;
  return true;
}
//--------------------------------------------------------------
/* Retrieve an element from the buffer */
template<class T, size_t BUFFER_SIZE>
inline std::optional<T> CircularBuffer<T, BUFFER_SIZE>::pop()
{
  if (isEmpty())
    return {};     // Buffer is empty, cannot pop

  // Read (copy) the item at the current tail position
  T item = m_buffer[m_tail];

  // Move tail pointer, wrapping around using modulo
  m_tail = (m_tail + 1) % BUFFER_SIZE;

  // Decrease the current size of the data in the buffer
  m_size = m_size - 1;
  return item;
}
//--------------------------------------------------------------
/* Check if the buffer is empty */
template<class T, size_t BUFFER_SIZE>
inline bool CircularBuffer<T, BUFFER_SIZE>::isEmpty() const
{
  return m_size == 0;
}
//--------------------------------------------------------------
/* Check if the buffer is full */
template<class T, size_t BUFFER_SIZE>
inline bool CircularBuffer<T, BUFFER_SIZE>::isFull() const
{
  return m_size == BUFFER_SIZE;
}
//--------------------------------------------------------------
/* Get the current number of elements */
template<class T, size_t BUFFER_SIZE>
inline size_t CircularBuffer<T, BUFFER_SIZE>::size() const
{
  return m_size;
}
//--------------------------------------------------------------
}     // namespace sdi_toolBox::generic
