//////////////////////////////////////////////////////////////////////
//	task stack
//
//	used to keep track of bot's tasks so that a bot can perform a
//	function and then return to the function it was performing
//	previously
//
//	when popping to an old task, any old paths must be recalculated 
//	from the current node
//
//////////////////////////////////////////////////////////////////////

//	FIXME:	only here for DebugBreak!!!
//#include	<windows.h>

#include	"ai.h"
#include	"ai_task.h"
#include	"ai_move.h"

#include	"nodelist.h"


//////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////
//	task_name
//
//////////////////////////////////////

char	*task_name (int task_type)
{
	switch (task_type)
	{
		case	TASK_STAND:
			return	"TASK_STAND";
			break;
		case	TASK_CHASE:
			return	"TASK_CHASE";
			break;
		case	TASK_WANDER:
			return	"TASK_WANDER";
			break;
		case	TASK_SWITCH:
			return	"TASK_SWITCH";
			break;
		case	TASK_DOOR:
			return	"TASK_DOOR";
			break;
		case	TASK_PLAT:
			return	"TASK_PLAT";
			break;
		case	TASK_ACQUIRE:
			return	"TASK_ACQUIRE";
			break;
		case	TASK_ATTACK:
			return	"TASK_ATTACK";
			break;
		case	TASK_NODE_WANDER:
			return	"TASK_NODE_WANDER";
			break;
		case	TASK_PATH_WANDER:
			return	"TASK_PATH_WANDER";
			break;
		case	TASK_RIDE_PLAT:
			return	"TASK_RIDE_PLAT";
			break;
		case	TASK_FOLLOW:
			return	"TASK_FOLLOW";
			break;
		default:
			return	"invalid task";
			break;
	}
}

//////////////////////////////////////
//	task_check_top
//
//////////////////////////////////////
void	task_check_top (taskStack_t *stack)
{
	taskData_t	*task_data;
	int			task_type;

	task_type = stack->task_type;
	task_data = (taskData_t *) stack->task_data;

	if (task_type == TASK_CHASE)
	{
		if (task_data->goal_type != GOAL_ENEMY)
			com->Error ("task_check_top: TASK_CHASE without GOAL_ENEMY");
	}
}

//////////////////////////////////////
//	task_clear
//
//	clean up the stack if we manage to get
//	too many tasks pushed on at once
//
//	removes all tasks from the stack
//////////////////////////////////////

void	task_clear (taskStack_t **stack)
{
	taskStack_t	*item, *next_item;

	if (*stack)
	{
		for (item = *stack; item; item = next_item)
		{
			next_item = item->next;

			//	free the task data first
			com->free (item->task_data);

			//	free the task
			com->free (item);
		}
	}

	*stack = NULL;
}

//////////////////////////////////////
//	task_push
//
//	task_data should already be malloced
//////////////////////////////////////

void	task_push (taskStack_t **stack, taskData_t *task_data, int task_type)
{
	taskStack_t	*item;
	taskData_t	*old_data;

	//////////////////////////////////////////////////////////////////
	//	determine if current task should simply be updated
	//////////////////////////////////////////////////////////////////
	if (*stack)
	{
		item = *stack;
		old_data = (taskData_t *) item->task_data;

		if (item->task_type == task_type && old_data->object == task_data->object)
		{
			//	update current stack top instead of pushing a new one
			memcpy (old_data, task_data, sizeof (taskData_t));
			
			//	free task data since we just copied it
			com->free (task_data);

			gstate->Con_Dprintf ("updated %s\n", task_name (task_type));

			return;
		}
	}

	item = (taskStack_t *) com->malloc (sizeof (taskStack_t), MEM_MALLOC);
	
	item->task_data = task_data;
	item->task_type = task_type;
	item->next = *stack;
	*stack = item;

	gstate->Con_Dprintf ("pushed %s\n", task_name (task_type));

	task_check_top (*stack);
}

