//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: A panel that display particle systems
//
//=============================================================================//

#include "cbase.h"
#include <KeyValues.h>
#include <vgui/IScheme.h>
#include <vgui/ISurface.h>
#include <vgui_controls/EditablePanel.h>
#include "vgui/IVGui.h"

#include "tf_particlepanel.h"
#include "matsys_controls/matsyscontrols.h"
#include "VGuiMatSurface/IMatSystemSurface.h"
#include "tier2/renderutils.h"
#include "renderparm.h"

using namespace vgui;


CTFParticlePanel::ParticleEffect_t::ParticleEffect_t()
	: m_bLoop( true )
	, m_pParticleSystem( NULL )
	, m_flLastTime( FLT_MAX )
	, m_ParticleSystemName( NULL )
	, m_bStartActivated( true )
	, m_flScale( 1.f )
	, m_flEndTime( FLT_MAX )
	, m_nXPos( 0 )
	, m_nYPos( 0 )
	, m_Angles( 0.f, 0.f, 0.f )
	, m_pParent( NULL )
	, m_bForceStopped( false )
	, m_bAutoDelete( false )
	, m_bStarted( false )
{}


DECLARE_BUILD_FACTORY( CTFParticlePanel );
//-----------------------------------------------------------------------------
// Constructor, destructor
//-----------------------------------------------------------------------------
CTFParticlePanel::CTFParticlePanel( vgui::Panel *pParent, const char *pName ) 
	: BaseClass( pParent, pName )
{
	m_Camera.m_flZNear = 3.0f;
	m_Camera.m_flZFar = 16384.0f * 1.73205080757f;
	m_Camera.m_flFOV = 30.0f;
	m_Camera.m_origin = Vector(0,0,0);
	m_Camera.m_angles = QAngle(0,0,0);

	m_pLightmapTexture.Init( "//platform/materials/debug/defaultlightmap", "editor" );
	m_DefaultEnvCubemap.Init( "editor/cubemap", "editor", true );
}

CTFParticlePanel::~CTFParticlePanel()
{
	m_pLightmapTexture.Shutdown();
	m_DefaultEnvCubemap.Shutdown();
	m_vecParticleEffects.PurgeAndDeleteElements();
}


void CTFParticlePanel::ApplySettings( KeyValues *inResourceData )
{
	BaseClass::ApplySettings( inResourceData );

	KeyValues *pKVParticleEffects = inResourceData->FindKey( "ParticleEffects" );
	if ( pKVParticleEffects )
	{
		FOR_EACH_SUBKEY( pKVParticleEffects, pKVEffect )
		{
			m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t();
			ParticleEffect_t* pEffect = m_vecParticleEffects.Tail();

			// get the position
			int alignScreenWide = GetWide(), alignScreenTall = GetTall();	// screen dimensions used for pinning in splitscreen

			int x, y;
			GetPos(x, y);
			const char *xstr = pKVEffect->GetString( "particle_xpos", NULL );
			const char *ystr = pKVEffect->GetString( "particle_ypos", NULL );

			if (xstr)
			{
				bool bRightAlign = false;
				bool bCenterAlign = false;
				// look for alignment flags
				if (xstr[0] == 'r' || xstr[0] == 'R')
				{
					bRightAlign = true;
					xstr++;
				}
				else if (xstr[0] == 'c' || xstr[0] == 'C')
				{
					bCenterAlign = true;
					xstr++;
				}

				// get the value
				x = atoi(xstr);
				// scale the x up to our screen co-ords
				if ( IsProportional() )
				{
					x = scheme()->GetProportionalScaledValueEx(GetScheme(), x);
				}
				// now correct the alignment
				if ( bRightAlign )
				{
					x = alignScreenWide - x;
				}
				else if ( bCenterAlign )
				{
					x = (alignScreenWide / 2) + x;
				}
			}

			if (ystr)
			{
				bool bBottomAlign = false;
				bool bCenterAlign = false;
				// look for alignment flags
				if (ystr[0] == 'r' || ystr[0] == 'R')
				{
					bBottomAlign = true;
					ystr++;
				}
				else if (ystr[0] == 'c' || ystr[0] == 'C')
				{
					bCenterAlign = true;
					ystr++;
				}
				y = atoi(ystr);
				if (IsProportional())
				{
					// scale the y up to our screen co-ords
					y = scheme()->GetProportionalScaledValueEx(GetScheme(), y);
				}
				// now correct the alignment
				if ( bBottomAlign )
				{
					y = alignScreenTall - y;
				}
				else if ( bCenterAlign )
				{
					y = (alignScreenTall / 2) + y;
				}
			}

			pEffect->m_nXPos = x;
			pEffect->m_nYPos = y;

			pEffect->m_flScale	= pKVEffect->GetFloat( "particle_scale", 1.f );
			// Scale the scale factor the same way we do the XY position coordinates
			if( IsProportional() )
			{
				int wide, tall;
				surface()->GetScreenSize( wide, tall );

				int proH, proW;
				surface()->GetProportionalBase( proW, proH );
				double scale = (double)tall / (double)proH;
				pEffect->m_flScale *= scale;
			}

			pEffect->m_pParent	= this;
			pEffect->m_bLoop		= pKVEffect->GetBool( "loop", true );
			pEffect->m_bStartActivated = pKVEffect->GetBool( "start_activated", true );
			pEffect->SetParticleSystem( pKVEffect->GetString( "particleName" ) );

			// Read angles for the particle system
			{
				float x1,y1,z1;
				const char* pszAngles = pKVEffect->GetString( "angles" );
				if( *pszAngles )
				{
					if( pEffect->m_pParticleSystem && sscanf( pszAngles, "%f %f %f", &x1, &y1, &z1 ) == 3 )
					{
						pEffect->m_Angles = QAngle( x1, y1, z1 );
						Quaternion q;
						AngleQuaternion( pEffect->m_Angles , q );
						pEffect->m_pParticleSystem->SetControlPointOrientation( 0, q );
					}
				}
			}

			pEffect->SetControlPointValue( 0, Vector(0,0,0) );
			// Read all control point values
			const char* pszControlPoint = NULL;
			int nControlPointNumber = 0;
			do
			{
				pszControlPoint = pKVEffect->GetString( VarArgs("control_point%d", nControlPointNumber), "" );
				if ( *pszControlPoint )
				{
					float x2,y2,z2;
					if (sscanf(pszControlPoint, "%f %f %f", &x2, &y2, &z2 ) == 3)
					{
						pEffect->SetControlPointValue( nControlPointNumber, Vector( x2, y2, z2 ) );
					}
				}

				++nControlPointNumber;
			}
			while( *pszControlPoint );
		}
	}
}

