#include "BsSceneObject.h"
#include "BsComponent.h"
#include "BsCoreSceneManager.h"
#include "BsException.h"
#include "BsDebug.h"
#include "BsSceneObjectRTTI.h"
#include "BsMemorySerializer.h"
#include "BsGameObjectManager.h"
#include "BsPrefabUtility.h"
#include "BsMatrix3.h"

namespace BansheeEngine
{
	SceneObject::SceneObject(const String& name, UINT32 flags)
		:GameObject(), mPosition(Vector3::ZERO), mRotation(Quaternion::IDENTITY), mScale(Vector3::ONE),
		mWorldPosition(Vector3::ZERO), mWorldRotation(Quaternion::IDENTITY), mWorldScale(Vector3::ONE),
		mCachedLocalTfrm(Matrix4::IDENTITY), mDirtyFlags(0xFFFFFFFF), mCachedWorldTfrm(Matrix4::IDENTITY), 
		mActiveSelf(true), mActiveHierarchy(true), mDirtyHash(0), mFlags(flags), mPrefabHash(0)
	{
		setName(name);
	}

	SceneObject::~SceneObject()
	{
		if(!mThisHandle.isDestroyed())
		{
			LOGWRN("Object is being deleted without being destroyed first?");
			destroyInternal(mThisHandle, true);
		}
	}

	HSceneObject SceneObject::create(const String& name, UINT32 flags)
	{
		HSceneObject newObject = createInternal(name, flags);

		if (newObject->isInstantiated())
			gCoreSceneManager().registerNewSO(newObject);

		return newObject;
	}

	HSceneObject SceneObject::createInternal(const String& name, UINT32 flags)
	{
		SPtr<SceneObject> sceneObjectPtr = SPtr<SceneObject>(new (bs_alloc<SceneObject>()) SceneObject(name, flags),
			&bs_delete<SceneObject>, StdAlloc<SceneObject>());
		
		HSceneObject sceneObject = GameObjectManager::instance().registerObject(sceneObjectPtr);
		sceneObject->mThisHandle = sceneObject;

		return sceneObject;
	}

	HSceneObject SceneObject::createInternal(const SPtr<SceneObject>& soPtr, UINT64 originalId)
	{
		HSceneObject sceneObject = GameObjectManager::instance().registerObject(soPtr, originalId);
		sceneObject->mThisHandle = sceneObject;

		return sceneObject;
	}

	void SceneObject::destroy(bool immediate)
	{
		// Parent is our owner, so when his reference to us is removed, delete might be called.
		// So make sure this is the last thing we do.
		if(mParent != nullptr)
		{
			if(!mParent.isDestroyed())
				mParent->removeChild(mThisHandle);

			mParent = nullptr;
		}

		destroyInternal(mThisHandle, immediate);
	}

	void SceneObject::destroyInternal(GameObjectHandleBase& handle, bool immediate)
	{
		if (immediate)
		{
			for (auto iter = mChildren.begin(); iter != mChildren.end(); ++iter)
				(*iter)->destroyInternal(*iter, true);

			mChildren.clear();

			for (auto iter = mComponents.begin(); iter != mComponents.end(); ++iter)
			{
				HComponent component = *iter;
				component->_setIsDestroyed();

				if (isInstantiated())
				{
					if (getActive())
						component->onDisabled();

					component->onDestroyed();
				}

				component->destroyInternal(*iter, true);
			}

			mComponents.clear();

			GameObjectManager::instance().unregisterObject(handle);
		}
		else
			GameObjectManager::instance().queueForDestroy(handle);
	}

	void SceneObject::_setInstanceData(GameObjectInstanceDataPtr& other)
	{
		GameObject::_setInstanceData(other);

		// Instance data changed, so make sure to refresh the handles to reflect that
		mThisHandle._setHandleData(mThisHandle.getInternalPtr());
	}

	String SceneObject::getPrefabLink() const
	{
		const SceneObject* curObj = this;

		while (curObj != nullptr)
		{
			if (!curObj->mPrefabLinkUUID.empty())
				return curObj->mPrefabLinkUUID;

			if (curObj->mParent != nullptr)
				curObj = curObj->mParent.get();
			else
				curObj = nullptr;
		}

		return "";
	}