//////////////////////////////////////
//	task_pop
//
//	removes top task in the stack
//////////////////////////////////////

void	task_pop (taskStack_t **stack)
{
	taskStack_t	*cur_top, *new_top;
	int			task_type;

	cur_top = *stack;
	new_top = cur_top->next;
	task_type = cur_top->task_type;

	//	free task_data
	com->free (cur_top->task_data);

	//	free the task
	com->free (cur_top);

	*stack = new_top;

	if (*stack)
		gstate->Con_Dprintf ("popped %s, top task is %s\n", task_name (task_type), task_name (new_top->task_type));

	task_check_top (new_top);
}

//////////////////////////////////////
//	task_pop_thru
//
//	pops off all tasks up to and including
//	the task_type for object
//////////////////////////////////////

void	task_pop_thru (taskStack_t **stack, userEntity_t *object, int thru_type)
{
	taskData_t	*task_data;
	taskStack_t	*st;
	taskStack_t	*new_top, *cur_top;
	int			task_type;

	cur_top = *stack;
	new_top = NULL;

	//	go to bottom of stack and find first item with matching object/task
	for (st = cur_top; st; st = st->next)
	{
		task_current (st, &task_data, &task_type);

		if (task_data->object == object && task_type == thru_type)
			new_top = st;		
	}

//	if (!new_top)
//		DebugBreak ();

	while (*stack != new_top && *stack)
		task_pop (stack);

	if (*stack)
		task_pop (stack);
}

///////////////////////////////////////
//	task_current
//
//	returns info about the current task
///////////////////////////////////////

void	task_current (taskStack_t *stack, taskData_t **task_data, int *task_type)
{
	*task_data = (taskData_t *) stack->task_data;
	*task_type = stack->task_type;
}

///////////////////////////////////////
//	task_current_type
//
//	returns the current task type
///////////////////////////////////////

int	task_current_type (taskStack_t *stack)
{
	return	stack->task_type;
}

///////////////////////////////////////
//	task_current_data
//
//	returns the current task type
///////////////////////////////////////

taskData_t	*task_current_data (taskStack_t *stack)
{
	return	(taskData_t *) stack->task_data;
}

///////////////////////////////////////
//	task_update_goal
//
//	updates the goal_node of the current task
///////////////////////////////////////

void	task_update_goal (taskStack_t *stack, mapNode_t *goal_node)
{
	taskData_t *task_data;

	task_data = task_current_data (stack);

	task_data->goal_node = goal_node;
}

/******************************* TASK_SWITCH *******************************/

//////////////////////////////////////
//	task_switch
//
//	set up the bot to use a switch
//////////////////////////////////////

void	task_switch (userEntity_t *self, userEntity_t *sw)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			org;
	taskData_t  	*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = sw;
	task_data->base_node = hook->node_list->cur_node;

	//	find center of button
	org = (Vector (sw->absmax) - Vector (sw->absmin)) * 0.5 + Vector (sw->absmin);
	org.set (task_data->obj_origin);

	//	find node that is closest to button
	task_data->goal_node = node_closest (hook->node_list, org.vec (), 8192.0);
	if (!task_data->goal_node)
		com->Error ("No closest node found!!\n");

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);

	task_data->goal_type = GOAL_OBJECT;

	task_push (&hook->task_stack, task_data, TASK_SWITCH);
}

/******************************* TASK_CHASE *******************************/

//////////////////////////////////////
//	task_chase
//
//	set up the bot to chase an enemy
//////////////////////////////////////

void	task_chase (userEntity_t *self, userEntity_t *enemy)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*ehook = (playerHook_t *) enemy->userHook;
	Vector			org;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = enemy;
	com->CopyVector (task_data->obj_origin, enemy->s.origin);

	task_data->base_node = hook->node_list->cur_node;

	if (self->enemy->flags & (FL_BOT + FL_CLIENT + FL_MONSTER))
		task_data->goal_node = ehook->node_list->cur_node;
	else
		task_data->goal_node = node_closest (hook->node_list, enemy->s.origin, 8192.0);

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);

	task_data->goal_type = GOAL_ENEMY;

	task_push (&hook->task_stack, task_data, TASK_CHASE);
}

