// SharedFunctorUtils.h
// Useful functors for client and server
//========= Copyright Valve Corporation, All rights reserved. ============//

//--------------------------------------------------------------------------------------------------------
/**
* NOTE: The functors in this file should ideally be game-independant, 
* and work for any Source based game
*/
//--------------------------------------------------------------------------------------------------------
#ifndef _SHARED_FUNCTOR_UTILS_H_
#define _SHARED_FUNCTOR_UTILS_H_

#include "debugoverlay_shared.h"
#include "vprof.h"

//--------------------------------------------------------------------------------------------------------
/**
 * Finds visible player on given team that we are pointing at.
 * "team" can be TEAM_ANY 
 * Use with ForEachPlayer()
 */
template < class PlayerType >
class TargetScan
{
public:
	TargetScan( PlayerType *me, int team, float aimTolerance = 0.01f, float maxRange = 2000.0f, float closestPointTestDistance = 50.0f, bool debug = false )
	{
		m_me = me;
		AngleVectors( m_me->EyeAngles(), &m_viewForward );
		m_team = team;
		m_closeDot = 1.0f - aimTolerance;
		m_bestDot = m_closeDot;
		m_maxRange = maxRange;
		m_target = NULL;
		m_closestPointTestDistance = closestPointTestDistance;
		m_debug = debug;
	}


	virtual bool operator() ( PlayerType *them )
	{
		VPROF( "TargetScan()" );
		if ( them != m_me && 
			 them->IsAlive() && 
			 (m_team == TEAM_ANY || them->GetTeamNumber() == m_team) &&
			 IsPotentialTarget( them ) )
		{
			// move the start point out for determining closestPos, to help with close-in checks (healing, etc)
			Vector closestPos;
			Vector start = m_me->EyePosition();
			Vector end = start + m_viewForward * m_closestPointTestDistance;
			Vector testPos;
			CalcClosestPointOnLineSegment( them->WorldSpaceCenter(), start, end, testPos );

			start = them->GetAbsOrigin();
			end = start;
			end.z += them->CollisionProp()->OBBMaxs().z;
			CalcClosestPointOnLineSegment( testPos, start, end, closestPos );
			if ( m_debug )
			{
				NDebugOverlay::Cross3D( closestPos, 1, 255, 255, 255, true, -1.0f );
				NDebugOverlay::Line( end, start, 255, 0, 0, true, -1.0f );
			}

			Vector to = closestPos - m_me->EyePosition();
			to.NormalizeInPlace();

			Vector meRangePoint, themRangePoint;
			m_me->CollisionProp()->CalcNearestPoint( closestPos, &meRangePoint );
			them->CollisionProp()->CalcNearestPoint( meRangePoint, &themRangePoint );
			float range = meRangePoint.DistTo( themRangePoint );

			if ( range > m_maxRange )
			{
				// too far away
				return true;
			}

			float dot = ViewDot( to );
			if ( dot > m_closeDot )
			{
				// target is within angle cone, check visibility
				if ( IsTargetVisible( them ) )
				{
					if ( dot >= m_bestDot )
					{
						m_target = them;
						m_bestDot = dot;
					}

					m_allTargets.AddToTail( them );
				}
			}
		}

		return true;
	}

	PlayerType *GetTarget( void ) const
	{
		return m_target;
	}

	const CUtlVector< PlayerType * > &GetAllTargets( void ) const
	{
		return m_allTargets;
	}

	float GetTargetDot( void ) const
	{
		return m_bestDot;
	}

protected:
	/**
	 * Is the point in our FOV?
	 */
	virtual float ViewDot( const Vector &dir ) const
	{
		return DotProduct( m_viewForward, dir );
	}

	/**
	 * Is the given actor a visible target?
	 */
	virtual bool IsTargetVisible( PlayerType *them ) const
	{
		// The default check is a straight-up IsAbleToSee
		return m_me->IsAbleToSee( them, CBaseCombatCharacter::DISREGARD_FOV ); // already have a dot product checking FOV
	}

	/**
	 * Is the given player a possible target at all?
	 */
	virtual bool IsPotentialTarget( PlayerType *them ) const
	{
		return true;
	}

	PlayerType *m_me;	
	Vector m_viewForward;
	int m_team;

	float m_closeDot;
	float m_bestDot;
	float m_maxRange;
	float m_closestPointTestDistance;
	bool m_debug;

	PlayerType *m_target;
	CUtlVector< PlayerType * > m_allTargets;
};


#endif