#pragma once
#include "Deer/Components.h"
#include "Deer/Log.h"
#include "Deer/Tools/Memory.h"
#include "Deer/Resource.h"

#include "entt/entt.hpp"

#ifdef DEER_RENDER
#include "DeerRender/Render/FrameBuffer.h"
#include "DeerRender/Scene.h"
#endif

#include <algorithm>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>

namespace Deer {
	class Entity;

	class Environment {
		// Note: Outdated note
		///////// NOTES ///////////
		// - The entity id means the position in a array defined in Environment
		// - The entity id is relative to a Environment so it can be a complete
		// diferent entity in other environments
		// - The entity number 0 is allways the root
		// - There is a limit defined by ENVIRONMENT_MAX_ENTITIES of how many
		// entities can be in an Environment
		///////// NOTES ///////////
	  public:
		Environment();
		~Environment();
		// This class can not be copyed
		Environment(const Environment&) = delete;
		Environment& operator=(Environment&) = delete;

		// Clears all entities
		void clear();
		// Obtains the entity
		Entity& getEntity(uint16_t id);
		bool entityExists(uint16_t id) const;
		uint16_t getEntityCount() const;

		// Creates a entity child at root
		// WARNING: This method can change internal pointers and invalidate entitiy references
		Entity& createEntity(const std::string& name = "");
		// Can be slow! This has to empty the stack of empty entities in case its necessary so use it in ascendent order!
		// WARNING: This method can change internal pointers and invalidate entitiy references
		Entity& createEntityWithId(uint16_t id, const std::string& name = "");
		void destroyEntity(uint16_t id);

		// Special behaviour
		// WARNING: This method can change internal pointers and invalidate entitiy references
		Entity createEmptyEntity();

		// FEO
		uint16_t tryGetMainCamera() const;
		void setMainCamera(Entity& entity);

		// Obtains the entity that is on the root of the environment
		inline Entity& getRoot() { return getEntity(0); }
#ifdef DEER_RENDER
		void render(SceneCamera& camera);
#endif
		Scope<entt::registry> m_registry;

	  private:
		uint16_t m_mainCamera = 0;

		std::stack<u_int16_t> unused_entities_spaces;
		std::vector<Entity> entities;

		friend class Entity;
	};

	struct EntityData {
		int toDo;
	};

	struct EnvironmentData {
		std::vector<EntityData> entities;
	};

	template <>
	class ResourceBuilder<Environment> {
	  public:
		using BaseDataType = EnvironmentData;
		static Scope<Environment> buildResource(const BaseDataType& baseData);
	};

	// Warning: This calss does not initialize for performance
	class Entity {
	  public:
		Entity() {}
		Entity(const Entity&) = delete;

		Entity(Entity&&) noexcept;
		Entity& operator=(Entity&&) noexcept;

		Entity& duplicate();
		void destroy();

		uint16_t getId() const { return entId; }
		Entity& getParent() const;
		inline uint16_t getParentId() const { return parentId; }

		// TODO, enable transfer entitys from difrent environments
		void setParent(Entity& parent);
		bool isDescendantOf(Entity& parent) const;

		Environment* getEnvironment() const { return environment; }

		bool isRoot() const { return entId == 0; }
		glm::mat4 getWorldMatrix() const;
		glm::mat4 getRelativeMatrix() const;

		void tickExecution();
		void clear();

		bool isValid() const;

	  private:
		Environment* environment = nullptr;
		entt::entity entHandle = entt::null;
		uint16_t entId = 0;
		uint16_t parentId = 0;

		Entity(entt::entity handle, Environment* scene, uint16_t entityID);

		friend class Environment;

	  public:
		template <typename T, typename... Args>
		T& addComponent(Args&&... args) const {
			DEER_CORE_ASSERT(
			    !environment->m_registry->all_of<T>(entHandle),
			    "Entity already have component {0}", typeid(T).name());

			return environment->m_registry->emplace<T>(
			    entHandle, std::forward<Args>(args)...);
		}

		template <typename T>
		T& getComponent() const {
			DEER_CORE_ASSERT(
			    environment->m_registry->all_of<T>(entHandle),
			    "Entity has no component {0}", typeid(T).name());

			return environment->m_registry->get<T>(entHandle);
		}

		template <typename T>
		bool hasComponent() const {
			return environment->m_registry->all_of<T>(entHandle);
		}

		template <typename T>
		void removeComponent() const {
			DEER_CORE_ASSERT(
			    environment->m_registry->all_of<T>(entHandle),
			    "Entity does not have component {0}", typeid(T).name());

			environment->m_registry->remove<T>(entHandle);
		}
	};
} // namespace Deer
