#pragma once

#include "spinlock.h"
#include <thread>

namespace vlc
{
	template<typename T, size_t max_capacity = 8> class FixedRingBuffer
	{
		static_assert((max_capacity & (max_capacity - 1)) == 0, "Capacity is not power of 2!");

		Spinlock m_lock;
		volatile uint32_t m_write = 0;
		volatile uint32_t m_read = 0;

		T m_buffer[max_capacity];

		static inline size_t index(uint32_t x)
		{
			return x & (max_capacity - 1);
		}

	public:
		FixedRingBuffer() = default;

		FixedRingBuffer(const FixedRingBuffer& src) : m_write(src.m_write), m_read(src.m_read)
		{
			memcpy(m_buffer, src.m_buffer, max_capacity * sizeof(T));
		}

		FixedRingBuffer& operator = (const FixedRingBuffer& src)
		{
			*this = src;
			return *this;
		}

		FixedRingBuffer(FixedRingBuffer&& src) = delete;
		FixedRingBuffer& operator = (FixedRingBuffer&&) = delete;

		size_t size() const
		{
			return m_write - m_read;
		}

		size_t capacity() const
		{
			return max_capacity;
		}

		bool empty() const
		{
			return m_write == m_read;
		}

		bool full() const
		{
			return size() == capacity();
		}

		void clear()
		{
			m_write = m_read = 0;
		}

		bool try_push(const T& value)
		{
			uint32_t target_idx;
			{
				SpinlockGuard guard(m_lock);

				if (full())
					return false;

				target_idx = m_write;

				m_buffer[index(target_idx)] = value;
				
				std::atomic_thread_fence(std::memory_order_release);

				m_write = target_idx + 1;
			}			

			return true;
		}

		bool try_push(T&& value)
		{
			uint32_t target_idx;
			{
				SpinlockGuard guard(m_lock);

				if (full())
					return false;

				target_idx = m_write;

				m_buffer[index(target_idx)] = std::forward<T>(value);

				std::atomic_thread_fence(std::memory_order_release);

				m_write = target_idx + 1;
			}			

			return true;
		}

		bool try_pop(T& value)
		{
			uint32_t target_idx;
			{
				SpinlockGuard guard(m_lock);

				if (empty())
					return false;

				target_idx = m_read;

				value = std::move(m_buffer[index(target_idx)]);

				m_read = target_idx + 1;

				assert(m_read <= m_write);
			}			

			return true;
		}

		void push(const T& value)
		{
			while (!try_push(value))
			{
				std::this_thread::yield();
			}
		}

		void push(T& value)
		{
			while (!try_push(value))
			{
				std::this_thread::yield();
			}
		}

		T pop()
		{
			T result;

			while (!try_pop(result))
			{
				std::this_thread::yield();
			}

			return result;
		}
	};
}