//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//=============================================================================//

#include "cbase.h"
#include "c_basehelicopter.h"
#include "fx_impact.h"
#include "IEffects.h"
#include "simple_keys.h"
#include "fx_envelope.h"
#include "fx_line.h"
#include "iefx.h"
#include "dlight.h"
#include "c_sprite.h"
#include "clienteffectprecachesystem.h"
#include <bitbuf.h>

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define GUNSHIP_MSG_BIG_SHOT			1
#define GUNSHIP_MSG_STREAKS				2
#define GUNSHIP_MSG_DEAD				3

#define	GUNSHIPFX_BIG_SHOT_TIME			3.0f

CLIENTEFFECT_REGISTER_BEGIN( PrecacheGunshipFX )
CLIENTEFFECT_MATERIAL( "sprites/bluelaser1" )
CLIENTEFFECT_REGISTER_END()

//-----------------------------------------------------------------------------
// Big belly shot FX
//-----------------------------------------------------------------------------

class C_GunshipFX : public C_EnvelopeFX
{
public:
	typedef C_EnvelopeFX	BaseClass;

	C_GunshipFX();
	void			Update( C_BaseEntity *pOwner, const Vector &targetPos );

	// Returns the bounds relative to the origin (render bounds)
	virtual void	GetRenderBounds( Vector& mins, Vector& maxs )
	{
		ClearBounds( mins, maxs );
		AddPointToBounds( m_worldPosition, mins, maxs );
		AddPointToBounds( m_targetPosition, mins, maxs );
		mins -= GetRenderOrigin();
		maxs -= GetRenderOrigin();
	}

	virtual int	DrawModel( int flags );

	C_BaseEntity			*m_pOwner;
	Vector					m_targetPosition;
	Vector					m_beamEndPosition;
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
C_GunshipFX::C_GunshipFX( void )
{
	m_pOwner = NULL;
}

enum 
{
	GUNSHIPFX_WARP_SCALE = 0,
	GUNSHIPFX_DARKNESS,
	GUNSHIPFX_FLARE_COLOR,
	GUNSHIPFX_FLARE_SIZE,

	GUNSHIPFX_NARROW_BEAM_COLOR,
	GUNSHIPFX_NARROW_BEAM_SIZE,
	
	GUNSHIPFX_WIDE_BEAM_COLOR,
	GUNSHIPFX_WIDE_BEAM_SIZE,

	GUNSHIPFX_AFTERGLOW_COLOR,

	GUNSHIPFX_WIDE_BEAM_LENGTH,

	// must be last
	GUNSHIPFX_PARAMETERS,
};

class CGunshipFXEnvelope
{
public:
	CGunshipFXEnvelope();

	void AddKey( int parameterIndex, const CSimpleKeyInterp &key )
	{
		Assert( parameterIndex >= 0 && parameterIndex < GUNSHIPFX_PARAMETERS );

		if ( parameterIndex >= 0 && parameterIndex < GUNSHIPFX_PARAMETERS )
		{
			m_parameters[parameterIndex].Insert( key );
		}

	}

