///////////////////////////////////////////////////////////////////////////////
//
// File: Asura_CFX_Effect.h
//
// Created by Martin G.Harris (10/10/2007)
// 
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
// Classes to implement effects for the collision FX system.
//
// Note: Generally most projects will want to derive their own effect
// from Asura_CFX_Effect_PFX_Sound_Event, overriding UpdateParameters
// to support changes to the parameters (hashes, volumes etc) as the
// effect plays. This class can support both impact effects (generally
// non-looping sound events with a short duration, possibly combined
// with a PFX to generate sparks/splashes etc) and sliding effects
// (generally looping sound events).
//
///////////////////////////////////////////////////////////////////////////////

#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef __ASURA_CFX_EFFECT_H__
#define __ASURA_CFX_EFFECT_H__

// Only compile this code if the symbol is defined.
#ifdef ASURA_USE_COLLISIONFX

#include "Core/Asura_Maths.h"

// Forward declarations
class Asura_CollisionFX_Entry;
class Asura_Physics_CollisionFX_Report;
class Asura_CFX_PFX_Handle;
class Asura_CFX_SE_Handle;


///////////////
// Constants //
///////////////
// Default time (in seconds) that we track collisions for before forgetting about them.
const float fASURA_CFX_DESTROY_DELAY = 2.0f;

// Default minimum/maximum repeat delays (in seconds).
const float fASURA_CFX_MIN_REPEAT_DELAY = 0.3f;
const float fASURA_CFX_MAX_REPEAT_DELAY = 0.5f;


///////////
// Flags //
///////////

// Update the SFX/PFX position with each subsequent collision.
#define ASURA_CFX_FLAG_UPDATE_POSITION		0x00000001

// Update the PFX direction with each subsequent collision.
#define ASURA_CFX_FLAG_UPDATE_DIRECTION		0x00000002

// When there is a subsequent collision, restart the effect if the current one has finished.
#define ASURA_CFX_FLAG_RESTART_IF_FINISHED	0x00000004

// When there is a subsequent collision, restart the effect if the delay has expired
// (measured from the time that the current effect was started).
// Delay is randomly chosen between specified minimum and maximum repeat times.
// This will stop the current effect if it is still running.
#define ASURA_CFX_FLAG_RESTART_AFTER_DELAY	0x00000008

// For sliding collisions, restart the effect if it has slid on to a new surface type,
// (assuming that the UpdateParameters function has returned true). Otherwise just
// ignore the new surface type.
#define ASURA_CFX_FLAG_RESTART_SLIDE_ON_TO_NEW_SURFACE 0x00000010

// When there is a subsequent collision, restart the effect if the delay has expired
// (measured from the time that the current effect was started), but only if the current
// effect has finished.
// Delay is randomly chosen between specified minimum and maximum repeat times.
#define ASURA_CFX_FLAG_RESTART_AFTER_DELAY_IF_FINISHED	(ASURA_CFX_FLAG_RESTART_IF_FINISHED | ASURA_CFX_FLAG_RESTART_AFTER_DELAY)

// Align decals with their top in the direction of the relative velocity.
// Useful for doing scratch marks that need to be parallel to the direction of motion.
#define ASURA_CFX_FLAG_ALIGN_DECAL_TOP_WITH_VELOCITY	0x00000020

// Effect local to the current machine. Don't try to replicate it on remote-client machines.
#define ASURA_CFX_LOCAL_EFFECT				0x00000040

// Typical default values of the flags.
// This combination should work well for both sliding and impact collisions.
#define ASURA_CFX_FLAG_DEFAULT		(ASURA_CFX_FLAG_UPDATE_POSITION | ASURA_CFX_FLAG_RESTART_AFTER_DELAY_IF_FINISHED | ASURA_CFX_FLAG_RESTART_SLIDE_ON_TO_NEW_SURFACE)


//////////////////////////
// Internal state flags //
//////////////////////////
// Don't try to pass these flags in to the CFX effect, as they will be masked out.