	void SceneObject::breakPrefabLink()
	{
		SceneObject* rootObj = this;

		while (rootObj == nullptr)
		{
			if (!rootObj->mPrefabLinkUUID.empty())
				return;

			if (rootObj->mParent != nullptr)
				rootObj = rootObj->mParent.get();
			else
				rootObj = nullptr;
		}

		if (rootObj != nullptr)
		{
			rootObj->mPrefabLinkUUID = "";
			rootObj->mPrefabDiff = nullptr;
			PrefabUtility::clearPrefabIds(rootObj->getHandle());
		}
	}

	bool SceneObject::hasFlag(UINT32 flag) const
	{
		return (mFlags & flag) != 0;
	}

	void SceneObject::setFlags(UINT32 flags)
	{
		mFlags |= flags;

		for (auto& child : mChildren)
			child->setFlags(flags);
	}

	void SceneObject::unsetFlags(UINT32 flags)
	{
		mFlags &= ~flags;

		for (auto& child : mChildren)
			child->unsetFlags(flags);
	}

	void SceneObject::_instantiate()
	{
		std::function<void(SceneObject*)> instantiateRecursive = [&](SceneObject* obj)
		{
			obj->mFlags &= ~SOF_DontInstantiate;

			if (obj->mParent == nullptr)
				gCoreSceneManager().registerNewSO(obj->mThisHandle);

			for (auto& component : obj->mComponents)
				component->instantiate();

			for (auto& child : obj->mChildren)
				instantiateRecursive(child.get());
		};

		std::function<void(SceneObject*)> triggerEventsRecursive = [&](SceneObject* obj)
		{
			for (auto& component : obj->mComponents)
			{
				component->onInitialized();

				if (obj->getActive())
					component->onEnabled();
			}

			for (auto& child : obj->mChildren)
				triggerEventsRecursive(child.get());
		};

		instantiateRecursive(this);
		triggerEventsRecursive(this);
	}

	/************************************************************************/
	/* 								Transform	                     		*/
	/************************************************************************/

	void SceneObject::setPosition(const Vector3& position)
	{
		mPosition = position;
		markTfrmDirty();
	}

	void SceneObject::setRotation(const Quaternion& rotation)
	{
		mRotation = rotation;
		markTfrmDirty();
	}

	void SceneObject::setScale(const Vector3& scale)
	{
		mScale = scale;
		markTfrmDirty();
	}

	void SceneObject::setWorldPosition(const Vector3& position)
	{
		if (mParent != nullptr)
		{
			Vector3 invScale = mParent->getWorldScale();
			if (invScale.x != 0) invScale.x = 1.0f / invScale.x;
			if (invScale.y != 0) invScale.y = 1.0f / invScale.y;
			if (invScale.z != 0) invScale.z = 1.0f / invScale.z;

			Quaternion invRotation = mParent->getWorldRotation().inverse();

			mPosition = invRotation.rotate(position - mParent->getWorldPosition()) *  invScale;
		}
		else
			mPosition = position;

		markTfrmDirty();
	}

	void SceneObject::setWorldRotation(const Quaternion& rotation)
	{
		if (mParent != nullptr)
		{
			Quaternion invRotation = mParent->getWorldRotation().inverse();

			mRotation = invRotation * rotation;
		}
		else
			mRotation = rotation;

		markTfrmDirty();
	}

	void SceneObject::setWorldScale(const Vector3& scale)
	{
		if (mParent != nullptr)
		{
			Matrix3 rotScale;
			mParent->getWorldTfrm().extract3x3Matrix(rotScale);
			rotScale.inverse();

			Matrix3 scaleMat = Matrix3(Quaternion::IDENTITY, scale);
			scaleMat = rotScale * scaleMat;

			Quaternion rotation;
			Vector3 localScale;
			scaleMat.decomposition(rotation, localScale);

			mScale = localScale;
		}
		else
			mScale = scale;

		markTfrmDirty();
	}

	const Vector3& SceneObject::getWorldPosition() const
	{ 
		if (!isCachedWorldTfrmUpToDate())
			updateWorldTfrm();

		return mWorldPosition; 
	}