	CSimpleKeyList		m_parameters[GUNSHIPFX_PARAMETERS];
};

// NOTE: Beam widths are half-widths or radii, so this is a beam that represents a cylinder with 2" radius
const float NARROW_BEAM_WIDTH = 32;
const float WIDE_BEAM_WIDTH = 2;
const float FLARE_SIZE = 128;
const float	DARK_SIZE = 16;
const float AFTERGLOW_SIZE = 64;

CGunshipFXEnvelope::CGunshipFXEnvelope()
{
	// Glow flare
	AddKey( GUNSHIPFX_FLARE_COLOR, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( GUNSHIPFX_FLARE_COLOR, CSimpleKeyInterp( 0.5, KEY_SPLINE, 1 ) );
	AddKey( GUNSHIPFX_FLARE_COLOR, CSimpleKeyInterp( 2.9, KEY_LINEAR, 1 ) );
	AddKey( GUNSHIPFX_FLARE_COLOR, CSimpleKeyInterp( 3.2, KEY_DECELERATE, 0 ) );

	AddKey( GUNSHIPFX_FLARE_SIZE, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( GUNSHIPFX_FLARE_SIZE, CSimpleKeyInterp( 2.9, KEY_ACCELERATE, 1 ) );
	AddKey( GUNSHIPFX_FLARE_SIZE, CSimpleKeyInterp( 5.0, KEY_LINEAR, 1 ) );

	// Ground beam
	AddKey( GUNSHIPFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0.0 ) );
	AddKey( GUNSHIPFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 2.5, KEY_SPLINE, 1.0 ) );
	AddKey( GUNSHIPFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 3.0, KEY_LINEAR, 1 ) );
	AddKey( GUNSHIPFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 3.2, KEY_ACCELERATE, 0 ) );

	AddKey( GUNSHIPFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( GUNSHIPFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 2.5, KEY_SPLINE, 0.25 ) );
	AddKey( GUNSHIPFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 2.8, KEY_ACCELERATE, 1 ) );
	AddKey( GUNSHIPFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 3.0, KEY_ACCELERATE, 4 ) );
	AddKey( GUNSHIPFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 3.2, KEY_DECELERATE, 0 ) );

	// Glow color on the ship
	AddKey( GUNSHIPFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) );
	AddKey( GUNSHIPFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 3.0, KEY_LINEAR, 1 ) );
	AddKey( GUNSHIPFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 5.0, KEY_SPLINE, 0 ) );
}

CGunshipFXEnvelope g_GunshipCannonEnvelope;

static void ScaleColor( color32 &out, const color32 &in, float scale )
{
	out.r = (byte)(int)((float)in.r * scale);
	out.g = (byte)(int)((float)in.g * scale);
	out.b = (byte)(int)((float)in.b * scale);
	out.a = (byte)(int)((float)in.a * scale);
}

static void DrawSpriteTangentSpace( const Vector &vecOrigin, float flWidth, float flHeight, color32 color )
{
	unsigned char pColor[4] = { color.r, color.g, color.b, color.a };

	// Generate half-widths
	flWidth *= 0.5f;
	flHeight *= 0.5f;

	// Compute direction vectors for the sprite
	Vector fwd, right( 1, 0, 0 ), up( 0, 1, 0 );
	VectorSubtract( CurrentViewOrigin(), vecOrigin, fwd );
	float flDist = VectorNormalize( fwd );
	if (flDist >= 1e-3)
	{
		CrossProduct( CurrentViewUp(), fwd, right );
		flDist = VectorNormalize( right );
		if (flDist >= 1e-3)
		{
			CrossProduct( fwd, right, up );
		}
		else
		{
			// In this case, fwd == g_vecVUp, it's right above or 
			// below us in screen space
			CrossProduct( fwd, CurrentViewRight(), up );
			VectorNormalize( up );
			CrossProduct( up, fwd, right );
		}
	}

	Vector left = -right;
	Vector down = -up;
	Vector back = -fwd;

	CMatRenderContextPtr pRenderContext( materials );
	
	CMeshBuilder meshBuilder;
	Vector point;
	IMesh* pMesh = pRenderContext->GetDynamicMesh( );

	meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 0, 1);
	VectorMA (vecOrigin, -flHeight, up, point);
	VectorMA (point, -flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 0, 0);
	VectorMA (vecOrigin, flHeight, up, point);
	VectorMA (point, -flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 1, 0);
	VectorMA (vecOrigin, flHeight, up, point);
	VectorMA (point, flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();

	meshBuilder.Color4ubv (pColor);
	meshBuilder.TexCoord2f (0, 1, 1);
	VectorMA (vecOrigin, -flHeight, up, point);
	VectorMA (point, flWidth, right, point);
	meshBuilder.TangentS3fv( left.Base() );
	meshBuilder.TangentT3fv( down.Base() );
	meshBuilder.Normal3fv( back.Base() );
	meshBuilder.Position3fv (point.Base());
	meshBuilder.AdvanceVertex();
	
	meshBuilder.End();
	pMesh->Draw();
}