//-----------------------------------------------------------------------------
// Scheme
//-----------------------------------------------------------------------------
void CTFParticlePanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	SetMouseInputEnabled( false );
	SetKeyBoardInputEnabled( false );
}

void CTFParticlePanel::OnCommand( const char *command )
{
	if ( !Q_strnicmp( command, "start", ARRAYSIZE("start") - 1 ) )
	{
		// Check if they specified a particular one to turn on
		const char* pszNum = command + ARRAYSIZE( "start" ) - 1;
		int nIndex = m_vecParticleEffects.InvalidIndex();

		if( pszNum && pszNum[0] )
		{
			nIndex = atoi(pszNum);
			Assert( nIndex >= 0 && nIndex < m_vecParticleEffects.Count() );
		}

		FOR_EACH_VEC( m_vecParticleEffects, i )
		{
			if ( nIndex != m_vecParticleEffects.InvalidIndex() && i != nIndex )
				continue;

			ParticleEffect_t* pEffect = m_vecParticleEffects[ i ];

			if( !pEffect->m_pParticleSystem )
			{
				pEffect->SetParticleSystem( pEffect->m_ParticleSystemName );
			}

			if( !pEffect->m_pParticleSystem )
				continue;
	
			pEffect->StartupParticleCollection();
			pEffect->m_pParticleSystem->StartEmission();
			pEffect->m_bForceStopped = false;
		}
	}
	else if ( !Q_strnicmp( command, "stop", ARRAYSIZE("stop") - 1 ) )
	{
		// Check if they specified a specific one to turn off
		const char* pszNum = command + ARRAYSIZE( "start" ) - 1;
		if( pszNum && pszNum[0] )
		{
			int iIndex = atoi(pszNum);
			Assert( iIndex >= 0 && iIndex < m_vecParticleEffects.Count() );

			ParticleEffect_t* pEffect = m_vecParticleEffects[iIndex];
			if( pEffect->m_pParticleSystem )
			{
				pEffect->m_pParticleSystem->StopEmission();
				pEffect->m_bForceStopped = true;
			}
		}
		else
		{
			// Turn them ALL off
			FOR_EACH_VEC( m_vecParticleEffects, i )
			{
				ParticleEffect_t* pEffect = m_vecParticleEffects[ i ];
				if( pEffect->m_pParticleSystem )
				{
					pEffect->m_pParticleSystem->StopEmission();
					pEffect->m_bForceStopped = true;
				}
			}
		}
	}
}

