/*
   Copyright (C) 1997-2001 Id Software, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

   See the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   --------------------------------------------------------------
   The ACE Bot is a product of Steve Yeager, and is available from
   the ACE Bot homepage, at http://www.axionfx.com/ace.

   This program is a modification of the ACE Bot, and is therefore
   in NO WAY supported by Steve Yeager.
 */

#include "../g_local.h"
#include "ai_local.h"



//==========================================
// AI_FindCost
// Determine cost of moving from one node to another
//==========================================
int AI_FindCost( int from, int to, int movetypes )
{
	astarpath_t path;

	if( !AStar_GetPath( from, to, movetypes, &path ) )
		return -1;

	return path.numNodes;
}


//==========================================
// AI_FindClosestReachableNode
// Find the closest node to the player within a certain range
//==========================================
int AI_FindClosestReachableNode( vec3_t origin, edict_t *passent, int range, int flagsmask )
{
	int i;
	float closest = 99999;
	float dist;
	int node = -1;
	vec3_t v;
	trace_t	tr;
	float rng;
	vec3_t maxs, mins;

	VectorSet( mins, -15, -15, -15 );
	VectorSet( maxs, 15, 15, 15 );

	// For Ladders, do not worry so much about reachability
	if( flagsmask & NODEFLAGS_LADDER )
	{
		VectorCopy( vec3_origin, maxs );
		VectorCopy( vec3_origin, mins );
	}

	rng = (float)( range * range ); // square range for distance comparison (eliminate sqrt)

	for( i = 0; i < nav.num_nodes; i++ )
	{
		if( flagsmask == NODE_ALL || nodes[i].flags & flagsmask )
		{
			VectorSubtract( nodes[i].origin, origin, v );

			dist = v[0]*v[0] + v[1]*v[1] + v[2]*v[2];

			if( dist < closest && dist < rng )
			{
				vec3_t tmp_orig;
				VectorNormalize( v );
				VectorMA( origin, 15, v, tmp_orig );

				// make sure it is visible
				G_Trace( &tr, tmp_orig, mins, maxs, nodes[i].origin, passent, MASK_AISOLID );
				if( tr.fraction == 1.0 )
				{
					node = i;
					closest = dist;
				}
			}
		}
	}
	return node;
}

//==========================================
// AI_FindClosestNode - //jabot092(2)
// Doesn't take reachability in consideration
//==========================================
int AI_FindClosestNode( vec3_t origin, float mindist, int range, int flagsmask )
{
	int i;
	float closest = 99999;
	float dist;
	int node = INVALID;

	if( mindist > range ) return -1;

	for( i = 0; i < nav.num_nodes; i++ )
	{
		if( flagsmask == NODE_ALL || nodes[i].flags & flagsmask )
		{
			dist = DistanceFast( nodes[i].origin, origin ); //using sqrt: this isn't done while in game
			if( dist > mindist && dist < closest && dist < range )
			{
				node = i;
				closest = dist;
			}
		}
	}
	return node;
}


//==========================================
// AI_SetGoal
// set the goal
//==========================================
void AI_SetGoal( edict_t *self, int goal_node )
{
	int node;

	self->ai.goal_node = goal_node;
	node = AI_FindClosestReachableNode( self->s.origin, self, NODE_DENSITY*3, NODE_ALL );

	if( node == -1 )
	{
		AI_SetUpMoveWander( self );
		return;
	}

	//------- ASTAR -----------
	if( !AStar_GetPath( node, goal_node, self->ai.pers.moveTypesMask, &self->ai.path ) )
	{
		AI_SetUpMoveWander( self );
		return;
	}
	self->ai.current_node = self->ai.path.nodes[self->ai.path.numNodes];
	//-------------------------

	if( AIDevel.debugChased && bot_showlrgoal->integer > 1 )
		G_PrintMsg( AIDevel.chaseguy, "%s: GOAL: new START NODE selected %d\n", self->ai.pers.netname, node );

	self->ai.next_node = self->ai.current_node; // make sure we get to the nearest node first
	self->ai.node_timeout = 0;
}


