// Copyright (C) 2020 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 <NazaraSDK/World.hpp>
#include <Nazara/Core/Clock.hpp>
#include <Nazara/Core/Error.hpp>
#include <NazaraSDK/BaseComponent.hpp>
#include <NazaraSDK/Systems/LifetimeSystem.hpp>
#include <NazaraSDK/Systems/PhysicsSystem2D.hpp>
#include <NazaraSDK/Systems/PhysicsSystem3D.hpp>
#include <NazaraSDK/Systems/VelocitySystem.hpp>

#ifndef NDK_SERVER
#include <NazaraSDK/Systems/ListenerSystem.hpp>
#endif

namespace Ndk
{
	/*!
	* \ingroup NDK
	* \class Ndk::World
	* \brief NDK class that represents a world
	*/

	/*!
	* \brief Destructs the object and calls Clear
	*
	* \see Clear
	*/

	World::~World() noexcept
	{
		// The destruct must be done in an ordered way
		Clear();
	}

	/*!
	* \brief Adds default systems to the world
	*/

	void World::AddDefaultSystems()
	{
		AddSystem<LifetimeSystem>();
		AddSystem<PhysicsSystem2D>();
		AddSystem<PhysicsSystem3D>();
		AddSystem<VelocitySystem>();

		#ifndef NDK_SERVER
		AddSystem<ListenerSystem>();
		#endif
	}

	/*!
	* \brief Creates an entity in the world
	* \return The entity created
	*/

	const EntityHandle& World::CreateEntity()
	{
		EntityId id;
		EntityBlock* entBlock;

		std::size_t freeEntityId = m_freeEntityIds.FindFirst();
		if (freeEntityId != m_freeEntityIds.npos)
		{
			// We get an identifier
			m_freeEntityIds.Reset(freeEntityId); //< Remove id from free entity id

			id = static_cast<EntityId>(freeEntityId);

			entBlock = &m_entities[id];
			entBlock->handle.Reset(&entBlock->entity); //< Reset handle (as it was reset when entity got destroyed)

			m_entityBlocks[id] = entBlock;
		}
		else
		{
			// We allocate a new entity
			id = static_cast<Ndk::EntityId>(m_entityBlocks.size());

			if (m_entities.capacity() > m_entities.size())
			{
				NazaraAssert(m_waitingEntities.empty(), "There should be no waiting entities if space is available in main container");

				m_entities.emplace_back(Entity(this, id)); //< We can't make our vector create the entity due to the scope
				entBlock = &m_entities.back();
			}
			else
			{
				// Pushing to entities would reallocate vector and thus, invalidate EntityHandles (which we don't want until world update)
				// To prevent this, allocate them into a separate vector and move them at update
				// For now, we are counting on m_entities grow strategy to keep allocation frequency low
				m_waitingEntities.emplace_back(std::make_unique<EntityBlock>(Entity(this, id)));
				entBlock = m_waitingEntities.back().get();
			}

			if (id >= m_entityBlocks.size())
				m_entityBlocks.resize(id + 1);

			m_entityBlocks[id] = entBlock;
		}

		// We initialize the entity and we add it to the list of alive entities
		entBlock->entity.Create();

		m_aliveEntities.Insert(&entBlock->entity);

		return entBlock->handle;
	}

	/*!
	* \brief Clears the world from every entities
	*
	* \remark Every handles are correctly invalidated, entities are immediately invalidated
	*/

	void World::Clear() noexcept
	{
		// Destroy every valid entity first, to ensure entities are still accessible by ID while being destroyed
		for (EntityBlock* entBlock : m_entityBlocks)
		{
			if (entBlock->entity.IsValid())
				entBlock->entity.Destroy();
		}
		m_entityBlocks.clear();

		m_entities.clear();
		m_waitingEntities.clear();

		m_aliveEntities.Clear();
		m_dirtyEntities.front.Clear();
		m_freeEntityIds.Clear();
		m_killedEntities.front.Clear();
	}

	/*!
	* \brief Clones the entity
	* \return The clone newly created
	*
	* \param id Identifier of the entity
	*
	* \remark Cloning a disabled entity will produce an enabled clone
	*/
	const EntityHandle& World::CloneEntity(EntityId id)
	{
		const EntityHandle& original = GetEntity(id);
		if (!original)
		{
			NazaraError("Invalid entity ID");
			return EntityHandle::InvalidHandle;
		}

		return CloneEntity(original);
	}

	/*!
	* \brief Clones the entity
	* \return The clone newly created
	*
	* \param original Entity handle
	*
	* \remark Cloning a disabled entity will produce an enabled clone
	*/
	const EntityHandle& World::CloneEntity(const EntityHandle& original)
	{
		const EntityHandle& clone = CreateEntity();
		if (!original->IsEnabled())
			clone->Disable();

		const Nz::Bitset<>& componentBits = original->GetComponentBits();
		for (std::size_t i = componentBits.FindFirst(); i != componentBits.npos; i = componentBits.FindNext(i))
			clone->AddComponent(original->GetComponent(ComponentIndex(i)).Clone());

		clone->Enable();

		return clone;
	}

