/// © MiroZ 2024

#include <stdint.h>
#include <esp_log.h>
#include <System.h>

#include "app_config.h"

static uint8_t mem[CIRC_BUFFER_SIZE_BYTES];


// template<typename T>
class CircBuffer
{
protected:
	uint8_t * m_buffer = (uint8_t *)mem;
	size_t head_ = 0;
	size_t tail_ = 0;
	bool full_ = false;
	const uint32_t m_total_size = CIRC_BUFFER_SIZE_BYTES;
	Mutex m_mutex;	// internal race condition protection mutex
	CountingSemaphore m_data_available_semaphore;

public:
	CircBuffer() : m_data_available_semaphore(CIRC_BUFFER_SIZE_BYTES/2, 0)
	{
		// assert(m_buffer = (T*)calloc(size, sizeof(T)));
	}

	~CircBuffer()
	{
		// free(m_buffer);
	}

	CountingSemaphore getSemaphore(){ return m_data_available_semaphore; }

	bool put_block(uint8_t * data, uint8_t len)
	{
		m_mutex.take();
		bool ret_val = true;
		
		if(m_total_size - size(false) >= len)
		{
			put_val(len);
			for(int n = 0; n < len; n++)
				put_val(data[n]);
		}
		else
			ret_val = false;

		ESP_LOGI("CircBuffer", "Current buffer size: %d", size(false));

		m_mutex.give();
		m_data_available_semaphore.give();

		return ret_val;
	}

	bool get_block(uint8_t * data, uint8_t & len)
	{
		m_mutex.take();
		bool ret_val = true;

		if(size(false) >= 2)
		{
			get_val(len);
			for(int n = 0; n < len; n++)
				get_val(data[n]);			
		}
		else
			ret_val = false;

		m_mutex.give();

		return ret_val;
	}

	bool peek_block_len(uint8_t & len)
	{
		return peek_val(len);
	}

protected:
	bool put_val(uint8_t item)
	{
		if (full_)
			return false;

		m_buffer[head_++] = item;

		if(head_ >= m_total_size)
			head_ = 0;

		full_ = head_ == tail_;

		return true;
	}

	bool get_val(uint8_t & val)
	{
		if ((!full_ && (head_ == tail_)))
			return false;

		full_ = false;
		val = m_buffer[tail_++];

		if(tail_ >= m_total_size)
			tail_ = 0;

		return true;
	}

	bool peek_val(uint8_t & val)
	{
		if ((!full_ && (head_ == tail_)))
			return false;

		val = m_buffer[tail_];
		return true;
	}

public:

	void reset()
	{
		m_mutex.take();
		head_ = tail_ = 0;
		full_ = false;
		m_mutex.give();
	}

	bool empty()
	{
		m_mutex.take();
		bool ret = (!full_ && (head_ == tail_));
		m_mutex.give();
		return ret;
	}

	bool full()
	{
		return full_;
	}

	size_t capacity()
	{
		return m_total_size;
	}

	size_t size(bool use_mutex = true)
	{
		if(use_mutex)
			m_mutex.take();

		size_t size = m_total_size;

		if (!full_)
		{
			if (head_ >= tail_)
				size = head_ - tail_;
			else
				size = m_total_size + head_ - tail_;
		}

		if(use_mutex)
			m_mutex.give();

		return size;
	}
};