/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2009 Torus Knot Software Ltd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------------------------------------------------------------------------
*/
#include "OgreStableHeaders.h"
#include "OgreInstancedEntity.h"
#include "OgreInstanceBatch.h"
#include "OgreSkeletonInstance.h"
#include "OgreAnimationState.h"
#include "OgreOptimisedUtil.h"
#include "OgreSceneNode.h"
#include "OgreStringConverter.h"
#include "OgreCamera.h"
#include "OgreException.h"

namespace Ogre
{
	NameGenerator InstancedEntity::msNameGenerator("");

	InstancedEntity::InstancedEntity( InstanceBatch *batchOwner, uint32 instanceID, InstancedEntity* sharedTransformEntity ) :
				MovableObject(),
				mInstanceId( instanceID ),
                mInUse( false ),
				mBatchOwner( batchOwner ),
				mAnimationState( 0 ),
				mSkeletonInstance( 0 ),
				mBoneMatrices(0),
				mBoneWorldMatrices(0),
				mLastParentXform( Matrix4::ZERO ),
				mFrameAnimationLastUpdated(std::numeric_limits<unsigned long>::max()),
				mSharedTransformEntity( 0 ),
				mTransformLookupNumber(instanceID)
	
	{
		//Use a static name generator to ensure this name stays unique (which may not happen
		//otherwise due to reparenting when defragmenting)
		mName = batchOwner->getName() + "/InstancedEntity_" + StringConverter::toString(mInstanceId) + "/"+
				msNameGenerator.generate();

		if (sharedTransformEntity)
		{
			sharedTransformEntity->shareTransformWith(this);
		}
		else
		{
			createSkeletonInstance();
		}
	}

	InstancedEntity::~InstancedEntity()
	{
		unlinkTransform();
		destroySkeletonInstance();
	}

