// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Development Kit"
// For conditions of distribution and use, see copyright notice in Prerequisites.hpp

#include <NDK/Entity.hpp>
#include <NDK/BaseComponent.hpp>
#include <NDK/World.hpp>

namespace Ndk
{
	/*!
	* \ingroup NDK
	* \class Ndk::Entity
	* \brief NDK class that represents an entity in a world
	*/

	// Must exists in .cpp file because of BaseComponent unique_ptr
	Entity::Entity(Entity&&) noexcept = default;

	/*!
	* \brief Constructs a Entity object linked to a world and with an id
	*
	* \param world World in which the entity interact
	* \param id Identifier of the entity
	*/
	Entity::Entity(World* world, EntityId id) :
	m_id(id),
	m_world(world)
	{
	}

	/*!
	* \brief Destructs the object and calls Destroy
	*
	* \see Destroy
	*/
	Entity::~Entity()
	{
		if (m_world && m_valid)
			Destroy();
	}

	/*!
	* \brief Adds a component to the entity
	* \return A reference to the newly added component
	*
	* \param componentPtr Component to add to the entity
	*
	* \remark Produces a NazaraAssert if component is nullptr
	*/

	BaseComponent& Entity::AddComponent(std::unique_ptr<BaseComponent>&& componentPtr)
	{
		NazaraAssert(componentPtr, "Component must be valid");

		ComponentIndex index = componentPtr->GetIndex();

		// We ensure that the vector has enough space
		if (index >= m_components.size())
			m_components.resize(index + 1);

		// Affectation and return of the component
		m_components[index] = std::move(componentPtr);
		m_componentBits.UnboundedSet(index);
		m_removedComponentBits.UnboundedReset(index);

		Invalidate();

		// We get the new component and we alert other existing components of the new one
		BaseComponent& component = *m_components[index].get();
		component.SetEntity(this);

		for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i))
		{
			if (i != index)
				m_components[i]->OnComponentAttached(component);
		}

		// If we are currently disabled, inform the component
		if (!m_enabled)
			component.OnEntityDisabled();

		return component;
	}

	/*!
	* \brief Clones the entity
	* \return The clone newly created
	*
	* \remark The close is enable by default, even if the original is disabled
	* \remark Produces a NazaraAssert if the entity is not valid
	*/
	const EntityHandle& Entity::Clone() const
	{
		NazaraAssert(IsValid(), "Invalid entity");

		return m_world->CloneEntity(m_id);
	}

	/*!
	* \brief Enables the entity
	*
	* \param enable Should the entity be enabled
	*/
	void Entity::Enable(bool enable)
	{
		if (m_enabled != enable)
		{
			m_enabled = enable;
			if (m_enabled)
			{
				for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i))
					m_components[i]->OnEntityEnabled();
			}
			else
			{
				for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i))
					m_components[i]->OnEntityDisabled();
			}

			Invalidate();
		}
	}

	/*!
	* \brief Kills the entity
	*/
	void Entity::Kill()
	{
		m_world->KillEntity(this);
	}

	/*!
	* \brief Invalidates the entity
	*/

	void Entity::Invalidate()
	{
		// We alert everyone that we have been updated
		m_world->Invalidate(m_id);
	}

	/*!
	* \brief Creates the entity
	*/

	void Entity::Create()
	{
		m_enabled = true;
		m_valid = true;
	}

	/*!
	* \brief Destroys the entity
	*/

	void Entity::Destroy()
	{
		OnEntityDestruction(this);
		OnEntityDestruction.Clear();

		// We prepare components for entity destruction (some components needs this to handle some final callbacks while the entity is still valid)
		for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i))
			m_components[i]->OnEntityDestruction();

		// Detach components while they're still attached to systems
		for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i))
			m_components[i]->SetEntity(nullptr);

		// We alert each system
		for (std::size_t index = m_systemBits.FindFirst(); index != m_systemBits.npos; index = m_systemBits.FindNext(index))
		{
			auto sysIndex = static_cast<Ndk::SystemIndex>(index);

			if (m_world->HasSystem(sysIndex))
			{
				BaseSystem& system = m_world->GetSystem(sysIndex);
				system.RemoveEntity(this);
			}
		}
		m_systemBits.Clear();

		// Destroy components
		m_components.clear();
		m_componentBits.Reset();

		// Free every handle
		UnregisterAllHandles();

		// Remove from every list
		for (EntityList* list : m_containedInLists)
			list->NotifyEntityDestruction(this);

		m_containedInLists.clear();

		m_valid = false;
	}

	/*!
	* \brief Destroys a component by index
	*
	* \param index Index of the component
	*
	* \remark If component is not available, no action is performed
	*/

	void Entity::DestroyComponent(ComponentIndex index)
	{
		if (HasComponent(index))
		{
			// We get the component and we alert existing components of the deleted one
			BaseComponent& component = *m_components[index].get();
			for (std::size_t i = m_componentBits.FindFirst(); i != m_componentBits.npos; i = m_componentBits.FindNext(i))
			{
				if (i != index)
					m_components[i]->OnComponentDetached(component);
			}

			component.SetEntity(nullptr);

			m_components[index].reset();
			m_componentBits.Reset(index);
		}
	}

}
