// =====================================================================================================================
//  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/containers/deque.h
/// \brief A header containing the definition for a double-ended queue
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_CONTAINERS_DEQUE_H
#define FENNEC_CONTAINERS_DEQUE_H

#include <fennec/memory/allocator.h>

namespace fennec
{

///
///
/// \brief Data structure defining a double-ended queue.
///
/// \details
/// This behaves the similar to fennec::list, however it does not allow arbitrary access, insertion, or deletion.
/// It is one of the few data structures in this library that is stable, i.e. pointers to elements do not change.
///
/// | Property    | Value       |
/// |:-----------:|:-----------:|
/// | stable      |      ✅     |
/// | dynamic     |      ✅     |
/// | homogeneous |      ✅     |
/// | distinct    |      ⛔     |
/// | ordered     |      ⛔     |
/// | linear      |      ⛔     |
/// | space       | \emph{O(N)} |
/// | access      | \emph{O(1)} |
/// | find        | \emph{O(N)} |
/// | insertion   | \emph{O(1)} |
/// | deletion    | \emph{O(1)} |
///
/// \tparam TypeT value type
template<typename TypeT, typename AllocT = allocator<TypeT>>
struct deque {

// Definitions =========================================================================================================
private:
	struct node;

public:

	/// \name Definitions
	/// @{

	using value_t = TypeT; //!< Alias for the value type
	using alloc_t = allocator_traits<AllocT>::template rebind<node>; //!< The underlying allocator type
	using elem_t  = node*;                                           //!< The underlying element type

	/// @}


	class iterator;


// Constructors ========================================================================================================

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

	///
	/// \brief Default Constructor, initializes an empty deque
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	deque()
		: _alloc()
		, _first(nullptr)
		, _last(nullptr)
		, _size(0) {
	}

	///
	/// \brief Alloc Constructor, initializes an empty deque with the specified allocator
	/// \param alloc the allocator to copy
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	deque(const alloc_t& alloc)
		: _alloc(alloc)
		, _first(nullptr)
		, _last(nullptr)
		, _size(0) {
	}

	///
	/// \brief Copy Constructor
	/// \param deque the deque to copy
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	deque(const deque& deque)
		: _alloc(deque._alloc)
		, _first(nullptr)
		, _last(nullptr)
		, _size(0) {
		const elem_t node = deque._first;
		while (node) {
			this->push_back(node->value);
			node = node->next;
		}
	}

	///
	/// \brief Deque Move Constructor
	/// \param deque the deque to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	deque(deque&& deque) noexcept
		: _alloc(deque._alloc)
		, _first(deque._first)
		, _last(deque._last)
		, _size(deque._size) {
		deque._first = nullptr;
		deque._last  = nullptr;
	}

	///
	/// \brief Destructor, calls deque::clear
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	~deque() {
		clear();
	}

	/// @}

// Properties ==========================================================================================================

	/// \name Properties
	/// @{

	///
	/// \returns \emph{true} when the deque is empty, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return _size == 0;
	}

	///
	/// \returns the number of elements in the deque
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t size() const {
		return _size;
	}

	/// @}


// Access ==============================================================================================================

	/// \name Access
	/// @{

	///
	/// \returns a reference to the first element in the deque
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	value_t& front() {
		assert(not is_empty(), "Attempted to access an empty deque.");
		return _first->value;
	}

	///
	/// \returns a const-qualified reference to the first element in the deque
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	const value_t& front() const {
		assert(not is_empty(), "Attempted to access an empty deque.");
		return _first->value;
	}

	///
	/// \returns a reference to the last element in the deque
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	value_t& back() {
		assert(not is_empty(), "Attempted to access an empty deque.");
		return _last->value;
	}

	///
	/// \returns a const-qualified reference to the last element in the deque
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	const value_t& back() const {
		assert(not is_empty(), "Attempted to access an empty deque.");
		return _last->value;
	}

	/// @}


// Modifiers ===========================================================================================================

	/// \name Modifiers
	/// @{

	///
	/// \brief Push Front Move, moves a value to the front of the deque
	/// \param elem the value to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void push_front(value_t&& elem) {
		this->_push_front(elem);
	}

	///
	/// \brief Push Front Copy, copies a value to the front of the deque
	/// \param elem the value to copy
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void push_front(const value_t& elem) {
		this->_push_front(elem);
	}

	///
	/// \brief Emplace Front, constructs a new value at the front of the deque
	/// \tparam ArgsT Argument types
	/// \param args Arguments used to construct the value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	void emplace_front(ArgsT&&...args) {
		this->_push_front(fennec::forward<ArgsT>(args)...);
	}


	///
	/// \brief Push Back Move, moves a value to the back of the deque
	/// \param elem the value to move
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void push_back(value_t&& elem) {
		this->_push_back(elem);
	}

	///
	/// \brief Push Back Copy, copies a value to the back of the deque
	/// \param elem the value to copy
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void push_back(const value_t& elem) {
		this->_push_back(elem);
	}

	///
	/// \brief Emplace Back, constructs a new value at the back of the deque
	/// \tparam ArgsT Argument types
	/// \param args Arguments used to construct the value
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	void emplace_back(ArgsT&&...args) {
		this->_push_back(fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Clears the contents of the deque
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	void clear() {
		elem_t it = _first;
		while (it) {
			elem_t next = it->next;
			fennec::destruct(it);
			_alloc.deallocate(it);
			it = next;
		}
		_first = nullptr;
		_last  = nullptr;
		_size  = 0;
	}

	///
	/// \brief Erase the First Element
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void pop_front() {
		if (_first == nullptr) {
			return;
		}
		elem_t next = _first->next;
		fennec::destruct(_first);
		_alloc.deallocate(_first);
		_first = next;
		_last = next ? _last : nullptr;
		--_size;
	}

	///
	/// \brief Erase the Last Element
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	void pop_back() {
		if (_last == nullptr) {
			return;
		}
		elem_t prev = _last->prev;
		fennec::destruct(_last);
		_alloc.deallocate(_last);
		_last = prev;
		_first = prev ? _first : nullptr;
		--_size;
	}

	/// @}


// Iteration ===========================================================================================================

	/*
	 * TODO: Decide whether you should be able to iterate over a deque
	 */


// Private Member Variables ============================================================================================
private:
	alloc_t _alloc;
	node   *_first, *_last;
	size_t  _size;


// Private Helpers =====================================================================================================
private:
	template<typename...ArgsT>
	void _push_front(ArgsT&&...args) {
		elem_t next = _first;
		_first = _alloc.allocate(1);
		fennec::construct(_first, nullptr, next, fennec::forward<ArgsT>(args)...);
		if (next) {
			next->prev = _first;
		} else {
			_last = _first;
		}
		++_size;
	}

	template<typename...ArgsT>
	void _push_back(ArgsT&&...args) {
		elem_t prev = _last;
		_last = _alloc.allocate(1);
		fennec::construct(_last, prev, nullptr, fennec::forward<ArgsT>(args)...);
		if (prev) {
			prev->next = _last;
		} else {
			_first = _last;
		}
		++_size;
	}


// Private Definitions =================================================================================================
private:
	struct node {
		value_t value;
		node *prev, *next;

		template<typename...ArgsT>
		node(node* prev, node* next, ArgsT&&...args)
			: value(fennec::forward<ArgsT>(args)...)
			, prev(prev), next(next) {
		}

		~node() = default;
	};
};


}

#endif // FENNEC_CONTAINERS_DEQUE_H