/*************************************************************************
  Crytek Source File.
  Copyright (C), Crytek Studios, 2001-2004.
 -------------------------------------------------------------------------
  $Id$
  $DateTime$
  Description: Simple Actor implementation
  
 -------------------------------------------------------------------------
  History:
  - 7:10:2004   14:46 : Created by Márcio Martins

*************************************************************************/
#ifndef __Actor_H__
#define __Actor_H__

#if _MSC_VER > 1000
# pragma once
#endif

#include <IActorSystem.h>
#include <IActionMapManager.h>

#include "IAgent.h" // for stance enum
#include "IAnimationGraph.h"
#include "IAnimatedCharacter.h"
#include "IMovementController.h"
#include "Intensity.h"

struct SActorFrameMovementParams
{
	SActorFrameMovementParams() : 
		desiredVelocity(ZERO), 
		deltaAngles(ZERO),
		lookTarget(ZERO),
		aimTarget(ZERO),
		lookIK(false), 
		aimIK(false), 
		stance(STANCE_NULL),
		jump(false),
		sprint(0.0f)
	{
	}

	// desired velocity for this frame (meters/second)
	Vec3 desiredVelocity;
	// desired lean
	float desiredLean;
	// desired turn
	Ang3 deltaAngles;
	Vec3 lookTarget;
	Vec3 aimTarget;
	// look target
	bool lookIK;
	// aim target
	bool aimIK;
	bool jump;
	float sprint;
	// stance
	EStance stance;
};

struct IActorMovementController : public IMovementController
{
	virtual void Reset() = 0;
	// return true if params was filled out, false if not
	virtual bool Update( float frameTime, SActorFrameMovementParams& params ) = 0;
	virtual void Release() = 0;
};

//FIXME:not sure to put this here
#define ZEROG_AREA_ID PHYS_FOREIGN_ID_USER+1

// Collision ray piercability to ignore leaves and other things
#define COLLISION_RAY_PIERCABILITY 10

enum EActorPhysicalization
{
	eAP_Alive,
	eAP_Ragdoll
};

enum EBonesID
{
	BONE_BIP01 = 0,
	BONE_SPINE,
	BONE_SPINE2,
	BONE_SPINE3,
	BONE_HEAD,
	BONE_EYE_R,
	BONE_EYE_L,
	BONE_WEAPON,
	BONE_FOOT_R,
	BONE_FOOT_L,	
	BONE_LEFT_HAND,
	BONE_RIGHT_HAND,
	BONE_ID_NUM
};

//represent the key status of the actor
#define ACTION_JUMP						(1<<0)
#define ACTION_CROUCH					(1<<1)
#define ACTION_PRONE					(1<<2)
#define ACTION_SPRINT					(1<<3)
#define ACTION_LEANLEFT				(1<<4)
#define ACTION_LEANRIGHT			(1<<5)
#define ACTION_GYROSCOPE			(1<<6)
#define ACTION_GRAVITYBOOTS		(1<<7)
#define ACTION_RELAXED				(1<<8)
#define ACTION_AIMING					(1<<9)
#define ACTION_STEALTH				(1<<10)

struct IVehicle;
struct IInventory;

struct SActorParams
{
	float maxGrabMass;
	float maxGrabVolume;
	bool nanoSuitActive;

	Vec3	viewPivot;
	float	viewDistance;
	float	viewHeightOffset;

	float	viewFoVScale;
	float	viewSensitivity;

	Vec3	vLimitDir;
	float	vLimitRangeH;
	float	vLimitRangeV;

	float headBobbingMultiplier;
	float	weaponBobbingMultiplier;
	float	weaponInertiaMultiplier;

	float speedMultiplier;

	SActorParams()
	{
		viewPivot.Set(0,0,0);
		viewDistance = 0;
		viewHeightOffset = 0;

		vLimitDir.Set(0,0,0);
		vLimitRangeH = 0.0f;
		vLimitRangeV = 0.0f;

		viewFoVScale = 1.0f;
		viewSensitivity = 1.0f;

		headBobbingMultiplier = 1.0f;
		weaponInertiaMultiplier = 1.0f;
		weaponBobbingMultiplier = 1.0f;

		speedMultiplier = 1.0f;
	}
};

struct SActorStats
{
	float inAir;//double purpose, tells if the actor is in air and for how long
	float onGround;//double purpose, tells if the actor is on ground and for how long

	bool isRagDoll;
	int movementDir;
	float inFiring;
	Vec3 forceUpVector;    
};

