/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#include "hsTypes.h"
#include "plCoordinateInterface.h"
#include "plDrawInterface.h"
#include "plSimulationInterface.h"
#include "plAudioInterface.h"
#include "../pnMessage/plWarpMsg.h"
#include "../pnMessage/plTimeMsg.h"
#include "../pnMessage/plCorrectionMsg.h"
#include "../pnMessage/plIntRefMsg.h"
#include "../pnNetCommon/plSDLTypes.h"
#include "plSceneObject.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "../pnKeyedObject/plKey.h"
#include "hsStream.h"

#include "plProfile.h"

UInt8 plCoordinateInterface::fTransformPhase = plCoordinateInterface::kTransformPhaseNormal;
hsBool plCoordinateInterface::fDelayedTransformsEnabled = true;

plCoordinateInterface::plCoordinateInterface()
: fParent(nil),
  fReason(kReasonUnknown)
{
	fLocalToParent.Reset();
	fParentToLocal.Reset();

	fLocalToWorld.Reset();
	fWorldToLocal.Reset();

	fState = 0;
}

plCoordinateInterface::~plCoordinateInterface()
{
	if( fParent )
		fParent->IRemoveChild(IGetOwner());
	int i;
	for( i = fChildren.GetCount()-1; i >= 0; i-- )
		IRemoveChild(i);
}

void plCoordinateInterface::ISetSceneNode(plKey newNode)
{
	int i;
	for( i = 0; i < fChildren.GetCount(); i++ )
	{
		if( fChildren[i] )
			fChildren[i]->SetSceneNode(newNode);
	}
}

void plCoordinateInterface::ISetOwner(plSceneObject* so)
{
	plObjInterface::ISetOwner(so);
	
	IDirtyTransform();
	fReason |= kReasonUnknown;
}

void plCoordinateInterface::ISetParent(plCoordinateInterface* par)
{
	fParent = par;

	// This won't have any effect if my owner is NetGroupConstant
	if( fParent )
		ISetNetGroupRecur(fParent->GetNetGroup());

	IDirtyTransform();
	fReason |= kReasonUnknown;
}

plCoordinateInterface* plCoordinateInterface::GetChild(int i) const 
{ 
	return fChildren[i] ? fChildren[i]->GetVolatileCoordinateInterface() : nil; 
}

void plCoordinateInterface::IRemoveChild(int i)
{
	if( fChildren[i] )
	{
		plCoordinateInterface* childCI = fChildren[i]->GetVolatileCoordinateInterface();
		if( childCI )
			childCI->ISetParent(nil);
	}
	fChildren.Remove(i);
}

void plCoordinateInterface::IRemoveChild(plSceneObject* child)
{
	int idx = fChildren.Find(child);
	if( idx != fChildren.kMissingIndex )
		IRemoveChild(idx);
}

void plCoordinateInterface::ISetChild(plSceneObject* child, int which)
{
	hsAssert(child, "Setting a nil child");
	plCoordinateInterface* childCI = child->GetVolatileCoordinateInterface();
	hsAssert(childCI, "Child with no coordinate interface");
	childCI->ISetParent(this);

	if( which < 0 )
		which = fChildren.GetCount();
	fChildren.ExpandAndZero(which+1);
	fChildren[which] = child;

	// If we can't delay our transform update, neither can any of our parents.
	if (!childCI->GetProperty(kDelayedTransformEval))
	{
		plCoordinateInterface *current = childCI->GetParent();
		while (current)
		{
			current->SetProperty(kDelayedTransformEval, false);
			current = current->GetParent();
		}
	}
}

void plCoordinateInterface::IAddChild(plSceneObject* child)
{
	ISetChild(child, -1);
}

void plCoordinateInterface::IAttachChild(plSceneObject* child, UInt8 flags)
{
	hsAssert(child, "Attaching a nil child");
	plCoordinateInterface* childCI = child->GetVolatileCoordinateInterface();
	hsAssert(childCI, "Owner without CoordinateInterface being attached");

	if (childCI->GetParent() == this)
		return; // We're already attached! Who told us to do this?
	
	hsMatrix44 l2w = childCI->GetLocalToWorld();
	hsMatrix44 w2l = childCI->GetWorldToLocal();

	if( childCI->GetParent() )
		childCI->GetParent()->IDetachChild(child, flags | kAboutToAttach);

	childCI->IUnRegisterForTransformMessage();

	IAddChild(child);

	if( flags & kMaintainWorldPosition )
		childCI->WarpToWorld(l2w,w2l);
}