	void InstancedEntity::shareTransformWith( InstancedEntity *slave )
	{
		if( !this->mBatchOwner->_getMeshRef()->hasSkeleton() ||
			this->mBatchOwner->_getMeshRef()->getSkeleton().isNull() ||
			!this->mBatchOwner->_supportsSkeletalAnimation() )
		{
			return;
		}

		if( this->mSharedTransformEntity  )
		{
			OGRE_EXCEPT( Exception::ERR_INVALID_STATE, "Attempted to share '" + mName + "' transforms "
											"with slave '" + slave->mName + "' but '" + mName +"' is "
											"already sharing. Hierarchical sharing not allowed.",
											"InstancedEntity::shareTransformWith" );
		}

		if( this->mBatchOwner->_getMeshRef()->getSkeleton() !=
			slave->mBatchOwner->_getMeshRef()->getSkeleton() )
		{
			OGRE_EXCEPT( Exception::ERR_INVALID_STATE, "Sharing transforms requires both instanced"
											" entities to have the same skeleton",
											"InstancedEntity::shareTransformWith" );
		}

		slave->unlinkTransform();
		slave->destroySkeletonInstance();
		
		slave->mSkeletonInstance	= this->mSkeletonInstance;
		slave->mAnimationState		= this->mAnimationState;
		slave->mBoneMatrices		= this->mBoneMatrices;
		if (mBatchOwner->useBoneWorldMatrices())
		{
			slave->mBoneWorldMatrices	= this->mBoneWorldMatrices;
		}
		slave->mSharedTransformEntity = this;
		//The sharing partners are kept in the parent entity 
		this->mSharingPartners.push_back( slave );
		
		slave->mBatchOwner->_markTransformSharingDirty();
	}
	//-----------------------------------------------------------------------
	void InstancedEntity::stopSharingTransform()
	{
		if( mSharedTransformEntity )
		{
			unlinkTransform();
			createSkeletonInstance();
		}
		else
		{
			//Tell the ones sharing skeleton with us to use their own
			InstancedEntityVec::const_iterator itor = mSharingPartners.begin();
			InstancedEntityVec::const_iterator end  = mSharingPartners.end();
			while( itor != end )
			{
				(*itor)->stopSharingTransform();
				++itor;
			}
			mSharingPartners.clear();
		}
	}
	//-----------------------------------------------------------------------
	const String& InstancedEntity::getMovableType(void) const
	{
		static String sType = "InstancedEntity";
		return sType;
	}
	//-----------------------------------------------------------------------
	size_t InstancedEntity::getTransforms( Matrix4 *xform ) const
	{
		size_t retVal = 1;

		//When not attached, returns zero matrix to avoid rendering this one, not identity
		if( mParentNode && isVisible() )
		{
			if( !mSkeletonInstance )
			{
				*xform = mBatchOwner->useBoneWorldMatrices() ? 
						mParentNode->_getFullTransform() : Matrix4::IDENTITY;
			}
			else
			{
				Matrix4* matrices = mBatchOwner->useBoneWorldMatrices() ? mBoneWorldMatrices : mBoneMatrices;
				const Mesh::IndexMap *indexMap = mBatchOwner->_getIndexToBoneMap();
				Mesh::IndexMap::const_iterator itor = indexMap->begin();
				Mesh::IndexMap::const_iterator end  = indexMap->end();

				while( itor != end )
					*xform++ = matrices[*itor++];

				retVal = indexMap->size();
			}
		}
		else
		{
			if( mSkeletonInstance )
				retVal = mSkeletonInstance->getNumBones();

			std::fill_n( xform, retVal, Matrix4::ZERO );
		}

		return retVal;
	}
	//-----------------------------------------------------------------------
	size_t InstancedEntity::getTransforms3x4( float *xform ) const
	{
		size_t retVal;
		//When not attached, returns zero matrix to avoid rendering this one, not identity
		if( mParentNode && isVisible() )
		{
			if( !mSkeletonInstance )
			{
				const Matrix4& mat = mBatchOwner->useBoneWorldMatrices() ? 
					mParentNode->_getFullTransform() : Matrix4::IDENTITY;
				for( int i=0; i<3; ++i )
				{
					Real const *row = mat[i];
					for( int j=0; j<4; ++j )
						*xform++ = *row++;
				}

				retVal = 12;
			}
			else
			{
				Matrix4* matrices = mBatchOwner->useBoneWorldMatrices() ? mBoneWorldMatrices : mBoneMatrices;

				const Mesh::IndexMap *indexMap = mBatchOwner->_getIndexToBoneMap();
				Mesh::IndexMap::const_iterator itor = indexMap->begin();
				Mesh::IndexMap::const_iterator end  = indexMap->end();

				while( itor != end )
				{
					const Matrix4 &mat = matrices[*itor++];
					for( int i=0; i<3; ++i )
					{
						Real const *row = mat[i];
						for( int j=0; j<4; ++j )
							*xform++ = *row++;
					}
				}

				retVal = indexMap->size() * 4 * 3;
			}
		}
		else
		{
			if( mSkeletonInstance )
				retVal = mSkeletonInstance->getNumBones() * 3 * 4;
			else
				retVal = 12;

			std::fill_n( xform, retVal, 0.0f );
		}

		return retVal;
	}
	//-----------------------------------------------------------------------
	bool InstancedEntity::findVisible( Camera *camera ) const
	{
		//Object is explicitly visible and attached to a Node
		bool retVal = isVisible() & isInScene();

		//Object's bounding box is viewed by the camera
		const SceneNode *parentSceneNode = getParentSceneNode();
		if( parentSceneNode && camera )
			retVal &= camera->isVisible( parentSceneNode->_getWorldAABB() );

		return retVal;
	}
	//-----------------------------------------------------------------------
	void InstancedEntity::createSkeletonInstance()
	{
		//Is mesh skeletally animated?
		if( mBatchOwner->_getMeshRef()->hasSkeleton() &&
			!mBatchOwner->_getMeshRef()->getSkeleton().isNull() &&
			mBatchOwner->_supportsSkeletalAnimation() )
		{
			mSkeletonInstance = OGRE_NEW SkeletonInstance( mBatchOwner->_getMeshRef()->getSkeleton() );
			mSkeletonInstance->load();

			mBoneMatrices		= static_cast<Matrix4*>(OGRE_MALLOC_SIMD( sizeof(Matrix4) *
																	mSkeletonInstance->getNumBones(),
																	MEMCATEGORY_ANIMATION));
			if (mBatchOwner->useBoneWorldMatrices())
			{
				mBoneWorldMatrices	= static_cast<Matrix4*>(OGRE_MALLOC_SIMD( sizeof(Matrix4) *
																	mSkeletonInstance->getNumBones(),
																	MEMCATEGORY_ANIMATION));
			}

			mAnimationState = OGRE_NEW AnimationStateSet();
			mBatchOwner->_getMeshRef()->_initAnimationState( mAnimationState );
		}
	}
	//-----------------------------------------------------------------------
	void InstancedEntity::destroySkeletonInstance()
	{
		if( mSkeletonInstance )
		{
			//Tell the ones sharing skeleton with us to use their own
			//sharing partners will remove themselves from notifyUnlink
			while( mSharingPartners.empty() == false )
			{
				mSharingPartners.front()->stopSharingTransform();
			}
			mSharingPartners.clear();

			OGRE_DELETE mSkeletonInstance;
			OGRE_DELETE mAnimationState;
			OGRE_FREE_SIMD( mBoneMatrices, MEMCATEGORY_ANIMATION );
			OGRE_FREE_SIMD( mBoneWorldMatrices, MEMCATEGORY_ANIMATION );

			mSkeletonInstance	= 0;
			mAnimationState		= 0;
			mBoneMatrices		= 0;
			mBoneWorldMatrices	= 0;
		}
	}
	//-----------------------------------------------------------------------
	void InstancedEntity::unlinkTransform()
	{
		if( mSharedTransformEntity )
		{
			//Tell our master we're no longer his slave
			mSharedTransformEntity->notifyUnlink( this );
			mBatchOwner->_markTransformSharingDirty();

			mSkeletonInstance	= 0;
			mAnimationState		= 0;
			mBoneMatrices		= 0;
			mBoneWorldMatrices	= 0;
			mSharedTransformEntity = 0;
			

		}
	}
	//-----------------------------------------------------------------------
	void InstancedEntity::notifyUnlink( const InstancedEntity *slave )
	{
		//Find the slave and remove it
		InstancedEntityVec::iterator itor = mSharingPartners.begin();
		InstancedEntityVec::iterator end  = mSharingPartners.end();
		while( itor != end )
		{
			if( *itor == slave )
			{
				std::swap(*itor,mSharingPartners.back());
				mSharingPartners.pop_back();
				break;
			}

			++itor;
		}
	}
	//-----------------------------------------------------------------------
    const AxisAlignedBox& InstancedEntity::getBoundingBox(void) const
    {
		//TODO: Add attached objects (TagPoints) to the bbox
		return mBatchOwner->_getMeshReference()->getBounds();
    }

