/***
*
*	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
*   Use, distribution, and modification of this source code and/or resulting
*   object code is restricted to non-commercial enhancements to products from
*   Valve LLC.  All other use, distribution, or modification is prohibited
*   without written permission from Valve LLC.
*
****/
//
// ========================== PATH_CORNER ===========================
//

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "trains.h"
#include "saverestore.h"

class CPathCorner : public CPointEntity
{
public:
	void Spawn();
	void KeyValue( KeyValueData* pkvd );
	float GetDelay( void ) { return m_flWait; }
	//void Touch( CBaseEntity *pOther );
	virtual int Save( CSave &save );
	virtual int Restore( CRestore &restore );

	static TYPEDESCRIPTION m_SaveData[];

private:
	float m_flWait;
};

LINK_ENTITY_TO_CLASS( path_corner, CPathCorner )

// Global Savedata for Delay
TYPEDESCRIPTION	CPathCorner::m_SaveData[] =
{
	DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ),
};

IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity )

//
// Cache user-entity-field values until spawn is called.
//
void CPathCorner::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "wait" ) )
	{
		m_flWait = atof( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else 
		CPointEntity::KeyValue( pkvd );
}

void CPathCorner::Spawn()
{
	ASSERTSZ( !FStringNull( pev->targetname ), "path_corner without a targetname" );
}

#if 0
void CPathCorner::Touch( CBaseEntity *pOther )
{
	entvars_t *pevToucher = pOther->pev;
		
	if( FBitSet( pevToucher->flags, FL_MONSTER ) )
	{
		// monsters don't navigate path corners based on touch anymore
		return;
	}

	// If OTHER isn't explicitly looking for this path_corner, bail out
	if( pOther->m_pGoalEnt != this )
	{
		return;
	}

	// If OTHER has an enemy, this touch is incidental, ignore
	if( !FNullEnt( pevToucher->enemy ) )
	{
		return;		// fighting, not following a path
	}
	
	// UNDONE: support non-zero flWait
	/*
	if( m_flWait != 0 )
		ALERT( at_warning, "Non-zero path-cornder waits NYI" );
	*/

	// Find the next "stop" on the path, make it the goal of the "toucher".
	if( FStringNull( pev->target ) )
	{
		ALERT( at_warning, "PathCornerTouch: no next stop specified" );
	}

	pOther->m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) ) );

	// If "next spot" was not found (does not exist - level design error)
	if( !pOther->m_pGoalEnt )
	{
		ALERT( at_console, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING( pev->classname ), STRING( pev->target ) );
		return;
	}

	// Turn towards the next stop in the path.
	pevToucher->ideal_yaw = UTIL_VecToYaw( pOther->m_pGoalEnt->pev->origin - pevToucher->origin );
}
#endif

TYPEDESCRIPTION	CPathTrack::m_SaveData[] =
{
	DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ),
	DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ),
	DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ),
	DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ),
	DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ),
};

IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity )
LINK_ENTITY_TO_CLASS( path_track, CPathTrack )

//
// Cache user-entity-field values until spawn is called.
//
void CPathTrack::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "altpath" ) )
	{
		m_altName = ALLOC_STRING( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else
		CPointEntity::KeyValue( pkvd );
}

void CPathTrack::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	int on;

	// Use toggles between two paths
	if( m_paltpath )
	{
		on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE );
		if( ShouldToggle( useType, on ) )
		{
			if( on )
				SetBits( pev->spawnflags, SF_PATH_ALTERNATE );
			else
				ClearBits( pev->spawnflags, SF_PATH_ALTERNATE );
		}
	}
	else	// Use toggles between enabled/disabled
	{
		on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED );

		if( ShouldToggle( useType, on ) )
		{
			if( on )
				SetBits( pev->spawnflags, SF_PATH_DISABLED );
			else
				ClearBits( pev->spawnflags, SF_PATH_DISABLED );
		}
	}
}

void CPathTrack::Link( void )
{
	edict_t *pentTarget;

	if( !FStringNull( pev->target ) )
	{
		pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( pev->target ) );
		if( !FNullEnt(pentTarget) )
		{
			m_pnext = CPathTrack::Instance( pentTarget );

			if( m_pnext )		// If no next pointer, this is the end of a path
			{
				m_pnext->SetPrevious( this );
			}
		}
		else
			ALERT( at_console, "Dead end link %s\n", STRING( pev->target ) );
	}

	// Find "alternate" path
	if( m_altName )
	{
		pentTarget = FIND_ENTITY_BY_TARGETNAME( NULL, STRING( m_altName ) );
		if( !FNullEnt( pentTarget ) )
		{
			m_paltpath = CPathTrack::Instance( pentTarget );

			if( m_paltpath )		// If no next pointer, this is the end of a path
			{
				m_paltpath->SetPrevious( this );
			}
		}
	}
}

void CPathTrack::Spawn( void )
{
	pev->solid = SOLID_TRIGGER;
	UTIL_SetSize( pev, Vector( -8, -8, -8 ), Vector( 8, 8, 8 ) );

	m_pnext = NULL;
	m_pprevious = NULL;
// DEBUGGING CODE
#if PATH_SPARKLE_DEBUG
	SetThink( &Sparkle );
	pev->nextthink = gpGlobals->time + 0.5;
#endif
}

