#include "DeerStudio/StudioAPI/Entity.h"
#include "DeerStudio/AngelScriptEngine.h"

#include "DeerRender/Components.h"
#include "DeerRender/Enviroment.h"
#include "DeerRender/Scene.h"

#include "DeerRender/Resource.h"

#include <vector>

#define GET_ENV(env) ((env < 0)? Scene::environment : Deer::Resource<Environment>::unsafeFromId(environmentId).getData())

#define GET_ENTITY(env, id) GET_ENV(env).getEntity(id)
#define GET_MESH_COMPONENT(env, id) \
	GET_ENV(env).getEntity(id).getComponent<MeshComponent>()
#define GET_SHADER_COMPONENT(env, id) \
	GET_ENV(env).getEntity(id).getComponent<ShaderComponent>()
#define GET_CAMERA_COMPONENT(env, id) \
	GET_ENV(env).getEntity(id).getComponent<CameraComponent>()

#define ASSERT_ENTITY(func, ret) \
	if (!assertEntity(func))     \
		ret;
#define ASSERT_MESH_COMPONENT(func, ret) \
	if (!assertMeshComponent(func))      \
		ret;
#define ASSERT_SHADER_COMPONENT(func, ret) \
	if (!assertShaderComponent(func))      \
		ret;
#define ASSERT_CAMERA_COMPONENT(func, ret) \
	if (!assertCameraComponent(func))      \
		ret;

namespace Deer {
	namespace StudioAPI {
		extern std::vector<Scope<Environment>> environments;

		EntityHandleStruct getRoot() { return EntityStruct(0); }

		void constructEntityStruct(int id, void* memory) {
			new (memory) EntityStruct(id);
		}

		int EntityStruct::getId() { return entityId; }

		EntityStruct::EntityStruct(uint16_t _entId, int32_t _envId) : EntityHandleStruct(_entId, _envId) {}

		Environment* EntityHandleStruct::getEntityEnvironment() {
			if (environmentId < 0) 
				return &Scene::environment;
			return &Resource<Environment>::unsafeFromId(environmentId).getData();
		}

		bool EntityHandleStruct::assertEntity(const char* funcName) {
			Environment* env = getEntityEnvironment();
			if (!env->entityExists(entityId)) {
				DEER_EDITOR_ENGINE_ERROR(
				    "Error, invalid entity calling {0}, entityId : {1}, environmentId : {2}",
				    funcName, entityId, environmentId);
				AngelScriptEngine::raiseError();
				return false;
			}
			return true;
		}

		std::string EntityStruct::getName() {
			ASSERT_ENTITY("getName()", return "NULL");

			return GET_ENTITY(environmentId, entityId).getComponent<TagComponent>().tag;
		}

		void EntityStruct::setName(std::string& name) {
			ASSERT_ENTITY("setName()", return);

			GET_ENTITY(environmentId, entityId)
			    .getComponent<TagComponent>()
			    .tag = name;
		}

		void EntityStruct::destroy() {
			ASSERT_ENTITY("destroy()", return);

			GET_ENTITY(environmentId, entityId).destroy();
		}

		bool EntityStruct::isRoot() {
			ASSERT_ENTITY("isRoot()", return false);

			return entityId == 0;
		}

		bool EntityStruct::exists() {
			ASSERT_ENTITY("exists()", return false);

			return Scene::environment.entityExists(entityId);
		}

		void EntityStruct::setParent(EntityHandleStruct parent_struct) {
			ASSERT_ENTITY("setParent()", return);

			Entity& parent =
			    GET_ENTITY(parent_struct.environmentId, parent_struct.entityId);

			GET_ENTITY(environmentId, entityId).setParent(parent);
		}

		EntityHandleStruct EntityStruct::getParent() {
			ASSERT_ENTITY("getParent()", return *this);

			Entity& self = GET_ENTITY(environmentId, entityId);
			if (self.isRoot())
				return *this;

			return EntityStruct(self.getParentId());
		}

		bool EntityStruct::isDescendantOf(EntityHandleStruct parent_struct) {
			ASSERT_ENTITY("isDescendantOf()", return false);

			Entity& parent =
			    GET_ENTITY(parent_struct.environmentId, parent_struct.entityId);

			return GET_ENTITY(environmentId, entityId).isDescendantOf(parent);
		}

		bool EntityStruct::opEquals(const EntityHandleStruct& other) {
			ASSERT_ENTITY("opEquals()", return false);

			return entityId == other.entityId;
		}