	const Quaternion& SceneObject::getWorldRotation() const 
	{ 
		if (!isCachedWorldTfrmUpToDate())
			updateWorldTfrm();

		return mWorldRotation; 
	}

	const Vector3& SceneObject::getWorldScale() const 
	{ 
		if (!isCachedWorldTfrmUpToDate())
			updateWorldTfrm();

		return mWorldScale; 
	}

	void SceneObject::lookAt(const Vector3& location, const Vector3& up)
	{
		Vector3 forward = location - mPosition;
		forward.normalize();

		setForward(forward);

		Quaternion upRot = Quaternion::getRotationFromTo(getUp(), up);
		setRotation(getRotation() * upRot);
	}

	const Matrix4& SceneObject::getWorldTfrm() const
	{
		if (!isCachedWorldTfrmUpToDate())
			updateWorldTfrm();

		return mCachedWorldTfrm;
	}

	const Matrix4& SceneObject::getLocalTfrm() const
	{
		if (!isCachedLocalTfrmUpToDate())
			updateLocalTfrm();

		return mCachedLocalTfrm;
	}

	void SceneObject::move(const Vector3& vec)
	{
		setPosition(mPosition + vec);
	}

	void SceneObject::moveRelative(const Vector3& vec)
	{
		// Transform the axes of the relative vector by camera's local axes
		Vector3 trans = mRotation.rotate(vec);

		setPosition(mPosition + trans);
	}

	void SceneObject::rotate(const Vector3& axis, const Radian& angle)
	{
		Quaternion q;
		q.fromAxisAngle(axis, angle);
		rotate(q);
	}

	void SceneObject::rotate(const Quaternion& q)
	{
		// Note the order of the mult, i.e. q comes after

		// Normalize the quat to avoid cumulative problems with precision
		Quaternion qnorm = q;
		qnorm.normalize();
		setRotation(qnorm * mRotation);
	}

	void SceneObject::roll(const Radian& angle)
	{
		// Rotate around local Z axis
		Vector3 zAxis = mRotation.rotate(Vector3::UNIT_Z);
		rotate(zAxis, angle);
	}

	void SceneObject::yaw(const Radian& angle)
	{
		Vector3 yAxis = mRotation.rotate(Vector3::UNIT_Y);
		rotate(yAxis, angle);
	}

	void SceneObject::pitch(const Radian& angle)
	{
		// Rotate around local X axis
		Vector3 xAxis = mRotation.rotate(Vector3::UNIT_X);
		rotate(xAxis, angle);
	}

	void SceneObject::setForward(const Vector3& forwardDir)
	{
		if (forwardDir == Vector3::ZERO) 
			return;

		Vector3 nrmForwardDir = Vector3::normalize(forwardDir);
		Vector3 currentForwardDir = getForward();
		
		const Quaternion& currentRotation = getWorldRotation();
		Quaternion targetRotation;
		if ((nrmForwardDir+currentForwardDir).squaredLength() < 0.00005f)
		{
			// Oops, a 180 degree turn (infinite possible rotation axes)
			// Default to yaw i.e. use current UP
			targetRotation = Quaternion(-currentRotation.y, -currentRotation.z, currentRotation.w, currentRotation.x);
		}
		else
		{
			// Derive shortest arc to new direction
			Quaternion rotQuat = Quaternion::getRotationFromTo(currentForwardDir, nrmForwardDir);
			targetRotation = rotQuat * currentRotation;
		}

		setRotation(targetRotation);
	}

	void SceneObject::updateTransformsIfDirty()
	{
		if (!isCachedLocalTfrmUpToDate())
			updateLocalTfrm();

		if (!isCachedWorldTfrmUpToDate())
			updateWorldTfrm();
	}

	void SceneObject::markTfrmDirty() const
	{
		mDirtyFlags |= DirtyFlags::LocalTfrmDirty | DirtyFlags::WorldTfrmDirty;
		mDirtyHash++;

		for(auto iter = mChildren.begin(); iter != mChildren.end(); ++iter)
		{
			(*iter)->markTfrmDirty();
		}
	}

