// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// Gravity path - Used for underwater currents and wells.

#include "g_local.h"
#include "entity.h"
#include "gravpath.h"
#include "container.h"
#include "navigate.h"
#include "misc.h"
#include "player.h"

GravPathManager gravPathManager;

CLASS_DECLARATION( Class, GravPathManager, NULL );

ResponseDef GravPathManager::Responses[] =
{
	{ NULL,NULL }
};

GravPathManager::~GravPathManager()
{
	Reset();
}

void GravPathManager::Reset( void )
{
	while( pathList.NumObjects() > 0 )
	{
		delete ( GravPath * )pathList.ObjectAt( 1 );
	}

	pathList.FreeObjectList();
}

void GravPathManager::AddPath( GravPath *p )
{
	int num;
	num = pathList.AddObject( p );
	pathList.Resize( pathList.NumObjects() );
}

void GravPathManager::RemovePath( GravPath *p )
{
	pathList.RemoveObject( p );
	pathList.Resize( pathList.NumObjects() );
}

Vector GravPathManager::CalculateGravityPull( Entity &ent, Vector pos, qboolean *force )
{
	int i,num;
	GravPath       *p;
	GravPathNode   *node;
	Vector point;
	Vector newpoint;
	Vector dir;
	float bestdist = 99999;
	float dist;
	float speed;
	float radius;
	Vector velocity;
	int bestpath = 0;
	int entity_contents, grav_contents;

	num = pathList.NumObjects();

	entity_contents = gi.pointcontents( ent.worldorigin.vec3() );

	for( i = 1; i <= num; i++ )
	{
		p = ( GravPath * )pathList.ObjectAt( i );

		if ( !p )
			continue;

		// Check to see if path is active
		node = p->GetNode( 1 );
		if ( !node || !node->active )
			continue;

		// Check to see if the contents are the same
		grav_contents = gi.pointcontents( node->worldorigin.vec3() );

		// If grav node is in water, make sure ent is too.
		if ( ( grav_contents & CONTENTS_WATER ) && !( entity_contents & CONTENTS_WATER ) )
			continue;

		// Test to see if we are in this path's bounding box
		if ( (pos.x < p->maxs.x) && (pos.y < p->maxs.y) && (pos.z < p->maxs.z) &&
		     (pos.x > p->mins.x) && (pos.y > p->mins.y) && (pos.z > p->mins.z) )
		{
			point = p->ClosestPointOnPath( pos, ent, &dist, &speed, &radius );

			// If the closest distance on the path is greater than the radius, then
			// do not consider this path.

			if (dist > radius)
			{
				continue;
			}
			else if (dist < bestdist)
			{
				bestpath = i;
				bestdist  = dist;
			}
		}
	}

	if (!bestpath)
	{
		return vec_zero;
	}

	p = ( GravPath * )pathList.ObjectAt( bestpath );
	if ( !p )
		return velocity;
	*force    = p->force;
	dist     = p->DistanceAlongPath( pos, &speed );
	newpoint = p->PointAtDistance( dist + speed );
	dir      = newpoint-pos;
	dir.normalize();
	velocity = dir * speed;
	return velocity;
}

/*****************************************************************************/
/*SINED info_grav_pathnode (0 0 .5) (-16 -16 0) (16 16 32) HEADNODE FORCE
   "radius" Radius of the effect of the pull (Default is 256)
   "speed"  Speed of the pull (Use negative for a repulsion) (Default is 100)

   Set HEADNODE to signify the head of the path.
   Set FORCE if you want un-fightable gravity ( i.e. can't go backwards )
   /*****************************************************************************/

CLASS_DECLARATION( Entity, GravPathNode, "info_grav_pathnode" );

Event EV_GravPath_Create( "gravpath_create" );
Event EV_GravPath_Activate( "activate" );
Event EV_GravPath_Deactivate( "deactivate" );

ResponseDef GravPathNode::Responses[] =
{
	{ &EV_GravPath_Create,           ( Response )GravPathNode::CreatePath },
	{ &EV_GravPath_Activate,         ( Response )GravPathNode::Activate },
	{ &EV_GravPath_Deactivate,       ( Response )GravPathNode::Deactivate },
	{ NULL, NULL }
};

GravPathNode::GravPathNode()
{
	setMoveType( MOVETYPE_NONE );
	setSolidType( SOLID_NOT );
	hideModel();

	speed    = G_GetFloatArg( "speed",100.0f );
	radius   = G_GetFloatArg( "radius",256.0f );
	headnode = spawnflags & 1;
	active   = true;

	// This is the head of a new path, post an event to create the path
	if ( headnode )
	{
		PostEvent( EV_GravPath_Create, 0 );
	}
}

float GravPathNode::Speed( void )
{
	if ( active )
		return speed;
	else
		return 0;
};

