#pragma once
#include <algorithm>
#include <array>
#include <string>
#include <unordered_map>
#include <vector>

#include "Deer/Components.h"
#include "Deer/Log.h"
#include "Deer/Memory.h"
#include "entt/entt.hpp"

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

#define ENVIRONMENT_MAX_ENTITIES 2048
#define MAX_ENVIRONMENT_COUNT 64

namespace Deer {
	class Entity;

	class Environment {
		///////// 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) const;
		bool entityExists(uint16_t id) const;
		uint16_t getEntityCount() const { return m_entityCount; }

		// Creates a entity child at root
		Entity& createEntity(const std::string& name = "");
		Entity& createEntityWithId(uint16_t id, const std::string& name = "");
		void destroyEntity(uint16_t id);

		// Special behaviour
		Entity createEmptyEntity();

		// FEO
		uint16_t tryGetMainCamera();
		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
		entt::registry m_registry;

	   private:
		uint16_t m_mainCamera = 0;
		// To avoid inecessary loop we set this variable that means that smaller
		// id's than this numbers are not null, empty or used
		uint16_t m_idCreationOffset = 0;
		// Since we don't initialize Entity in the big array for performance we
		// want to keep track the ones we initialized defined by this value, it
		// the id is greater it means that any information that it has can't be
		// seen as valid
		uint16_t m_maxIdValue = 0;
		// Number of entities that exists inside
		uint16_t m_entityCount = 0;
		// This is where we save the entities, in a const list defined by
		// ENVIRONMENT_MAX_ENTITIES, we have to make sure this system is as fast
		// as possible
		Scope<std::array<Entity, ENVIRONMENT_MAX_ENTITIES>> entities;

		inline uint16_t pullEntityID() {
			m_idCreationOffset++;
			if (m_idCreationOffset > m_maxIdValue)
				m_maxIdValue = m_idCreationOffset;
			return m_idCreationOffset;
		}

		friend class Entity;
	};

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

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

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

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

			return m_environment->m_registry.get<T>(m_entityHandle);
		}

		template <typename T>
		bool hasComponent() const {
			return m_environment->m_registry.all_of<T>(m_entityHandle);
		}

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

			m_environment->m_registry.remove<T>(m_entityHandle);
		}

		Entity& duplicate();
		void destroy();

		uint16_t getId() const { return m_entityID; }
		Entity& getParent() const;
		inline uint16_t getParentId() const { return m_parentID; }

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

		Environment* getEnvironment() const { return m_environment; }

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

		void tickExecution();

		inline bool isValid() const { return m_exists; }

	   private:
		Environment* m_environment = nullptr;
		entt::entity m_entityHandle = entt::null;
		uint16_t m_entityID = 0;
		uint16_t m_parentID = 0;
		bool m_exists = false;

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

		friend class Environment;
	};

	namespace EnvironmentManager {
		uint16_t createEnvironment(const std::string& name);
		uint16_t getEnvironmentId(const std::string& name);

		Environment& getEnvironment(uint16_t);
		const std::string& getEnvironmentName(uint16_t);

		void clearAllEnvironments();
	}
}  // namespace Deer