	void SceneObject::updateWorldTfrm() const
	{
		if(mParent != nullptr)
		{
			// Update orientation
			const Quaternion& parentOrientation = mParent->getWorldRotation();
			mWorldRotation = parentOrientation * mRotation;

			// Update scale
			const Vector3& parentScale = mParent->getWorldScale();
			// Scale own position by parent scale, just combine
			// as equivalent axes, no shearing
			mWorldScale = parentScale * mScale;

			// Change position vector based on parent's orientation & scale
			mWorldPosition = parentOrientation.rotate(parentScale * mPosition);

			// Add altered position vector to parents
			mWorldPosition += mParent->getWorldPosition();

			mCachedWorldTfrm.setTRS(mWorldPosition, mWorldRotation, mWorldScale);
		}
		else
		{
			mWorldRotation = mRotation;
			mWorldPosition = mPosition;
			mWorldScale = mScale;

			mCachedWorldTfrm = getLocalTfrm();
		}

		mDirtyFlags &= ~DirtyFlags::WorldTfrmDirty;
	}

	void SceneObject::updateLocalTfrm() const
	{
		mCachedLocalTfrm.setTRS(mPosition, mRotation, mScale);

		mDirtyFlags &= ~DirtyFlags::LocalTfrmDirty;
	}

	/************************************************************************/
	/* 								Hierarchy	                     		*/
	/************************************************************************/

	void SceneObject::setParent(const HSceneObject& parent, bool keepWorldTransform)
	{
		if (parent.isDestroyed())
			return;

#if BS_EDITOR_BUILD
		String originalPrefab = getPrefabLink();
#endif

		_setParent(parent, keepWorldTransform);

#if BS_EDITOR_BUILD
		String newPrefab = getPrefabLink();
		if (originalPrefab != newPrefab)
			PrefabUtility::clearPrefabIds(mThisHandle);
#endif
	}

	void SceneObject::_setParent(const HSceneObject& parent, bool keepWorldTransform)
	{
		if (mThisHandle == parent)
			return;

		if (mParent == nullptr || mParent != parent)
		{
			Vector3 worldPos;
			Quaternion worldRot;
			Vector3 worldScale;

			if (keepWorldTransform)
			{
				// Make sure the object keeps its world coordinates
				worldPos = getWorldPosition();
				worldRot = getWorldRotation();
				worldScale = getWorldScale();
			}

			if (mParent != nullptr)
				mParent->removeChild(mThisHandle);

			if (parent != nullptr)
				parent->addChild(mThisHandle);

			mParent = parent;

			if (keepWorldTransform)
			{
				setWorldPosition(worldPos);
				setWorldRotation(worldRot);
				setWorldScale(worldScale);
			}

			markTfrmDirty();
		}
	}

	HSceneObject SceneObject::getChild(UINT32 idx) const
	{
		if(idx < 0 || idx >= mChildren.size())
		{
			BS_EXCEPT(InternalErrorException, "Child index out of range.");
		}

		return mChildren[idx];
	}

	int SceneObject::indexOfChild(const HSceneObject& child) const
	{
		for(int i = 0; i < (int)mChildren.size(); i++)
		{
			if(mChildren[i] == child)
				return i;
		}

		return -1;
	}

	void SceneObject::addChild(const HSceneObject& object)
	{
		mChildren.push_back(object); 

		object->setFlags(mFlags);
	}

	void SceneObject::removeChild(const HSceneObject& object)
	{
		auto result = find(mChildren.begin(), mChildren.end(), object);

		if(result != mChildren.end())
			mChildren.erase(result);
		else
		{
			BS_EXCEPT(InternalErrorException, 
				"Trying to remove a child but it's not a child of the transform.");
		}
	}

	HSceneObject SceneObject::findChild(const String& name, bool recursive)
	{
		for (auto& child : mChildren)
		{
			if (child->getName() == name)
				return child;
		}

		if (recursive)
		{
			for (auto& child : mChildren)
			{
				HSceneObject foundObject = child->findChild(name, true);
				if (foundObject != nullptr)
					return foundObject;
			}
		}

		return HSceneObject();
	}

