// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025  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 object_pool.h
/// \brief A header containing the definition for a pool of objects associated by ids
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 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
/// \tparam TypeT The value type
/// \tparam AllocT The allocator type
template<typename TypeT, typename AllocT = allocator<TypeT>>
struct object_pool {

// Definitions =========================================================================================================
public:
	using value_t = TypeT;
	using elem_t  = optional<TypeT>;
	using table_t = dynarray<elem_t, AllocT>;


// Constructors & Destructor ===========================================================================================

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

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

	///
	/// \brief Default Destructor, destructs objects then releases the allocation.
	constexpr ~object_pool() = default;

	/// @}


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

	/// \name Properties
	/// @{

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

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

	///
	/// \returns `true` when there are no objects in the pool, `false` otherwise
	constexpr bool empty() const {
		return size() == 0;
	}

	///
	/// \brief Retrieve the next id `i` that would be assigned to an object `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
	constexpr size_t next_id() const {
		size_t next = _size;
		if (not _freed.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 `i`
	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 `i`
	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];
	}

	/// @}


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

	/// \name Modifiers
	/// @{

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

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


	///
	/// \brief Move Insertion, inserts a copy of `x` into the pool
	/// \param x the object to copy
	/// \returns An integer corresponding to the id of the node
	template<typename...ArgsT>
	constexpr size_t emplace(ArgsT&&...args) {
		return this->_insert(fennec::forward<ArgsT>(args)...);
	}

	constexpr void erase(size_t i) {
		_table[i] = nullopt;
		_freed.push_back(i);
		--_size;
	}

private:
	dynarray<elem_t, AllocT> _table;
	list<size_t> _freed;
	size_t      _size;

	size_t _next_free() {
		size_t next = _size;
		if (not _freed.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.emplace_back();
		}
		_table[i].emplace(fennec::forward<ArgsT>(args)...);
		return i;
	}
};

}

#endif // FENNEC_CONTAINERS_OBJECT_POOL_H