void plCoordinateInterface::IDetachChild(plSceneObject* child, UInt8 flags)
{
	hsAssert(child, "Detaching a nil child");
	plCoordinateInterface* childCI = child->GetVolatileCoordinateInterface();
	hsAssert(childCI, "Owner without CoordinateInterface being attached");

	hsMatrix44 l2w = childCI->GetLocalToWorld();
	hsMatrix44 w2l = childCI->GetWorldToLocal();

	GetKey()->Release(child->GetKey());
	if( IGetOwner() && IGetOwner()->GetKey() )
		IGetOwner()->GetKey()->Release(child->GetKey());
	IRemoveChild(child);

	if( flags & kMaintainWorldPosition )
		childCI->WarpToWorld(l2w,w2l);

	// If the child was keeping us from delaying our transform, 
	// maybe we can, now that it's gone.
	if (!childCI->GetProperty(kDelayedTransformEval))
		IUpdateDelayProp();
}

/*
 *	A few notes on the delay transform properties...
 *
 *		The kCanEverDelayTransform prop is independent of any parents/children.
 *	It means this particular node must always update its transform in response
 *	to a plTransformMsg. It is intended for objects with physics, because they
 *	need to be up-to-date before the simulationMgr updates the physical world.
 *
 *		The kDelayedTransformEval prop is for nodes that are free of physics. (So no
 *	physical descendants either). If the property is set, we won't update our
 *	transform until AFTER the simulationMgr does its work.
 *
 *		When we attach a child that can't delay its eval (at the moment), we recurse
 *	up to the root, turning off the kDelayedTransformEval prop as we go. When we
 *	remove such a child, we check if that child was the only reason we weren't
 *	delaying our transform. If so, we update ourself and tell our parent to check.
 *
 *	BTW: The POINT of all this is that when we update our l2w transforms because
 *	we're animated, and then we update AGAIN after a parent node of ours involved
 *	in physics gets a slight nudge, the first update becomes pointless. The
 *	delay prop bookkeeping keeps us from doing the wasted calculations. And since 
 *	nearly all bones on the avatar are in this exact situation, it's worth doing.
 */
void plCoordinateInterface::IUpdateDelayProp()
{
	int i;
	if (!GetProperty(kCanEverDelayTransform))
		return;

	for (i = 0; i < GetNumChildren(); i++)
	{
		// If we still have a child that needs the delay...		
		if (!GetChild(i)->GetProperty(kDelayedTransformEval))
			return;
	}

	// Cool, we can delay now, which means maybe our parent can too.
	SetProperty(kDelayedTransformEval, true);
	if (GetParent())
		GetParent()->IUpdateDelayProp();
}

plCoordinateInterface* plCoordinateInterface::IGetRoot()
{
	return fParent ? fParent->IGetRoot() : this;
}

void plCoordinateInterface::IRegisterForTransformMessage(hsBool delayed)
{
	if( IGetOwner() )
	{
		if ((delayed || fTransformPhase == kTransformPhaseDelayed) && fDelayedTransformsEnabled)
			plgDispatch::Dispatch()->RegisterForExactType(plDelayedTransformMsg::Index(), IGetOwner()->GetKey());
		else
			plgDispatch::Dispatch()->RegisterForExactType(plTransformMsg::Index(), IGetOwner()->GetKey());
	}
}

void plCoordinateInterface::IUnRegisterForTransformMessage()
{
	if( IGetOwner() )
		plgDispatch::Dispatch()->UnRegisterForExactType(plTransformMsg::Index(), IGetOwner()->GetKey());
}


void plCoordinateInterface::IDirtyTransform()
{
	fState |= kTransformDirty;

	IGetRoot()->IRegisterForTransformMessage(GetProperty(kDelayedTransformEval));
}

void plCoordinateInterface::MultTransformLocal(const hsMatrix44& move, const hsMatrix44& invMove)
{
	fReason |= kReasonUnknown;
	fLocalToParent = move * fLocalToParent;
	fParentToLocal = fParentToLocal * invMove;

	IDirtyTransform();
}

