// =====================================================================================================================
//  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/object_pool.h
/// \brief A header containing the definition for a pool of objects associated by ids
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///

#ifndef FENNEC_CONTAINERS_OBJECT_POOL_H
#define FENNEC_CONTAINERS_OBJECT_POOL_H

#include <fennec/containers/dynarray.h>
#include <fennec/containers/list.h>
#include <fennec/containers/optional.h>

namespace fennec
{

///
/// \brief Struct which holds a pool of objects associated with ids
/// \details
/// | 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 The value type
/// \tparam AllocT The allocator type
template<typename TypeT, typename AllocT = allocator<TypeT>>
struct object_pool {

// Definitions =========================================================================================================
public:

	/// \name Definitions
	/// @{

	using value_t = TypeT;                    //!< The held object type
	using elem_t  = optional<value_t>;        //!< The element type
	using table_t = dynarray<elem_t, AllocT>; //!< The underlying table type
	using freed_t = list<size_t, AllocT>;     //!< The underlying free queue

	/// @}

	class iterator;
	class const_iterator;


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

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

	///
	/// \brief Default Constructor, initializes an empty object pool
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr object_pool()
		: _size(0) {
	}

	///
	/// \brief Default Destructor, destructs objects then releases the allocation.
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr ~object_pool() = default;

	/// @}


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

	/// \name Properties
	/// @{

	///
	/// \returns The number of active objects in the pool
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t size() const {
		return _size;
	}

	///
	/// \returns The capacity of the underlying allocation
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t capacity() const {
		return _table.capacity();
	}

	///
	/// \returns \emph{true} when there are no objects in the pool, \emph{false} otherwise
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr bool is_empty() const {
		return size() == 0;
	}

	///
	/// \brief Retrieve the next id \emph{i} that would be assigned to an object \math{o} were it added to the object pool
	///
	/// \details This can be useful if there are constant members that need to be assigned at construction.
	/// \returns The id of the next inserted node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t next_id() const {
		size_t next = _size;
		if (not _freed.is_empty()) {
			next = _freed.front();
		}
		return next;
	}

	/// @}

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

	/// \name Access
	/// @{

	///
	/// \brief Array Access Operator
	/// \param i id of the object
	/// \returns a reference to the object with id \emph{i}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr value_t& operator[](size_t i) {
		assert(i < capacity(), "Index out of Bounds!");
		assert(_table[i], "Attempted to access null object.")
		return *_table[i];
	}

	///
	/// \brief Array Const Access Operator
	/// \param i id of the object
	/// \returns a const-qualified reference to the object with id \emph{i}
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr const value_t& operator[](size_t i) const {
		assert(i < capacity(), "Index out of Bounds!");
		assert(_table[i], "Attempted to access null object.")
		return *_table[i];
	}

	constexpr bool operator()(size_t i) const {
		assert(i < capacity(), "Index out of Bounds!");
		return _table[i];
	}

	/// @}


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

	/// \name Modifiers
	/// @{

	///
	/// \brief Move Insertion, inserts \emph{x} into the pool
	/// \param x the object to move
	/// \returns An integer corresponding to the id of the node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t insert(value_t&& x) {
		return this->_insert(fennec::forward<value_t>(x));
	}

	///
	/// \brief Move Insertion, inserts a copy of \emph{x} into the pool
	/// \param x the object to copy
	/// \returns An integer corresponding to the id of the node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr size_t insert(const value_t& x) {
		return this->_insert(x);
	}


	///
	/// \brief Emplacement, constructs a new object using \emph{args...}
	/// \param args The arguments to construct the new object with
	/// \returns An integer corresponding to the id of the node
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	template<typename...ArgsT>
	constexpr size_t emplace(ArgsT&&...args) {
		return this->_insert(fennec::forward<ArgsT>(args)...);
	}

	///
	/// \brief Erase an object from the pool
	/// \param i The id of the object
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	constexpr void erase(size_t i) {
		_table[i] = nullopt;
		_freed.push_back(i);
		--_size;
	}

	///
	/// \brief Clear the object pool
	///
	/// \par Complexity
	/// \emph{O(N)}
	///
	constexpr void clear() {
		_table.clear();
		_freed.clear();
		_size = 0;
	}

	/// @}