void GravPathNode::Activate( Event *ev )
{
	GravPathNode   *node;
	int num;
	const char     *target;

	active = true;
	node = this;
	// Go through the entire path and activate it
	target = node->Target();
	while (target[0])
	{
		if (num = G_FindTarget( 0, target ))
		{
			node = (GravPathNode *)G_GetEntity( num );
			assert( node );
			node->active = true;
		}
		else
		{
			gi.error( "GravPathNode::CreatePath: target %s not found\n",target );
		}
		target = node->Target();
	}
}

void GravPathNode::Deactivate( Event *ev )
{
	GravPathNode   *node;
	int num;
	const char     *target;

	active = false;
	node = this;
	// Go through the entire path and activate it
	target = node->Target();
	while (target[0])
	{
		if (num = G_FindTarget( 0, target ))
		{
			node = (GravPathNode *)G_GetEntity( num );
			assert( node );
			node->active = false;
		}
		else
		{
			gi.error( "GravPathNode::CreatePath: target %s not found\n",target );
		}
		target = node->Target();
	}
}

void GravPathNode::CreatePath( Event *ev )
{
	const char     *target;
	GravPath       *path = new GravPath;
	GravPathNode   *node;
	int num;

	ClearBounds( path->mins.vec3(),path->maxs.vec3());

	// This node is the head of a path, create a new path in the path manager.
	// and add it in, then add all of it's children in the path.
	node = this;
	path->AddNode( node );
	path->force = spawnflags & 2;

	// Make the path from the targetlist.
	target = node->Target();
	while (target[0])
	{
		if (num = G_FindTarget( 0, target ))
		{
			node = (GravPathNode *)G_GetEntity( num );
			assert( node );
			path->AddNode( node );
		}
		else
		{
			gi.error( "GravPathNode::CreatePath: target %s not found\n",target );
		}
		target = node->Target();
	}

	// Set the origin.
	path->origin = path->mins + path->maxs;
	path->origin *= 0.5f;
}

CLASS_DECLARATION( Listener, GravPath, NULL );

Event EV_DrawGravPath( "drawpath" );

ResponseDef GravPath::Responses[] =
{
	{ &EV_DrawGravPath, (Response)GravPath::DrawPath },
	{ NULL, NULL }
};

GravPath::GravPath()
{
	// Event *event;

	pathlength  = 0;
	from        = NULL;
	to          = NULL;
	nextnode    = 1;

	if ( !LoadingSavegame )
	{
		gravPathManager.AddPath( this );
	}

	// event = new Event(EV_DrawGravPath);
	// event->AddFloat(1);
	// event->AddFloat(0);
	// event->AddFloat(0);
	// PostEvent(event,0.1f);
}

GravPath::~GravPath()
{
	pathlength  = 0;
	from        = NULL;
	to          = NULL;
	nextnode    = 1;
	gravPathManager.RemovePath( this );
}

void GravPath::Clear( void )
{
	nextnode = 1;
	pathlength = 0;
	from = NULL;
	to = NULL;
	pathlist.FreeObjectList();
}

void GravPath::Reset( void )
{
	nextnode = 1;
}

GravPathNode *GravPath::Start( void )
{
	return from;
}

GravPathNode *GravPath::End( void )
{
	return to;
}

void GravPath::AddNode( GravPathNode *node )
{
	int num;
	Vector r,addp;

	if ( !from )
	{
		from = node;
	}

	to = node;
	pathlist.AddObject( GravPathNodePtr( node ) );

	num = NumNodes();
	if ( num > 1 )
	{
		pathlength += ( node->worldorigin - GetNode( num )->worldorigin ).length();
	}

	r.setXYZ( node->Radius(),node->Radius(),node->Radius());
	addp = node->worldorigin + r;
	AddPointToBounds( addp.vec3(),mins.vec3(),maxs.vec3());
	addp = node->worldorigin - r;
	AddPointToBounds( addp.vec3(),mins.vec3(),maxs.vec3());
}

GravPathNode *GravPath::GetNode( int num )
{
	return pathlist.ObjectAt( num );
}

GravPathNode *GravPath::NextNode( void )
{
	if ( nextnode <= NumNodes() )
	{
		return pathlist.ObjectAt( nextnode++ );
	}
	return NULL;
}