void CTFParticlePanel::FireParticleEffect( const char *pszName, int xPos, int yPos, float flScale, bool bLoop, float flEndTime )
{
	m_vecParticleEffects[ m_vecParticleEffects.AddToTail() ] = new ParticleEffect_t();
	ParticleEffect_t* pEffect = m_vecParticleEffects.Tail();

	int iParentAbsX, iParentAbsY;
	vgui::ipanel()->GetAbsPos( GetParent()->GetVPanel(), iParentAbsX, iParentAbsY );

	pEffect->m_pParent = this;
	pEffect->m_nXPos = xPos - iParentAbsX;
	pEffect->m_nYPos = yPos - iParentAbsY;
	pEffect->m_flScale = flScale;
	pEffect->m_bLoop = bLoop;
	pEffect->m_bAutoDelete = true; // This will get automatically deleted once it stops
	pEffect->m_bStartActivated = true;
	pEffect->m_flEndTime = gpGlobals->curtime + flEndTime;

	if( IsProportional() )
	{
		int wide, tall;
		surface()->GetScreenSize( wide, tall );

		int proH, proW;
		surface()->GetProportionalBase( proW, proH );
		double scale = (double)tall / (double)proH;
		pEffect->m_flScale *= scale;
	}

	for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i )
	{
		pEffect->SetControlPointValue( i, Vector( 0, 0, 10.0f * i ) );
	}
	pEffect->SetParticleSystem( pszName );
}


static bool IsValidHierarchy( CParticleCollection *pCollection )
{
	if ( !pCollection->IsValid() )
		return false;

	for( CParticleCollection *pChild = pCollection->m_Children.m_pHead; pChild; pChild = pChild->m_pNext )
	{
		if ( !IsValidHierarchy( pChild ) )
			return false;
	}
	return true;
}


//-----------------------------------------------------------------------------
// Simulate the particle system
//-----------------------------------------------------------------------------
void CTFParticlePanel::OnTick()
{
	BaseClass::OnTick();
	
	float flTime = engine->Time();

	bool bAnyActive = false;
	// Update all particles
	FOR_EACH_VEC_BACK( m_vecParticleEffects, i )
	{
		bAnyActive |= m_vecParticleEffects[i]->Update( flTime );
		// If this effect is done and should auto-delete, then now is when we delete
		if( m_vecParticleEffects[i]->m_pParticleSystem == NULL && m_vecParticleEffects[i]->m_bAutoDelete )
		{
			delete m_vecParticleEffects[i];
			m_vecParticleEffects.FastRemove( i );
		}
	}

	if ( !bAnyActive )
	{
		vgui::ivgui()->RemoveTickSignal( GetVPanel() );
	}
}


void CTFParticlePanel::Paint()
{
	// This needs calling to reset various counters.
	g_pParticleSystemMgr->SetLastSimulationTime( gpGlobals->curtime );

	// No particles?  Do nothing.
	if( m_vecParticleEffects.Count() == 0 )
		return;

	int screenW, screenH;
	vgui::surface()->GetScreenSize( screenW, screenH );

	vgui::MatSystemSurface()->Begin3DPaint( 0, 0, screenW, screenH, false );

	VMatrix view, projection;
	ComputeViewMatrix( &view, m_Camera );
	ComputeProjectionMatrix( &projection, m_Camera, screenW, screenH );

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );

	pRenderContext->CullMode( MATERIAL_CULLMODE_CCW );
	pRenderContext->SetIntRenderingParameter( INT_RENDERPARM_WRITE_DEPTH_TO_DESTALPHA, false );

	pRenderContext->MatrixMode( MATERIAL_MODEL );
	pRenderContext->LoadIdentity( );

	pRenderContext->MatrixMode( MATERIAL_VIEW );
	pRenderContext->LoadMatrix( view );

	pRenderContext->MatrixMode( MATERIAL_PROJECTION );
	pRenderContext->LoadMatrix( projection );

	int iXOffset, iYOffset;
	vgui::ipanel()->GetAbsPos( GetVPanel(), iXOffset, iYOffset );
	if ( iXOffset > 0 )
		iXOffset = 0;
	if ( iYOffset > 0 )
		iYOffset = 0;

	float flXScale = 1.f;
	if ( GetWide() > screenW )
		flXScale = (float)screenW / GetWide();
	float flYScale = 1.f;
	if ( GetTall() > screenH )
		flYScale = (float)screenH / GetTall();

	FOR_EACH_VEC( m_vecParticleEffects, i )
	{
		m_vecParticleEffects[i]->Paint( pRenderContext, iXOffset, iYOffset, flXScale, flYScale, screenW, screenH );
	}

	pRenderContext->CullMode( MATERIAL_CULLMODE_CW );

	vgui::MatSystemSurface()->End3DPaint();
}