void plCoordinateInterface::SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l)
{
	fReason |= kReasonUnknown;
	
	if( fParent )
	{
		SetLocalToParent(fParent->GetWorldToLocal() * l2w, w2l * fParent->GetLocalToWorld());
	}
	else
	{
		SetLocalToParent(l2w, w2l);
	}
}

void plCoordinateInterface::SetTransformPhysical(const hsMatrix44& l2w, const hsMatrix44& w2l)
{
	// since we use public interfaces to do the details
	// AND those public interfaces could be called by anyone
	// AND those public interfaces therefore have to set their own "reason" for the transform change
	// THEREFORE: we need to preserve the "reason" flags before we call the public interfaces
	//			  so that we don't get reasonPhysics + reasonUnknown, just reasonPhysics
	UInt16 oldReason = fReason;

	if( fParent )
	{
		SetLocalToParent(fParent->GetWorldToLocal() * l2w, w2l * fParent->GetLocalToWorld());
	}
	else
	{
		SetLocalToParent(l2w, w2l);
	}
	fReason = oldReason | kReasonPhysics;
}

UInt16 plCoordinateInterface::GetReasons()
{
	return fReason;
}

void plCoordinateInterface::ClearReasons()
{
	fReason = nil;
}

void plCoordinateInterface::SetLocalToParent(const hsMatrix44& l2p, const hsMatrix44& p2l)
{
	fReason |= kReasonUnknown;
	fLocalToParent = l2p;
	fParentToLocal = p2l;

	IDirtyTransform();
}

void plCoordinateInterface::WarpToLocal(const hsMatrix44& l2p, const hsMatrix44& p2l)
{
	fReason |= kReasonUnknown;
	SetLocalToParent(l2p, p2l);

	// update physical state when an object is warped
	if (IGetOwner())
		IGetOwner()->DirtySynchState(kSDLPhysical, 0);
}

void plCoordinateInterface::WarpToWorld(const hsMatrix44& l2w, const hsMatrix44& w2l)
{
	fReason |= kReasonUnknown;
	if( fParent )
	{
		hsMatrix44 l2p = fParent->GetWorldToLocal() * l2w;
		hsMatrix44 p2l = w2l * fParent->GetLocalToWorld();
		WarpToLocal(l2p, p2l);
	}
	else
	{
		WarpToLocal(l2w, w2l);
	}
}

plProfile_CreateCounter("CITrans", "Object", CITrans);
plProfile_CreateCounter("   CIRecalc", "Object", CIRecalc);
plProfile_CreateCounter("   CIDirty", "Object", CIDirty);
plProfile_CreateCounter("   CISet", "Object", CISet);

plProfile_CreateTimer("CITransT", "Object", CITransT);
plProfile_CreateTimer("   CIRecalcT", "Object", CIRecalcT);
plProfile_CreateTimer("   CIDirtyT", "Object", CIDirtyT);
plProfile_CreateTimer("   CISetT", "Object", CISetT);

