//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=====================================================================================//

#include "cbase.h"
#include "particles_simple.h"
#include "citadel_effects_shared.h"
#include "particles_attractor.h"
#include "iefx.h"
#include "dlight.h"
#include "ClientEffectPrecacheSystem.h"
#include "c_te_effect_dispatch.h"
#include "fx_quad.h"

#include "c_ai_basenpc.h"

// For material proxy
#include "ProxyEntity.h"
#include "materialsystem/IMaterial.h"
#include "materialsystem/IMaterialVar.h"

#define NUM_INTERIOR_PARTICLES	8

#define DLIGHT_RADIUS (150.0f)
#define DLIGHT_MINLIGHT (40.0f/255.0f)

class C_NPC_Vortigaunt : public C_AI_BaseNPC
{
	DECLARE_CLASS( C_NPC_Vortigaunt, C_AI_BaseNPC );
	DECLARE_CLIENTCLASS();

public:
	virtual void	OnDataChanged( DataUpdateType_t updateType );
	virtual void	ClientThink( void );
	virtual void	ReceiveMessage( int classID, bf_read &msg );

public:
	bool  m_bIsBlue;           ///< wants to fade to blue
	float m_flBlueEndFadeTime; ///< when to end fading from one skin to another

	bool  m_bIsBlack;    ///< wants to fade to black (networked)
	float m_flBlackFade; ///< [0.00 .. 1.00] where 1.00 is all black. Locally interpolated.
};

IMPLEMENT_CLIENTCLASS_DT( C_NPC_Vortigaunt, DT_NPC_Vortigaunt, CNPC_Vortigaunt )
	RecvPropTime( RECVINFO(m_flBlueEndFadeTime ) ),
	RecvPropBool( RECVINFO(m_bIsBlue) ),
	RecvPropBool( RECVINFO(m_bIsBlack) ),
END_RECV_TABLE()