	Vector<HSceneObject> SceneObject::findChildren(const String& name, bool recursive)
	{
		std::function<void(const HSceneObject&, Vector<HSceneObject>&)> findChildrenInternal = 
			[&](const HSceneObject& so, Vector<HSceneObject>& output)
		{
			for (auto& child : so->mChildren)
			{
				if (child->getName() == name)
					output.push_back(child);
			}

			if (recursive)
			{
				for (auto& child : so->mChildren)
					findChildrenInternal(child, output);
			}
		};

		Vector<HSceneObject> output;
		findChildrenInternal(mThisHandle, output);

		return output;
	}

	void SceneObject::setActive(bool active)
	{
		mActiveSelf = active;
		setActiveHierarchy(active);
	}

	void SceneObject::setActiveHierarchy(bool active) 
	{ 
		bool activeHierarchy = active && mActiveSelf;

		if (mActiveHierarchy != activeHierarchy)
		{
			mActiveHierarchy = activeHierarchy;

			if (activeHierarchy)
			{
				for (auto& component : mComponents)
					component->onEnabled();
			}
			else
			{
				for (auto& component : mComponents)
					component->onDisabled();
			}
		}
		
		for (auto child : mChildren)
		{
			child->setActiveHierarchy(mActiveHierarchy);
		}
	}

	bool SceneObject::getActive(bool self)
	{
		if (self)
			return mActiveSelf;
		else
			return mActiveHierarchy;
	}

	HSceneObject SceneObject::clone(bool instantiate)
	{
		if (!instantiate)
			setFlags(SOF_DontInstantiate);

		UINT32 bufferSize = 0;

		MemorySerializer serializer;
		UINT8* buffer = serializer.encode(this, bufferSize, (void*(*)(UINT32))&bs_alloc);

		GameObjectManager::instance().setDeserializationMode(GODM_UseNewIds | GODM_RestoreExternal);
		std::shared_ptr<SceneObject> cloneObj = std::static_pointer_cast<SceneObject>(serializer.decode(buffer, bufferSize));
		bs_free(buffer);

		if (!instantiate)
			unsetFlags(SOF_DontInstantiate);

		return cloneObj->mThisHandle;
	}

	HComponent SceneObject::getComponent(UINT32 typeId) const
	{
		for(auto iter = mComponents.begin(); iter != mComponents.end(); ++iter)
		{
			if((*iter)->getRTTI()->getRTTIId() == typeId)
				return *iter;
		}

		return HComponent();
	}

	void SceneObject::destroyComponent(const HComponent component, bool immediate)
	{
		if(component == nullptr)
		{
			LOGDBG("Trying to remove a null component");
			return;
		}

		auto iter = std::find(mComponents.begin(), mComponents.end(), component);

		if(iter != mComponents.end())
		{
			(*iter)->_setIsDestroyed();

			if (isInstantiated())
			{
				if (getActive())
					component->onDisabled();

				(*iter)->onDestroyed();
			}
			
			(*iter)->destroyInternal(*iter, immediate);
			mComponents.erase(iter);
		}
		else
			LOGDBG("Trying to remove a component that doesn't exist on this SceneObject.");
	}

	void SceneObject::destroyComponent(Component* component, bool immediate)
	{
		auto iterFind = std::find_if(mComponents.begin(), mComponents.end(), 
			[component](const HComponent& x) 
		{ 
			if(x.isDestroyed())
				return false;

			return x._getHandleData()->mPtr->object.get() == component; }
		);

		if(iterFind != mComponents.end())
		{
			destroyComponent(*iterFind, immediate);
		}
	}

	void SceneObject::addComponentInternal(const std::shared_ptr<Component> component)
	{
		GameObjectHandle<Component> newComponent = GameObjectManager::instance().getObject(component->getInstanceId());
		newComponent->mParent = mThisHandle;
		mComponents.push_back(newComponent);
	}

	RTTITypeBase* SceneObject::getRTTIStatic()
	{
		return SceneObjectRTTI::instance();
	}

	RTTITypeBase* SceneObject::getRTTI() const
	{
		return SceneObject::getRTTIStatic();
	}
}