void Gunship_DrawSprite( const Vector &vecOrigin, float size, const color32 &color, bool glow )
{
	if ( glow )
	{
		pixelvis_queryparams_t params;
		params.Init( vecOrigin );
		if ( PixelVisibility_FractionVisible( params, NULL ) <= 0.0f )
			return;
	}

	DrawSpriteTangentSpace( vecOrigin, size, size, color );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : int - 
//-----------------------------------------------------------------------------
int	C_GunshipFX::DrawModel( int )
{
	static color32 white = {255,255,255,255};
	Vector params[GUNSHIPFX_PARAMETERS];
	bool hasParam[GUNSHIPFX_PARAMETERS];

	if ( !m_active )
		return 1;

	C_BaseEntity *ent = cl_entitylist->GetEnt( m_entityIndex );
	if ( ent )
	{
		QAngle angles;
		ent->GetAttachment( m_attachment, m_worldPosition, angles );
	}

	Vector test;
	m_t += gpGlobals->frametime;
	if ( m_tMax > 0 )
	{
		m_t = clamp( m_t, 0, m_tMax );
		m_beamEndPosition = m_worldPosition;
	}
	float t = m_t;

	bool hasAny = false;
	memset( hasParam, 0, sizeof(hasParam) );
	for ( int i = 0; i < GUNSHIPFX_PARAMETERS; i++ )
	{
		hasParam[i] = g_GunshipCannonEnvelope.m_parameters[i].Interp( params[i], t );
		hasAny = hasAny || hasParam[i];
	}

	// draw the narrow beam
	if ( hasParam[GUNSHIPFX_NARROW_BEAM_COLOR] && hasParam[GUNSHIPFX_NARROW_BEAM_SIZE] )
	{
		IMaterial *pMat = materials->FindMaterial( "sprites/bluelaser1", TEXTURE_GROUP_CLIENT_EFFECTS );
		float width = NARROW_BEAM_WIDTH * params[GUNSHIPFX_NARROW_BEAM_SIZE].x;
		color32 color;
		float bright = params[GUNSHIPFX_NARROW_BEAM_COLOR].x;
		ScaleColor( color, white, bright );

		//Gunship_DrawLine( m_beamEndPosition, m_targetPosition, width, pMat, color );
		FX_DrawLine( m_beamEndPosition, m_targetPosition, width, pMat, color );
	}

	// glowy blue flare sprite
	if ( hasParam[GUNSHIPFX_FLARE_COLOR] && hasParam[GUNSHIPFX_FLARE_SIZE] )
	{
		IMaterial *pMat = materials->FindMaterial( "effects/blueblackflash", TEXTURE_GROUP_CLIENT_EFFECTS );
		float size = FLARE_SIZE * params[GUNSHIPFX_FLARE_SIZE].x;
		color32 color;
		float bright = params[GUNSHIPFX_FLARE_COLOR].x;
		ScaleColor( color, white, bright );
		color.a = (int)(255 * params[GUNSHIPFX_DARKNESS].x);
		CMatRenderContextPtr pRenderContext( materials );
		pRenderContext->Bind( pMat, (IClientRenderable*)this );
		Gunship_DrawSprite( m_worldPosition, size, color, true );
	}

	if ( hasParam[GUNSHIPFX_AFTERGLOW_COLOR] )
	{
		// Muzzle effect
		dlight_t *dl = effects->CL_AllocDlight( m_entityIndex );
		dl->origin = m_worldPosition;
		dl->color.r = 40*params[GUNSHIPFX_AFTERGLOW_COLOR].x;
		dl->color.g = 60*params[GUNSHIPFX_AFTERGLOW_COLOR].x;
		dl->color.b = 255*params[GUNSHIPFX_AFTERGLOW_COLOR].x;
		dl->color.exponent = 5;
		dl->radius = 128.0f;
		dl->die = gpGlobals->curtime + 0.001;
	}

	if ( m_t >= 4.0 && !hasAny )
	{
		EffectShutdown();
	}

	return 1;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pOwner - 
//			&targetPos - 
//-----------------------------------------------------------------------------
void C_GunshipFX::Update( C_BaseEntity *pOwner, const Vector &targetPos )
{
	BaseClass::Update();

	m_pOwner = pOwner;
	
	if ( m_active )
	{
		m_targetPosition = targetPos;
	}
}

//-----------------------------------------------------------------------------
// Gunship
//-----------------------------------------------------------------------------

class C_CombineGunship : public C_BaseHelicopter
{
	DECLARE_CLASS( C_CombineGunship, C_BaseHelicopter );
public:
	DECLARE_CLIENTCLASS();

	C_CombineGunship( void ) {}
	
	virtual ~C_CombineGunship( void )
	{
		m_cannonFX.EffectShutdown();
	}

	C_GunshipFX	m_cannonFX;
	Vector		m_vecHitPos;

	//-----------------------------------------------------------------------------
	// Purpose: 
	// Input  : length - 
	//			*data - 
	// Output : 	void
	//-----------------------------------------------------------------------------
	void ReceiveMessage( int classID, bf_read &msg )
	{
		if ( classID != GetClientClass()->m_ClassID )
		{
			// message is for subclass
			BaseClass::ReceiveMessage( classID, msg );
			return;
		}

		int messageType = msg.ReadByte();
		switch( messageType )
		{
		case GUNSHIP_MSG_STREAKS:
			{
				Vector	pos;
				msg.ReadBitVec3Coord( pos );
				m_cannonFX.SetRenderOrigin( pos );
				m_cannonFX.EffectInit( entindex(), LookupAttachment( "BellyGun" ) );
				m_cannonFX.LimitTime( GUNSHIPFX_BIG_SHOT_TIME );
			}
			break;

		case GUNSHIP_MSG_BIG_SHOT:
			{
				Vector tmp;
				msg.ReadBitVec3Coord( tmp );
				m_cannonFX.SetTime( GUNSHIPFX_BIG_SHOT_TIME );
				m_cannonFX.LimitTime( 0 );
			}
			break;

		case GUNSHIP_MSG_DEAD:
			{
				m_cannonFX.EffectShutdown();
			}
			break;
		}
	}

	void OnDataChanged( DataUpdateType_t updateType )
	{
		BaseClass::OnDataChanged( updateType );

		m_cannonFX.Update( this, m_vecHitPos );
	}

	virtual RenderGroup_t GetRenderGroup()
	{
		if ( hl2_episodic.GetBool() == true )
		{
			return RENDER_GROUP_TWOPASS;
		}
		else
		{
			return BaseClass::GetRenderGroup();
		}
	}

private:
	C_CombineGunship( const C_CombineGunship & ) {}
};

IMPLEMENT_CLIENTCLASS_DT( C_CombineGunship, DT_CombineGunship, CNPC_CombineGunship )
	RecvPropVector(RECVINFO(m_vecHitPos)),
END_RECV_TABLE()

//-----------------------------------------------------------------------------
// Purpose: Handle gunship impacts
//-----------------------------------------------------------------------------
void ImpactGunshipCallback( const CEffectData &data )
{
	trace_t tr;
	Vector vecOrigin, vecStart, vecShotDir;
	int iMaterial, iDamageType, iHitbox;
	short nSurfaceProp;
	C_BaseEntity *pEntity = ParseImpactData( data, &vecOrigin, &vecStart, &vecShotDir, nSurfaceProp, iMaterial, iDamageType, iHitbox );

	if ( !pEntity )
		return;

	// If we hit, perform our custom effects and play the sound
	if ( Impact( vecOrigin, vecStart, iMaterial, iDamageType, iHitbox, pEntity, tr ) )
	{
		// Check for custom effects based on the Decal index
		PerformCustomEffects( vecOrigin, tr, vecShotDir, iMaterial, 3 );
	}

	PlayImpactSound( pEntity, tr, vecOrigin, nSurfaceProp );
}

DECLARE_CLIENT_EFFECT( "ImpactGunship", ImpactGunshipCallback );