	//-----------------------------------------------------------------------
    Real InstancedEntity::getBoundingRadius(void) const
	{
		Real rad = mBatchOwner->_getMeshReference()->getBoundingSphereRadius();
        // Scale by largest scale factor
        if( mParentNode )
        {
            const Vector3& s = mParentNode->_getDerivedScale();
			rad *=  std::max( Math::Abs(s.x), std::max( Math::Abs(s.y), Math::Abs(s.z) ) );
        }

		return rad;
	}
	//-----------------------------------------------------------------------
	Real InstancedEntity::getSquaredViewDepth( const Camera* cam ) const
	{
		Real retVal = std::numeric_limits<Real>::infinity();

		if( mParentNode )
			retVal = mParentNode->getSquaredViewDepth( cam );

		return retVal;
	}
	//-----------------------------------------------------------------------
	void InstancedEntity::_notifyMoved(void)
	{
		mBatchOwner->_boundsDirty();
		MovableObject::_notifyMoved();
	}
	//-----------------------------------------------------------------------
	void InstancedEntity::_notifyAttached( Node* parent, bool isTagPoint )
	{
		mBatchOwner->_boundsDirty();
		MovableObject::_notifyAttached( parent, isTagPoint );
	}
	//-----------------------------------------------------------------------
	AnimationState* InstancedEntity::getAnimationState(const String& name) const
    {
        if (!mAnimationState)
        {
            OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND, "Entity is not animated",
                "InstancedEntity::getAnimationState");
        }

		return mAnimationState->getAnimationState(name);
    }
    //-----------------------------------------------------------------------
    AnimationStateSet* InstancedEntity::getAllAnimationStates(void) const
    {
        return mAnimationState;
    }
	//-----------------------------------------------------------------------
	bool InstancedEntity::_updateAnimation(void)
	{
		if (mSharedTransformEntity)
		{
			return mSharedTransformEntity->_updateAnimation();
		}
		else
		{
			const bool animationDirty =
				(mFrameAnimationLastUpdated != mAnimationState->getDirtyFrameNumber()) ||
				(mSkeletonInstance->getManualBonesDirty());

			if( animationDirty || mLastParentXform != _getParentNodeFullTransform() )
			{
				mSkeletonInstance->setAnimationState( *mAnimationState );
				mSkeletonInstance->_getBoneMatrices( mBoneMatrices );

				// Cache last parent transform for next frame use too.
				mLastParentXform = _getParentNodeFullTransform();
				if (mBatchOwner->useBoneWorldMatrices())
				{
					OptimisedUtil::getImplementation()->concatenateAffineMatrices(
													mLastParentXform,
													mBoneMatrices,
													mBoneWorldMatrices,
													mSkeletonInstance->getNumBones() );
				}
				
				mFrameAnimationLastUpdated = mAnimationState->getDirtyFrameNumber();

				return true;
			}
		}

		return false;
	}
}
