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

#include "cbase.h"
#include "tf_hud_annotationspanel.h"
#include "vgui_controls/AnimationController.h"
#include "iclientmode.h"
#include "c_tf_player.h"
#include "c_tf_playerresource.h"
#include <vgui_controls/Label.h>
#include <vgui/ILocalize.h>
#include <vgui/ISurface.h>
#include "c_baseobject.h"
#include "fmtstr.h"
#include "tf_gamerules.h"
#include "tf_hud_statpanel.h"
#include "view.h"
#include "ivieweffects.h"
#include "viewrender.h"
#include "tf_gamerules.h"
#include "tf_hud_training.h"

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

DECLARE_HUDELEMENT_DEPTH( CTFAnnotationsPanel, 1 );

static const float LIFE_TIME = 1.0f;

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CTFAnnotationsPanel::CTFAnnotationsPanel( const char *pElementName )
	: EditablePanel( NULL, "AnnotationsPanel" ), CHudElement( pElementName )
{
	vgui::Panel *pParent = g_pClientMode->GetViewport();
	SetParent( pParent );
	m_bShouldBeVisible = false;
	SetScheme( "ClientScheme" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFAnnotationsPanel::~CTFAnnotationsPanel()
{
	Reset();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::Reset()
{
	RemoveAll();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::Init()
{
	// listen for events
	ListenForGameEvent( "show_annotation" );
	ListenForGameEvent( "hide_annotation" );
	
	RemoveAll();

	CHudElement::Init();
}

//-----------------------------------------------------------------------------
// Purpose: Applies scheme settings
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::FireGameEvent( IGameEvent * event )
{
	const char *pEventName = event->GetName();

	if ( Q_strcmp( "hide_annotation", pEventName ) == 0 )
	{
		HideAnnotation( event->GetInt("id") );
	}
	else if ( Q_strcmp( "show_annotation", pEventName ) == 0 )
	{
		AddAnnotation( event );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFAnnotationsPanelCallout *CTFAnnotationsPanel::TestAndAddCallout( int id, Vector &origin, const char *text )
{
	int insertSlot = -1;

	// Find an available slot and also see if this call out already exists in the list.
	for (int i=0; i<m_pCalloutPanels.Count(); ++i)
	{
		if (NULL == m_pCalloutPanels[i])
		{
			insertSlot = i;
			continue;
		}
		// If we already have this annotation, return and don't add it again.
		if (m_pCalloutPanels[i]->GetAnnotationID() == id)
		{
			m_pCalloutPanels[i]->SetText( text );
			m_pCalloutPanels[i]->SetLocation( origin );
			m_pCalloutPanels[i]->SetFollowEntity( NULL );
			m_pCalloutPanels[i]->InvalidateLayout();
			return m_pCalloutPanels[i];
		}
		// if one is available, use it
		if ( m_pCalloutPanels[i]->IsVisible() == false )
		{
			insertSlot = i;
			continue;
		}
	}

	CTFAnnotationsPanelCallout *pCallout = new CTFAnnotationsPanelCallout( g_pClientMode->GetViewport(), "AnnotationsPanelCallout", id, origin, text );

	if (-1 == insertSlot)
	{
		m_pCalloutPanels.AddToTail( vgui::SETUP_PANEL(pCallout) );
	}
	else
	{
		if ( m_pCalloutPanels[insertSlot] != NULL )
		{
			m_pCalloutPanels[insertSlot]->MarkForDeletion();
		}
		m_pCalloutPanels[insertSlot] = vgui::SETUP_PANEL(pCallout);
	}

	return pCallout;
}


void CTFAnnotationsPanel::UpdateAnnotations( void )
{

	//Find an available slot and also see if this call out already exists in the list.
	for ( int i = m_pCalloutPanels.Count()-1; i >= 0; i-- )
	{
		if ( !m_pCalloutPanels[i] ) 
		{
			m_pCalloutPanels.Remove(i);
			continue;
		}

		// Update the callout. If it says it's finished, remove it.
		if ( m_pCalloutPanels[i]->UpdateCallout() )
		{
			m_pCalloutPanels[i]->MarkForDeletion();
			m_pCalloutPanels.Remove(i);
		}
	}

	// If we have no active callouts, hide
	if ( !m_pCalloutPanels.Count() )
	{
		m_bShouldBeVisible = false;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::AddAnnotation( IGameEvent * event )
{
	const char *text = event->GetString( "text" );
	int id = event->GetInt("id");
	float x = event->GetFloat("worldPosX");
	float y = event->GetFloat("worldPosY");
	float z = event->GetFloat("worldPosZ");
	int iVisibilityBitfield = event->GetInt("visibilityBitfield");
	float flLifetime = event->GetFloat("lifetime");
	int iFollowEntIndex = event->GetInt("follow_entindex");
	bool bShowDistance = event->GetBool("show_distance");
	const char *pSound = event->GetString( "play_sound" );
	bool bShowEffect = event->GetBool( "show_effect" );
					   
	Vector location;

	location.x = x;
	location.y = y;
	location.z = z;

	m_bShouldBeVisible = true;

	// Try and add the callout
	CTFAnnotationsPanelCallout *pCallout = TestAndAddCallout( id, location, text );

	if ( pCallout )
	{
		C_BaseEntity *pFollowEntity = iFollowEntIndex != 0 ? ClientEntityList().GetEnt( iFollowEntIndex) : NULL;
		pCallout->Touch();
		pCallout->SetLifetime( flLifetime );
		pCallout->SetVisibilityBitfield( iVisibilityBitfield );
		pCallout->SetFollowEntity( pFollowEntity );
		pCallout->SetShowDistance( bShowDistance );
		pCallout->UpdateCallout();

		if ( pCallout->IsVisible() )
		{
			if ( pSound )
			{
				vgui::surface()->PlaySound( pSound );
			}

			if ( bShowEffect )
			{
				if ( pFollowEntity && pFollowEntity->ParticleProp())
				{
					pFollowEntity->ParticleProp()->Create( "ping_circle", PATTACH_ABSORIGIN_FOLLOW );
				}
				else
				{
					Vector vecNormal( event->GetFloat("worldNormalX"), event->GetFloat("worldNormalY"), event->GetFloat("worldNormalZ") );
					Vector vecOrigin( x, y, z );
					vecOrigin += vecNormal * 20.0f;

					QAngle vecAngles;
					VectorAngles( vecNormal, vecAngles );
					DispatchParticleEffect( "ping_circle", vecOrigin, vecAngles );
				}				
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::HideAnnotation( int id )
{
	// Delete all our callout panels
	for ( int i = m_pCalloutPanels.Count()-1; i >= 0; i-- )
	{
		if ( m_pCalloutPanels[i]->GetAnnotationID() == id )
		{
			 m_pCalloutPanels[i]->FadeAndRemove();
			 break;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::RemoveAll()
{
	m_bShouldBeVisible = false;

	// Delete all our callout panels
	for ( int i = m_pCalloutPanels.Count()-1; i >= 0; i-- )
	{
		m_pCalloutPanels[i]->MarkForDeletion();
	}
	m_pCalloutPanels.RemoveAll();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFAnnotationsPanel::ShouldDraw( void )
{
	return m_bShouldBeVisible;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanel::OnThink( void )
{
	BaseClass::OnThink();

	if ( IsVisible() )
	{
		UpdateAnnotations();
	}

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFAnnotationsPanelCallout::~CTFAnnotationsPanelCallout()
{
	if ( m_pArrowImages )
	{
		m_pArrowImages->deleteThis();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CTFAnnotationsPanelCallout::CTFAnnotationsPanelCallout( Panel *parent, const char *name, int id, Vector &location, const char* text ) : EditablePanel( parent, name )
{
	m_pAnnotationLabel = NULL;
	m_pDistanceLabel = NULL;
	m_pArrowImages = NULL;
	m_ID = id;
	m_Location = location;
	m_Text = text;
	m_DeathTime = 0.0f;
	m_flLerpPercentage = 1.0f;
	m_bWasOffscreen = false;
	m_bShowDistance = false;
	m_flAlpha[0] = m_flAlpha[1] = 0.0f;
	SetWorldPositionCurrentFrame( true );
}

//-----------------------------------------------------------------------------
// Purpose: Applies scheme settings
//-----------------------------------------------------------------------------
void CTFAnnotationsPanelCallout::ApplySettings( KeyValues *pInResourceData )
{
	BaseClass::ApplySettings( pInResourceData );

	if ( !m_pArrowImages )
	{
		KeyValues *pArrowImagesSubKey = pInResourceData->FindKey( "ArrowIcons" );	AssertMsg( pArrowImagesSubKey, "This must exist!" );
		m_pArrowImages = pArrowImagesSubKey->MakeCopy();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Applies scheme settings
//-----------------------------------------------------------------------------
void CTFAnnotationsPanelCallout::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	LoadControlSettings( "resource/UI/AnnotationsPanelCallout.res" );

	m_pDistanceLabel = dynamic_cast<Label *>( FindChildByName( "DistanceLabel" ) );

	m_pBackground = FindChildByName( "CalloutBG" );
	if ( m_pBackground )
	{
		m_pAnnotationLabel = dynamic_cast<CExLabel *>( FindChildByName( "CalloutLabel" ) );
		m_pAnnotationLabel->SetParent( m_pBackground );
		m_pDistanceLabel->SetParent( m_pBackground );
	}

	wchar_t outputText[MAX_TRAINING_MSG_LENGTH];
	if ( m_pAnnotationLabel && CTFHudTraining::FormatTrainingText( m_Text, outputText ) )
	{
		m_pAnnotationLabel->SetText(outputText);
	}

	m_pArrow = dynamic_cast<ImagePanel *>( FindChildByName( "ArrowIcon" ) );
}

//-----------------------------------------------------------------------------
// Purpose: Applies scheme settings
//-----------------------------------------------------------------------------
void CTFAnnotationsPanelCallout::PerformLayout( void )
{
	if ( !m_pAnnotationLabel || !m_pDistanceLabel )
		return;

	C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( pLocalTFPlayer == NULL )
		return;

	// @todo Tom Bui: not sure why we need to do this again, but if we don't
	// we still have the camera lag!
	InvalidateLayout();

	// Reposition the callout based on our target's position
	Vector vecTarget = m_Location;
	if ( m_FollowEntity.Get() )
	{
		vecTarget = m_FollowEntity->GetAbsOrigin();
		if ( m_FollowEntity->CollisionProp() )
		{
			vecTarget.z += m_FollowEntity->CollisionProp()->OBBSize().z;
		}
	}
	Vector vDelta = vecTarget - MainViewOrigin();
	float flDistance = vDelta.Length();
	VectorNormalize( vDelta );	// Only necessary so we can use it as part of our alpha calculation

	// Is the target visible on screen?
	int iX, iY;
	bool bOnscreen = GetVectorInHudSpace( vecTarget, iX, iY ); // Tested - confirmed NOT GetVectorInScreenSpace

	// Calculate the perp dot product
	QAngle angPlayerView = MainViewAngles();
	Vector vView, vRight, vUp;
	AngleVectors( angPlayerView, &vView, &vRight, &vUp );
	const float flPerpDot = vDelta.x * vView.y - vDelta.y * vView.x;

	// Calculate the alpha - the more the user looks away from the target, the greater the alpha
	if ( m_DeathTime > 0.0f && m_DeathTime - LIFE_TIME >= gpGlobals->curtime )
	{
		m_flAlpha[0] = m_flAlpha[1] = 255 * clamp( ( m_DeathTime - gpGlobals->curtime ) / LIFE_TIME, 0.0f, 1.0f );
	}
	else
	{
		m_flAlpha[1] = 255;
		// BUGBUG: the following lines don't do anything because of the clamp range
		//const float flDot = DotProduct( vDelta, vView );	// As the player looks away the target to the target, this will go from -1 to 1
		//m_flAlpha[1] = clamp( -255 * flDot, 255, 255 );	// Set target.
		m_flAlpha[0] = Lerp( gpGlobals->frametime, m_flAlpha[0], m_flAlpha[1] );	 // Move towards target
	}

	const int fade_alpha = m_flAlpha[0];

	SetAlpha( fade_alpha );
	m_pArrow->SetAlpha( fade_alpha );
	m_pBackground->SetAlpha( fade_alpha );

	const int halfWidth = m_pBackground->GetWide() / 2;
	bool bOffscreen = !bOnscreen || iX < halfWidth || iX > ScreenWidth()-halfWidth;
	if ( bOffscreen )
	{
		m_pArrow->SetSize( XRES( 10 ), YRES( 20 ) );

		const int nHorizontalBuffer = XRES( 20 );
		iX = flPerpDot <= 0.0f ? nHorizontalBuffer : ( ScreenWidth() - nHorizontalBuffer - m_pBackground->GetWide() - m_pArrow->GetWide() );
		iY = ( ScreenHeight() - m_pBackground->GetTall() ) / 2;
	}
	else
	{
		// On screen
		// If our target isn't visible, we draw transparently
		trace_t	tr;
		UTIL_TraceLine( vecTarget, MainViewOrigin(), MASK_OPAQUE, NULL, COLLISION_GROUP_NONE, &tr );
		
		if ( tr.fraction < 1.0f )
		{
			// Not visible ie obstructed by some objects in the world.
			// Do *not* show entities that are not the same team
			if ( m_FollowEntity.Get() &&
				 m_FollowEntity->GetTeamNumber() != TEAM_UNASSIGNED && m_FollowEntity->GetTeamNumber() != TEAM_INVALID &&
				 m_FollowEntity->GetTeamNumber() != pLocalTFPlayer->GetTeamNumber() && 
				 pLocalTFPlayer->GetTeamNumber() != TEAM_SPECTATOR )
			{
				SetAlpha( 0 );
				m_pArrow->SetAlpha( 0 );
			}
		}
		
		iX = iX - m_pBackground->GetWide() / 2;
		iY = iY - m_pBackground->GetTall() - m_pArrow->GetTall();
	}
	
	if ( m_bWasOffscreen != bOffscreen )
	{
		m_flLerpPercentage = 0.0f;
		m_bWasOffscreen = bOffscreen;
	}
	// lerp to the new position if applicable
	// note that this accelerates, cause it uses the current position as the "last position"
	if ( m_flLerpPercentage < 1.0f )
	{
		const float kInvLerpTime = 1.0f / 2.5f;
		m_flLerpPercentage = MIN( m_flLerpPercentage + gpGlobals->frametime * kInvLerpTime, 1.0f );
		int currentX, currentY;
		GetPos( currentX, currentY );
		iX = Lerp( m_flLerpPercentage, currentX, iX );
		iY = Lerp( m_flLerpPercentage, currentY, iY );
	}
	SetPos( iX, iY );

	int wide, tall;
	m_pAnnotationLabel->GetContentSize( wide, tall );

	if ( m_bShowDistance )
	{
		wchar_t *wzFollowEntityName = NULL;
		if ( m_FollowEntity.Get() )
		{
			C_BaseObject *pBuilding = dynamic_cast< C_BaseObject* >( m_FollowEntity.Get() );
			if ( pBuilding && pBuilding->GetType() < OBJ_LAST )
			{
				// Must match resource/tf_objects.txt!!!
				const char *szLocalizedObjectNames[OBJ_LAST] =
				{
					"#TF_Object_Dispenser",
					"#TF_Object_Tele",
					"#TF_Object_Sentry",
					"#TF_object_Sapper"
				};
				wzFollowEntityName = g_pVGuiLocalize->Find( szLocalizedObjectNames[pBuilding->GetType()] );
			}
		}
		const float kInchesToMeters = 0.0254f;
		int distance = RoundFloatToInt( flDistance * kInchesToMeters );
		wchar_t wzValue[32];
		_snwprintf( wzValue, ARRAYSIZE( wzValue ), L"%u", distance );

		wchar_t wzText[256];
		if ( wzFollowEntityName == NULL )
		{
			g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( "#TR_DistanceTo" ), 1, wzValue );
		}
		else
		{
			g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( "#TR_DistanceToObject" ), 2, wzFollowEntityName, wzValue );
		}

		m_pDistanceLabel->SetText( wzText );
		int distanceWide, distanceTall;
		m_pDistanceLabel->GetContentSize( distanceWide, distanceTall );
		wide = MAX( distanceWide, wide );
		tall += distanceTall;
	}

	wide += XRES(24);
	tall += YRES(18);

	// Set this panel, the label, and the background to contain the text
	const int aArrowBuffers[2] = { (int)(XRES( 20 ) * 2), (int)YRES( 20 ) };	// Leave enough room for arrows
	SetSize( wide + aArrowBuffers[0], tall + aArrowBuffers[1] );
	m_pDistanceLabel->SetSize( wide, m_pDistanceLabel->GetTall() );
	if ( m_pBackground )
	{
		// also adjust the background image
		m_pBackground->SetSize( wide, tall );
		m_pAnnotationLabel->SetSize( m_pBackground->GetWide(), m_pBackground->GetTall() );
		m_pDistanceLabel->SetSize( m_pBackground->GetWide(), m_pDistanceLabel->GetTall() );
	}

	// position background and arrows
	if ( bOffscreen )
	{
		// Set the arrow to left or right, depending on which side of the screen the panel is on
		if ( m_pBackground )
		{
			if ( iX + m_pBackground->GetWide() / 2 < ScreenWidth() / 2 )
			{
				m_pArrow->SetImage( m_pArrowImages->GetString( "left" ) );
				m_pArrow->SetPos( 0, ( m_pBackground->GetTall() - m_pArrow->GetTall() ) / 2 );
				m_pBackground->SetPos( m_pArrow->GetWide() + XRES( 1 ), 0 );
			}
			else
			{
				m_pArrow->SetImage( m_pArrowImages->GetString( "right" ) );
				m_pArrow->SetPos( m_pBackground->GetWide(), ( m_pBackground->GetTall() - m_pArrow->GetTall() ) / 2 );
				m_pBackground->SetPos( 0, 0 );
			}
		}
	}
	else
	{
		if ( m_pBackground )
		{
			// Set the arrow image to the one that points down
			m_pBackground->SetPos( 0, 0 );
			m_pArrow->SetImage( m_pArrowImages->GetString( "down" ) );
			m_pArrow->SetSize( XRES( 20 ), YRES( 10 ) );
			m_pArrow->SetPos( ( m_pBackground->GetWide() - m_pArrow->GetWide() ) / 2, m_pBackground->GetTall() );
		}
	}

	// check that we haven't run off the right side of the screen
	int x,y;
	GetPos( x, y );
	if ( (x + wide) > ScreenWidth() )
	{
		// push ourselves to the left to fit on the screen
		SetPos( ScreenWidth() - wide - XRES(8), y );
	}
}

void CTFAnnotationsPanelCallout::Touch()
{
	SetVisible( true );
	m_DeathTime = gpGlobals->curtime + LIFE_TIME;
	m_bWasOffscreen = true;
	SetPos( ScreenWidth() * 0.125f, ScreenHeight() * 0.125f );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanelCallout::SetLifetime( float flLifetime )
{
	if ( flLifetime < 0 )
	{
		m_DeathTime = 0.0f;
	}
	else if ( flLifetime == 0 )
	{
		m_DeathTime = gpGlobals->curtime + LIFE_TIME;
	}
	else
	{
		m_DeathTime = gpGlobals->curtime + flLifetime;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanelCallout::SetShowDistance( bool bShowDistance )
{
	m_bShowDistance = bShowDistance;
	if ( m_pDistanceLabel && m_pDistanceLabel->IsVisible() != m_bShowDistance )
	{
		m_pDistanceLabel->SetVisible( bShowDistance );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanelCallout::SetText( const char *text )
{
	m_Text = text;
	wchar_t outputText[MAX_TRAINING_MSG_LENGTH];
	if ( m_pAnnotationLabel && CTFHudTraining::FormatTrainingText( m_Text, outputText ) )
	{
		m_pAnnotationLabel->SetText(outputText);
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTFAnnotationsPanelCallout::FadeAndRemove()
{
	m_DeathTime = gpGlobals->curtime + 0.25f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTFAnnotationsPanelCallout::UpdateCallout()
{
	if ( m_DeathTime > 0.0f )
	{
		float pct_life = clamp( (m_DeathTime - gpGlobals->curtime) / LIFE_TIME, 0.0f, 1.0f );
		if (pct_life <= 0.0f)
		{
			SetVisible( false );
			return true;
		}
	}

	C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer();
	if ( !pLocalTFPlayer )
		return true;
	
	if ( m_iVisibilityBitfield != 0 )
	{
		int bit = 1 << pLocalTFPlayer->entindex();
		if (  ( m_iVisibilityBitfield & bit ) == 0 )
		{
			SetVisible( false );
			return true;
		}
	}

	if ( !IsVisible() )
	{
		SetVisible( true );
	}

	InvalidateLayout();

	return false;
}