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


void AI_CategorizePosition (edict_t *ent);


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

edict_t *AI_PlayerDroppingNodesPassent(void)
{
	return player.ent;
}


//==========================================
// AI_AddNode
// Add a node of normal navigation types.
// Not valid to add nodes from entities nor items
//==========================================
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
}



//==========================================
// AI_UpdateNodeEdge
// Add/Update node connections (paths)
//==========================================
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);
	} else
		link = AI_FindLinkType( from, to );

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



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


//==========================================
// AI_DropLadderNodes
// drop nodes all along the ladder
//==========================================
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 );
	trap_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 );
		trap_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 );
			trap_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
//==========================================
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.
//==========================================
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( trap_PointContents(waterorigin) & MASK_WATER )
	{
		//reverse
		trap_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( trap_PointContents(waterorigin) & MASK_WATER )
			return;
	}
	
	//find water limit
	trap_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( !(trap_PointContents(waterorigin) & MASK_WATER) ) {
		int	k = 0;
		while( !(trap_PointContents(waterorigin) & MASK_WATER) ){
			waterorigin[2]--;
			k++;
		}
	}

	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.
//==========================================
static float last_update=0;
#define NODE_UPDATE_DELAY	0.10;
void AI_PathMap( void )
{
	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 ) );
}


//==========================================
// 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();
}


//==========================================
//
//==========================================
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
//==========================================
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
//
//===========================================================


int AI_LinkCloseNodes_JumpPass( int start );
//==================
// 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();
}