struct SStanceInfo
{
	//dimensions
	float heightCollider;
	float heightPivot;

	Vec3 size;

	//view
	Vec3 viewOffset;

	//movement
	float maxSpeed;

	//misc
	char name[32];

	Vec3 modelOffset;

	SStanceInfo()
	{
		heightCollider = 0;
		heightPivot = 0;

		size.Set(0.5f,0.5f,0.5f);
		viewOffset.Set(0,0,0);
		modelOffset.Set(0,0,0);

		maxSpeed = 0.001f;

		strcpy(name,"null");
	}
};

#define IKLIMB_RIGHTHAND (1<<0)
#define IKLIMB_LEFTHAND (1<<1)

struct SIKLimb
{
	int flags;

	int rootBoneID;
	int endBoneID;
	int middleBoneID;//optional for custom IK

	Vec3 goalWPos;
	Vec3 currentWPos;

	Vec3 goalNormal;

	//limb local position in the animation
	Vec3 lAnimPos;
	Vec3 lAnimPosLast;

	char name[64];

	float recoverTime;//the time to go back to the animation pose
	float recoverTimeMax;

	float blendTime;
	float blendTimeMax;

	int blendID;

	int characterSlot;

	bool keepGoalPos;

	SIKLimb()
	{
		memset(this,0,sizeof(SIKLimb));
	}

	void SetLimb(int slot,const char *limbName,int rootID,int midID,int endID,int iFlags)
	{
		rootBoneID = rootID;
		endBoneID = endID;
		middleBoneID = midID;

		strncpy(name,limbName,64);
		name[63] = 0;

		blendID = -1;

		flags = iFlags;

		characterSlot = slot;
	}

	void SetWPos(IEntity *pOwner,const Vec3 &pos,const Vec3 &normal,float blend,float recover,int requestID);
	void Update(IEntity *pOwner,float frameTime);
};

typedef std::vector<SIKLimb> TIKLimbs;

struct SGrabStats
{
	#define GRAB_MAXLIMBS 4

	EntityId grabId;
	EntityId dropId;
	
	Vec3 lHoldPos;//holding position relative to the entity matrix

	Vec3 wHoldPos;//optional, if set is used instead of lHoldPos
	Quat wHoldQuat;//same as above, for rotation

	Vec3 throwVector;

	Quat additionalRotation;

	int limbId[GRAB_MAXLIMBS];
	int limbNum;

	float resetFlagsDelay;//this is used to reset the grabbed object collision flags after its thrown
	float grabDelay;
	float throwDelay;

	float maxDelay;//used for both grabDelay and throwDelay

	float followSpeed;

	bool useIKRotation;

	unsigned int collisionFlags;

	//animation
	bool usingAnimation;

	char grabAnimState[64];
	char throwAnimState[64];

	float releaseIKTime;

	int followBoneID;
	Vec3 followBoneWPos;

	Vec3 animationLimbOffset;
	Vec3 animDummyOfs;
  
	SGrabStats()
	{
		memset(this,0,sizeof(SGrabStats));

		lHoldPos.Set(0,0.5f,0.25f);
		additionalRotation.SetIdentity();
	}

	void Reset()
	{
		grabId = 0;
		wHoldPos.Set(0,0,0);
		wHoldQuat.SetIdentity();
		followBoneWPos.Set(0,0,0);
	}
};


