/*==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==*/
#ifndef plCameraBrain_inc
#define plCameraBrain_inc

#include "pnKeyedObject/hsKeyedObject.h"
#include "hsMatrix44.h"
#include "hsBitVector.h"
#include "hsTemplates.h"

class plMessage;
class plCameraModifier1;
class plSceneObject;
class plRailCameraMod;

class plCameraBrain1 : public hsKeyedObject
{
	
public:
	enum
	{
		kCutPos = 0,
		kCutPosOnce,
		kCutPOA,
		kCutPOAOnce,
		kAnimateFOV,
		kFollowLocalAvatar,
		kPanicVelocity,
		kRailComponent,
		kSubject,
		kCircleTarget,
		kMaintainLOS,
		kZoomEnabled,
		kIsTransitionCamera,
		kWorldspacePOA,
		kWorldspacePos,
		kCutPosWhilePan,
		kCutPOAWhilePan,
		kNonPhys,
		kNeverAnimateFOV,
		kIgnoreSubworldMovement,
		kFalling,
		kRunning,
		kVerticalWhenFalling,
		kSpeedUpWhenRunning,
		kFallingStopped,
		kBeginFalling,
	};
	plCameraBrain1(plCameraModifier1* pMod);
	plCameraBrain1();
	~plCameraBrain1();
	
	CLASSNAME_REGISTER( plCameraBrain1 );
	GETINTERFACE_ANY( plCameraBrain1, hsKeyedObject );

	void SetCamera(plCameraModifier1* pMod) { fCamera = pMod; }
	
	void SetAccel		(hsScalar f) { fAccel = f; }
	void SetDecel		(hsScalar f) { fDecel = f; }
	void SetVelocity	(hsScalar f) { fVelocity = f; }
	void SetPOAAccel	(hsScalar f) { fPOAAccel = f; }
	void SetPOADecel	(hsScalar f) { fPOADecel = f; }
	void SetPOAVelocity	(hsScalar f) { fPOAVelocity = f; }

	const plCameraModifier1* GetCamera() { return fCamera; }

	virtual void		Update(hsBool forced = false);
	virtual hsBool		MsgReceive(plMessage* msg);

	virtual plSceneObject*	GetSubject();
	virtual void SetSubject(plSceneObject* sub);
	
	virtual hsVector3	GetPOAOffset() { return fPOAOffset; }	
	void		SetPOAOffset(hsVector3 pt) { fPOAOffset = pt; }
	void AddTarget();
 
	virtual void Read(hsStream* stream, hsResMgr* mgr);
	virtual void Write(hsStream* stream, hsResMgr* mgr);

	virtual hsBool	GetFaded() { return false; }
	virtual hsBool	SetFaded(hsBool b) { return false; }

	hsBool	HasMovementFlag(int f) { return fMoveFlags.IsBitSet(f); }
	void	SetMovementFlag(int f); 
	void	ClearMovementFlag(int which) { fMoveFlags.ClearBit( which ); }
	void	SetFlags(int i) { fFlags.SetBit(i); }
	void	ClearFlags(int which) { fFlags.ClearBit( which ); }
	hsBool	HasFlag(int f) { return fFlags.IsBitSet(f); }

	void	SetGoal(hsPoint3 pt) { fGoal = pt; }
	void	SetPOAGoal(hsPoint3 pt) { fPOAGoal = pt; }
	void	SetFOVGoal(hsScalar h, double t);
	void	SetZoomParams(hsScalar max, hsScalar min, hsScalar rate);
	
	void	SetXPanLimit(hsScalar x) {fXPanLimit = x;}
	void	SetZPanLimit(hsScalar y) {fZPanLimit = y;}
	hsScalar	GetXPanLimit() {return fXPanLimit;}
	hsScalar	GetZPanLimit() {return fZPanLimit;}

	void	SetRail(plRailCameraMod* m) { fRail = m; }

	hsPoint3 GetGoal() { return fGoal; }
	hsPoint3 GetPOAGoal() { return fPOAGoal; }

	virtual void Push(hsBool recenter = true);
	virtual void Pop();
	
	hsScalar GetVelocity()		{ return fVelocity; }
	hsScalar GetAccel()			{ return fAccel; }
	hsScalar GetDecel()			{ return fDecel; }
	hsScalar GetPOAAccel()		{ return fPOAAccel; }
	hsScalar GetPOAVelocity()	{ return fPOAVelocity; }
	hsScalar GetPOADecel()		{ return fPOADecel; }

	hsScalar GetCurrentCamSpeed() { return fCurCamSpeed; }
	hsScalar GetCurrentViewSpeed() { return fCurViewSpeed; }