bool CTFParticlePanel::ParticleEffect_t::Update( float flTime )
{
	if ( !m_pParticleSystem || !m_bStarted )
		return false;

	if ( m_flLastTime == FLT_MAX )
	{
		m_flLastTime = flTime;
	}

	float flDt = flTime - m_flLastTime;
	m_flLastTime = flTime;

	Quaternion q;
	AngleQuaternion( m_Angles, q );

	for ( int i = 0; i < MAX_PARTICLE_CONTROL_POINTS; ++i )
	{
		if ( !m_pParticleSystem->ReadsControlPoint( i ) )
			continue;

		m_pParticleSystem->SetControlPoint( i, m_pControlPointValue[i] );
		m_pParticleSystem->SetControlPointOrientation( i, q );
		m_pParticleSystem->SetControlPointParent( i, i );
	}

	// Restart the particle system if it's finished
	bool bIsInvalid = !IsValidHierarchy( m_pParticleSystem );

	if ( !bIsInvalid )
	{
		m_pParticleSystem->Simulate( flDt, false );
	}

	// Past our end time?
	bool bEnd = gpGlobals->curtime >= m_flEndTime;

	if ( m_pParticleSystem->IsFinished() || bIsInvalid || bEnd )
	{
		delete m_pParticleSystem;
		m_pParticleSystem = NULL;

		// Loop if we're supposed to
		if ( m_bLoop && m_ParticleSystemName.Length() && !m_bForceStopped )
		{
			m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( m_ParticleSystemName );
		}

		if ( bIsInvalid && m_pParent )
		{
			m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) );
		}
		m_flLastTime = FLT_MAX;
	}

	return m_pParticleSystem != NULL;
}


//-----------------------------------------------------------------------------
// Startup, shutdown particle collection
//-----------------------------------------------------------------------------
void CTFParticlePanel::ParticleEffect_t::StartupParticleCollection()
{
	if ( m_pParticleSystem && m_pParent )
	{
		vgui::ivgui()->AddTickSignal( m_pParent->GetVPanel(), 0 );
	}
	m_flLastTime = FLT_MAX;
	m_bStarted = true;
}

void CTFParticlePanel::ParticleEffect_t::ShutdownParticleCollection()
{
	if ( m_pParticleSystem && m_pParent )
	{
		delete m_pParticleSystem;
		m_pParticleSystem = NULL;
	}
	m_bStarted = false;
}

//-----------------------------------------------------------------------------
// Set the particle system to draw
//-----------------------------------------------------------------------------
void CTFParticlePanel::ParticleEffect_t::SetParticleSystem( const char* pszParticleSystemName )
{
	ShutdownParticleCollection();

	if( !g_pParticleSystemMgr->IsParticleSystemDefined( pszParticleSystemName ) )
	{
		AssertMsg1( false, "%s is not a valid particle system name", pszParticleSystemName );
		return;
	}
	m_pParticleSystem = g_pParticleSystemMgr->CreateParticleCollection( pszParticleSystemName );
	m_ParticleSystemName = pszParticleSystemName;

	m_pParent->PostActionSignal( new KeyValues( "ParticleSystemReconstructed" ) );
	
	if( m_bStartActivated )
	{
		StartupParticleCollection();
	}
}


void CTFParticlePanel::ParticleEffect_t::Paint( CMatRenderContextPtr& pRenderContext, int iXOffset, int iYOffset, float flXScale, float flYScale, int screenW, int screenH )
{
	if ( !m_pParticleSystem || !m_bStarted )
		return;

	pRenderContext->MatrixMode( MATERIAL_PROJECTION );
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();
	
	pRenderContext->Ortho( 0, 0, screenW, screenH, -9999, 9999 );

	pRenderContext->Translate( flXScale * ( m_nXPos + iXOffset ), screenH - flYScale * ( m_nYPos + iYOffset ), 0.f );
	pRenderContext->Scale( m_flScale, m_flScale, m_flScale );

	// Render Particles
	pRenderContext->MatrixMode( MATERIAL_MODEL );
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity( );

	m_pParticleSystem->Render( pRenderContext );

	pRenderContext->MatrixMode( MATERIAL_MODEL );
	pRenderContext->PopMatrix();

	pRenderContext->MatrixMode( MATERIAL_PROJECTION );
	pRenderContext->PopMatrix();
}