		EntityHandleStruct EntityStruct::getSelf() {
			ASSERT_ENTITY("getSelf()", return *this);

			return *this;
		}

		glm::vec3 TransformComponentStruct::getPosition() {
			ASSERT_ENTITY("getPosition()", return glm::vec3());

			return GET_ENTITY(environmentId, entityId)
			    .getComponent<TransformComponent>()
			    .position;
		}

		glm::vec3 TransformComponentStruct::getScale() {
			ASSERT_ENTITY("getScale()", return glm::vec3());

			return GET_ENTITY(environmentId, entityId)
			    .getComponent<TransformComponent>()
			    .scale;
		}

		glm::vec3 TransformComponentStruct::getRotation() {
			ASSERT_ENTITY("getRotation()", return glm::vec3());

			return GET_ENTITY(environmentId, entityId)
			    .getComponent<TransformComponent>()
			    .getEulerAngles();
		}

		void TransformComponentStruct::setPosition(glm::vec3 value) {
			ASSERT_ENTITY("setPosition()", return);

			GET_ENTITY(environmentId, entityId)
			    .getComponent<TransformComponent>()
			    .position = value;
		}

		void TransformComponentStruct::setScale(glm::vec3 value) {
			ASSERT_ENTITY("setScale()", return);

			GET_ENTITY(environmentId, entityId)
			    .getComponent<TransformComponent>()
			    .scale = value;
		}

		void TransformComponentStruct::setRotation(glm::vec3 value) {
			ASSERT_ENTITY("setRotation()", return);

			GET_ENTITY(environmentId, entityId)
			    .getComponent<TransformComponent>()
			    .setEulerAngles(value);
		}

		int EntityChildArrayStruct::getChildCount() {
			ASSERT_ENTITY("getChildCount()", return 0);

			return GET_ENTITY(environmentId, entityId)
			    .getComponent<RelationshipComponent>()
			    .getChildCount();
		}

		bool MeshComponentStruct::assertMeshComponent(const char* funcName) {
			if (!assertEntity(funcName))
				return false;

			Environment* env;
			if (environmentId < 0)
				env = &Scene::environment;
			else
				env = &Resource<Environment>::unsafeFromId(environmentId).getData();

			Entity& ent = env->getEntity(entityId);

			if (!ent.hasComponent<MeshComponent>()) {
				DEER_EDITOR_ENGINE_ERROR(
				    "Error, calling MeshComponent.{0} entity {1} with id {2} "
				    "has no MeshComponent",
				    funcName, ent.getComponent<TagComponent>().tag.c_str(),
				    entityId);

				AngelScriptEngine::raiseError();
				return false;
			}

			return true;
		}

		EntityHandleStruct EntityChildArrayStruct::getChild(int i) {
			ASSERT_ENTITY("getChild()", return *this);

			RelationshipComponent& rc =
			    GET_ENTITY(environmentId, entityId)
			        .getComponent<RelationshipComponent>();

			if (i < 0 || i >= rc.getChildCount()) {
				DEER_EDITOR_ENGINE_ERROR(
				    "Error while executing EntityChild.getChild(..), id {0} is "
				    "invalid for child count of {1}",
				    i, rc.getChildCount());
				AngelScriptEngine::raiseError();

				return EntityStruct(0);
			}

			return EntityStruct(rc.getChildId(i));
		}

		EntityHandleStruct EntityStruct::createChild(std::string& name) {
			ASSERT_ENTITY("createChild()", return *this);

			DEER_CORE_INFO("Creating child on env {}", environmentId);
			Environment* entityEnv = getEntityEnvironment();
			
			Entity& newEnt = entityEnv->createEntity(name);
			Entity& me = GET_ENTITY(environmentId, entityId);

			newEnt.setParent(me);

			return EntityStruct(newEnt.getId(), environmentId);
		}

		EntityHandleStruct EntityStruct::getMeshComponent() {
			ASSERT_ENTITY("getMeshComponent()", return *this);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (!self.hasComponent<MeshComponent>()) {
				DEER_CORE_ERROR(
				    "Error, entity {0} with id {1} does not have MeshComponent",
				    GET_ENTITY(environmentId, entityId)
				        .getComponent<TagComponent>()
				        .tag.c_str(),
				    entityId);

				AngelScriptEngine::raiseError();
			}

			return *this;
		}

		EntityHandleStruct EntityStruct::createMeshComponent() {
			ASSERT_ENTITY("createMeshComponent()", return *this);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (!self.hasComponent<MeshComponent>()) {
				self.addComponent<MeshComponent>();
			}

			return *this;
		}