// Internal flag, indicating whether the effect has been started.
// Mostly relevant for sliding collisions for which the effect starts and stops.
#define ASURA_CFX_FLAG_STATE_STARTED				0x01000000

// Internal flag, indicating that this is a sliding effect.
#define ASURA_CFX_FLAG_STATE_SLIDING				0x02000000

// Internal flags, indicating that an update message needs to be sent to remote client
// machines, because some data has changed.
#define ASURA_CFX_FLAG_STATE_CREATED_SE				0x04000000
#define ASURA_CFX_FLAG_STATE_CREATED_PFX			0x08000000
#define ASURA_CFX_FLAG_STATE_UPDATED_POSITION		0x10000000
#define ASURA_CFX_FLAG_STATE_UPDATED_NORMAL			0x20000000
#define ASURA_CFX_FLAG_STATE_UPDATED_VOLUME			0x40000000

// Mask for created/updated state flags.
#define ASURA_CFX_FLAG_STATE_CREATEDUPDATED_MASK	0x7c000000

// Mask for the internal state flags.
#define ASURA_CFX_FLAG_STATE_MASK					0xff000000


//////////////////////
// Asura_CFX_Effect //
//////////////////////
// Interface for collision FX effects, with some code to handle repeat times.

// For the first collision Start() is called.
// For subsequent collisions, Update() is called, and this will call Restart()
// if the effect is to be restarted.

// UpdateParameters is called when a finished/stopped effect restarts, or when
// a sliding entity has another collision (after any specified delay has expired).
// Projects may wish to override UpdateParameters to check whether the collision
// has slid on to a new surface type, or to update sound volume scales etc.

class Asura_CFX_Effect
{
public:
	Asura_CFX_Effect(u_int uFlags);
	
	virtual ~Asura_CFX_Effect() {}

	// First collision has happened or restarting effect.
	virtual void Start(const Asura_Physics_CollisionFX_Report& xCollInfo) = 0;

	// Subsequent collision has happened.
	virtual void Update(const Asura_Physics_CollisionFX_Report& xCollInfo) = 0;

	// Collision entry has been removed or restarting effect.
	virtual void Stop(bool bImmediate) = 0;

	// Called if a subsequent collision is restarting the effect.
	void Restart(const Asura_Physics_CollisionFX_Report& xCollInfo)
	{
		Stop(false);
		Start(xCollInfo);
	}

	// Update the sound event and PFX hash parameters, sound volume and any project parameters,
	// if necessary. Return true if we need to restart the effect (e.g. surface type has changed).
	// Override this in project code to allow effects to change when the entity slides on to a
	// different surface type.
	virtual bool UpdateParameters(const Asura_Physics_CollisionFX_Report& xCollInfo)
	{
		return false;
	}

	// Return the time that the collision entry persists after the last collision.
	inline float GetDestroyDelay() const { return m_fDestroyDelay; }

	// Set the destroy time.
	void SetDestroyDelay(float fDestroyTime)  { m_fDestroyDelay = fDestroyTime; }

	// Should we update positions and orientations for the effects?
	bool GetUpdatePosition() const  { return (m_uFlags & ASURA_CFX_FLAG_UPDATE_POSITION) != 0; }
	bool GetUpdateDirection() const { return (m_uFlags & ASURA_CFX_FLAG_UPDATE_DIRECTION) != 0; }
	
	// Should the decal top direction be aligned with the relative velocity?
	bool GetAlignDecalTopWithVelocity() const   { return (m_uFlags & ASURA_CFX_FLAG_ALIGN_DECAL_TOP_WITH_VELOCITY) != 0; }

	// Should we restart effects that have finished on subsequent collision?
	bool GetShouldRestartIfFinished() const { return (m_uFlags & ASURA_CFX_FLAG_RESTART_IF_FINISHED) != 0; }