#define	VORTIGAUNT_BLUE_FADE_TIME			2.25f		// takes this long to fade from green to blue or back
#define VORT_BLACK_FADE_TIME 2.2f	// time to interpolate up or down in fading to black


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : updateType - 
//-----------------------------------------------------------------------------
void C_NPC_Vortigaunt::OnDataChanged( DataUpdateType_t updateType )
{
	BaseClass::OnDataChanged( updateType );

	// start thinking if we need to fade.
	if ( m_flBlackFade != (m_bIsBlack ? 1.0f : 0.0f) )
	{
		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_NPC_Vortigaunt::ClientThink( void )
{
	// Don't update if our frame hasn't moved forward (paused)
	if ( gpGlobals->frametime <= 0.0f )
		return;

	if ( m_bIsBlack )
	{
		// are we done?
		if ( m_flBlackFade >= 1.0f )
		{
			m_flBlackFade = 1.0f;
			SetNextClientThink( CLIENT_THINK_NEVER );
		}
		else // interpolate there
		{
			float lerpQuant = gpGlobals->frametime / VORT_BLACK_FADE_TIME;
			m_flBlackFade += lerpQuant;
			if ( m_flBlackFade > 1.0f )
			{
				m_flBlackFade = 1.0f;
			}
		}
	}
	else 
	{
		// are we done?
		if ( m_flBlackFade <= 0.0f )
		{
			m_flBlackFade = 0.0f;
			SetNextClientThink( CLIENT_THINK_NEVER );
		}
		else // interpolate there
		{
			float lerpQuant = gpGlobals->frametime / VORT_BLACK_FADE_TIME;
			m_flBlackFade -= lerpQuant;
			if ( m_flBlackFade < 0.0f )
			{
				m_flBlackFade = 0.0f;
			}
		}
	}
}

// FIXME: Move to shared code!
#define VORTFX_ZAPBEAM	0
#define VORTFX_ARMBEAM	1

//-----------------------------------------------------------------------------
// Purpose: Receive messages from the server
//-----------------------------------------------------------------------------
void C_NPC_Vortigaunt::ReceiveMessage( int classID, bf_read &msg )
{
	// Is the message for a sub-class?
	if ( classID != GetClientClass()->m_ClassID )
	{
		BaseClass::ReceiveMessage( classID, msg );
		return;
	}
	
	int messageType = msg.ReadByte();
	switch( messageType )
	{
	case VORTFX_ZAPBEAM:
		{
			// Find our attachment point
			unsigned char nAttachment = msg.ReadByte();
			
			// Get our attachment position
			Vector vecStart;
			QAngle vecAngles;
			GetAttachment( nAttachment, vecStart, vecAngles );

			// Get the final position we'll strike
			Vector vecEndPos;
			msg.ReadBitVec3Coord( vecEndPos );

			// Place a beam between the two points
			CNewParticleEffect *pEffect = ParticleProp()->Create( "vortigaunt_beam", PATTACH_POINT_FOLLOW, nAttachment );
			if ( pEffect )
			{
				pEffect->SetControlPoint( 0, vecStart );
				pEffect->SetControlPoint( 1, vecEndPos );
			}
		}
		break;

	case VORTFX_ARMBEAM:
		{
			int nIndex = msg.ReadLong();
			C_BaseEntity *pEnt = ClientEntityList().GetBaseEntityFromHandle( ClientEntityList().EntIndexToHandle( nIndex ) );

			if ( pEnt )
			{
				unsigned char nAttachment = msg.ReadByte();
				Vector vecEndPos;
				msg.ReadBitVec3Coord( vecEndPos );
				
				Vector vecNormal;
				msg.ReadBitVec3Normal( vecNormal );
				
				CNewParticleEffect *pEffect = pEnt->ParticleProp()->Create( "vortigaunt_beam_charge", PATTACH_POINT_FOLLOW, nAttachment );
				if ( pEffect )
				{
					// Set the control point's angles to be the surface normal we struct
					Vector vecRight, vecUp;
					VectorVectors( vecNormal, vecRight, vecUp );
					pEffect->SetControlPointOrientation( 1, vecNormal, vecRight, vecUp );
					pEffect->SetControlPoint( 1, vecEndPos );
				}
			}
		}
		break;
	default:
		AssertMsg1( false, "Received unknown message %d", messageType);
	}
}

class C_VortigauntChargeToken : public C_BaseEntity
{
	DECLARE_CLASS( C_VortigauntChargeToken, C_BaseEntity );
	DECLARE_CLIENTCLASS();

public:
	virtual void	UpdateOnRemove( void );
	virtual void	ClientThink( void );
	virtual void	NotifyShouldTransmit( ShouldTransmitState_t state );
	virtual void	OnDataChanged( DataUpdateType_t type );

	// For RecvProxy handlers
	float							m_flFadeOutTime;
	float							m_flFadeOutStart;

private:
	bool		SetupEmitters( void );

	bool							m_bFadeOut;
	CNewParticleEffect				*m_hEffect;
	dlight_t						*m_pDLight;
};

void RecvProxy_FadeOutDuration( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	C_VortigauntChargeToken *pVortToken = (C_VortigauntChargeToken *) pStruct;
	Assert( pOut == &pVortToken->m_flFadeOutTime );

	pVortToken->m_flFadeOutStart = gpGlobals->curtime;
	pVortToken->m_flFadeOutTime = ( pData->m_Value.m_Float - gpGlobals->curtime );
}

IMPLEMENT_CLIENTCLASS_DT( C_VortigauntChargeToken, DT_VortigauntChargeToken, CVortigauntChargeToken )
	RecvPropBool( RECVINFO( m_bFadeOut ) ),
END_RECV_TABLE()

void C_VortigauntChargeToken::UpdateOnRemove( void )
{
	if ( m_hEffect )
	{
		m_hEffect->StopEmission();
		m_hEffect = NULL;
	}

	if ( m_pDLight != NULL )
	{
		m_pDLight->die = gpGlobals->curtime;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Change our transmission state 
//-----------------------------------------------------------------------------
void C_VortigauntChargeToken::NotifyShouldTransmit( ShouldTransmitState_t state )
{
	BaseClass::NotifyShouldTransmit( state );

	// Turn off
	if ( state == SHOULDTRANSMIT_END )
	{
		if ( m_hEffect )
		{
			m_hEffect->StopEmission();
			m_hEffect = NULL;
		}
	}

	// Turn on
	if ( state == SHOULDTRANSMIT_START )
	{
		m_hEffect = ParticleProp()->Create( "vortigaunt_charge_token", PATTACH_ABSORIGIN_FOLLOW );
		m_hEffect->SetControlPointEntity( 0, this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_VortigauntChargeToken::OnDataChanged( DataUpdateType_t type )
{
	if ( m_bFadeOut )
	{
		if ( m_hEffect )
		{
			m_hEffect->StopEmission();
			m_hEffect = NULL;
		}
	}

	BaseClass::OnDataChanged( type );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_VortigauntChargeToken::ClientThink( void )
{
	//
	// -- DLight 
	//

	if ( m_pDLight != NULL )
	{
		m_pDLight->origin = GetAbsOrigin();
		m_pDLight->radius = DLIGHT_RADIUS;
	}
}

//=============================================================================
// 
//  Dispel Effect
//	
//=============================================================================

class C_VortigauntEffectDispel : public C_BaseEntity
{
	DECLARE_CLASS( C_VortigauntEffectDispel, C_BaseEntity );
	DECLARE_CLIENTCLASS();

public:
	virtual void	UpdateOnRemove( void );
	virtual void	ClientThink( void );
	virtual void	NotifyShouldTransmit( ShouldTransmitState_t state );
	virtual void	OnDataChanged( DataUpdateType_t type );

	// For RecvProxy handlers
	float							m_flFadeOutTime;
	float							m_flFadeOutStart;

private:
	bool	SetupEmitters( void );

	CNewParticleEffect				*m_hEffect;
	bool							m_bFadeOut;
	dlight_t						*m_pDLight;
};

void RecvProxy_DispelFadeOutDuration( const CRecvProxyData *pData, void *pStruct, void *pOut )
{
	C_VortigauntEffectDispel *pVortToken = (C_VortigauntEffectDispel *) pStruct;
	Assert( pOut == &pVortToken->m_flFadeOutTime );

	pVortToken->m_flFadeOutStart = gpGlobals->curtime;
	pVortToken->m_flFadeOutTime = ( pData->m_Value.m_Float - gpGlobals->curtime );
}

IMPLEMENT_CLIENTCLASS_DT( C_VortigauntEffectDispel, DT_VortigauntEffectDispel, CVortigauntEffectDispel )
	RecvPropBool( RECVINFO( m_bFadeOut ) ),
END_RECV_TABLE()

void C_VortigauntEffectDispel::UpdateOnRemove( void )
{
	if ( m_hEffect )
	{
		m_hEffect->StopEmission();
		m_hEffect = NULL;
	}

	if ( m_pDLight != NULL )
	{
		m_pDLight->die = gpGlobals->curtime;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_VortigauntEffectDispel::OnDataChanged( DataUpdateType_t type )
{
	if ( m_bFadeOut )
	{
		if ( m_hEffect )
		{
			m_hEffect->StopEmission();
			m_hEffect = NULL;
		}
	}

	BaseClass::OnDataChanged( type );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_VortigauntEffectDispel::NotifyShouldTransmit( ShouldTransmitState_t state )
{
	BaseClass::NotifyShouldTransmit( state );

	// Turn off
	if ( state == SHOULDTRANSMIT_END )
	{
		if ( m_hEffect )
		{
			m_hEffect->StopEmission();
			m_hEffect = NULL;
		}
	}

	// Turn on
	if ( state == SHOULDTRANSMIT_START )
	{
		m_hEffect = ParticleProp()->Create( "vortigaunt_hand_glow", PATTACH_ABSORIGIN_FOLLOW );
		m_hEffect->SetControlPointEntity( 0, this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Create our emitter
//-----------------------------------------------------------------------------
bool C_VortigauntEffectDispel::SetupEmitters( void )
{
	m_pDLight = NULL;

#ifndef _X360
	m_pDLight = effects->CL_AllocDlight ( index );
	m_pDLight->origin = GetAbsOrigin();
	m_pDLight->color.r = 64;
	m_pDLight->color.g = 255;
	m_pDLight->color.b = 64;
	m_pDLight->radius = 0;
	m_pDLight->minlight = DLIGHT_MINLIGHT;
	m_pDLight->die = FLT_MAX;
#endif // _X360

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void C_VortigauntEffectDispel::ClientThink( void )
{
	if ( m_pDLight != NULL )
	{
		m_pDLight->origin = GetAbsOrigin();
		m_pDLight->radius = DLIGHT_RADIUS;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void DispelCallback( const CEffectData &data )
{
	// Kaboom!
	Vector startPos = data.m_vOrigin + Vector(0,0,16);
	Vector endPos = data.m_vOrigin + Vector(0,0,-64);

	trace_t tr;
	UTIL_TraceLine( startPos, endPos, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );

	if ( tr.fraction < 1.0f )
	{
		//Add a ripple quad to the surface
		FX_AddQuad( tr.endpos + ( tr.plane.normal * 8.0f ), 
			Vector( 0, 0, 1 ), 
			64.0f, 
			600.0f, 
			0.8f,
			1.0f,	// start alpha
			0.0f,	// end alpha
			0.3f,
			random->RandomFloat( 0, 360 ),
			0.0f,
			Vector( 0.5f, 1.0f, 0.5f ), 
			0.75f, 
			"effects/ar2_altfire1b", 
			(FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA|FXQUAD_COLOR_FADE) );
		
		//Add a ripple quad to the surface
		FX_AddQuad( tr.endpos + ( tr.plane.normal * 8.0f ), 
			Vector( 0, 0, 1 ), 
			16.0f, 
			300.0f,
			0.9f,
			1.0f,	// start alpha
			0.0f,	// end alpha
			0.9f,
			random->RandomFloat( 0, 360 ),
			0.0f,
			Vector( 0.5f, 1.0f, 0.5f ), 
			1.25f, 
			"effects/rollerglow", 
			(FXQUAD_BIAS_SCALE|FXQUAD_BIAS_ALPHA) );
	}
}

DECLARE_CLIENT_EFFECT( "VortDispel", DispelCallback );

//-----------------------------------------------------------------------------
// Purpose: Used for emissive lightning layer on vort
//-----------------------------------------------------------------------------
class CVortEmissiveProxy : public CEntityMaterialProxy
{
public:
	CVortEmissiveProxy( void );
	virtual				~CVortEmissiveProxy( void );
	virtual bool		Init( IMaterial *pMaterial, KeyValues* pKeyValues );
	virtual void		OnBind( C_BaseEntity *pC_BaseEntity );
	virtual IMaterial *	GetMaterial();

private:

	IMaterialVar *m_pMatEmissiveStrength;
	IMaterialVar *m_pMatDetailBlendStrength;
};

//-----------------------------------------------------------------------------
CVortEmissiveProxy::CVortEmissiveProxy( void )
{
	m_pMatEmissiveStrength = NULL;
	m_pMatDetailBlendStrength = NULL;
}

CVortEmissiveProxy::~CVortEmissiveProxy( void )
{
	// Do nothing
}

//-----------------------------------------------------------------------------
bool CVortEmissiveProxy::Init( IMaterial *pMaterial, KeyValues* pKeyValues )
{
	Assert( pMaterial );

	// Need to get the material var
	bool bFound;
	m_pMatEmissiveStrength = pMaterial->FindVar( "$emissiveblendstrength", &bFound );

	if ( bFound )
	{
		// Optional
		bool bFound2;
		m_pMatDetailBlendStrength = pMaterial->FindVar( "$detailblendfactor", &bFound2 );
	}

	return bFound;
}

//-----------------------------------------------------------------------------
void CVortEmissiveProxy::OnBind( C_BaseEntity *pEnt )
{
	C_NPC_Vortigaunt *pVort = dynamic_cast<C_NPC_Vortigaunt *>(pEnt);

	float flBlendValue;

	if (pVort)
	{
		// do we need to crossfade?
		if (gpGlobals->curtime < pVort->m_flBlueEndFadeTime)
		{
			// will be 0 when fully faded and 1 when not faded at all:
			float fadeRatio = (pVort->m_flBlueEndFadeTime - gpGlobals->curtime) / VORTIGAUNT_BLUE_FADE_TIME;
			if (pVort->m_bIsBlue)
			{
				fadeRatio = 1.0f - fadeRatio;
			}
			flBlendValue = clamp( fadeRatio, 0.0f, 1.0f );
		}
		else // no crossfade
		{
			flBlendValue = pVort->m_bIsBlue ? 1.0f : 0.0f;
		}

		// ALEX VLACHOS: 
		// The following variable varies on [0 .. 1]. 0.0 means the vort wants to be his normal
		// color. 1.0 means he wants to be all black. It is interpolated in the
		// C_NPC_Vortigaunt::ClientThink() function. 
		// 
		// pVort->m_flBlackFade
	}
	else
	{	// if you bind this proxy to anything non-vort (eg a ragdoll) it's always green
		flBlendValue = 0.0f;
	}


	/*
	// !!! Change me !!! I'm using a clamped sin wave for debugging
	float flBlendValue = sinf( gpGlobals->curtime * 4.0f ) * 0.75f + 0.25f;

	// Clamp 0-1
	flBlendValue = ( flBlendValue < 0.0f ) ? 0.0f : ( flBlendValue > 1.0f ) ? 1.0f : flBlendValue;
	*/

	if( m_pMatEmissiveStrength != NULL )
	{
		m_pMatEmissiveStrength->SetFloatValue( flBlendValue );
	}

	if( m_pMatDetailBlendStrength != NULL )
	{
		m_pMatDetailBlendStrength->SetFloatValue( flBlendValue );
	}
}

//-----------------------------------------------------------------------------
IMaterial *CVortEmissiveProxy::GetMaterial()
{
	if ( m_pMatEmissiveStrength != NULL )
		return m_pMatEmissiveStrength->GetOwningMaterial();
	else if ( m_pMatDetailBlendStrength != NULL )
		return m_pMatDetailBlendStrength->GetOwningMaterial();
	else
		return NULL;
}

EXPOSE_INTERFACE( CVortEmissiveProxy, IMaterialProxy, "VortEmissive" IMATERIAL_PROXY_INTERFACE_VERSION );