static inline hsMatrix44 IMatrixMul34(const hsMatrix44& lhs, const hsMatrix44& rhs)
{
	hsMatrix44 ret;
	ret.NotIdentity();
	ret.fMap[3][0] = ret.fMap[3][1] = ret.fMap[3][2] = 0;
	ret.fMap[3][3] = 1.f;

	ret.fMap[0][0] = lhs.fMap[0][0] * rhs.fMap[0][0]
		+ lhs.fMap[0][1] * rhs.fMap[1][0]
		+ lhs.fMap[0][2] * rhs.fMap[2][0];

	ret.fMap[0][1] = lhs.fMap[0][0] * rhs.fMap[0][1]
		+ lhs.fMap[0][1] * rhs.fMap[1][1]
		+ lhs.fMap[0][2] * rhs.fMap[2][1];

	ret.fMap[0][2] = lhs.fMap[0][0] * rhs.fMap[0][2]
		+ lhs.fMap[0][1] * rhs.fMap[1][2]
		+ lhs.fMap[0][2] * rhs.fMap[2][2];

	ret.fMap[0][3] = lhs.fMap[0][0] * rhs.fMap[0][3]
		+ lhs.fMap[0][1] * rhs.fMap[1][3]
		+ lhs.fMap[0][2] * rhs.fMap[2][3]
		+ lhs.fMap[0][3];

	ret.fMap[1][0] = lhs.fMap[1][0] * rhs.fMap[0][0]
		+ lhs.fMap[1][1] * rhs.fMap[1][0]
		+ lhs.fMap[1][2] * rhs.fMap[2][0];

	ret.fMap[1][1] = lhs.fMap[1][0] * rhs.fMap[0][1]
		+ lhs.fMap[1][1] * rhs.fMap[1][1]
		+ lhs.fMap[1][2] * rhs.fMap[2][1];

	ret.fMap[1][2] = lhs.fMap[1][0] * rhs.fMap[0][2]
		+ lhs.fMap[1][1] * rhs.fMap[1][2]
		+ lhs.fMap[1][2] * rhs.fMap[2][2];

	ret.fMap[1][3] = lhs.fMap[1][0] * rhs.fMap[0][3]
		+ lhs.fMap[1][1] * rhs.fMap[1][3]
		+ lhs.fMap[1][2] * rhs.fMap[2][3]
		+ lhs.fMap[1][3];

	ret.fMap[2][0] = lhs.fMap[2][0] * rhs.fMap[0][0]
		+ lhs.fMap[2][1] * rhs.fMap[1][0]
		+ lhs.fMap[2][2] * rhs.fMap[2][0];

	ret.fMap[2][1] = lhs.fMap[2][0] * rhs.fMap[0][1]
		+ lhs.fMap[2][1] * rhs.fMap[1][1]
		+ lhs.fMap[2][2] * rhs.fMap[2][1];

	ret.fMap[2][2] = lhs.fMap[2][0] * rhs.fMap[0][2]
		+ lhs.fMap[2][1] * rhs.fMap[1][2]
		+ lhs.fMap[2][2] * rhs.fMap[2][2];

	ret.fMap[2][3] = lhs.fMap[2][0] * rhs.fMap[0][3]
		+ lhs.fMap[2][1] * rhs.fMap[1][3]
		+ lhs.fMap[2][2] * rhs.fMap[2][3]
		+ lhs.fMap[2][3];

	return ret;
}

void plCoordinateInterface::IRecalcTransforms()
{
	plProfile_IncCount(CIRecalc, 1);
	plProfile_BeginTiming(CIRecalcT);
	if( fParent )
	{
#if 0
		fLocalToWorld = fParent->GetLocalToWorld() * fLocalToParent;
		fWorldToLocal = fParentToLocal * fParent->GetWorldToLocal();
#else
		fLocalToWorld = IMatrixMul34(fParent->GetLocalToWorld(), fLocalToParent);
		fWorldToLocal = IMatrixMul34(fParentToLocal, fParent->GetWorldToLocal());
#endif
	}
	else
	{
		fLocalToWorld = fLocalToParent;
		fWorldToLocal = fParentToLocal;
	}
	plProfile_EndTiming(CIRecalcT);
}

void plCoordinateInterface::ITransformChanged(hsBool force, UInt16 reasons, hsBool checkForDelay)
{
	plProfile_IncCount(CITrans, 1);
	plProfile_BeginTiming(CITransT);

	// inherit reasons for transform change from our parents
	fReason |= reasons;
	
	UInt16 propagateReasons = fReason;

	hsBool process = !(checkForDelay && GetProperty(kDelayedTransformEval)) || !fDelayedTransformsEnabled;

	if (process)
	{
		if( fState & kTransformDirty )
			force = true;
	}

	if( force )
	{
		IRecalcTransforms();

		plProfile_IncCount(CISet, 1);
		plProfile_BeginTiming(CISetT);
		if( IGetOwner() )
		{
			IGetOwner()->ISetTransform(fLocalToWorld, fWorldToLocal);
		}
		plProfile_EndTiming(CISetT);
		fState &= ~kTransformDirty;		
	}

	plProfile_EndTiming(CITransT);
	if (process)
	{
		int i;
		for( i = 0; i < fChildren.GetCount(); i++ )
		{
			if( fChildren[i] && fChildren[i]->GetVolatileCoordinateInterface() )
				fChildren[i]->GetVolatileCoordinateInterface()->ITransformChanged(force, propagateReasons, checkForDelay);
		}
	}
	else if (force)
	{
		plProfile_IncCount(CIDirty, 1);
		plProfile_BeginTiming(CITransT);
		// Our parent is dirty and we're bailing out on evaluating right now.
		// Need to ensure we'll be evaluated in the delay pass
		plProfile_BeginTiming(CIDirtyT);
		IDirtyTransform();
		plProfile_EndTiming(CIDirtyT);
		plProfile_EndTiming(CITransT);
	}		
}