	// Should we restart effects after a random delay on subsequent collision?
	bool GetShouldRestartAfterDelay() const { return (m_uFlags & ASURA_CFX_FLAG_RESTART_AFTER_DELAY) != 0; }

	// Is the effect local (in which case it shouldn't be sent to remote client machines)?
	bool GetLocalEffect() const { return (m_uFlags & ASURA_CFX_LOCAL_EFFECT) != 0; }

	// Set the flags, but preserve the state flags.
	void SetFlags(u_int uFlags);

	// Returns true if Start() has been called, but Stop() has not.
	// NOTE: this doesn't necessarily mean that an effect is playing (e.g. a
	// non-looping effect may have finished, or the effect may not have been found).
	bool GetStartedState() const { return (m_uFlags & ASURA_CFX_FLAG_STATE_STARTED) != 0; }

	// Is this a sliding effect?
	bool GetSlidingState() const { return (m_uFlags & ASURA_CFX_FLAG_STATE_SLIDING) != 0; }

	// Set the sliding state.
	void SetSlidingState() { m_uFlags |= ASURA_CFX_FLAG_STATE_SLIDING; }

	// Flags related to remote client effect messaging, indicating that data has changed.
	void SetCreatedSEState()		{ m_uFlags |= ASURA_CFX_FLAG_STATE_CREATED_SE; }
	void SetCreatedPFXState()		{ m_uFlags |= ASURA_CFX_FLAG_STATE_CREATED_PFX; }
	void SetUpdatedPositionState()	{ m_uFlags |= ASURA_CFX_FLAG_STATE_UPDATED_POSITION; }
	void SetUpdatedNormalState()	{ m_uFlags |= ASURA_CFX_FLAG_STATE_UPDATED_NORMAL; }
	void SetUpdatedVolumeState()	{ m_uFlags |= ASURA_CFX_FLAG_STATE_UPDATED_VOLUME; }

	bool GetCreatedSEState()		{ return (m_uFlags & ASURA_CFX_FLAG_STATE_CREATED_SE) != 0; }
	bool GetCreatedPFXState()		{ return (m_uFlags & ASURA_CFX_FLAG_STATE_CREATED_PFX) != 0; }
	bool GetUpdatedPositionState()	{ return (m_uFlags & ASURA_CFX_FLAG_STATE_UPDATED_POSITION) != 0; }
	bool GetUpdatedNormalState()	{ return (m_uFlags & ASURA_CFX_FLAG_STATE_UPDATED_NORMAL) != 0; }
	bool GetUpdatedVolumeState()	{ return (m_uFlags & ASURA_CFX_FLAG_STATE_UPDATED_VOLUME) != 0; }

	void ClearCreatedUpdatedFlags() { m_uFlags &= ~ASURA_CFX_FLAG_STATE_CREATEDUPDATED_MASK; }

	u_int GetStateFlags() const		{ return m_uFlags & ASURA_CFX_FLAG_STATE_MASK; }

	// Set the minimum and maximum delays between repeating the effect.
	// Only used if ASURA_CFX_FLAG_RESTART_AFTER_DELAY is set.
	void SetMinMaxRepeatDelay(float fMinRepeatDelay, float fMaxRepeatDelay)
	{
		m_fMinRepeatDelay = fMinRepeatDelay;
		m_fMaxRepeatDelay = fMaxRepeatDelay;
	}

	void SetLastFaceID(const Asura_Physics_CollisionFX_Report& xCollInfo);

	virtual Asura_CFX_PFX_Handle* GetPFXHandle() { return 0; }
	virtual Asura_CFX_SE_Handle*  GetSEHandle()  { return 0; }

	const Asura_Vector_3& GetLastPosition() const { return m_xLastPosition; }
	void SetLastPosition(const Asura_Physics_CollisionFX_Report& xCollInfo);

	// Functions for remote client effects...
	// Start (or restart) the remote client effect.
	virtual void StartClient(const Asura_Vector_3& xPosition, const Asura_Vector_3& xNormal) {}
	