	void SetCurrentCamSpeed(hsScalar s) { fCurCamSpeed = s;	}
	void SetCurrentViewSpeed(hsScalar s) { fCurViewSpeed = s;	}
	
	hsMatrix44 GetTargetMatrix() { return fTargetMatrix; }

	static hsScalar fFallVelocity;
	static hsScalar fFallAccel;
	static hsScalar fFallDecel;

	static hsScalar fFallPOAVelocity;
	static hsScalar fFallPOAAccel;
	static hsScalar fFallPOADecel;

protected:
		
	virtual void AdjustForInput(double secs);
	void IMoveTowardGoal(double time);
	void IPointTowardGoal(double time);
	void IAnimateFOV(double time);
	void IAdjustVelocity(hsScalar adjAccelRate, 
						 hsScalar adjDecelRate, 
						 hsVector3* dir, 
						 hsVector3* vel, 
						 hsScalar maxSpeed, 
						 hsScalar distToGoal,
						 double elapsedTime);

	hsScalar IClampVelocity(hsVector3* vel, hsScalar maxSpeed, double elapsedTime);
	hsBool   IShouldDecelerate(hsScalar decelSpeed, hsScalar curSpeed, hsScalar distToGoal);

	plCameraModifier1*	fCamera;
	plKey				fSubjectKey;
	plRailCameraMod*	fRail;
	hsScalar			fCurCamSpeed;
	hsScalar			fCurViewSpeed;
	double				fLastTime;

	hsScalar			fVelocity;
	hsScalar			fAccel;
	hsScalar			fDecel;
	hsScalar			fPOAVelocity;
	hsScalar			fPOAAccel;
	hsScalar			fPOADecel;
	hsVector3			fPOAOffset;
	hsPoint3			fGoal;
	hsPoint3			fPOAGoal;
	hsScalar			fXPanLimit;
	hsScalar			fZPanLimit;
	hsScalar			fPanSpeed;
	hsScalar			fFOVGoal;
	double				fFOVStartTime;
	double				fFOVEndTime;
	hsScalar			fFOVAnimRate; 
	hsScalar			fZoomRate;
	hsScalar			fZoomMax;
	hsScalar			fZoomMin;
	hsBitVector			fMoveFlags;
	hsBitVector			fFlags;
	hsMatrix44			fTargetMatrix;
	hsScalar			fOffsetLength;
	hsScalar			fOffsetPct;
	double				fFallTimer;
};

class plControlEventMsg;

class plCameraBrain1_Drive : public plCameraBrain1
{
protected:
	hsPoint3	fDesiredPosition;
	hsPoint3	fFacingTarget;
	hsBool		bUseDesiredFacing;
	hsScalar	deltaX;
	hsScalar	deltaY;
	hsBool		bDisregardY; // these are here to prevent
	hsBool		bDisregardX; // the camera from jumping when the mouse cursor recenters / wraps around.
	hsVector3	fUp;

public:

	plCameraBrain1_Drive();
	plCameraBrain1_Drive(plCameraModifier1* pMod);
	~plCameraBrain1_Drive();

	static void SetSensitivity(hsScalar f) { fTurnRate = f; }
	
	CLASSNAME_REGISTER( plCameraBrain1_Drive );
	GETINTERFACE_ANY( plCameraBrain1_Drive, plCameraBrain1 );

	virtual void		Update(hsBool forced = false);
	virtual hsBool		MsgReceive(plMessage* msg);
	virtual void Push(hsBool recenter = true);
	virtual void Pop();

	static hsScalar	fAcceleration;
	static hsScalar	fDeceleration;
	static hsScalar	fMaxVelocity;
	static hsScalar	fTurnRate;
};


class plCameraBrain1_Avatar : public plCameraBrain1
{
public:
	
	plCameraBrain1_Avatar();
	plCameraBrain1_Avatar(plCameraModifier1* pMod);
	~plCameraBrain1_Avatar();
	
	CLASSNAME_REGISTER( plCameraBrain1_Avatar );
	GETINTERFACE_ANY( plCameraBrain1_Avatar, plCameraBrain1 );


	virtual void		Update(hsBool forced = false);
	virtual hsBool		MsgReceive(plMessage* msg);
	
	virtual void		CalculatePosition();				
	hsVector3	GetOffset() { return fOffset; }	
	void		SetOffset(hsVector3 pt) { fOffset = pt; }

	virtual void Read(hsStream* stream, hsResMgr* mgr);
	virtual void Write(hsStream* stream, hsResMgr* mgr);
	
	virtual hsBool	GetFaded() { return fFaded; }
	virtual hsBool	SetFaded(hsBool b) { fFaded = b; return true; }

