#pragma once

#include <BAN/Assert.h>
#include <BAN/Move.h>
#include <BAN/PlacementNew.h>

#include <stdint.h>
#include <stddef.h>

namespace BAN
{

	template<typename T, size_t S>
	class CircularQueue
	{
	public:
		using size_type = size_t;
		using value_type = T;

	public:
		CircularQueue() = default;
		~CircularQueue();

		void push(const T&);
		void push(T&&);
		template<typename... Args>
		void emplace(Args&&... args) requires is_constructible_v<T, Args...>;

		void pop();

		const T& front() const;
		T& front();

		const T& back() const;
		T& back();

		size_type size() const { return m_size; }
		bool empty() const { return size() == 0; }
		bool full() const { return size() == capacity(); }

		static constexpr size_type capacity() { return S; }

	private:
		T* element_at(size_type);
		const T* element_at(size_type) const;

	private:
		alignas(T) uint8_t m_storage[sizeof(T) * capacity()];
		size_type m_first { 0 };
		size_type m_size { 0 };
	};

	template<typename T, size_t S>
	CircularQueue<T, S>::~CircularQueue()
	{
		for (size_type i = 0; i < m_size; i++)
			element_at((m_first + i) % capacity())->~T();
	}

	template<typename T, size_t S>
	void CircularQueue<T, S>::push(const T& value)
	{
		emplace(BAN::move(T(value)));
	}

	template<typename T, size_t S>
	void CircularQueue<T, S>::push(T&& value)
	{
		emplace(BAN::move(value));
	}

	template<typename T, size_t S>
	template<typename... Args>
	void CircularQueue<T, S>::emplace(Args&&... args) requires is_constructible_v<T, Args...>
	{
		ASSERT(!full());
		new (element_at(((m_first + m_size) % capacity()))) T(BAN::forward<Args>(args)...);
		m_size++;
	}

	template<typename T, size_t S>
	void CircularQueue<T, S>::pop()
	{
		ASSERT(!empty());
		element_at(m_first)->~T();
		m_first = (m_first + 1) % capacity();
		m_size--;
	}

	template<typename T, size_t S>
	const T& CircularQueue<T, S>::front() const
	{
		ASSERT(!empty());
		return *element_at(m_first);
	}

	template<typename T, size_t S>
	T& CircularQueue<T, S>::front()
	{
		ASSERT(!empty());
		return *element_at(m_first);
	}

	template<typename T, size_t S>
	const T& CircularQueue<T, S>::back() const
	{
		ASSERT(!empty());
		return *element_at((m_first + m_size - 1) % capacity());
	}

	template<typename T, size_t S>
	T& CircularQueue<T, S>::back()
	{
		ASSERT(!empty());
		return *element_at((m_first + m_size - 1) % capacity());
	}

	template<typename T, size_t S>
	const T* CircularQueue<T, S>::element_at(size_type index) const
	{
		ASSERT(index < capacity());
		return (const T*)(m_storage + index * sizeof(T));
	}

	template<typename T, size_t S>
	T* CircularQueue<T, S>::element_at(size_type index)
	{
		ASSERT(index < capacity());
		return (T*)(m_storage + index * sizeof(T));
	}

}