	// Update some or all of the parameters for remote clients.
	virtual void UpdateClient(const Asura_Vector_3* pxPosition, const Asura_Vector_3* pxNormal, const float* pfVolumeScale) {}

	// Stop a remote client effect.
	virtual void StopClient() {}

protected:
	void SetNextRepeatTime();

	bool ShouldRestart(bool bPlaying) const;
	bool HasRepeatDelayExpired() const;

	// Set or clear the started state flag.
	void SetStartedState(bool bStarted);

	// Set the corresponding entry.
	void SetEntry(const Asura_CollisionFX_Entry* pxEntry) { m_pxEntry = pxEntry; }

	// Check whether we should restart the effect.
	// Return true, if we have restarted the effect.
	bool CheckForRestart(const Asura_Physics_CollisionFX_Report& xCollInfo, bool bPlaying);

protected:
	const Asura_CollisionFX_Entry* m_pxEntry; // Parent entry.

	float m_fDestroyDelay;	// Time that the entry persists after the last collision.
	u_int m_uFlags;			// Flags taken from ASURA_CFX_FLAG_*

	// Minimum and maximum repeat delays are only used if ASURA_CFX_FLAG_RESTART_AFTER_DELAY is set.
	float m_fMinRepeatDelay;
	float m_fMaxRepeatDelay;
	
	// Time at which the next repeat of the effect is allowed; only used
	// if ASURA_CFX_FLAG_RESTART_AFTER_DELAY is set.
	float m_fNextRepeatTime;

	// Polygon face ID used for last effect. Used to help determine
	// whether the entity has slid on to a new surface type.
	u_int m_uLastFaceID;

	// Last position at which a collision occurred.
	Asura_Vector_3 m_xLastPosition;

	// Friends
	friend class Asura_CollisionFX_Entry;
};


//////////////////////////
// Asura_CFX_PFX_Handle //
//////////////////////////
// Wrapper for PFX hash, handle and flags.
class Asura_CFX_PFX_Handle
{
public:
	Asura_CFX_PFX_Handle(Asura_Hash_ID uPFXHash, u_int uFlags);

	void Start (Asura_CFX_Effect& xParent, const Asura_Physics_CollisionFX_Report& xCollInfo);
	void Start (Asura_CFX_Effect& xParent, const Asura_Vector_3& xPosition, const Asura_Vector_3& xNormal);
	void Update(Asura_CFX_Effect& xParent, const Asura_Physics_CollisionFX_Report& xCollInfo);
	void Update(Asura_CFX_Effect& xParent);
	void Stop  (bool bImmediate);

	bool IsPlaying() const;

	// Set or clear the specified PFX flag(s), leaving others unchanged.
	void SetFlag  (u_int uFlag) { m_uPFXFlags |= uFlag;  }
	void ClearFlag(u_int uFlag) { m_uPFXFlags &= ~uFlag; }

	const Asura_Vector_3& GetLastNormal() const { return m_xLastNormal; }

public:
	Asura_Hash_ID m_uPFXHash;	// PFX entity effect hash ID.
	u_int m_uPFXFlags;			// PFX entity flags.
	u_int m_uPFXEffectID;		// PFX entity effect handle.
	Asura_Vector_3 m_xLastNormal;	// Last used normal direction
};


/////////////////////////
// Asura_CFX_SE_Handle //
/////////////////////////
// Wrapper for sound event hash, handle and volume scale.
class Asura_CFX_SE_Handle
{
public:
	Asura_CFX_SE_Handle(Asura_Hash_ID uSEHash, float fVolumeScale);

	void Start (Asura_CFX_Effect& xParent, const Asura_Physics_CollisionFX_Report& xCollInfo);
	void Start (Asura_CFX_Effect& xParent, const Asura_Vector_3& xPosition);
	void Update(Asura_CFX_Effect& xParent, const Asura_Physics_CollisionFX_Report& xCollInfo);
	void Update(Asura_CFX_Effect& xParent, const Asura_Vector_3& xPosition);
	void Stop  (bool bImmediate);