		bool EntityStruct::hasMeshComponent() {
			ASSERT_ENTITY("hasMeshComponent()", return false);

			Entity& self = GET_ENTITY(environmentId, entityId);

			return self.hasComponent<MeshComponent>();
		}

		void EntityStruct::removeMeshComponent() {
			ASSERT_ENTITY("removeMeshComponent()", return);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (self.hasComponent<MeshComponent>()) {
				self.removeComponent<MeshComponent>();
			}
		}

		EntityHandleStruct EntityStruct::getCameraComponent() {
			ASSERT_ENTITY("getCameraComponent()", return *this);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (!self.hasComponent<CameraComponent>()) {
				DEER_CORE_ERROR("Error, entity {0} with id {1} does not have "
				                "Camera Component",
				                GET_ENTITY(environmentId, entityId)
				                    .getComponent<TagComponent>()
				                    .tag.c_str(),
				                entityId);

				AngelScriptEngine::raiseError();
			}

			return *this;
		}

		EntityHandleStruct EntityStruct::createCameraComponent() {
			ASSERT_ENTITY("createCameraComponent()", return *this);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (!self.hasComponent<CameraComponent>()) {
				self.addComponent<CameraComponent>();
			}

			return *this;
		}

		bool EntityStruct::hasCameraComponent() {
			ASSERT_ENTITY("hasCameraComponent()", return false);

			Entity& self = GET_ENTITY(environmentId, entityId);

			return self.hasComponent<CameraComponent>();
		}

		void EntityStruct::removeCameraComponent() {
			ASSERT_ENTITY("removeMeshComponent()", return);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (self.hasComponent<CameraComponent>()) {
				self.removeComponent<CameraComponent>();
			}
		}

		EntityHandleStruct EntityStruct::getShaderComponent() {
			ASSERT_ENTITY("getShaderComponent()", return *this);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (!self.hasComponent<ShaderComponent>()) {
				DEER_CORE_ERROR("Error, entity {0} with id {1} does not have "
				                "Shader Component",
				                GET_ENTITY(environmentId, entityId)
				                    .getComponent<TagComponent>()
				                    .tag.c_str(),
				                entityId);

				AngelScriptEngine::raiseError();
			}

			return *this;
		}

		EntityHandleStruct EntityStruct::createShaderComponent() {
			ASSERT_ENTITY("createShaderComponent()", return *this);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (!self.hasComponent<ShaderComponent>()) {
				self.addComponent<ShaderComponent>();
			}

			return *this;
		}

		bool EntityStruct::hasShaderComponent() {
			ASSERT_ENTITY("hasShaderComponent()", return false);

			Entity& self = GET_ENTITY(environmentId, entityId);
			return self.hasComponent<ShaderComponent>();
		}

		void EntityStruct::removeShaderComponent() {
			ASSERT_ENTITY("removeShaderComponent()", return);

			Entity& self = GET_ENTITY(environmentId, entityId);

			if (self.hasComponent<ShaderComponent>()) {
				self.removeComponent<ShaderComponent>();
			}
		}

		bool MeshComponentStruct::isActive() {
			ASSERT_MESH_COMPONENT("isActive()", return false);
			return GET_MESH_COMPONENT(environmentId, entityId).active;
		}

		void MeshComponentStruct::setActive(bool value) {
			ASSERT_MESH_COMPONENT("setActive(bool)", return);
			GET_MESH_COMPONENT(environmentId, entityId).active = value;
		}

		Resource<GPUMesh> MeshComponentStruct::getMesh() {
			ASSERT_MESH_COMPONENT("getMesh()", return Resource<GPUMesh>());
			return GET_MESH_COMPONENT(environmentId, entityId).mesh;
		}

		bool MeshComponentStruct::hasMesh() {
			ASSERT_MESH_COMPONENT("hasMesh()", return false);
			return (bool)GET_MESH_COMPONENT(environmentId, entityId).mesh;
		}

		void MeshComponentStruct::setMesh(Resource<GPUMesh> mesh) {
			ASSERT_MESH_COMPONENT("setMesh()", return);
			GET_MESH_COMPONENT(environmentId, entityId).mesh = mesh;
		}

		void MeshComponentStruct::clear() {
			ASSERT_MESH_COMPONENT("clear()", return);

			GET_MESH_COMPONENT(environmentId, entityId).mesh = Resource<GPUMesh>();
		}