void CPathTrack::Activate( void )
{
	if( !FStringNull( pev->targetname ) )		// Link to next, and back-link
		Link();
}

CPathTrack *CPathTrack::ValidPath( CPathTrack *ppath, int testFlag )
{
	if( !ppath )
		return NULL;

	if( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) )
		return NULL;

	return ppath;
}

void CPathTrack::Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist )
{
	if( pstart && pend )
	{
		Vector dir = pend->pev->origin - pstart->pev->origin;
		dir = dir.Normalize();
		*origin = pend->pev->origin + dir * dist;
	}
}

CPathTrack *CPathTrack::GetNext( void )
{
	if( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) )
		return m_paltpath;

	return m_pnext;
}

CPathTrack *CPathTrack::GetPrevious( void )
{
	if( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) )
		return m_paltpath;

	return m_pprevious;
}

void CPathTrack::SetPrevious( CPathTrack *pprev )
{
	// Only set previous if this isn't my alternate path
	if( pprev && !FStrEq( STRING( pprev->pev->targetname ), STRING( m_altName ) ) )
		m_pprevious = pprev;
}

// Assumes this is ALWAYS enabled
CPathTrack *CPathTrack::LookAhead( Vector *origin, float dist, int move )
{
	CPathTrack *pcurrent;
	float originalDist = dist;

	pcurrent = this;
	Vector currentPos = *origin;

	if( dist < 0 )		// Travelling backwards through path
	{
		dist = -dist;
		while( dist > 0 )
		{
			Vector dir = pcurrent->pev->origin - currentPos;
			float length = dir.Length();
			if( !length )
			{
				if( !ValidPath( pcurrent->GetPrevious(), move ) ) 	// If there is no previous node, or it's disabled, return now.
				{
					if( !move )
						Project( pcurrent->GetNext(), pcurrent, origin, dist );
					return NULL;
				}
				pcurrent = pcurrent->GetPrevious();
			}
			else if( length > dist )	// enough left in this path to move
			{
				*origin = currentPos + ( dir * ( dist / length ) );
				return pcurrent;
			}
			else
			{
				dist -= length;
				currentPos = pcurrent->pev->origin;
				*origin = currentPos;
				if( !ValidPath( pcurrent->GetPrevious(), move ) )	// If there is no previous node, or it's disabled, return now.
					return NULL;

				pcurrent = pcurrent->GetPrevious();
			}
		}
		*origin = currentPos;
		return pcurrent;
	}
	else 
	{
		while( dist > 0 )
		{
			if( !ValidPath( pcurrent->GetNext(), move ) )	// If there is no next node, or it's disabled, return now.
			{
				if( !move )
					Project( pcurrent->GetPrevious(), pcurrent, origin, dist );
				return NULL;
			}
			Vector dir = pcurrent->GetNext()->pev->origin - currentPos;
			float length = dir.Length();
			if( !length  && !ValidPath( pcurrent->GetNext()->GetNext(), move ) )
			{
				if ( dist == originalDist ) // HACK -- up against a dead end
					return NULL;
				return pcurrent;
			}
			if( length > dist )	// enough left in this path to move
			{
				*origin = currentPos + (dir * (dist / length));
				return pcurrent;
			}
			else
			{
				dist -= length;
				currentPos = pcurrent->GetNext()->pev->origin;
				pcurrent = pcurrent->GetNext();
				*origin = currentPos;
			}
		}
		*origin = currentPos;
	}

	return pcurrent;
}

// Assumes this is ALWAYS enabled
CPathTrack *CPathTrack::Nearest( Vector origin )
{
	int deadCount;
	float minDist, dist;
	Vector delta;
	CPathTrack *ppath, *pnearest;

	delta = origin - pev->origin;
	delta.z = 0;
	minDist = delta.Length();
	pnearest = this;
	ppath = GetNext();

	// Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :)
	deadCount = 0;
	while( ppath && ppath != this )
	{
		deadCount++;
		if( deadCount > 9999 )
		{
			ALERT( at_error, "Bad sequence of path_tracks from %s", STRING( pev->targetname ) );
			return NULL;
		}
		delta = origin - ppath->pev->origin;
		delta.z = 0;
		dist = delta.Length();
		if( dist < minDist )
		{
			minDist = dist;
			pnearest = ppath;
		}
		ppath = ppath->GetNext();
	}
	return pnearest;
}

CPathTrack *CPathTrack::Instance( edict_t *pent )
{
	if( FClassnameIs( pent, "path_track" ) )
		return (CPathTrack *)GET_PRIVATE( pent );
	return NULL;
}

// DEBUGGING CODE
#if PATH_SPARKLE_DEBUG
void CPathTrack::Sparkle( void )
{
	pev->nextthink = gpGlobals->time + 0.2;
	if( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) )
		UTIL_ParticleEffect( pev->origin, Vector( 0, 0,100 ), 210, 10 );
	else
		UTIL_ParticleEffect( pev->origin, Vector( 0, 0, 100 ), 84, 10 );
}
#endif