	/*!
	* \brief Refreshes the world
	*
	* This function will perform all pending operations in the following order:
	* - Reorder systems according to their update order if needed
	* - Moving newly created entities (whose which allocate never-used id) data and handles to normal entity list, this will invalidate references to world EntityHandle
	* - Destroying dead entities and allowing their ids to be used by newly created entities
	* - Update dirty entities, destroying their removed components and filtering them along systems
	*
	* \remark This is called automatically by Update and you most likely won't need to call it yourself
	* \remark Calling this outside of Update will not increase the profiler values
	*
	* \see GetProfilerData
	* \see Update
	*/
	void World::Refresh()
	{
		if (!m_orderedSystemsUpdated)
			ReorderSystems();

		// Move waiting entities to entity list
		if (!m_waitingEntities.empty())
		{
			constexpr std::size_t MinEntityCapacity = 10; //< We want to be able to grow maximum entity count by at least ten without going to the waiting list

			m_entities.reserve(m_entities.size() + m_waitingEntities.size() + MinEntityCapacity);
			for (auto& blockPtr : m_waitingEntities)
				m_entities.push_back(std::move(*blockPtr));

			m_waitingEntities.clear();

			// Update entity blocks pointers
			for (std::size_t i = 0; i < m_entities.size(); ++i)
				m_entityBlocks[i] = &m_entities[i];
		}

		// Handle killed entities before last call
		std::swap(m_killedEntities.front, m_killedEntities.back);
		for (std::size_t i = m_killedEntities.back.FindFirst(); i != m_killedEntities.back.npos; i = m_killedEntities.back.FindNext(i))
		{
			NazaraAssert(i < m_entityBlocks.size(), "Entity index out of range");

			Entity* entity = &m_entityBlocks[i]->entity;

			// Destruction of the entity (invalidation of handle by the same way)
			entity->Destroy();

			// Send back the identifier of the entity to the free queue
			m_freeEntityIds.UnboundedSet(i);
		}
		m_killedEntities.back.Clear();

		// Handle of entities which need an update from the systems
		std::swap(m_dirtyEntities.front, m_dirtyEntities.back);
		for (std::size_t i = m_dirtyEntities.back.FindFirst(); i != m_dirtyEntities.back.npos; i = m_dirtyEntities.back.FindNext(i))
		{
			NazaraAssert(i < m_entityBlocks.size(), "Entity index out of range");

			Entity* entity = &m_entityBlocks[i]->entity;

			// Check entity validity (as it could have been reported as dirty and killed during the same iteration)
			if (!entity->IsValid())
				continue;

			Nz::Bitset<>& removedComponents = entity->GetRemovedComponentBits();
			for (std::size_t j = removedComponents.FindFirst(); j != m_dirtyEntities.back.npos; j = removedComponents.FindNext(j))
				entity->DropComponent(static_cast<Ndk::ComponentIndex>(j));

			for (auto& system : m_orderedSystems)
			{
				// Is our entity already part of this system?
				bool partOfSystem = system->HasEntity(entity);

				// Should it be part of it?
				if (entity->IsEnabled() && system->Filters(entity))
				{
					// Yes it should, add it to the system if not already done and validate it (again)
					if (!partOfSystem)
						system->AddEntity(entity);

					system->ValidateEntity(entity, !partOfSystem);
				}
				else
				{
					// No it shouldn't, remove it if it's part of the system
					if (partOfSystem)
						system->RemoveEntity(entity);
				}
			}
		}
		m_dirtyEntities.back.Clear();
	}

	/*!
	* \brief Updates the world
	* \param elapsedTime Delta time used for the update
	*
	* This function Refreshes the world and calls the Update function of every active system part of it with the elapsedTime value.
	* It also increase the profiler data with the elapsed time passed in Refresh and every system update.
	*/
	void World::Update(float elapsedTime)
	{
		if (m_isProfilerEnabled)
		{
			Nz::UInt64 t1 = Nz::GetElapsedMicroseconds();
			Refresh();
			Nz::UInt64 t2 = Nz::GetElapsedMicroseconds();

			m_profilerData.refreshTime += t2 - t1;

			for (auto& systemPtr : m_orderedSystems)
			{
				systemPtr->Update(elapsedTime);

				Nz::UInt64 t3 = Nz::GetElapsedMicroseconds();
				m_profilerData.updateTime[systemPtr->GetIndex()] += t3 - t2;
				t2 = t3;
			}

			m_profilerData.updateCount++;
		}
		else
		{
			Refresh();

			for (auto& systemPtr : m_orderedSystems)
				systemPtr->Update(elapsedTime);
		}
	}

	void World::ReorderSystems()
	{
		m_orderedSystems.clear();

		for (auto& systemPtr : m_systems)
		{
			if (systemPtr)
				m_orderedSystems.push_back(systemPtr.get());
		}

		std::sort(m_orderedSystems.begin(), m_orderedSystems.end(), [] (BaseSystem* first, BaseSystem* second)
		{
			return first->GetUpdateOrder() < second->GetUpdateOrder();
		});

		m_orderedSystemsUpdated = true;
	}
}