	bool IsPlaying() const;

	// Set the volume scale and optionally update the playing sound event.
	void SetVolumeScale(Asura_CFX_Effect& xParent, float fScale, bool bUpdateSE);
	float GetVolumeScale() const { return m_fVolumeScale; }

public:
	Asura_Hash_ID m_uSEHash;		// Sound event hash ID.
	Asura_Handle m_iSoundHandle;	// A sound-event handle.
private:
	float m_fVolumeScale;			// Volume scale in range [0, 1]
};

////////////////////////////
// Asura_CFX_Decal_Handle //
////////////////////////////
// Wrapper for decal.
#ifdef ASURA_USE_DECAL_MANAGER
class Asura_CFX_Decal_Handle
{
public:
	Asura_CFX_Decal_Handle(Asura_Hash_ID uDecalSetHash,
						   int iDecalIndex,
						   float fScale = 1.0f,
						   float fOrientation = 0.0f);

	void Start(const Asura_CFX_Effect& xParent, const Asura_Physics_CollisionFX_Report& xCollInfo);

public:
	Asura_Hash_ID m_uDecalSetHash;		// Decal set hash.
	int m_iDecalIndex;					// Index within set.
	float m_fScale;
	float m_fOrientation;
};
#endif  //ASURA_USE_DECAL_MANAGER

//////////////////////////
// Asura_CFX_Effect_PFX //
//////////////////////////
// Implementation for entity effect PFX.
class Asura_CFX_Effect_PFX : public Asura_CFX_Effect
{
public:
	Asura_CFX_Effect_PFX(Asura_Hash_ID uPFXHash,
						 u_int uFlags);

