/*==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 "plViewFaceModifier.h"
#include "plgDispatch.h"
#include "pnSceneObject/plSceneObject.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "hsFastMath.h"
#include "plPipeline.h"

#include "plMessage/plRenderMsg.h"
#include "plMessage/plListenerMsg.h"
#include "plMessage/plAvatarMsg.h"
#include "plAvatar/plAvBrainHuman.h"
#include "plAvatar/plArmatureMod.h"

plViewFaceModifier::plViewFaceModifier()
:	fFacePoint(0,0,0),
	fLastDirY(0,1.f,0),
	fScale(1.f,1.f,1.f),
	fOffset(0,0,0)
{
	fOrigLocalToParent.Reset();
	fOrigParentToLocal.Reset();

	SetFlag(kFaceCam); // default
}

plViewFaceModifier::~plViewFaceModifier()
{
}

void plViewFaceModifier::SetOrigTransform(const hsMatrix44& l2p, const hsMatrix44& p2l)
{
	fOrigLocalToParent = l2p;
	fOrigParentToLocal = p2l;
}

void plViewFaceModifier::Read(hsStream* s, hsResMgr* mgr)
{
	plSingleModifier::Read(s, mgr);

	fScale.Read(s);

	fOrigLocalToParent.Read(s);
	fOrigParentToLocal.Read(s);

	if( HasFlag(kFaceObj) )
		mgr->ReadKeyNotifyMe(s, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, kRefFaceObj), plRefFlags::kPassiveRef);

	fOffset.Read(s);

	if( HasFlag(kMaxBounds) )
		fMaxBounds.Read(s);
}

void plViewFaceModifier::Write(hsStream* s, hsResMgr* mgr)
{
	plSingleModifier::Write(s, mgr);

	fScale.Write(s);

	fOrigLocalToParent.Write(s);
	fOrigParentToLocal.Write(s);

	if( HasFlag(kFaceObj) )
		mgr->WriteKey(s, fFaceObj); 

	fOffset.Write(s);

	if( HasFlag(kMaxBounds) )
		fMaxBounds.Write(s);
}

void plViewFaceModifier::SetMaxBounds(const hsBounds3Ext& bnd)
{
	SetFlag(kMaxBounds);
	fMaxBounds = bnd;
}

void plViewFaceModifier::SetTarget(plSceneObject* so)
{
	plSingleModifier::SetTarget(so);

	plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey());
	if( HasFlag(kFaceList) )
		plgDispatch::Dispatch()->RegisterForExactType(plListenerMsg::Index(), GetKey());
	if( HasFlag(kFacePlay) )
		plgDispatch::Dispatch()->RegisterForExactType(plArmatureUpdateMsg::Index(), GetKey());
}

hsBool plViewFaceModifier::IEval(double secs, hsScalar del, UInt32 dirty)
{
	return false;
}

hsBool plViewFaceModifier::IFacePoint(plPipeline* pipe, const hsPoint3& at)
{
#if 1 // BOUNDSTEST
	extern int mfCurrentTest;
	if( mfCurrentTest != 101 )
	if( HasFlag(kMaxBounds) )
	{
		if( !pipe->TestVisibleWorld(fMaxBounds) )
			return false;
	}
#endif // BOUNDSTEST

	if( !(GetTarget() && GetTarget()->GetCoordinateInterface()) )
		return false;
		
	hsMatrix44 worldToLocal = fOrigParentToLocal;	// parentToLocal
	if( GetTarget()->GetCoordinateInterface()->GetParent() && GetTarget()->GetCoordinateInterface()->GetParent() )
	{
		hsMatrix44 m;
		worldToLocal = worldToLocal *  GetTarget()->GetCoordinateInterface()->GetParent()->GetWorldToLocal();
	}
	
	hsPoint3 localAt = worldToLocal * at;
	hsScalar len = localAt.MagnitudeSquared();
	if( len <= 0 )
		return false;
	len = -hsFastMath::InvSqrtAppr(len);
	
	hsVector3 dirX, dirY, dirZ;
	dirZ.Set(localAt.fX * len, localAt.fY * len, localAt.fZ * len);
	
	if( HasFlag(kPivotFace) )
	{
		dirY.Set(0.f, 0.f, 1.f);
		dirX = dirY % dirZ;
		dirX = hsFastMath::NormalizeAppr(dirX);
		dirY = dirZ % dirX;
	}
	else if( HasFlag(kPivotFavorY) )
	{
		dirY.Set(0.f, 1.f, 0.f);
		dirX = dirY % dirZ;
		dirX = hsFastMath::NormalizeAppr(dirX);
		dirY = dirZ % dirX;
	}
	else if( HasFlag(kPivotY) )
	{
		dirY.Set(0.f, 1.f, 0.f);
		dirX = dirY % dirZ;
		dirX = hsFastMath::NormalizeAppr(dirX);
		dirZ = dirX % dirY;
	}
	else if( HasFlag(kPivotTumble) )
	{
		dirY = fLastDirY;
		dirX = dirY % dirZ;
		dirX = hsFastMath::NormalizeAppr(dirX);
		dirY = dirZ % dirX;
		fLastDirY = dirY;
	}
	else
	{
		hsAssert(false, "I've no idea what you're getting at here in ViewFace land");
	}

	hsMatrix44 x;
	hsMatrix44 xInv;

	xInv.fMap[0][0] = x.fMap[0][0] = dirX[0];
	xInv.fMap[1][0] = x.fMap[0][1] = dirY[0];
	xInv.fMap[2][0] = x.fMap[0][2] = dirZ[0];
	xInv.fMap[3][0] = x.fMap[0][3] = 0;
	
	xInv.fMap[0][1] = x.fMap[1][0] = dirX[1];
	xInv.fMap[1][1] = x.fMap[1][1] = dirY[1];
	xInv.fMap[2][1] = x.fMap[1][2] = dirZ[1];
	xInv.fMap[3][1] = x.fMap[1][3] = 0;
	
	xInv.fMap[0][2] = x.fMap[2][0] = dirX[2];
	xInv.fMap[1][2] = x.fMap[2][1] = dirY[2];
	xInv.fMap[2][2] = x.fMap[2][2] = dirZ[2];
	xInv.fMap[3][2] = x.fMap[2][3] = 0;
	
	x.fMap[3][0] = x.fMap[3][1] = x.fMap[3][2] = 0;
	xInv.fMap[0][3] = xInv.fMap[1][3] = xInv.fMap[2][3] = 0;
	
	xInv.fMap[3][3] = x.fMap[3][3] = hsScalar1;
	
	x.NotIdentity();
	xInv.NotIdentity();

	if( HasFlag(kScale) )
	{
		x.fMap[0][0] *= fScale.fX;
		x.fMap[0][1] *= fScale.fX;
		x.fMap[0][2] *= fScale.fX;

		x.fMap[1][0] *= fScale.fY;
		x.fMap[1][1] *= fScale.fY;
		x.fMap[1][2] *= fScale.fY;

		x.fMap[2][0] *= fScale.fZ;
		x.fMap[2][1] *= fScale.fZ;
		x.fMap[2][2] *= fScale.fZ;

		hsScalar inv = 1.f / fScale.fX;
		xInv.fMap[0][0] *= inv;
		xInv.fMap[1][0] *= inv;
		xInv.fMap[2][0] *= inv;

		inv = 1.f / fScale.fY;
		xInv.fMap[0][1] *= inv;
		xInv.fMap[1][1] *= inv;
		xInv.fMap[2][1] *= inv;

		inv = 1.f / fScale.fZ;
		xInv.fMap[0][2] *= inv;
		xInv.fMap[1][2] *= inv;
		xInv.fMap[2][2] *= inv;
	}

	hsMatrix44 l2p = fOrigLocalToParent * x;
	hsMatrix44 p2l = xInv * fOrigParentToLocal;

	if( l2p != IGetTargetCoordinateInterface(0)->GetLocalToParent() ) // TERRORDAN
	{
		IGetTargetCoordinateInterface(0)->SetLocalToParent(l2p, p2l);
		IGetTargetCoordinateInterface(0)->FlushTransform(false);
	}

	return true;
}



#include "plProfile.h"
plProfile_CreateTimer("ViewFacing", "RenderSetup", ViewFace);

hsBool plViewFaceModifier::MsgReceive(plMessage* msg)
{
	plRenderMsg* rend = plRenderMsg::ConvertNoRef(msg);

	if( rend )
	{
		plProfile_BeginLap(ViewFace, this->GetKey()->GetUoid().GetObjectName());

		if( HasFlag(kFaceCam) )
		{
			fFacePoint = rend->Pipeline()->GetViewPositionWorld();
			if( HasFlag(kOffset) )
			{
				if( HasFlag(kOffsetLocal) )
				{
					fFacePoint += rend->Pipeline()->GetViewAcrossWorld() * fOffset.fX;
					fFacePoint += rend->Pipeline()->GetViewUpWorld() * fOffset.fY;
					fFacePoint += rend->Pipeline()->GetViewDirWorld() * fOffset.fZ;
				}
				else
				{
					fFacePoint += fOffset;
				}
			}
		}
		else
		if( HasFlag(kFaceObj) )
		{
			if( !fFaceObj )
				return true;
			fFacePoint = fFaceObj->GetLocalToWorld().GetTranslate();
			if( HasFlag(kOffset) )
			{
				if( HasFlag(kOffsetLocal) )
					fFacePoint += fFaceObj->GetLocalToWorld() * fOffset;
				else
					fFacePoint += fOffset;
			}
		}

		IFacePoint(rend->Pipeline(), fFacePoint);
		
		plProfile_EndLap(ViewFace, this->GetKey()->GetUoid().GetObjectName());
		return true;
	}
	plArmatureUpdateMsg* armMsg = plArmatureUpdateMsg::ConvertNoRef(msg);
	if( armMsg && armMsg->IsLocal() )
	{
		const plSceneObject* head = armMsg->fArmature->FindBone(plAvBrainHuman::Head);
		if( head )
		{
			fFacePoint = head->GetLocalToWorld().GetTranslate();
			if( HasFlag(kOffset) )
			{
				if( HasFlag(kOffsetLocal) )
					fFacePoint += head->GetLocalToWorld() * fOffset;
				else
					fFacePoint += fOffset;
			}
		}

		return true;
	}
	plListenerMsg* list = plListenerMsg::ConvertNoRef(msg);
	if( list )
	{
		fFacePoint = list->GetPosition();

		if( HasFlag(kOffset) )
		{
			if( HasFlag(kOffsetLocal) )
			{
				fFacePoint += (list->GetDirection() % list->GetUp()) * fOffset.fX;
				fFacePoint += list->GetDirection() * fOffset.fY;
				fFacePoint += list->GetUp() * fOffset.fZ;
			}
			else
			{
				fFacePoint += fOffset;
			}
		}

		return true;
	}
	plGenRefMsg* refMsg = plGenRefMsg::ConvertNoRef(msg);
	if( refMsg )
	{
		if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
			IOnReceive(refMsg);
		else
			IOnRemove(refMsg);
		return true;
	}
	return plSingleModifier::MsgReceive(msg);
}

void plViewFaceModifier::IOnReceive(plGenRefMsg* refMsg)
{
	switch(refMsg->fType)
	{
	case kRefFaceObj:
		fFaceObj = plSceneObject::ConvertNoRef(refMsg->GetRef());
		break;
	}
}

void plViewFaceModifier::IOnRemove(plGenRefMsg* refMsg)
{
	switch(refMsg->fType)
	{
	case kRefFaceObj:
		fFaceObj = nil;
		break;
	}
}

void plViewFaceModifier::ISetObject(plKey soKey)
{
	hsgResMgr::ResMgr()->SendRef(soKey, TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnRequest, 0, kRefFaceObj), plRefFlags::kPassiveRef);
}

void plViewFaceModifier::SetFollowMode(FollowMode m, plKey soKey)
{
	ClearFlag(kFaceCam);
	ClearFlag(kFaceList);
	ClearFlag(kFacePlay);
	ClearFlag(kFaceObj);
	
	switch(m)
	{
	case kFollowCamera:
		SetFlag(kFaceCam);
		break;
	case kFollowListener:
		SetFlag(kFaceList);
		break;
	case kFollowPlayer:
		SetFlag(kFacePlay);
		break;
	case kFollowObject:
		SetFlag(kFaceObj);
		ISetObject(soKey);
		break;
	default:
		hsAssert(false, "Unknown follow mode");
		SetFlag(kFaceCam);
		break;
	}
}

plViewFaceModifier::FollowMode plViewFaceModifier::GetFollowMode() const
{
	if( HasFlag(kFaceCam) )
		return kFollowCamera;
	if( HasFlag(kFaceList) )
		return kFollowListener;
	if( HasFlag(kFacePlay) )
		return kFollowPlayer;
	if( HasFlag(kFaceObj) )
		return kFollowObject;

	hsAssert(false, "Have no follow mode");
	return kFollowCamera;

}

void plViewFaceModifier::SetOffset(const hsVector3& off, hsBool local)
{
	fOffset = off;
	if( local )
		SetFlag(kOffsetLocal);
	else
		ClearFlag(kOffsetLocal);
}