class CItem;
class CWeapon;
class CActor :
	public CGameObjectExtensionHelper<CActor, IActor>,
	public IGameObjectView,
	public IGameObjectPhysics
{
public:
	struct ClDBGValidateInventoryParams
	{
		uint count;
		void SerializeWith(TSerialize ser)
		{
			ser.Value("Count", count);
		}
	};
	struct ItemIdParam
	{
		ItemIdParam(): itemId(0) {};
		ItemIdParam(EntityId item): itemId(item) {};
		void SerializeWith(TSerialize ser)
		{
			ser.Value("itemId", itemId, NSerPolicy::AC_EntityId());
		}
		EntityId itemId;
	};

	struct ReviveParams
	{
		ReviveParams(): pos(0,0,0), rot(0,0,0,1.0f) {};
		ReviveParams(const Vec3 &p, const Ang3 &a): pos(p), rot(Quat::CreateRotationXYZ(a)) {};
		ReviveParams(const Vec3 &p, const Quat &q): pos(p), rot(q) {};
		void SerializeWith(TSerialize ser)
		{
			ser.Value("pos", pos, NSerPolicy::AC_WorldPos());
			ser.Value("rot", rot, NSerPolicy::AC_Orientation(12));
		};

		Vec3 pos;
		Quat rot;
	};

	struct KillParams
	{
		KillParams() {};
		KillParams(bool _cleaninv, bool _ragdoll, EntityId _shooterId, EntityId _weaponId, float _damage, int _material, const Vec3 &_impulse)
		: cleaninv(_cleaninv),
			ragdoll(_ragdoll),
			shooterId(_shooterId),
			weaponId(_weaponId),
			damage(_damage),
			material(_material),
			impulse(_impulse)
		{};

		bool cleaninv;
		bool ragdoll;
		EntityId shooterId;
		EntityId weaponId;
		float damage;
		int material;
		Vec3 impulse;

		void SerializeWith(TSerialize ser)
		{
			ser.Value("cleaninv", cleaninv, NSerPolicy::A_Bool());
			ser.Value("ragdoll", cleaninv, NSerPolicy::A_Bool());
			ser.Value("shooterId", shooterId, NSerPolicy::AC_EntityId());
			ser.Value("weaponId", weaponId, NSerPolicy::AC_EntityId());
			ser.Value("damage", damage, NSerPolicy::AC_Float(0.0f, 500.0f, 8));
			ser.Value("material", material, NSerPolicy::AS_RangedInt32(-1, 255));
			ser.Value("impulse", impulse, NSerPolicy::AC_Vec3(Vec3(-1,-1,-1), Vec3(1,1,1), Vec3i(10,10,10)));
		};
	};
	struct MoveParams
	{
		MoveParams() {};
		MoveParams(const Vec3 &p, const Quat &q): pos(p), rot(q) {};
		void SerializeWith(TSerialize ser)
		{
			ser.Value("pos", pos, NSerPolicy::AC_WorldPos());
			ser.Value("rot", rot, NSerPolicy::AC_Orientation(12));
		}
		Vec3 pos;
		Quat rot;
	};

	DECLARE_CLIENT_RMI_POSTATTACH(ClDBGRMIValidateInventory, ClDBGValidateInventoryParams, eNRT_ReliableOrdered);

	DECLARE_SERVER_RMI_PREATTACH(SvRequestDropItem, ItemIdParam, eNRT_ReliableOrdered);
	DECLARE_SERVER_RMI_PREATTACH(SvRequestPickUpItem, ItemIdParam, eNRT_ReliableOrdered);
	
	DECLARE_CLIENT_RMI_PREATTACH(ClDropItem, ItemIdParam, eNRT_ReliableOrdered);
	DECLARE_CLIENT_RMI_PREATTACH(ClPickUpItem, ItemIdParam, eNRT_ReliableOrdered);

	DECLARE_CLIENT_RMI_PREATTACH(ClRevive, ReviveParams, eNRT_ReliableOrdered);
	DECLARE_CLIENT_RMI_PREATTACH(ClKill, KillParams, eNRT_ReliableOrdered);
	DECLARE_CLIENT_RMI_PREATTACH(ClMoveTo, MoveParams, eNRT_ReliableOrdered);


	static CItem *GetItem(EntityId itemId);
	static CWeapon *GetWeapon(EntityId itemId);
	virtual void SelectNextItem(int direction, bool keepHistory);
	void SelectLastItem(bool keepHistory);
	void SelectItemByName(const char *name, bool keepHistory);
	void SelectItem(EntityId itemId, bool keepHistory);
	void HolsterItem(bool holster);

	void PickUpItem(EntityId itemId, bool sound);
	void DropItem(EntityId itemId);
	
public:
	CActor();
	virtual ~CActor();

	// IActor
	virtual void ProcessEvent(SEntityEvent& event);
	virtual void Release() { delete this; };
	virtual void Serialize( TSerialize ser, unsigned aspects );
	virtual void SetChannelId(uint16 id);

	virtual bool IsClient() const;

	virtual bool Init( IGameObject * pGameObject );
	virtual void InitClient( int channelId ) {};
	virtual void PostInit( IGameObject * pGameObject );
	virtual void PostInitClient(int channelId) {};
	virtual void Update(SEntityUpdateContext& ctx, int updateSlot);
	virtual void UpdateView(SViewParams &viewParams) {};
	virtual void UpdateHUD();

	virtual void SetIKPos(const char *pLimbName, const Vec3& goalPos, int priority);

	//////////////////////////////////////////////////////////////////////////
	// 0/1 right/left arm
	// stores values for the callback, processed in processIKLimbs
	virtual void SetIKArm(const Vec3 &vGoal, int nArm);

	//////////////////////////////////////////////////////////////////////////

	virtual void HandleEvent( const SGameObjectEvent& event );
	virtual void PostUpdate(float frameTime) { assert(false); }

	virtual bool IsThirdPerson() const { return true; };
	// ~IActor

	// IActionListener
	virtual void OnAction(const ActionId& actionId, int activationMode, float value);
	// ~IActionListener

	// IGameObjectPhysics
	virtual bool SetProfile( uint8 profile );
	virtual bool SerializeProfile( TSerialize ser, uint8 profile );
	// ~IGameObjectPhysics

	virtual void ProfileChanged( uint8 newProfile );

	//------------------------------------------------------------------------

	// offset to add to the computed camera angles every frame
	virtual void SetViewAngleOffset(const Vec3 &offset) {};
	virtual Vec3 GetViewAngleOffset() { return Vec3(0, 0, 0); };

	//------------------------------------------------------------------------
	virtual void Revive( bool fromInit = false );
	//physicalization
	virtual void Physicalize();
	virtual void PostPhysicalize();
	virtual void RagDollize();
	//
  virtual int IsGod(){ return 0; }

	//get actor status
	virtual SActorStats *GetActorStats() { return 0; };
	virtual SActorParams *GetActorParams() { return 0; };

	virtual void SetStats(SmartScriptTable &rTable);
	virtual bool GetStats(SmartScriptTable &rTable) {return false;};
	virtual ICharacterInstance *GetFPArms() const { return GetEntity()->GetCharacter(4); };
	//set/get actor params
	virtual void SetParams(SmartScriptTable &rTable,bool resetFirst = false);
	virtual bool GetParams(SmartScriptTable &rTable) { return false; };
	//
	virtual void Freeze(bool frozen, float mass, const char *material);
	//
	virtual void LinkToVehicle(EntityId vehicleId) {};
	virtual void LinkToVehicleRemotely(EntityId vehicleId) {};
	virtual IVehicle *GetLinkedVehicle() const { return NULL; };
  virtual IEntity *GetLinkedEntity() const { return NULL; };

	//for animations
	virtual void PlayAction(const char *action,const char *extension) {};
	//
	virtual void SetMovementTarget(const Vec3 &position,const Vec3 &looktarget,const Vec3 &up,float speed) {};
	//
	virtual void CreateScriptEvent(const char *event,float value,const char *str = NULL);
	virtual void CreateCodeEvent(SmartScriptTable &rTable);
	//
	virtual void CameraShake(float angle,float shift,float duration,float frequency,Vec3 pos,int ID) {};
	//
	virtual void VectorToLocal(Vec3 &v) {};
	//
	virtual void SetAngles(const Ang3 &angles) {};
	virtual Ang3 GetAngles() {return Ang3(0,0,0);};
	virtual void AddAngularImpulse(const Ang3 &angular,float deceleration=0.0f,float duration=0.0f){}
	//
	virtual void SetViewLimits(Vec3 dir,float rangeH,float rangeV) {};
	//sets the object(id) the player is looking at (done by WorldQuery in GameObject)
	virtual void SetViewIntersectionEntity(EntityId id) {m_viewIntersectionEntity = id;}
	virtual void SetHealth( int health );
	void SetMaxHealth( int maxHealth );
	ILINE int32 GetHealth() const { return int32(m_health); }
  ILINE int32 GetMaxHealth() const { return m_maxHealth; }
	virtual void Kill();
	//returns the id of the object the actor is looking at (own actor-id if none)
	virtual EntityId GetViewIntersectionEntity() {return m_viewIntersectionEntity;}
  virtual float GetFrozenAmount() const { return m_frozenAmount; }

	virtual void BindInputs( IAnimationGraph * pGraph );

	//stances
	ILINE virtual EStance GetStance() const 
	{
		if(!m_pAnimatedCharacter)
			return STANCE_NULL;
		int stance = m_pAnimatedCharacter?m_pAnimatedCharacter->GetCurrentStance():STANCE_NULL;
		if (stance < 0 || stance > STANCE_LAST)
			return STANCE_NULL;
		return (EStance)stance;
	}
	ILINE const SStanceInfo *GetStanceInfo(EStance stance) const 
	{
		if (stance > STANCE_LAST)
			return NULL;
		return &m_stances[stance];
	}
	virtual void	SetupStance(EStance stance,SStanceInfo *info);
	//

	// forces the animation graph to select a state
	void QueueAnimationState( const char * state );
	void ChangeAnimGraph( const char *graph );

	//
	virtual int GetBoneID(int ID,int slot = 0) const;
	virtual Vec3 GetLocalEyePos(int slot = 0) const;

	bool CheckZeroG(Vec3 &ZAxis);

	//
	virtual void ProcessBonesRotation(ICharacterInstance *pCharacter,float frameTime);

	virtual void OnPhysicsPreStep(float frameTime){};

	//grabbing
	virtual void GrabObject(EntityId objectId);
	virtual void StartDropObject();
	virtual void DropObject();
	virtual void UpdateGrab(float frameTime);
	Vec3 GetGrabIKPos(IEntity *pGrab,int limbIdx);
	void IgnoreGrabCollision(EntityId eID,unsigned int flags,bool ignore);
	virtual bool CanPickUpObject(IEntity *obj);
	virtual bool CanPickUpObject(float mass, float volume);
	virtual float GetActorStrength() const {return 1.0f*(m_pStrengthScale?m_pStrengthScale->GetFVal():1.0f);}

	//
	virtual void ProcessIKLimbs(ICharacterInstance *pCharacter,float frameTime);

	//IK limbs
	int GetIKLimbIndex(const char *limbName);
	void CreateIKLimb(int characterSlot, const char *limbName, const char *rootBone, const char *midBone, const char *endBone, int flags = 0);

	//
	virtual IMovementController * GetMovementController()
	{
		return m_pMovementController;
	}

	//stances
	virtual void	SetStance(EStance stance);
	virtual void  StanceChanged(EStance last) {};
	//
	
	IAnimationGraphState * GetAnimationGraphState();

	//weapons
	IItem *GetCurrentItem() const;
	IInventory *GetInventory() const;

	//Net
	EntityId NetGetCurrentItem() const;
	void NetSetCurrentItem(EntityId id);

	//AI
	Vec3 GetAIAttentionPos();

	virtual void UpdateFootSteps(float frameTime);

	CIntensity *GetIntensityMonitor() {return m_intensityMonitor;};

protected:
	void DebugMessage( const char * format, ... )
	{
		if (DebugMode())
		{
			char buffer[MAX_WARNING_LENGTH];
			va_list args;
			va_start(args, format);
			vsprintf(buffer, format, args);
			va_end(args);
			PutDebugMessage(buffer);
		}
	}

	void DisplayDebugInfo();
	static bool DebugMode()
	{
		static ICVar * pDebugMode = GetISystem()->GetIConsole()->GetCVar("cl_actordebug");
		return pDebugMode->GetIVal() != 0;
	}

	virtual void UpdateAnimGraph( IAnimationGraphState * pState );

	//movement
	virtual IActorMovementController * CreateMovementController() = 0;
	//

	virtual Vec3 GetModelOffset() const { return GetStanceInfo(GetStance())->modelOffset; }
	
private:
	void PutDebugMessage( const char * msg );

	string m_debugInfo;

protected:
	virtual void SetActorModel();
	void UpdateStance();

	mutable int16 m_boneIDs[BONE_ID_NUM];

	float m_health;
	int32 m_maxHealth;
  
	EStance m_stance;

	SStanceInfo m_stances[STANCE_LAST];

	IAnimatedCharacter *m_pAnimatedCharacter;
	IActorMovementController * m_pMovementController;


	static IItemSystem			*m_pItemSystem;
	static IGameFramework	*m_pGameFramework;

	//
	IAnimationGraph::InputID m_inputHealth;
	IAnimationGraph::InputID m_inputStance;
	IAnimationGraph::InputID m_inputFiring;
	IAnimationGraph::InputID m_inputAction;

	SGrabStats m_grabStats;

	TIKLimbs m_IKLimbs;
  
  float m_frozenAmount; // internal amount. used to leave authority over frozen state at game

	// Intensity-related variables
	CIntensity *m_intensityMonitor;
	ICVar *m_pZoomAmount;
	ICVar *m_pZoomInTime;
	ICVar *m_pMoveZoomTime;
	ICVar *m_pZoomOutTime;
	ICVar	*m_pStrengthScale;

	Vec3 m_lastFootStepPos;
	bool m_rightFoot;
	bool m_bHasHUD;

	//this is the entity the actor is currently looking at
	EntityId m_viewIntersectionEntity;

	float m_hackTime;

	Vec3	m_vIKArmGoal;
	int		m_nIKArm;
};

#endif //__Actor_H__