/******************************* TASK_STAND *******************************/

//////////////////////////////////////
//	task_stand
//
//	default monster task -- do nothing
//////////////////////////////////////

void	task_stand (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = NULL;
	com->CopyVector (task_data->obj_origin, self->s.origin);

	task_data->base_node = hook->node_list->cur_node;
	task_data->goal_node = hook->node_list->cur_node;

	task_data->goal_type = GOAL_NONE;

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);
	task_push (&hook->task_stack, task_data, TASK_STAND);
}

/******************************** TASK_PLAT ********************************/

//////////////////////////////////////
//	task_plat
//
//	set up the bot to use a platform
//////////////////////////////////////

void	task_plat (userEntity_t *self, userEntity_t *plat, mapNode_t *base_node, mapNode_t *goal_node)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	doorHook_t		*phook = (doorHook_t *) plat->userHook;
	Vector			org;
	taskData_t  	*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = plat;
	task_data->base_node = base_node;
	task_data->goal_node = goal_node;

	//	find center of plat
	org = (Vector (plat->absmax) - Vector (plat->absmin)) * 0.5 + Vector (plat->absmin);
	org.set (task_data->obj_origin);

	//	find current state of plat
	task_data->obj_state = phook->state;

	if (phook->state == STATE_DOWN || phook->state == STATE_TOP)
		task_data->obj_desired_state = STATE_BOTTOM;
	else
		task_data->obj_desired_state = STATE_TOP;

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);

	task_data->goal_type = GOAL_OBJECT;

	task_push (&hook->task_stack, task_data, TASK_PLAT);
}

/******************************** TASK_DOOR ********************************/

//////////////////////////////////////
//	task_door
//
//	set up the bot to use a door
//////////////////////////////////////

void	task_door (userEntity_t *self, userEntity_t *door, mapNode_t *base_node, mapNode_t *goal_node)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	doorHook_t		*phook = (doorHook_t *) door->userHook;
	Vector			org;
	taskData_t  	*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = door;
	task_data->base_node = base_node;
	task_data->goal_node = goal_node;

	//	find center of door
	org = (Vector (door->absmax) - Vector (door->absmin)) * 0.5 + Vector (door->absmin);
	org.set (task_data->obj_origin);

	//	find current state of door
	task_data->obj_state = phook->state;

	if (phook->state == STATE_DOWN || phook->state == STATE_TOP)
		task_data->obj_desired_state = STATE_BOTTOM;
	else
		task_data->obj_desired_state = STATE_TOP;

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);

	task_data->goal_type = GOAL_OBJECT;

	task_push (&hook->task_stack, task_data, TASK_DOOR);
}

/******************************* TASK_ATTACK *******************************/

//////////////////////////////////////
//	task_attack
//
//	set up a task for attacking an enemy
//	basically a dummy task to make ai_do_task
//	more seemless
//////////////////////////////////////

void	task_attack (userEntity_t *self, userEntity_t *enemy)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*ehook = (playerHook_t *) enemy->userHook;
	Vector			org;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = enemy;
	com->CopyVector (task_data->obj_origin, enemy->s.origin);

	task_data->base_node = hook->node_list->cur_node;

	if (self->enemy->flags & (FL_BOT + FL_CLIENT + FL_MONSTER))
		task_data->goal_node = ehook->node_list->cur_node;
	else
		task_data->goal_node = node_closest (hook->node_list, enemy->s.origin, 128.0);

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);

	task_data->goal_type = GOAL_ENEMY;

	task_push (&hook->task_stack, task_data, TASK_ATTACK);
}

/******************************* TASK_WANDER *******************************/

