// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

///
/// \file fennec/threading/mpscq.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///


// Adapted to C++ from https://github.com/dbittman/waitfree-mpsc-queue


#ifndef FENNEC_THREADING_MPSCQ_H
#define FENNEC_THREADING_MPSCQ_H

#include <fennec/memory/allocator.h>
#include <fennec/memory/pointers.h>
#include <fennec/threading/atomic.h>

namespace fennec
{

///
/// \brief Multiple-Producer Single-Consumer Wait-free Queue
/// \tparam TypeT The held type
template<typename TypeT>
struct mpscq {
// Definitions =========================================================================================================
public:

	/// \name Definitions
	/// @{

	using value_t = TypeT;          //!< The value type
	using elem_t  = atomic<TypeT*>; //!< The element type

	/// @}

private:
	using table_t = allocation<elem_t>;
	using counter_t = atomic<size_t>;


// Constructors & Destructor ===========================================================================================
public:

	/// \name Constructors & Destructor
	/// @{

	///
	/// \brief Queue Constructor
	/// \param cap The capacity of the queue
	/// \details This style of queue requires a fixed capacity. Make sure you allocate enough
	mpscq(size_t cap)
		: _data(cap)
		, _head(0), _count(0)
		, _tail(0) {
	}

	///
	/// \brief Move Constructor
	/// \param queue The queue to take ownership of.
	mpscq(mpscq&& queue)
		: _data(queue._data)
		, _head(queue._head.load())
		, _count(queue._count.load())
		, _tail(queue._tail) {
	}

	///
	/// \brief Queue Destructor
	/// \details Releases the held queue.
	~mpscq() {
		for (TypeT* it : _data) {
			unique_ptr<TypeT> ptr(it);
		}
		_data.deallocate();
	}

	///
	/// \brief Default Constructor
	/// \details Deleted, mspcq must be initialized with a capacity.
	mpscq() = delete;

	///
	/// \brief Copy Constructor
	/// \details Deleted, copying a mspcq is not allowed
	mpscq(const mpscq&) = delete;

	/// @}


// Properties ==========================================================================================================
public:

	/// \name Properties
	/// @{

	///
	/// \returns The current size of the queue.
	size_t size() const {
		return _count.load(memory_order_relaxed);
	}

	///
	/// \returns The capacity of the queue.
	size_t capacity() const {
		return _data.capacity();
	}

	///
	/// \returns \emph{true} if the queue is empty, \emph{false} otherwise.
	bool is_empty() const {
		return size() == 0;
	}

	/// @}


// Producers ===========================================================================================================

	/// \name Producers
	/// @{

	///
	/// \param x The value to push.
	/// \returns \emph{true} on success, \emph{false} on fail.
	bool push(const TypeT& x) {
		return this->_push(x);
	}

	///
	/// \brief Push an element to the queue.
	/// \param x The value to push.
	/// \returns \emph{true} on success, \emph{false} on fail.
	bool push(TypeT&& x) noexcept {
		return this->_push(fennec::forward<TypeT>(x));
	}

	///
	/// \brief Emplace an element in the queue.
	/// \tparam ArgsT The argument types.
	/// \param args The arguments to construct the element with.
	/// \returns \emph{true} on success, \emph{false} on fail.
	template<typename...ArgsT>
	bool emplace(ArgsT&&...args) {
		return this->_push(fennec::forward<ArgsT>(args)...);
	}

	/// @}


// Consumers ===========================================================================================================

	/// \name Consumers
	/// @{

	///
	/// \brief Pop an element from the queue.
	/// \returns A unique pointer to the popped value.
	/// \note This function should only be called on one thread.
	unique_ptr<TypeT> pop() {
		unique_ptr<TypeT> res(_data[_tail].exchange(nullptr, memory_order_acquire));
		if (not res) {
			return nullptr;
		}
		_tail = (_tail + 1) % _data.capacity();
		const size_t r = _count.fetch_sub(1, memory_order_release);
		assertf(r > 0, "Popped element from empty queue!");
		return fennec::move(res);
	}

	/// @}


// Private Member Variables ============================================================================================
private:
	table_t   _data;
	counter_t _head, _count;
	size_t    _tail;


// Private Helpers =====================================================================================================
private:

	template<typename...ArgsT>
	bool _push(ArgsT&&...args) {
		unique_ptr<TypeT> ptr = fennec::make_unique<TypeT>(fennec::forward<ArgsT>(args)...);

		const size_t cap = _data.capacity();
		const size_t ct = _count.fetch_add(1, memory_order_acquire);
		if (ct >= cap) {
			_count.fetch_sub(1, memory_order_release);
			return false;
		}

		size_t hd = _head.fetch_add(1, memory_order_acquire);
		assertf(_data[hd % cap] == nullptr, "Encountered occupied slot.");
		TypeT* res = _data[hd % cap].exchange(ptr.get(), memory_order_release);
		assertf(res == nullptr, "Encountered occupied slot.");
		ptr.release();
		return true;
	}
};

}

#endif // FENNEC_THREADING_MPSCQ_H