// =====================================================================================================================
//  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/>.
// =====================================================================================================================

#ifndef FENNEC_SCENE_COMPONENT_H
#define FENNEC_SCENE_COMPONENT_H

#include <fennec/containers/dynarray.h>
#include <fennec/containers/map.h>
#include <fennec/containers/optional.h>
#include <fennec/langproc/strings/string.h>
#include <fennec/lang/typeuuid.h>

namespace fennec
{

class component {
public:

// TYPEDEFS & CONSTANTS ================================================================================================
	using component_create  = component  (*)(size_t);
	using component_find    = component* (*)(size_t);
	using component_destroy = void       (*)(size_t);
	using component_tick    = void       (*)(size_t, double);
	using component_frame   = void       (*)(size_t, uint64_t);


// TYPE OPERATIONS =====================================================================================================

	struct type_info {
		string            name;
		component_create  create;
		component_find    find;
		component_destroy destroy;
		component_tick    tick;
		component_frame   frame;
	};

private:
	// Private Registry

	static auto& _type_list() {
		static dynarray<optional<type_info>> type_list;
		return type_list;
	}

	static void _register_type(	uint64_t         id, const cstring&      name,
								component_create create, component_find  find, component_destroy destroy,
								component_tick   tick,   component_frame frame) {
		auto& type_list = _type_list();
		if (id > type_list.size()) {
			type_list.resize(id + 1);
		}
		type_list[id] = type_info{ name, create, find, destroy, tick, frame };
	}

	static void _unregister_type(uint64_t id) {
		_type_list()[id] = nullopt;
	}

	static auto& _default_storage() {
		static map<uint64_t, map<size_t, component*>> data;
		return data;
	}

public:
	// Public Registry

	///
	/// \brief Get an uuid for the specified component type
	/// \tparam ComponentT The component type
	/// \returns An uuid that is associated with the type
	template<typename ComponentT>
	static uint64_t uuid() {
		return typeuuid<ComponentT, component>();
	}

	///
	/// \brief Default storage function for default creation, retrieval, and destruction
	template<typename ComponentT>
	static constexpr auto& default_storage() {
		auto& data = _default_storage();
		return data[uuid<ComponentT>()];
	}

	///
	/// \brief Default creation function for a component
	/// \tparam ComponentT Type of the component
	/// \param node The node to associate the component with
	/// \returns The component created by this function
	template<typename ComponentT>
	static constexpr component* default_create(size_t node) {
		auto& storage = default_storage<ComponentT>();
		if (not storage[node]) {
			storage.emplace(node);
		}
		return storage[node];
	}

	///
	/// \brief Default retrieval function for a component
	/// \tparam ComponentT Type of the component
	/// \param node The node the component is associated with
	/// \returns The component found by this function
	template<typename ComponentT>
	static constexpr component* default_find(size_t node) {
		auto& storage = default_storage<ComponentT>();
		return storage[node];
	}

	///
	/// \brief Default destruction function for a component
	/// \tparam ComponentT Type of the component
	/// \param node The node the component is associated with
	template<typename ComponentT>
	static constexpr void default_destroy(size_t node) {
		default_storage<ComponentT>().erase(node);
	}

	///
	/// \brief Register a type with the component system
	/// \tparam ComponentT  The component type
	/// \param name The name of the type
	/// \param create The function used to create a component, given a scene node. **MUST NOT BE NULL**
	/// \param find The function used to find a component, given a scene node. **MUST NOT BE NULL**
	/// \param destroy The function used to destroy a component of a scene node. **MUST NOT BE NULL**
	template<typename ComponentT>
	static void register_type(const cstring&   name, component_tick tick, component_frame frame,
	                          component_create  create  = default_create<ComponentT>,
	                          component_find    find    = default_find<ComponentT>,
	                          component_destroy destroy = default_destroy<ComponentT>) {
		component::_register_type(uuid<ComponentT>(), name, create, find, destroy, tick, frame);
	}

	template<typename ComponentT>
	static void unregister_type() {
		component::_unregister_type(uuid<ComponentT>());
	}

	///
	/// \brief Create a component of type `ComponentT`
	/// \tparam ComponentT The type to create
	/// \param node The node to associate the component with
	/// \returns The created component
	template<typename ComponentT>
	static component* create(size_t node) {
		auto& type = _type_list()[uuid<ComponentT>()];
		return type ? *type->create(node) : nullptr;
	}

	template<typename ComponentT>
	static component* find(size_t node) {
		auto& type = _type_list()[uuid<ComponentT>()];
		return type ? *type->find(node) : nullptr;
	}

	template<typename ComponentT>
	static void destroy(size_t node) {
		auto& type = _type_list()[uuid<ComponentT>()];
		return type ? *type->destroy(node) : nullptr;
	}

	static void tick(size_t node, double dT) {
		for (auto& it : _type_list()) {
			it->tick(node, dT);
		}
	}

	static void frame(size_t node, uint64_t f) {
		for (auto& it : _type_list()) {
			it->tick(node, f);
		}
	}

	static const auto& type_list() {
		return _type_list();
	}

// MEMBERS =============================================================================================================
public:
	const type_info* type;
	const size_t     node;

	template<typename ComponentT>
	component(size_t node)
		: type(type_list()[uuid<ComponentT>()])
		, node(node) {
	}
};

}

#endif // FENNEC_SCENE_COMPONENT_H