	virtual void Pop();
	virtual void Push(hsBool recenter = true);

protected:
	void ISendFadeMsg(hsBool fade);
	void IHandleObstacle();

	hsPoint3			fHitPoint;
	hsVector3			fOffset;
	hsVector3			fHitNormal;
	hsBool				bObscured;
	hsBool				fFaded;
	plSceneObject*		fObstacle;
};

class plCameraBrain1_FirstPerson : public plCameraBrain1_Avatar
{
public:
	
	plCameraBrain1_FirstPerson();
	plCameraBrain1_FirstPerson(plCameraModifier1* pMod);
	~plCameraBrain1_FirstPerson();
	
	CLASSNAME_REGISTER( plCameraBrain1_FirstPerson );
	GETINTERFACE_ANY( plCameraBrain1_FirstPerson, plCameraBrain1_Avatar );
	
	virtual void CalculatePosition();				
	virtual void Push(hsBool recenter = true);
	virtual void Pop();
	virtual hsBool		MsgReceive(plMessage* msg);
	
	// for console hack
	static hsBool fDontFade;
protected:
	plSceneObject* fPosNode;
	

};


class plCameraBrain1_Fixed : public plCameraBrain1
{
public:
	
	plCameraBrain1_Fixed();
	plCameraBrain1_Fixed(plCameraModifier1* pMod);
	~plCameraBrain1_Fixed();
	
	CLASSNAME_REGISTER( plCameraBrain1_Fixed );
	GETINTERFACE_ANY( plCameraBrain1_Fixed, plCameraBrain1 );

	void SetTargetPoint(plCameraModifier1* pt) { fTargetPoint = pt; }

	virtual void	Update(hsBool forced = false);
	void			CalculatePosition();				
	virtual hsBool MsgReceive(plMessage* msg);
	virtual void Read(hsStream* stream, hsResMgr* mgr);
	virtual void Write(hsStream* stream, hsResMgr* mgr);

private:
	plCameraModifier1*	fTargetPoint;

};

//
// circle cam brain
//
class plCameraBrain1_Circle : public plCameraBrain1_Fixed
{

public:
	enum CircleFlags
	{
		kLagged			= 0x01,
		kAbsoluteLag	= (0x02 | kLagged),
		kFarthest		= 0x04,
		kTargetted		= 0x08,
		kHasCenterObject = 0x10,
		kPOAObject		= 0x20,
		kCircleLocalAvatar = 0x40,
	};
protected:
	UInt32			fCircleFlags;
	hsPoint3		fCenter;
	plSceneObject*	fCenterObject;	// optional, use instead of fCenter
	hsScalar		fRadius;
	hsScalar		fCurRad, fGoalRad;	// Radians
	plSceneObject*	fPOAObj; // in this case the subject is who we stay close to/away from
	hsScalar		fCirPerSec;

	hsPoint3	IGetClosestPointOnCircle(const hsPoint3* toThisPt);
public:
	plCameraBrain1_Circle(); 
	plCameraBrain1_Circle(plCameraModifier1* pMod); 
	~plCameraBrain1_Circle();

	CLASSNAME_REGISTER( plCameraBrain1_Circle );
	GETINTERFACE_ANY( plCameraBrain1_Circle, plCameraBrain1_Fixed );

	
    virtual void Read(hsStream *stream, hsResMgr* mgr);
    virtual void Write(hsStream *stream, hsResMgr* mgr);
	virtual hsPoint3	MoveTowardsFromGoal(const hsPoint3* fromGoal, double secs, hsBool warp = false);
	virtual void		Update(hsBool forced = false);
	virtual hsBool		MsgReceive(plMessage* msg);
	
	UInt32 GetCircleFlags() { return fCircleFlags; }
	hsPoint3* GetCenter() { return &fCenter; }		// use GetCenterPoint
	hsPoint3  GetCenterPoint();
	hsScalar GetRadius() { return fRadius; }
	plSceneObject* GetCenterObject() { return fCenterObject; }

	void SetCircumferencePerSec(hsScalar h) { fCirPerSec = h; }
	void SetCircleFlags(UInt32 f) { fCircleFlags|=f; }
	void SetCenter(hsPoint3* ctr) { fCenter = *ctr; }		// Circle lies in the plane z = ctr->z
	void SetRadius(hsScalar radius) { 	fRadius = radius; }
	void SetFarCircleCam(hsBool farType) { if (farType) fCircleFlags |= kFarthest; else fCircleFlags &= ~kFarthest; }
	void SetCenterObjectKey(plKey k);
	void SetPOAObject(plSceneObject* pObj) { fPOAObj = pObj; }

};


#endif