//==========================================
// AI_FollowPath
// Move closer to goal by pointing the bot to the next node
// that is closer to the goal
//==========================================
qboolean AI_FollowPath( edict_t *self )
{
	vec3_t v;
	float dist;
	qboolean reached = qfalse;
	qboolean dont_jump = qfalse;
	int i;

	// Show the path
	if( bot_showpath->integer )
	{
		if( AIDevel.debugChased )
			AITools_DrawPath( self, self->ai.goal_node );
	}

	// Check whether node is still valid (at least for timed items)
#if 0
	if( self->ai.vsay_goalent && AI_IsTimedItem( self->ai.vsay_goalent ) )
	{
		if( !( self->ai.vsay_goalent->nextthink < ( level.time + 5000 ) ) || !( self->ai.vsay_goalent->think == DoRespawn ) )
			return qfalse;
	}
#endif

	if( self->ai.goal_node == INVALID )
		return qfalse;

	// Try again?
	if( self->ai.node_timeout++ > 30 || self->ai.next_node == INVALID )
	{
		if( self->ai.tries++ > 3 )
			return qfalse;
		else
			AI_SetGoal( self, self->ai.goal_node );
	}

	if( self->ai.current_node == INVALID || self->ai.next_node == INVALID )
		return qfalse;

	// Are we there yet?
	VectorSubtract( self->s.origin, nodes[self->ai.next_node].origin, v );
	dist = VectorLengthFast( v );

	self->ai.rush_item = qfalse;
	self->ai.camp_item = qfalse;
	for( i = 0; i < nav.num_items; i++ )
	{
		if( ( nav.items[i].node == self->ai.next_node ) )
		{
			if( AI_IsTimedItem( nav.items[i].ent ) )
			{
				if(  ( nav.items[i].ent->think == DoRespawn )
					&& ( nav.items[i].ent->nextthink > level.time )
					&& ( nav.items[i].ent->nextthink < ( level.time + 5200 ) ) )
				{
					if( dist < 100 )
					{
						self->ai.node_timeout = 0; // don't allow the node to timeout
						dist = AI_NODE_TOUCH_RADIUS*3 + 1; // hack so the bot thinks it's never been touched.
						reached = qfalse;
						dont_jump = qtrue;
						self->ai.camp_item = qtrue;
					}
				}
				else
				{
					self->ai.rush_item = qtrue;
				}
			}
			else if( nav.items[i].ent->classname && !Q_stricmp( nav.items[i].ent->classname, "capture_area_indicator" ) )
			{
				if( nav.items[i].ent->s.team != self->s.team )
				{

					if( dist < 100 )
						self->ai.node_timeout = 0; // don't allow the node to timeout
					dist = AI_NODE_TOUCH_RADIUS*3 + 1; // hack so the bot thinks it's never been touched.
					reached = qfalse;
					dont_jump = qtrue;
					self->ai.camp_item = qtrue;
				}
				else
				{
					reached = qtrue;
					self->ai.camp_item = qfalse;
				}
			}
			else
				self->ai.camp_item = qfalse;

			break;
		}
	}

	// in timer ctf, we want to never reach the flag if a team mate has it (so the bot insists in touching it)
	if( game.gametype == GAMETYPE_CTF )
	{
		for( i = 0; i < nav.num_items; i++ )
		{
			// Ignore items that are not there (solid)
			if( !nav.items[i].ent || !nav.items[i].ent->item || nav.items[i].ent->r.solid == SOLID_NOT )
				continue;
			if( nav.items[i].node == self->ai.next_node )
			{
				// is it a flag base?
				if( nav.items[i].ent->item->type & IT_FLAG && nav.items[i].ent->s.type == ET_FLAG_BASE )
				{
					if( G_CTF_TIMER )
					{
						//it's the enemy flag base, and the flag is not at base, and we are not carrying a flag
						if( !G_Gametype_CTF_HasFlag( self, ( self->s.team == TEAM_ALPHA ) ? TEAM_BETA : TEAM_ALPHA )
						   && ( nav.items[i].ent->s.team != self->s.team ) && !( nav.items[i].ent->s.effects & EF_ENEMY_FLAG ) )
						{
							dist = AI_NODE_TOUCH_RADIUS*3 + 1; // hack so the bot thinks it's never been touched.
							reached = qfalse;
							self->ai.node_timeout = 0; // don't allow the node to timeout
						}
					}
					// it's the enemy base, with the flag, and there's a unlock timer
					else if( WSW_CTF_UNLOCKTIME > 0.25f
					        && ( nav.items[i].ent->s.team != self->s.team )
					        && ( nav.items[i].ent->s.effects & EF_ENEMY_FLAG ) )
					{
						dist = AI_NODE_TOUCH_RADIUS*3 + 1; // hack so the bot thinks it's never been touched.
						reached = qfalse;
						self->ai.node_timeout = 0; // don't allow the node to timeout
					}
					// it's our team one, there's a capture time, and we are carrying a flag
					else if( WSW_CTF_CAPTURETIME > 0.25f
					        && nav.items[i].ent->s.team == self->s.team
					        && G_Gametype_CTF_HasFlag( self, ( self->s.team == TEAM_ALPHA ) ? TEAM_BETA : TEAM_ALPHA ) )
					{
						dist = AI_NODE_TOUCH_RADIUS*3 + 1; // hack so the bot thinks it's never been touched.
						reached = qfalse;
						self->ai.node_timeout = 0; // don't allow the node to timeout
					}

					break;
				}
			}
		}
	}

	if( self->ai.current_link_type & LINK_PLATFORM )
	{
		if( self->groundentity && self->groundentity->use == Use_Plat )
		{
			reached = ( self->groundentity->moveinfo.state == STATE_TOP || !VectorCompare( self->groundentity->s.origin, self->groundentity->moveinfo.dest ) );
		}
		else
		{
			float disth;
			vec3_t o1, o2;
			o1[0] = self->s.origin[0];
			o1[1] = self->s.origin[1];
			o1[2] = 0;
			o2[0] = nodes[self->ai.next_node].origin[0];
			o2[1] = nodes[self->ai.next_node].origin[1];
			o2[2] = 0;
			disth = DistanceFast( o1, o2 );
			reached = ( ( disth < AI_NODE_TOUCH_RADIUS * 2 ) && ( ( nodes[self->ai.next_node].origin[2] - self->s.origin[2] ) < AI_JUMPABLE_HEIGHT ) );
		}
	}
	else if( nodes[self->ai.next_node].flags == NODEFLAGS_JUMPPAD ) // going to a jump pad
	{
		reached = self->ai.status.jumpadReached;
		dont_jump = qtrue;
	}
	if( !self->groundentity && ( self->ai.current_link_type & LINK_JUMPPAD ) ) // pushed by a jump pad
	{
		reached = qfalse; // not until touching ground
	}
	else if( nodes[self->ai.next_node].flags & NODEFLAGS_TELEPORTER_IN )
	{
		reached = self->ai.status.TeleportReached;
	}
	else if( !self->groundentity && ( self->ai.current_link_type & LINK_JUMP ) )
	{
		reached = qfalse;
	}
	else
	{
		if( self->ai.path.numNodes > 2 )
		{
			// XXX: -2 ?
			int nextnextnode = self->ai.path.nodes[self->ai.path.numNodes-2];
			trace_t tr;
			vec3_t nnvec, t1, t2;
			G_Trace( &tr, self->s.origin, self->r.mins, self->r.maxs, nodes[nextnextnode].origin, self, MASK_AISOLID );
			VectorSubtract( nodes[nextnextnode].origin, self->s.origin, nnvec );
			VectorSubtract( nodes[self->ai.next_node].origin, self->s.origin, t1 );
			VectorSubtract( nodes[nextnextnode].origin, nodes[self->ai.next_node].origin, t2 );
			if( VectorLengthFast( nnvec ) < VectorLengthFast( t1 ) )
				reached = qtrue;

//			if( nodes[nextnextnode].flags & NODEFLAGS_REACHATTOUCH )
//				dont_jump = qtrue;

			//self->ai.path.nodes[self->ai.path.numNodes-1];
			if( nodes[self->ai.next_node].flags & NODEFLAGS_REACHATTOUCH )
				dont_jump = qtrue;

			VectorNormalizeFast( t1 );
			VectorNormalizeFast( t2 );
//			if( DotProduct( t1, t2 ) < 0.3 )
//				dont_jump = qtrue;

			if( VectorLengthFast( nnvec ) < 250 )
				//if( t1[2] == 0 && t2[2] == 0 )
				if( tr.fraction == 1.0f )
					reached = qtrue;
		}

		if( nodes[self->ai.next_node].flags & NODEFLAGS_REACHATTOUCH )
			dont_jump = qtrue;

		if( !self->ai.camp_item )
		{
			if( nodes[self->ai.next_node].flags & NODEFLAGS_REACHATTOUCH )
				reached |= ( dist < 2 );
			else
				reached |= ( dist < AI_NODE_TOUCH_RADIUS );
		}
		else
		{
			reached = qfalse;
		}
	}
	self->ai.dont_jump = dont_jump;

	if( reached )
	{
		// reset timeout
		self->ai.node_timeout = 0;

		if( self->ai.next_node == self->ai.goal_node )
		{
			if( AIDevel.debugChased && bot_showlrgoal->integer > 1 )
				G_PrintMsg( AIDevel.chaseguy, "%s: GOAL REACHED!\n", self->ai.pers.netname );

			//if botroam, setup a timeout for it
			if( nodes[self->ai.goal_node].flags & NODEFLAGS_BOTROAM )
			{
				int i;
				for( i = 0; i < nav.num_broams; i++ ) //find the broam
				{
					if( nav.broams[i].node != self->ai.goal_node )
						continue;

					//if(AIDevel.debugChased && bot_showlrgoal->integer)
					//	G_PrintMsg (AIDevel.chaseguy, "%s: BotRoam Time Out set up for node %i\n", self->ai.pers.netname, nav.broams[i].node);
					//Com_Printf( "%s: BotRoam Time Out set up for node %i\n", self->ai.pers.netname, nav.broams[i].node);
					self->ai.status.broam_timeouts[i] = level.time + 15000;
					break;
				}
			}

			// Set bot's movement vector
			VectorSubtract( nodes[self->ai.next_node].origin, self->s.origin, self->ai.move_vector );
			return qfalse; // so move wander is set up, and it forces checking for a new long range goal
		}
		else
		{
			self->ai.current_node = self->ai.next_node;
			if( self->ai.path.numNodes )
				self->ai.path.numNodes--;
			self->ai.next_node = self->ai.path.nodes[self->ai.path.numNodes];
		}
	}

	if( self->ai.current_node == INVALID || self->ai.next_node == INVALID )
		return qfalse;

	// Set bot's movement vector
	VectorSubtract( nodes[self->ai.next_node].origin, self->s.origin, self->ai.move_vector );
	return qtrue;
}