	virtual void Start (const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Update(const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Stop  (bool bImmediate);

	Asura_Hash_ID GetPFXHash() const { return m_xPFXHandle.m_uPFXHash; }

	virtual Asura_CFX_PFX_Handle* GetPFXHandle() { return &m_xPFXHandle; }

protected:
	Asura_CFX_PFX_Handle m_xPFXHandle;
};


//////////////////////////////////
// Asura_CFX_Effect_Sound_Event //
//////////////////////////////////
// Implementation for sound events.
class Asura_CFX_Effect_Sound_Event : public Asura_CFX_Effect
{
public:
	Asura_CFX_Effect_Sound_Event(Asura_Hash_ID uSEHash,
								 u_int uFlags,
								 float fVolumeScale = 1.0f);

	virtual void Start (const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Update(const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Stop  (bool bImmediate);

	Asura_Hash_ID GetSEHash() const { return m_xSEHandle.m_uSEHash; }

	void SetVolumeScale(float fScale, bool bUpdateSE) { m_xSEHandle.SetVolumeScale(*this, fScale, bUpdateSE); }
	float GetVolumeScale() const { return m_xSEHandle.GetVolumeScale(); }

	virtual Asura_CFX_SE_Handle* GetSEHandle() { return &m_xSEHandle; }

protected:
	Asura_CFX_SE_Handle m_xSEHandle;
};


//////////////////////////////////////
// Asura_CFX_Effect_PFX_Sound_Event //
//////////////////////////////////////
// Implementation for combined PFX and sound event.
class Asura_CFX_Effect_PFX_Sound_Event : public Asura_CFX_Effect
{
public:
	// Pass in the relevant PFX and sound event hashes, and volume parameters.
	Asura_CFX_Effect_PFX_Sound_Event(Asura_Hash_ID uPFXHash,
									 Asura_Hash_ID uSEHash,
									 u_int uFlags = ASURA_CFX_FLAG_DEFAULT,
									 float fVolumeScale = 1.0f);

	virtual void Start (const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Update(const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Stop  (bool bImmediate);

	Asura_Hash_ID GetPFXHash() const { return m_xPFXHandle.m_uPFXHash; }
	Asura_Hash_ID GetSEHash() const  { return m_xSEHandle.m_uSEHash; }

	void SetPFXHash(Asura_Hash_ID uHash) { m_xPFXHandle.m_uPFXHash = uHash; }
	void SetSEHash(Asura_Hash_ID uHash)  { m_xSEHandle.m_uSEHash   = uHash; }

	// Set or clear the specified PFX flag(s), leaving others unchanged.
	void SetPFXFlag  (u_int uFlag) { m_xPFXHandle.SetFlag(uFlag); }
	void ClearPFXFlag(u_int uFlag) { m_xPFXHandle.ClearFlag(uFlag); }

	void SetVolumeScale(float fScale, bool bUpdateSE) { m_xSEHandle.SetVolumeScale(*this, fScale, bUpdateSE); }
	float GetVolumeScale() const { return m_xSEHandle.GetVolumeScale(); }

	virtual Asura_CFX_PFX_Handle* GetPFXHandle() { return &m_xPFXHandle; }
	virtual Asura_CFX_SE_Handle*  GetSEHandle()  { return &m_xSEHandle; }

protected:
	Asura_CFX_PFX_Handle m_xPFXHandle;
	Asura_CFX_SE_Handle  m_xSEHandle;
};

///////////////////////////////////
// Asura_CFX_Effect_PFX_SE_Decal //
///////////////////////////////////
// Implementation for combined PFX, sound event and decal.

#ifdef ASURA_USE_DECAL_MANAGER
class Asura_CFX_Effect_PFX_SE_Decal : public Asura_CFX_Effect_PFX_Sound_Event
{
public:
	Asura_CFX_Effect_PFX_SE_Decal(Asura_Hash_ID uPFXHash,
								  Asura_Hash_ID uSEHash,
								  Asura_Hash_ID uDecalSetHash,
								  int iDecalIndex,
								  u_int uFlags = ASURA_CFX_FLAG_DEFAULT,
								  float fVolumeScale = 1.0f);

	virtual void Start (const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Update(const Asura_Physics_CollisionFX_Report& xCollInfo);
	virtual void Stop  (bool bImmediate);

	Asura_Hash_ID GetDecalSetHash() const { return m_xDecalHandle.m_uDecalSetHash; }
	int GetDecalIndex() const			  { return m_xDecalHandle.m_iDecalIndex; }

	void SetDecalSetHash(Asura_Hash_ID uHash)	{ m_xDecalHandle.m_uDecalSetHash = uHash; }
	void SetDecalIndex(int iIndex)				{ m_xDecalHandle.m_iDecalIndex   = iIndex; }

protected:
	Asura_CFX_Decal_Handle m_xDecalHandle;
};
#endif //ASURA_USE_DECAL_MANAGER


//////////////////////////////////////////
// Asura_CFX_Effect_PFX_SE_RemoteClient //
//////////////////////////////////////////
// Implementation for combined PFX and sound event on a remote client machine.
class Asura_CFX_Effect_PFX_SE_RemoteClient : public Asura_CFX_Effect_PFX_Sound_Event
{
public:
	Asura_CFX_Effect_PFX_SE_RemoteClient(Asura_Hash_ID uPFXHash,
										 Asura_Hash_ID uSEHash,
										 u_int uFlags,
										 float fVolumeScale)
	: Asura_CFX_Effect_PFX_Sound_Event(uPFXHash, uSEHash, uFlags, fVolumeScale)
	{}

	// Start (or restart) the remote client effect.
	virtual void StartClient(const Asura_Vector_3& xPosition, const Asura_Vector_3& xNormal);
	
	// Update some or all of the parameters.
	virtual void UpdateClient(const Asura_Vector_3* pxPosition, const Asura_Vector_3* pxNormal, const float* pfVolumeScale);

	// Stop a remote client effect.
	virtual void StopClient();
};

#endif //ASURA_USE_COLLISIONFX

#endif //__ASURA_CFX_EFFECT_H__