Vector GravPath::ClosestPointOnPath( Vector pos,Entity &ent,float  *ret_dist,float  *speed,float  *radius )
{
	GravPathNode    *s;
	GravPathNode    *e;
	int num;
	int i;
	float bestdist;
	Vector bestpoint;
	float dist;
	float segmentlength;
	Vector delta;
	Vector p1;
	Vector p2;
	Vector p3;
	float t;
	trace_t trace;

	num = NumNodes();
	s = GetNode( 1 );
	trace = G_Trace( pos, ent.mins, ent.maxs, s->worldorigin, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 1" );
	bestpoint = s->worldorigin;
	delta = bestpoint - pos;
	bestdist = delta.length();
	*speed = s->Speed();
	*radius = s->Radius();

	for( i = 2; i <= num; i++ )
	{
		e = GetNode( i );

		// check if we're closest to the endpoint
		delta = e->worldorigin - pos;
		dist = delta.length();

		if ( dist < bestdist )
		{
			trace = G_Trace( pos, ent.mins, ent.maxs, e->worldorigin, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 2" );
			bestdist = dist;
			bestpoint = e->worldorigin;
			*speed = e->Speed();
			*radius = e->Radius();
		}

		// check if we're closest to the segment
		p1 = e->worldorigin - s->worldorigin;
		segmentlength = p1.length();
		p1   *= 1 / segmentlength;
		p2 = pos - s->worldorigin;

		t = p1 * p2;
		if ( ( t > 0 ) && ( t < segmentlength ) )
		{
			p3 = ( p1 * t ) + s->worldorigin;

			delta = p3 - pos;
			dist = delta.length();
			if ( dist < bestdist )
			{
				trace = G_Trace( pos, ent.mins, ent.maxs, p3, &ent, MASK_PLAYERSOLID, "GravPath::ClosestPointOnPath 3" );
				bestdist = dist;
				bestpoint = p3;
				*speed  = (e->Speed() * t) + (s->Speed() * (1.0f - t));
				*radius = (e->Radius() * t) + (s->Radius() * (1.0f - t));
			}
		}

		s = e;
	}
	*ret_dist = bestdist;
	return bestpoint;
}

float GravPath::DistanceAlongPath( Vector pos,float *speed )
{
	GravPathNode    *s;
	GravPathNode    *e;
	int num;
	int i;
	float bestdist;
	float dist;
	float segmentlength;
	Vector delta;
	Vector segment;
	Vector p1;
	Vector p2;
	Vector p3;
	float t;
	float pathdist;
	float bestdistalongpath;
	float oosl;
	pathdist = 0;

	num = NumNodes();
	s = GetNode( 1 );
	delta = s->worldorigin - pos;
	bestdist = delta.length();
	bestdistalongpath = 0;
	*speed = s->Speed();

	for( i = 2; i <= num; i++ )
	{
		e = GetNode( i );

		segment = e->worldorigin - s->worldorigin;
		segmentlength = segment.length();

		// check if we're closest to the endpoint
		delta = e->worldorigin - pos;
		dist = delta.length();

		if ( dist < bestdist )
		{
			bestdist = dist;
			bestdistalongpath = pathdist + segmentlength;
			*speed = e->Speed();
		}

		// check if we're closest to the segment
		oosl = ( 1 / segmentlength );
		p1 = segment * oosl;
		p1.normalize();
		p2 = pos - s->worldorigin;

		t = p1 * p2;
		if ( ( t > 0 ) && ( t < segmentlength ) )
		{
			p3 = ( p1 * t ) + s->worldorigin;

			delta = p3 - pos;
			dist = delta.length();
			if ( dist < bestdist )
			{
				bestdist = dist;
				bestdistalongpath = pathdist + t;

				t *= oosl;
				*speed = (e->Speed() * t) + (s->Speed() * (1.0f - t));
			}
		}

		s = e;
		pathdist += segmentlength;
	}

	return bestdistalongpath;
}

Vector GravPath::PointAtDistance( float dist )
{
	GravPathNode    *s;
	GravPathNode    *e;
	int num;
	int i;
	Vector delta;
	Vector p1;
	float t;
	float pathdist;
	float segmentlength;

	num = NumNodes();
	s = GetNode( 1 );
	pathdist = 0;

	for( i = 2; i <= num; i++ )
	{
		e = GetNode( i );

		delta = e->worldorigin - s->worldorigin;
		segmentlength = delta.length();

		if ( ( pathdist + segmentlength ) > dist )
		{
			t = dist - pathdist;

			p1 = delta * ( t / segmentlength );
			return p1 + s->worldorigin;
		}

		s = e;
		pathdist += segmentlength;
	}

	// cap it off at start or end of path
	return s->worldorigin;
}

void GravPath::DrawPath( Event *ev )
{
	Vector s;
	Vector e;
	Vector offset;
	GravPathNode    *node;
	int num;
	int i;
	float r = ev->GetFloat( 1 );
	float g = ev->GetFloat( 2 );
	float b = ev->GetFloat( 3 );
	Event          *event;

	num = NumNodes();
	node = GetNode( 1 );
	s = node->worldorigin;
	offset = Vector( r, g, b ) * 4 + Vector( 0, 0, 0 );
	offset = Vector( 0, 0, 0 );

	for( i = 2; i <= num; i++ )
	{
		node = GetNode( i );
		e = node->worldorigin;

		G_DebugLine( s + offset, e + offset, r, g, b, 1 );
		s = e;
	}

	G_DebugBBox( origin,mins-origin,maxs-origin,1,0,0,1 );

	event = new Event( EV_DrawGravPath );
	event->AddFloat( r );
	event->AddFloat( g );
	event->AddFloat( b );
	PostEvent( event,0.1f );
}

int GravPath::NumNodes( void )
{
	return pathlist.NumObjects();
}

float GravPath::Length( void )
{
	return pathlength;
}