// Iterator ============================================================================================================

	///
	/// \returns an iterator at the start of the object pool
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	iterator begin() {
		return iterator(this, 0);
	}

	///
	/// \brief C++ Iterator Specification \emph{begin()}
	/// \returns an iterator at the start of the object pool
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	const_iterator begin() const {
		return const_iterator(this, 0);
	}

	///
	/// \returns an iterator at the start of the end of the object pool
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	iterator end() {
		return iterator(this, _size);
	}

	///
	/// \brief C++ Iterator Specification \emph{end()}
	/// \returns an iterator at the start of the end of the object pool
	///
	/// \par Complexity
	/// \emph{O(1)}
	///
	const_iterator end() const {
		return const_iterator(this, _size);
	}

	///
	/// \brief C++ Iterator Specification \emph{iterator}
	class iterator {
	public:
		///
		/// \brief copy constructor
		/// \param it the iterator to copy
		iterator(const iterator& it) = default;

		///
		/// \brief move constructor
		/// \param it the iterator to move
		iterator(iterator&& it) noexcept = default;

		///
		/// \brief public destructor
		~iterator() = default;


		///
		/// \brief copy assignment
		/// \param it the iterator to copy
		/// \returns a reference to \emph{this} after having copied \emph{it}
		iterator& operator=(const iterator& it) = default;

		///
		/// \brief move assignment
		/// \param it the iterator to move
		/// \returns a reference to \emph{this} after having moved \emph{it}
		iterator& operator=(iterator&& it) noexcept = default;

		///
		/// \brief postfix increment operator
		/// \returns \emph{it} before having been incremented
		friend iterator operator++(iterator& it, int) {
			iterator ret = it;
			++it.curr;
			it._fix();
			return ret;
		}

		///
		/// \brief prefix increment operator
		/// \returns \emph{it} after having moved to the next element
		friend iterator& operator++(iterator& it) {
			++it.curr;
			it._fix();
			return it;
		}

		///
		/// \brief dereference operator
		/// \returns a reference to the value pointed by the iterator
		value_t& operator*() const {
			return *pool->_table[curr];
		}

		///
		/// \brief pointer access operator
		/// \returns a pointer to the value pointed by the iterator
		value_t* operator->() const {
			return *pool->_table[curr];
		}

		///
		/// \brief iterator equality operator
		/// \param it the iterator to compare with
		/// \returns \emph{true} if the iterators are identical, \emph{false} otherwise
		bool operator==(const iterator& it) {
			return pool == it.pool and curr == it.curr;
		}

		///
		/// \brief iterator inequality operator
		/// \param it the iterator to compare with
		/// \returns \emph{true} if the iterators are different, \emph{false} otherwise
		bool operator!=(const iterator& it) {
			return pool != it.pool or curr != it.curr;
		}

	private:
		object_pool* pool;
		size_t       curr;

		void _fix() {
			while (curr < pool->_size and not pool->_table[curr]) {
				++curr;
			}
		}

		iterator(object_pool* pool, size_t start)
			: pool(pool), curr(start) {
			_fix();
		}

		friend struct object_pool;
	};

	///
	/// \brief C++ Iterator Specification \emph{const_iterator}
	class const_iterator {
	public:
		///
		/// \brief copy constructor
		/// \param it the iterator to copy
		const_iterator(const const_iterator&it ) = default;

		///
		/// \brief move constructor
		/// \param it the iterator to move
		const_iterator(const_iterator&& it) noexcept = default;

		///
		/// \brief public destructor
		~const_iterator() = default;


		///
		/// \brief copy assignment
		/// \param it the iterator to copy
		/// \returns a reference to \emph{this} after having copied \emph{it}
		const_iterator& operator=(const const_iterator& it) = default;

		///
		/// \brief move assignment
		/// \param it the iterator to move
		/// \returns a reference to \emph{this} after having moved \emph{it}
		const_iterator& operator=(const_iterator&& it) noexcept = default;

		///
		/// \brief postfix increment operator
		/// \returns \emph{it} before having been incremented
		friend const_iterator operator++(const_iterator& it, int) {
			const_iterator ret = it;
			++it.curr;
			it._fix();
			return ret;
		}

		///
		/// \brief prefix increment operator
		/// \returns \emph{it} after having moved to the next element
		friend const_iterator& operator++(const_iterator& it) {
			++it.curr;
			it._fix();
			return it;
		}

		///
		/// \brief dereference operator
		/// \returns a reference to the value pointed by the iterator
		const value_t& operator*() const {
			return *pool->_table[curr];
		}

		///
		/// \brief pointer access operator
		/// \returns a pointer to the value pointed by the iterator
		const value_t* operator->() const {
			return *pool->_table[curr];
		}

		///
		/// \brief iterator equality operator
		/// \param it the iterator to compare with
		/// \returns \emph{true} if the iterators are identical, \emph{false} otherwise
		bool operator==(const const_iterator& it) {
			return pool == it.pool and curr == it.curr;
		}

		///
		/// \brief iterator inequality operator
		/// \param it the iterator to compare with
		/// \returns \emph{true} if the iterators are different, \emph{false} otherwise
		bool operator!=(const const_iterator& it) {
			return pool != it.pool or curr != it.curr;
		}

	private:
		const object_pool* pool;
		size_t             curr;

		void _fix() {
			while (curr < pool->_size and not pool->_table[curr]) {
				++curr;
			}
		}

		const_iterator(const object_pool* pool, size_t start)
			: pool(pool), curr(start) {
			_fix();
		}

		friend struct object_pool;
	};

private:
	table_t _table;
	freed_t _freed;
	size_t  _size;

	size_t _next_free() {
		size_t next = _size;
		if (not _freed.is_empty()) {
			next = _freed.front();
			_freed.pop_front();
		}
		++_size;
		return next;
	}

	template<typename...ArgsT>
	size_t _insert(ArgsT&&...args) {
		size_t i = _next_free();
		if (i >= _table.size()) {
			_table.resize(fennec::max(_table.size() * 2, size_t(8)));
		}
		_table[i].emplace(fennec::forward<ArgsT>(args)...);
		return i;
	}
};

}

#endif // FENNEC_CONTAINERS_OBJECT_POOL_H