//////////////////////////////////////
//	task_wander
//
//	set up the bot to chase an enemy
//////////////////////////////////////

void	task_wander (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			org;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = self;
	com->CopyVector (task_data->obj_origin, self->s.origin);

	task_data->base_node = hook->node_list->cur_node;
	task_data->goal_node = hook->node_list->cur_node;

	task_data->goal_type = GOAL_NONE;

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);
	task_push (&hook->task_stack, task_data, TASK_WANDER);
}

/**************************** TASK_NODE_WANDER *****************************/

//////////////////////////////////////
//	task_node_wander
//
//	set up bot to wander nodes
//////////////////////////////////////

void	task_node_wander (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			org;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = self;
	com->CopyVector (task_data->obj_origin, self->s.origin);

	task_data->base_node = hook->node_list->cur_node;
	task_data->goal_node = hook->node_list->cur_node;

	task_data->goal_type = GOAL_NONE;

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);
	task_push (&hook->task_stack, task_data, TASK_NODE_WANDER);
}

/**************************** TASK_PATH_WANDER *****************************/

//////////////////////////////////////
//	task_path_wander
//
//	set up bot to wander nodes
//////////////////////////////////////

void	task_path_wander (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			org;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = self;
	com->CopyVector (task_data->obj_origin, self->s.origin);

	task_data->base_node = hook->node_list->cur_node;
	task_data->goal_node = hook->node_list->cur_node;

	task_data->goal_type = GOAL_NONE;

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);
	task_push (&hook->task_stack, task_data, TASK_PATH_WANDER);
}


/***************************** TASK_RIDE_PLAT ******************************/

//////////////////////////////////////
//	task_ride_plat
//
//	set up bot to wander nodes
//////////////////////////////////////

void	task_ride_plat (userEntity_t *self, userEntity_t *plat, mapNode_t *goal_node)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	doorHook_t		*phook = (doorHook_t *) plat->userHook;
	Vector			org;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = plat;

	//	find center of door
	org = (Vector (plat->absmax) - Vector (plat->absmin)) * 0.5 + Vector (plat->absmin);
	org.z = goal_node->origin [2];
	org.set (task_data->obj_origin);

	task_data->base_node = hook->node_list->cur_node;
	task_data->goal_node = goal_node;

	if (phook->state == STATE_DOWN || phook->state == STATE_BOTTOM)
		task_data->obj_desired_state = STATE_TOP;
	else
		task_data->obj_desired_state = STATE_BOTTOM;

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);

	task_data->goal_type = GOAL_OBJECT;

	task_push (&hook->task_stack, task_data, TASK_RIDE_PLAT);
}

/****************************** TASK_FOLLOW *******************************/

//////////////////////////////////////
//	task_follow
//
//	set up the bot to follow its owner
//////////////////////////////////////

void	task_follow (userEntity_t *self, userEntity_t *owner)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*ohook = (playerHook_t *) owner->userHook;
	Vector			org;
	taskData_t		*task_data;

	///////////////////////////////////////////////////////////
	//	malloc up the memory for this task
	///////////////////////////////////////////////////////////

	task_data = (taskData_t *) com->malloc (sizeof (taskData_t), MEM_MALLOC);

	task_data->object = owner;
	com->CopyVector (task_data->obj_origin, owner->s.origin);

	task_data->base_node = hook->node_list->cur_node;

	if (owner->flags & (FL_BOT + FL_CLIENT + FL_MONSTER))
		task_data->goal_node = ohook->node_list->cur_node;
	else
		task_data->goal_node = node_closest (hook->node_list, owner->s.origin, 8192.0);

	///////////////////////////////////////////////////////////
	//	push this task onto the stack as the current task
	///////////////////////////////////////////////////////////

	gstate->Con_Dprintf ("%s:", self->className);

	task_data->goal_type = GOAL_OWNER;

	task_push (&hook->task_stack, task_data, TASK_FOLLOW);
}