void plCoordinateInterface::FlushTransform(hsBool fromRoot)
{
	if( fromRoot )
		IGetRoot()->ITransformChanged(false, 0, false);
	else
		ITransformChanged(false, 0, false);
}

void plCoordinateInterface::ISetNetGroupRecur(plNetGroupId netGroup)
{
	if( !IGetOwner() )
		return;

	if( IGetOwner()->GetSynchFlags() & kHasConstantNetGroup )
		return;

	IGetOwner()->plSynchedObject::SetNetGroup(netGroup);

	int i;
	for( i = 0; i < GetNumChildren(); i++ )
	{
		if( GetChild(i) )
		{
			GetChild(i)->ISetNetGroupRecur(netGroup);
		}
	}
}


void plCoordinateInterface::Read(hsStream* stream, hsResMgr* mgr)
{
	plObjInterface::Read(stream, mgr);

	fLocalToParent.Read(stream);
	fParentToLocal.Read(stream);

	fLocalToWorld.Read(stream);
	fWorldToLocal.Read(stream);

	int n = stream->ReadSwap32();
	int i;
	for( i = 0; i < n; i++ )
	{
		plIntRefMsg* refMsg = TRACKED_NEW plIntRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plIntRefMsg::kChildObject);
		mgr->ReadKeyNotifyMe(stream,refMsg, plRefFlags::kPassiveRef);
	}
}

void plCoordinateInterface::Write(hsStream* stream, hsResMgr* mgr)
{
	plObjInterface::Write(stream, mgr);

	fLocalToParent.Write(stream);
	fParentToLocal.Write(stream);

	fLocalToWorld.Write(stream);
	fWorldToLocal.Write(stream);

	stream->WriteSwap32(fChildren.GetCount());
	int i;
	for( i = 0; i < fChildren.GetCount(); i++ )
		mgr->WriteKey(stream, fChildren[i]);

}

hsBool plCoordinateInterface::MsgReceive(plMessage* msg)
{
	hsBool retVal = false;

	plIntRefMsg* intRefMsg;
	plCorrectionMsg* corrMsg;

	// warp message
	plWarpMsg* pWarpMsg = plWarpMsg::ConvertNoRef(msg);
	if (pWarpMsg)
	{
		hsMatrix44 l2w = pWarpMsg->GetTransform();
		hsMatrix44 inv;
		l2w.GetInverse(&inv);
		WarpToWorld(l2w,inv);
		if (pWarpMsg->GetWarpFlags() & plWarpMsg::kFlushTransform)
			ITransformChanged(false, kReasonUnknown, false);
		return true;
	}
	else if( intRefMsg = plIntRefMsg::ConvertNoRef(msg) )
	{
		switch( intRefMsg->fType )
		{
		case plIntRefMsg::kChildObject:
		case plIntRefMsg::kChild:
			{
				plSceneObject* co = nil;
				if( intRefMsg->fType == plIntRefMsg::kChildObject )
				{
					co = plSceneObject::ConvertNoRef(intRefMsg->GetRef());
				}
				else
				{
					plCoordinateInterface* ci = plCoordinateInterface::ConvertNoRef(intRefMsg->GetRef());
					co = ci ? ci->IGetOwner() : nil;
				}
				if( intRefMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnReplace) )
				{
					ISetChild(co, intRefMsg->fWhich);
				}
				else if( intRefMsg->GetContext() & plRefMsg::kOnDestroy )
				{
					IRemoveChild(co);
				}
				else if( intRefMsg->GetContext() & plRefMsg::kOnRequest )
				{
					IAttachChild(co, kMaintainWorldPosition|kMaintainSceneNode);
				}
				else if( intRefMsg->GetContext() & plRefMsg::kOnRemove )
				{
					IDetachChild(co, kMaintainWorldPosition|kMaintainSceneNode);
				}
			}
			return true;
		default:
			break;
		}
	}
	else if( corrMsg = plCorrectionMsg::ConvertNoRef(msg) )
	{
		SetTransformPhysical(corrMsg->fLocalToWorld, corrMsg->fWorldToLocal);

		if(corrMsg->fDirtySynch)
		{
			if (IGetOwner())
				IGetOwner()->DirtySynchState(kSDLPhysical, 0);
		}
		
		return true;
	}

	return plObjInterface::MsgReceive(msg);
}