/*
   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"


//ACE


typedef struct
{
	edict_t	*ent;

	qboolean was_falling;
	int last_node;

} player_dropping_nodes_t;
player_dropping_nodes_t	player;


//===========================================================
//
//				EDIT NODES
//
//===========================================================

#if 0
static edict_t *AI_PlayerDroppingNodesPassent( void )
{
	return player.ent;
}
#endif


//==========================================
// AI_AddNode
// Add a node of normal navigation types.
// Not valid to add nodes from entities nor items
//==========================================
static int AI_AddNode( vec3_t origin, int flagsmask )
{
	if( nav.num_nodes + 1 > MAX_NODES )
		return -1;

	if( flagsmask & NODEFLAGS_WATER )
		flagsmask |= NODEFLAGS_FLOAT;

	VectorCopy( origin, nodes[nav.num_nodes].origin );
	if( !( flagsmask & NODEFLAGS_FLOAT ) )
		AI_DropNodeOriginToFloor( nodes[nav.num_nodes].origin, player.ent );

	//if( !(flagsmask & NODEFLAGS_NOWORLD) ) {	//don't spawn inside solids
	//	trace_t	trace;
	//	trace = gi.trace( nodes[nav.num_nodes].origin, tv(-15, -15, -8), tv(15, 15, 8), nodes[nav.num_nodes].origin, player.ent, MASK_NODESOLID );
	//	if( trace.startsolid )
	//		return -1;
	//}

	nodes[nav.num_nodes].flags = flagsmask;
	nodes[nav.num_nodes].flags |= AI_FlagsForNode( nodes[nav.num_nodes].origin, player.ent );

	Com_Printf( "Dropped Node\n" );

	nav.num_nodes++;
	return nav.num_nodes-1; // return the node added
}

void AI_DeleteNode( int node )
{
	int i;
	if( !nav.editmode || nav.loaded )
	{
		Com_Printf( "       : Can't delete nodes when not in editing mode.\n" );
		return;
	}

	if( nodes[node].flags & ( NODEFLAGS_SERVERLINK|NODEFLAGS_JUMPPAD|NODEFLAGS_PLATFORM|NODEFLAGS_TELEPORTER_IN|NODEFLAGS_TELEPORTER_OUT ) )
	{
		Com_Printf( "Can't delete nodes generated by the server\n" );
		return;
	}
	for( i = 0; i < nav.num_items; i++ )
	{
		if( nav.items[i].node == node )
		{
			Com_Printf( "Can't delete item nodes\n" );
			return;
		}
	}
	if( node != INVALID && node >= 0 && node < nav.num_nodes )
	{
		for( i = node + 1; i < nav.num_nodes; i++ )
		{
			memcpy( &nodes[i - 1], &nodes[i], sizeof( nav_node_t ) );
			memcpy( &pLinks[i - 1], &pLinks[i], sizeof( nav_plink_t ) );
		}
		nav.num_nodes--;
		memset( &nodes[nav.num_nodes], 0, sizeof( nav_node_t ) );
		memset( &pLinks[nav.num_nodes], 0, sizeof( nav_plink_t ) );
	}
}

//==========================================
// AI_UpdateNodeEdge
// Add/Update node connections (paths)
//==========================================
static void AI_UpdateNodeEdge( int from, int to )
{
	int link;

	if( from == -1 || to == -1 || from == to )
		return; // safety


	if( AI_PlinkExists( from, to ) )
	{
		link = AI_PlinkMoveType( from, to );
		Com_Printf( "= Link: %i -> %i. %s\n", from, to, AI_LinkString( link ) );
	}
	else
	{
		link = AI_FindLinkType( from, to );

		Com_Printf( "^2+^7 Link: %i -> %i. %s\n", from, to, AI_LinkString( link ) );
	}
}



//===========================================================
//
//			PLAYER DROPPING NODES
//
//===========================================================


//==========================================
// AI_DropLadderNodes
// drop nodes all along the ladder
//==========================================
static void AI_DropLadderNodes( edict_t *self )
{
	vec3_t torigin;
	vec3_t borigin;
	vec3_t droporigin;
	int step;
	trace_t trace;

	//find top & bottom of the ladder
	VectorCopy( self->s.origin, torigin );
	VectorCopy( self->s.origin, borigin );

	while( AI_IsLadder( torigin, self->r.client->ps.viewangles, self->r.mins, self->r.maxs, self ) )
	{
		torigin[2]++;
	}
	torigin[2] += ( self->r.mins[2] + 8 );

	//drop node on top
	AI_AddNode( torigin, ( NODEFLAGS_LADDER|NODEFLAGS_FLOAT ) );

	//find bottom. Try simple first
	//trace = gi.trace( borigin, tv(-15,-15,-24), tv(15,15,0), tv(borigin[0], borigin[1], borigin[2] - 2048), self, MASK_NODESOLID );
	G_Trace( &trace, borigin, playerbox_crouch_mins, tv( playerbox_crouch_maxs[0], playerbox_crouch_maxs[1], 0 ), tv( borigin[0], borigin[1], borigin[2] - 2048 ), self, MASK_NODESOLID );
	if( !trace.startsolid && trace.fraction < 1.0
	   && AI_IsLadder( trace.endpos, self->r.client->ps.viewangles, self->r.mins, self->r.maxs, self ) )
	{
		VectorCopy( trace.endpos, borigin );

	}
	else
	{
		//it wasn't so easy

		//trace = gi.trace( borigin, tv(-15,-15,-25), tv(15,15,0), borigin, self, MASK_NODESOLID );
		G_Trace( &trace, borigin, tv( playerbox_crouch_mins[0], playerbox_crouch_mins[1], playerbox_crouch_mins[2]-1 ), tv( playerbox_crouch_maxs[0], playerbox_crouch_maxs[1], 0 ), borigin, self, MASK_NODESOLID );
		while( AI_IsLadder( borigin, self->r.client->ps.viewangles, self->r.mins, self->r.maxs, self )
		       && !trace.startsolid )
		{
			borigin[2]--;
			//trace = gi.trace( borigin, tv(-15,-15,-25), tv(15,15,0), borigin, self, MASK_NODESOLID );
			G_Trace( &trace, borigin, tv( playerbox_crouch_mins[0], playerbox_crouch_mins[1], playerbox_crouch_mins[2]-1 ), tv( playerbox_crouch_maxs[0], playerbox_crouch_maxs[1], 0 ), borigin, self, MASK_NODESOLID );
		}

		//if trace never reached solid, put the node on the ladder
		if( !trace.startsolid )
			borigin[2] -= self->r.mins[2];
	}

	//drop node on bottom
	AI_AddNode( borigin, ( NODEFLAGS_LADDER|NODEFLAGS_FLOAT ) );

	if( torigin[2] - borigin[2] < NODE_DENSITY )
		return;

	//make subdivisions and add nodes in between
	step = NODE_DENSITY*0.8;
	VectorCopy( borigin, droporigin );
	droporigin[2] += step;
	while( droporigin[2] < torigin[2] - 32 )
	{
		AI_AddNode( droporigin, ( NODEFLAGS_LADDER|NODEFLAGS_FLOAT ) );
		droporigin[2] += step;
	}
}


//==========================================
// AI_CheckForLadder
// Check for adding ladder nodes
//==========================================
static qboolean AI_CheckForLadder( edict_t *self )
{
	int closest_node;

	// If there is a ladder and we are moving up, see if we should add a ladder node
	if( self->velocity[2] < 5 )
		return qfalse;

	if( !AI_IsLadder( self->s.origin, self->r.client->ps.viewangles, self->r.mins, self->r.maxs, self ) )
		return qfalse;

	// If there is already a ladder node in here we've already done this ladder
	closest_node = AI_FindClosestReachableNode( self->s.origin, self, NODE_DENSITY, NODEFLAGS_LADDER );
	if( closest_node != -1 )
		return qfalse;

	//proceed:
	AI_DropLadderNodes( self );
	return qtrue;
}


//==========================================
// AI_TouchWater
// Capture when players touches water for mapping purposes.
//==========================================
static void AI_WaterJumpNode( void )
{
	int closest_node;
	vec3_t waterorigin;
	trace_t	trace;
	edict_t	ent;

	//don't drop if player is riding elevator or climbing a ladder
	if( player.ent->groundentity && player.ent->groundentity != world )
	{
		if( player.ent->groundentity->classname )
		{
			if( !strcmp( player.ent->groundentity->classname, "func_plat" )
			   || !strcmp( player.ent->groundentity->classname, "trigger_push" )
			   || !strcmp( player.ent->groundentity->classname, "func_train" )
			   || !strcmp( player.ent->groundentity->classname, "func_rotate" )
			   || !strcmp( player.ent->groundentity->classname, "func_bob" )
			   || !strcmp( player.ent->groundentity->classname, "func_door" ) )
				return;
		}
	}
	if( AI_IsLadder( player.ent->s.origin, player.ent->r.client->ps.viewangles, player.ent->r.mins, player.ent->r.maxs, player.ent ) )
		return;

	VectorCopy( player.ent->s.origin, waterorigin );

	//move the origin to water limit
	if( G_PointContents( waterorigin ) & MASK_WATER )
	{
		//reverse
		G_Trace( &trace, waterorigin,
		         //trace = gi.trace( waterorigin,
		         vec3_origin,
		         vec3_origin,
		         tv( waterorigin[0], waterorigin[1], waterorigin[2] + NODE_DENSITY*2 ),
		         player.ent,
		         MASK_ALL );

		VectorCopy( trace.endpos, waterorigin );
		if( trace.contents & MASK_WATER )
			return;
	}

	//find water limit
	G_Trace( &trace, waterorigin,
	         //trace = gi.trace( waterorigin,
	         vec3_origin,
	         vec3_origin,
	         tv( waterorigin[0], waterorigin[1], waterorigin[2] - NODE_DENSITY*2 ),
	         player.ent,
	         MASK_WATER );

	if( trace.fraction == 1.0 )
		return;
	else
		VectorCopy( trace.endpos, waterorigin );

	//tmp test (should just move 1 downwards)
	if( !( G_PointContents( waterorigin ) & MASK_WATER ) )
	{
		int k = 0;
		do
		{
			waterorigin[2]--;
			k++;
		}
		while( !( G_PointContents( waterorigin ) & MASK_WATER ) );
	}

	ent = *player.ent;
	VectorCopy( waterorigin, ent.s.origin );

	// Look for the closest node of type water
	closest_node = AI_FindClosestReachableNode( ent.s.origin, &ent, 32, NODEFLAGS_WATER );
	if( closest_node == -1 ) // we need to drop a node
	{
		closest_node = AI_AddNode( waterorigin, ( NODEFLAGS_WATER|NODEFLAGS_FLOAT ) );

		// Add an edge
		AI_UpdateNodeEdge( player.last_node, closest_node );
		player.last_node = closest_node;

	}
	else
	{

		AI_UpdateNodeEdge( player.last_node, closest_node );
		player.last_node = closest_node; // zero out so other nodes will not be linked
	}
}


//==========================================
// AI_PathMap
// This routine is called to hook in the pathing code and sets
// the current node if valid.
//==========================================

#define NODE_UPDATE_DELAY   100;
static void AI_PathMap( void )
{
	static unsigned int last_update = 0;
	int closest_node;

	//DROP WATER JUMP NODE (not limited by delayed updates)
	if( !player.ent->is_swim && player.last_node != -1
	    && player.ent->is_swim != player.ent->was_swim )
	{
		AI_WaterJumpNode();
		last_update = level.time + NODE_UPDATE_DELAY; // slow down updates a bit
		return;
	}

	if( level.time < last_update )
		return;

	last_update = level.time + NODE_UPDATE_DELAY; // slow down updates a bit

	//don't drop nodes when riding movers
	if( player.ent->groundentity && player.ent->groundentity != world )
	{
		if( player.ent->groundentity->classname )
		{
			if( !strcmp( player.ent->groundentity->classname, "func_plat" )
			   || !strcmp( player.ent->groundentity->classname, "trigger_push" )
			   || !strcmp( player.ent->groundentity->classname, "func_train" )
			   || !strcmp( player.ent->groundentity->classname, "func_rotate" )
			   || !strcmp( player.ent->groundentity->classname, "func_bob" )
			   || !strcmp( player.ent->groundentity->classname, "func_door" ) )
				return;
		}
	}

	// Special check for ladder nodes
	if( AI_CheckForLadder( player.ent ) )
		return;

	// Not on ground, and not in the water, so bail (deeper check by using a splitmodels function)
	if( !player.ent->is_step )
	{
		if( !player.ent->is_swim )
		{
			player.was_falling = qtrue;
			return;
		}
		else if( player.ent->is_swim )
			player.was_falling = qfalse;
	}

	//player just touched the ground
	if( player.was_falling == qtrue )
	{
		if( !player.ent->groundentity )  //not until it REALLY touches ground
			return;

		//normal nodes

		//check for duplicates (prevent adding too many)
		closest_node = AI_FindClosestReachableNode( player.ent->s.origin, player.ent, 64, NODE_ALL );

		//otherwise, add a new node
		if( closest_node == INVALID )
			closest_node = AI_AddNode( player.ent->s.origin, 0 ); //no flags = normal movement node

		// Now add link
		if( player.last_node != -1 && closest_node != -1 )
			AI_UpdateNodeEdge( player.last_node, closest_node );

		if( closest_node != -1 )
			player.last_node = closest_node; // set visited to last

		player.was_falling = qfalse;
		return;
	}


	//jal: I'm not sure of not having nodes in lava/slime
	//being actually a good idea. When the bots incidentally
	//fall inside it they don't know how to get out
	// Lava/Slime


	// Iterate through all nodes to make sure far enough apart
	closest_node = AI_FindClosestReachableNode( player.ent->s.origin, player.ent, NODE_DENSITY, NODE_ALL );

	// Add Nodes as needed
	if( closest_node == INVALID )
	{
		// Add nodes in the water as needed
		if( player.ent->is_swim )
			closest_node = AI_AddNode( player.ent->s.origin, ( NODEFLAGS_WATER|NODEFLAGS_FLOAT ) );
		else
			closest_node = AI_AddNode( player.ent->s.origin, 0 );

		// Now add link
		if( player.last_node != -1 )
			AI_UpdateNodeEdge( player.last_node, closest_node );
	}
	else if( closest_node != player.last_node && player.last_node != INVALID )
		AI_UpdateNodeEdge( player.last_node, closest_node );

	if( closest_node != -1 )
		player.last_node = closest_node; // set visited to last
}


//==========================================
// AITools_AddBotRoamNode_Cmd
// Drop a bot roam node by user console
//==========================================
void AITools_AddBotRoamNode_Cmd( void )
{
	if( !nav.editmode || nav.loaded )
	{
		Com_Printf( "       : Can't Create bot roam nodes when not in editing mode.\n" );
		return;
	}

	AI_AddNode( player.ent->s.origin, ( AI_FlagsForNode( player.ent->s.origin, player.ent ) | NODEFLAGS_BOTROAM ) );
}

//==========================================
// AITools_AddNode_Cmd
// Drop a node by user console
//==========================================
void AITools_AddNode_Cmd( void )
{
	if( !nav.editmode || nav.loaded )
	{
		Com_Printf( "       : Can't Add nodes when not being in editing mode.\n" );
		return;
	}

	AI_AddNode( player.ent->s.origin, AI_FlagsForNode( player.ent->s.origin, player.ent ) );
}

static int rj_start = 0;

void AITools_StartRJLink_Cmd( void )
{
	if( !nav.editmode || nav.loaded )
	{
		Com_Printf( "       : Can't Add nodes when not being in editing mode.\n" );
		return;
	}

	rj_start = AI_FindClosestNode( player.ent->s.origin, 0.0f, 100, NODE_ALL );
	Com_Printf( "RJ Sourcenode: %d\n", rj_start );

}

void AITools_AddRJLink_Cmd( void )
{
	int rj_end;
	if( !nav.editmode || nav.loaded )
	{
		Com_Printf( "       : Can't Add nodes when not being in editing mode.\n" );
		return;
	}

	rj_end = AI_FindClosestNode( player.ent->s.origin, 0.0f, 100, NODE_ALL );
	Com_Printf( "RJ dstnode: %d\n", rj_end );
	if( AI_AddLink( rj_start, rj_end, LINK_ROCKETJUMP ) )
	{
		Com_Printf( "Added RJ Link :)\n" );
	}
	else
	{
		Com_Printf( "No Way :(((((((((((((((((((((((((((((((((((((((((((((\n" );
	}

}


//==========================================
// AITools_DropNodes
// Clients try to create new nodes while walking the map
//==========================================
void AITools_DropNodes( edict_t *ent )
{
	if( nav.loaded || !nav.editmode )
		return;

	AI_CategorizePosition( ent );
	player.ent = ent;
	AI_PathMap();
}


//==========================================
//
//==========================================
static void AITools_EraseNodes( void )
{
	//Init nodes arrays
	nav.num_nodes = 0;
	memset( nodes, 0, sizeof( nav_node_t ) * MAX_NODES );
	memset( pLinks, 0, sizeof( nav_plink_t ) * MAX_NODES );

	nav.num_ents = 0;
	memset( nav.ents, 0, sizeof( nav_ents_t ) * MAX_EDICTS );

	nav.num_broams = 0;
	memset( nav.broams, 0, sizeof( nav_broam_t ) * MAX_BOT_ROAMS );

	nav.num_items = 0;
	memset( nav.items, 0, sizeof( nav_item_t ) * MAX_EDICTS );

	nav.loaded = qfalse;
}

void AITools_InitEditnodes( void )
{
	if( nav.editmode )
	{
		Com_Printf( "       : You are already in editing mode.\n" );
		return;
	}

	if( nav.loaded )
	{
		AITools_EraseNodes();
		AI_LoadPLKFile( level.mapname );
		//delete everything but nodes
		memset( pLinks, 0, sizeof( nav_plink_t ) * MAX_NODES );

		nav.num_ents = 0;
		memset( nav.ents, 0, sizeof( nav_ents_t ) * MAX_EDICTS );

		nav.num_broams = 0;
		memset( nav.broams, 0, sizeof( nav_broam_t ) * MAX_BOT_ROAMS );

		nav.num_items = 0;
		memset( nav.items, 0, sizeof( nav_item_t ) * MAX_EDICTS );
		nav.loaded = qfalse;
	}

	Com_Printf( "       : EDIT MODE: ON\n" );
	nav.editmode = qtrue;
}

void AITools_InitMakenodes( void )
{
	if( nav.editmode )
	{
		Com_Printf( "       : Your are already in editing mode.\n" );
		return;
	}

	if( nav.loaded )
		AITools_EraseNodes();

	Com_Printf( "       : EDIT MODE: ON\n" );
	nav.editmode = qtrue;
}


//-------------------------------------------------------------



//==========================================
// AI_SavePLKFile
// save nodes and plinks to file.
// Only navigation nodes are saved. Item nodes aren't
//==========================================
static qboolean AI_SavePLKFile( char *mapname )
{
	char filename[MAX_QPATH];
	int version = NAV_FILE_VERSION;
	int filenum;
	int length;
	int i;

	Q_snprintfz( filename, sizeof( filename ), "%s/%s.%s", NAV_FILE_FOLDER, mapname, NAV_FILE_EXTENSION );

	length = trap_FS_FOpenFile( filename, &filenum, FS_WRITE );
	if( length == -1 )
		return qfalse;

	trap_FS_Write( &version, sizeof( int ), filenum );
	trap_FS_Write( &nav.num_nodes, sizeof( int ), filenum );

	// write out nodes
	for( i = 0; i < nav.num_nodes; i++ )
	{
		trap_FS_Write( &nodes[i], sizeof( nav_node_t ), filenum );
	}

	// write out plinks array
	for( i = 0; i < nav.num_nodes; i++ )
	{
		trap_FS_Write( &pLinks[i], sizeof( nav_plink_t ), filenum );
	}

	trap_FS_FCloseFile( filenum );

	return qtrue;
}

//===========================================================
//
//				EDITOR
//
//===========================================================


//==================
// AITools_SaveNodes
//==================
void AITools_SaveNodes( void )
{
	int newlinks;
	int jumplinks;

	if( !nav.editmode )
	{
		Com_Printf( "       : Can't Save nodes when not being in editing mode.\n" );
		return;
	}

	if( !nav.num_nodes )
	{
		Com_Printf( "       : No nodes to save\n" );
		return;
	}

	//find links
	newlinks = AI_LinkCloseNodes();
	Com_Printf( "       : Added %i new links\n", newlinks );

	//find jump links
	jumplinks = AI_LinkCloseNodes_JumpPass( 0 );
	Com_Printf( "       : Added %i new jump links\n", jumplinks );

	if( !AI_SavePLKFile( level.mapname ) )
		Com_Printf( "       : Failed: Couldn't create the nodes file\n" );
	else
		Com_Printf( "       : Nodes files saved\n" );

	//restart navigation
	AITools_EraseNodes();
	AI_InitNavigationData();
}