		bool ShaderComponentStruct::hasShader() {
			ASSERT_ENTITY("hasShader()", return false);

			return (bool)GET_SHADER_COMPONENT(environmentId, entityId).shader;
		}

		bool ShaderComponentStruct::assertShaderComponent(const char* funcName) {
			if (!assertEntity(funcName))
				return false;
			Environment* env = getEntityEnvironment();

			Entity& ent = env->getEntity(entityId);

			if (!ent.hasComponent<ShaderComponent>()) {
				DEER_EDITOR_ENGINE_ERROR(
				    "Error, calling ShaderComponent.{0} entity {1} with id {2} "
				    "has no ShaderComponent",
				    funcName, ent.getComponent<TagComponent>().tag.c_str(),
				    entityId);

				AngelScriptEngine::raiseError();
				return false;
			}

			return true;
		}

		void ShaderComponentStruct::clear() {
			ASSERT_SHADER_COMPONENT("clear()", return);

			GET_SHADER_COMPONENT(environmentId, entityId).shader = Resource<Shader>();
		}

		Resource<Shader> ShaderComponentStruct::getShader() {
			ASSERT_SHADER_COMPONENT("getShader()", return Resource<Shader>());

			return GET_SHADER_COMPONENT(environmentId, entityId).shader;
		}

		void ShaderComponentStruct::setShader(Resource<Shader> shader) {
			ASSERT_SHADER_COMPONENT("setShader()", return);

			GET_SHADER_COMPONENT(environmentId, entityId).shader = shader;
		}

		Resource<Texture> ShaderComponentStruct::getTexture() {
			ASSERT_SHADER_COMPONENT("getTexture()", return Resource<Texture>());

			return GET_SHADER_COMPONENT(environmentId, entityId).texture;
		}

		void ShaderComponentStruct::setTexture(Resource<Texture> texture) {
			ASSERT_SHADER_COMPONENT("setTexture()", return);

			GET_SHADER_COMPONENT(environmentId, entityId).texture = texture;
		}

		bool CameraComponentStruct::assertCameraComponent(const char* funcName) {
			if (!assertEntity(funcName))
				return false;
			Environment* env = getEntityEnvironment();

			Entity& ent = env->getEntity(entityId);

			if (!ent.hasComponent<CameraComponent>()) {
				DEER_EDITOR_ENGINE_ERROR(
				    "Error, calling CameraComponent.{0} entity {1} with id {2} "
				    "has no CameraComponent",
				    funcName, ent.getComponent<TagComponent>().tag.c_str(),
				    entityId);

				AngelScriptEngine::raiseError();
				return false;
			}

			return true;
		}

		float CameraComponentStruct::getFov() {
			ASSERT_CAMERA_COMPONENT("getFov()", return 0);

			return GET_CAMERA_COMPONENT(environmentId, entityId).fov / 3.141f *
			       180.0f;
		}

		float CameraComponentStruct::getAspectRation() {
			ASSERT_CAMERA_COMPONENT("getAspectRation()", return 0);

			return GET_CAMERA_COMPONENT(environmentId, entityId).aspect;
		}

		float CameraComponentStruct::getNearZ() {
			ASSERT_CAMERA_COMPONENT("getNearZ()", return 0);

			return GET_CAMERA_COMPONENT(environmentId, entityId).nearZ;
		}

		float CameraComponentStruct::getFarZ() {
			ASSERT_CAMERA_COMPONENT("getFarZ()", return 0);

			return GET_CAMERA_COMPONENT(environmentId, entityId).farZ;
		}

		void CameraComponentStruct::setFov(float v) {
			ASSERT_CAMERA_COMPONENT("getFarZ()", return);

			GET_CAMERA_COMPONENT(environmentId, entityId).fov =
			    v * 3.141f / 180.0f;
		}
		void CameraComponentStruct::setAspectRation(float v) {
			ASSERT_CAMERA_COMPONENT("setAspectRation()", return);

			GET_CAMERA_COMPONENT(environmentId, entityId).aspect = v;
		}
		void CameraComponentStruct::setNearZ(float v) {
			ASSERT_CAMERA_COMPONENT("setNearZ()", return);

			GET_CAMERA_COMPONENT(environmentId, entityId).nearZ = v;
		}
		void CameraComponentStruct::setFarZ(float v) {
			ASSERT_CAMERA_COMPONENT("setFarZ()", return);

			GET_CAMERA_COMPONENT(environmentId, entityId).farZ = v;
		}

	} // namespace StudioAPI
} // namespace Deer