#include	"world.h"

#include	"ai.h"
#include	"ai_utils.h"

#include	"ai_move.h"
#include	"ai_weapons.h"
#include	"ai_task.h"
#include	"ai_frames.h"

#include	"skat.h"
#include	"nodelist.h"
#include	"actorlist.h"
#include	"hierarchy.h"

////////////////////////////////////////////////////////////////////////////////
//	exports
////////////////////////////////////////////////////////////////////////////////

DllExport	void	info_mikiko_start (userEntity_t *self);

////////////////////////////////////////////////////////////////////////////////
//	defines
////////////////////////////////////////////////////////////////////////////////

#define	NPC_MAX_TORSO_TWIST	60.0
#define	NPC_MAX_HEAD_TWIST	35.0
#define	NPC_MAX_HEAD_TILT	60.0

//////////////////////////////
//	npc_prethink
//
//	handles some things that must be done every frame
//////////////////////////////

void	npc_prethink (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook = (playerHook_t *) hook->legs->userHook;

	if (self->flags & FL_INWARP)
			return;

	//	FIXME: still using this?
	// determine if the player/bot has left the ground between nodes
	// used for determining when to link nodes back
	if (!(self->flags & FL_ONGROUND) && !(self->flags & FL_PARTIALGROUND))
		hook->off_ground = TRUE;

	// get the bot's water level for this frame
	ai_water_level (self);

	// do damage to bot if in water/lava/slime
	ai_water_damage (self);

	//////////////////////////////////////////////////////////////////
	//	manage hierarchical models
	//////////////////////////////////////////////////////////////////

	ai_frame_update (hook->legs);

	//	trick ai_move routines into thinking this is a normal model
	hook->frameTime = leg_hook->frameTime;
	hook->endFrame = leg_hook->endFrame;
	hook->startFrame = leg_hook->startFrame;
	hook->frameInc = leg_hook->frameInc;
	hook->frame_flags = leg_hook->frame_flags;
	hook->sound1Frame = leg_hook->sound1Frame;
	hook->sound2Frame = leg_hook->sound2Frame;
	hook->sound1 = leg_hook->sound1;
	hook->sound2 = leg_hook->sound2;
	hook->cur_sequence = leg_hook->cur_sequence;
	hook->start_seq_num = leg_hook->start_seq_num;
	hook->start_seq_type = leg_hook->start_seq_type;
	hook->main_seq_type = leg_hook->main_seq_type;
	hook->main_seq_num = leg_hook->main_seq_num;
	hook->end_seq_type = leg_hook->end_seq_type;
	hook->end_seq_num = leg_hook->end_seq_num;
	hook->cur_seq_type = leg_hook->cur_seq_type;
	hook->cur_seq_num = leg_hook->cur_seq_type;

	ai_frame_update (hook->torso);
	ai_frame_update (hook->head);
	ai_frame_update (hook->weapon);

	//	rotate visible head model to same yaw and pitch as self
	//	rotate head yaw to self's yaw
	hr_head_follow (self, hook->head);

	//	head/torso
	hr_yaw_follow (hook->head, hook->torso, NPC_MAX_HEAD_TWIST);

	//	torso/legs
	hr_yaw_follow (hook->torso, hook->legs, NPC_MAX_TORSO_TWIST);

	hr_torso_pitch (self, hook->torso, NPC_MAX_HEAD_TILT);

	//	find direction legs should face based on velocity
	hr_snap_legs (self, hook->legs);

	///////////////////////////////////////////////////////////////////////////
	//	turning done here for much smoother turning rates
	//	ang_speed is in degree's per second, so scale delta for each frame
	//	based on gstate->frametime
	///////////////////////////////////////////////////////////////////////////

	if (!(hook->dflags & DFL_FACENONE))
		ai_adjust_angles (self);

	if (hook->dflags & DFL_ORIENTTOFLOOR)
		ai_orient_to_floor (self);
}

///////////////////////////////
//	mikiko_first_think
//
//	find or place current node
//	find an owner for bot after client has connected
//	set up task follow
//	spread think time out randomly
///////////////////////////////

void	mikiko_first_think (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *)self->userHook;

	node_place_spawn_node (self, hook->node_list);

	/////////////////////////////////////////////////////////////////
	//	make the first client we find the owner
	/////////////////////////////////////////////////////////////////
	for (hook->owner = gstate->FirstEntity (); hook->owner && !(hook->owner->flags & FL_CLIENT); hook->owner = gstate->NextEntity (hook->owner));

	if (hook->owner)
	{
		gstate->Con_Printf ("%s-san, you ah my owna!\n", hook->owner->netname);

		task_follow (self, hook->owner);
	}
	else
		task_stand (self);

	self->think = hook->begin_stand;
	self->nextthink = gstate->time + hook->think_time;
}

///////////////////////////////////
//	npc_choose_wander_goal
//
//	puts self's goalentity slightly in front of it
//	lets ai_movetogoal handle turning along walls
///////////////////////////////////

int	npc_choose_wander_goal (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			end, ang;
	taskData_t		*task_data;
	int				task_type;
	float			dist, yaw_add;

	//////////////////////////////////////////////////////////
	//	decide if we need to catch up with our owner
	//////////////////////////////////////////////////////////

	task_current (hook->task_stack, &task_data, &task_type);

	switch	(task_type)
	{
		case	TASK_FOLLOW:
			//	turn head to follow owner
//			ai_face_owner (self);

			dist = qv_dist (hook->owner->s.origin, self->s.origin);
			if (dist > hook->follow_dist + 64.0)
			{
				ai_set_task_goal (self);
				ai_set_think (self, hook->begin_stand, "npc_choose_wander_goal");

				return	FALSE;
			}
			break;
	}

	ang = Vector (self->s.angles);
	ang.x = ang.z = 0;

	if (rnd () < 0.25)
	{
		yaw_add = rnd () * 15 + 30;
		if (rnd () < 0.5)
			yaw_add *= -1.0;

		ang.y = com->AngleMod (ang.y + yaw_add);
	}

	ang.AngleVectors (forward, right, up);
	end = Vector (self->s.origin) + forward * (REACHED_GOAL_DIST + 16.0);
	
	ai_set_goal (self, self->goalentity, end.vec (), GOAL_OBJECT);

	hook->goal_search_time = gstate->time + (REACHED_GOAL_DIST + 16.0) / hook->walk_speed;

	return	TRUE;
}

///////////////////////////////////
//  npc_wander
//
///////////////////////////////////

void	npc_wander (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	float			dist;
	int				i;

#ifdef DEBUGINFO
	ai_debug_info (self, "npc_wander", NULL);
#endif

	//////////////////////////////////////////////////////////
	//	if no client is near, then stop and do nothing
	//	this saves framerate when lots of monsters are wandering
	//////////////////////////////////////////////////////////

/*	
	if (!ai_client_near (self) && self->flags & FL_ONGROUND)
	{
		com->ZeroVector (self->velocity);

		ai_set_think (self, npc_wander, "npc_wander");
		return;
	}
*/
	if (hook->find_target (self))
		return;

	if (hook->wander_sound) hook->wander_sound (self);

	dist = ai_goal_dist (self, self->goalentity);

	ai_frame_sounds (self);

	/////////////////////////////////////////////////////////////////////////////
	//	temporary goal seeking (to get around obstructions)
	/////////////////////////////////////////////////////////////////////////////

	if (hook->goal_type == GOAL_TEMP)
	{
		ai_debug_print (self, "npc_wander: seeking temp goal", NULL);

		ai_seek_temporary_goal (self, dist);

		// only need to check this after movetogoal since that is when ai_jump is called
		if (!(hook->ai_flags & AI_JUMP) && !(hook->ai_flags & AI_TURN))
			ai_set_think (self, npc_wander, "npc_wander");

		return;
	}

	/////////////////////////////////////////////////////////////////////////////
	//	going towards real goal
	/////////////////////////////////////////////////////////////////////////////

	if (dist < REACHED_GOAL_DIST || hook->goal_search_time <= gstate->time)
	{
		if (!npc_choose_wander_goal (self) || rnd () > 0.9)
		{
			ai_set_think (self, hook->begin_stand, "npc_wander");
			return;
		}
	}

	// go to goal entity
	ai_movetogoal (self, hook->walk_speed, MOVE_FORWARD);

	if (!(hook->ai_flags & AI_JUMP) && !(hook->ai_flags & AI_TURN))
		ai_set_think (self, npc_wander, "npc_wander");
}

///////////////////////////////////
//	npc_begin_wander
//
///////////////////////////////////

void	npc_begin_wander (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	hook->th_state = TH_WANDER;

	//	set up a new goal
	if (!npc_choose_wander_goal (self))
	{
		ai_set_think (self, hook->begin_stand, "npc_begin_wander");

		return;
	}

	if (hook->dflags & DFL_HIERARCHICAL)
		hr_walk (self);
	else
	{
		if (self->movetype == MOVETYPE_FLY)
			ai_init_sequence (self, hook->frames->fly [0], SEQUENCE_FLY, 0);
		else
			ai_init_sequence (self, hook->frames->walk [0], SEQUENCE_WALK, 0);
	}

	npc_wander (self);
}

///////////////////////////////////////////////////////////////////////////////
//	npc_look_around
//
//	chooses a direction for an npc to look while standing around
///////////////////////////////////////////////////////////////////////////////

int	npc_look_around (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			ang, org;
	float			yaw_add, pitch_add;

	///////////////////////////////////////////////////////////////////////////
	//	already looking up or down yet, 
	///////////////////////////////////////////////////////////////////////////
	if (self->ideal_ang [PITCH] == self->s.angles [PITCH])
	{
		//	look up or down
		if (rnd () < 0.75)
		{
			self->ideal_ang [PITCH] = 0.0;
		}
	}

	if (rnd () < 0.6)
		return	FALSE;

	///////////////////////////////////////////////////////////////////////////
	//	look left or right
	///////////////////////////////////////////////////////////////////////////
	ang = Vector (self->s.angles);

	//	look left or right
	if (rnd () < 0.6)
	{
		yaw_add = rnd () * 20 + 70;
		if (rnd () < 0.5)
			yaw_add *= -1.0;

		ang.y = com->AngleMod (ang.y + yaw_add);

		ang.AngleVectors (forward, right, up);
		org = Vector (self->s.origin) + forward * 32.0;
		org.set (self->goalentity->s.origin);
	}

	///////////////////////////////////////////////////////////////////////////
	//	not looking up or down yet
	///////////////////////////////////////////////////////////////////////////
	if (self->s.angles [PITCH] == 0.0)
	{
		//	look up or down
		//	pitch is limited to +/- 30 degrees, because client model pitch
		//	is always client v_angle / 3.
		if (rnd () < 0.8)
		{
			pitch_add = rnd () * 25 + 5;
			if (rnd () < 0.5)
				pitch_add *= -1.0;

			self->ideal_ang [PITCH] = com->AngleMod (ang.x + pitch_add);
			
			if (hook->dflags & DFL_HIERARCHICAL && self->ideal_ang [PITCH] >= 180)
				self->ideal_ang [PITCH] = self->ideal_ang [PITCH] - 360;
		}
	}

	return	TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//	npc_end_stand
//	
///////////////////////////////////////////////////////////////////////////////

void	npc_end_stand (userEntity_t *self, think_t think_func)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	ai_set_think (self, think_func, "npc_end_stand");
	hook->dflags |= DFL_FACEPITCH;
}

///////////////////////////////////////////////////////////////////////////////
//	npc_stand
//	
///////////////////////////////////////////////////////////////////////////////

void	npc_stand (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*thook = (playerHook_t *) hook->torso->userHook;
	int				i, task_type;
	float			dist, yaw_add;
	Vector			goal_org, ang;
	taskData_t		*task_data;

#ifdef DEBUGINFO
	ai_debug_info (self, "npc_stand", NULL);
#endif

	if (hook->find_target (self))
		return;

	if (hook->wander_sound) 
		hook->wander_sound (self);

	ai_frame_sounds (self);

	task_current (hook->task_stack, &task_data, &task_type);

	switch	(task_type)
	{
		case	TASK_FOLLOW:
			//	turn head to follow owner
//			ai_face_owner (self);

			dist = qv_dist (hook->owner->s.origin, self->s.origin);
			if (dist > hook->follow_dist)
			{
				ai_set_task_goal (self);
				npc_end_stand (self, hook->begin_follow);
				//ai_set_think (self, hook->begin_follow, "npc_stand");
				return;
			}
			else if (dist > hook->walk_follow_dist)
			{
				ai_set_task_goal (self);
				npc_end_stand (self, hook->begin_follow);
				//ai_set_think (self, hook->begin_follow, "npc_stand");
			}
			break;
	}

	// do an ambient standing sequence
	// FIXME: won't search for targets while doing this...
	// may be a problem for long ambients, may need a special
	// transition think for this
	if (thook->frame_flags & FRAME_LAST)
	{
		thook->frame_flags -= FRAME_LAST;

		if (rnd () < 0.2)
		{
			if (hook->sequence_max [SEQUENCE_STAND] > 1)
			{
				i = (int) (rnd () * (hook->sequence_max [SEQUENCE_STAND] - 1)) + 1;
				
				ai_begin_transition (self, hook->frames->stand [i], SEQUENCE_STAND, i, hook->begin_stand);

				return;
			}
		}
		else if (!npc_look_around (self))
		{
			if (rnd () < 0.2)
			{
				self->ideal_ang [PITCH] = 0;		//	look straight ahead.
				ai_set_think (self, hook->begin_wander, "npc_stand");
				
				return;
			}
		}
	}

	ai_set_think (self, npc_stand, "npc_stand");
}


///////////////////////////////////////////////////////////////////////////////
//	npc_begin_stand
//	
///////////////////////////////////////////////////////////////////////////////

void	npc_begin_stand	(userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	Vector			goal_org, ang;

	hook->th_state = TH_STAND;
	//	don't FACEPITCH while standing and looking around
	hook->dflags -= (hook->dflags & DFL_FACEPITCH);

	if (self->flags & FL_ONGROUND && qv_len (self->velocity) > 0)
		com->ZeroVector (self->velocity);
	
	///////////////////////////////////////////////////////////////////////////////
	//	put goal out in front so that we don't turn away from initial facing
	//	directions
	///////////////////////////////////////////////////////////////////////////////
	self->enemy = self;

	ang = Vector (self->s.angles);
	ang.x = ang.z = 0;
	ang.AngleVectors (forward, right, up);
	goal_org = Vector (self->s.origin) + forward * 32.0;

	ai_set_goal (self, self, goal_org.vec (), GOAL_TEMP);

	if (hook->dflags & DFL_HIERARCHICAL)
		hr_stand (self);
	else
		ai_init_sequence (self, hook->frames->stand [0], SEQUENCE_STAND, 0);	

	npc_stand (self);
}

///////////////////////////////////////////////////////////////////////////////
//	mikiko_choose_best_weapon
//
//	search Mikiko's inventory for the best weapon to attack self->enemy with.
//
//	stub for now
///////////////////////////////////////////////////////////////////////////////

void	mikiko_choose_best_weapon (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;


}

///////////////////////////////////////////////////////////////////////////////
//	mikiko_attack
//
///////////////////////////////////////////////////////////////////////////////

void	mikiko_attack (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;

	mikiko_choose_best_weapon (self);
}


///////////////////////////////////////////////////////////////////////////////
//	mikiko_begin_attack
//
///////////////////////////////////////////////////////////////////////////////

void	mikiko_begin_attack (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
}

///////////////////////////////
//	mikiko_init
//
///////////////////////////////

void	mikiko_init (userEntity_t *self)
{
	playerHook_t		*hook;

	gstate->RemoveEntity (self);
	return;

	hook = ai_init_monster (self, TYPE_MIKIKO);
	
	self->className = "Mikiko";
	self->netname = "Mikiko Ibihara";

	////////////////////////////////////////////////////////////////
	//	set up hierarchical models
	////////////////////////////////////////////////////////////////

	hr_init_models (self);
	self->prethink = npc_prethink;

	hook->dflags -= DFL_EVIL;
	hook->dflags |= DFL_RANGEDATTACK + DFL_CANSTRAFE + DFL_GOOD + DFL_FACEPITCH;

	hook->begin_stand = npc_begin_stand;
	hook->begin_wander = npc_begin_wander;

//	hook->run_speed = 320;
	hook->run_speed = 160;
	hook->walk_speed = 80;
	hook->attack_dist = 1024;
	hook->follow_dist = 256.0;
	hook->walk_follow_dist = 128.0;

	self->health = 100;
	hook->max_health = 100;
	hook->pain_chance = 10;

	hook->think_time = 0.05;

	self->mass = 1.0;
	self->gravity = 1.0;
	self->ang_speed [YAW] = 360;

	self->think = mikiko_first_think;
	self->nextthink = gstate->time + rnd () + 1.0;

	///////////////////////////////////////////////////////////////////////////
	//	give Mikiko her weapons
	///////////////////////////////////////////////////////////////////////////
}

///////////////////////////////
//	info_mikiko_start
//
///////////////////////////////

void	info_mikiko_start (userEntity_t *self)
{
	userEntity_t *mk;

	gstate->Con_Printf ("info_mikiko_start called.\n");

	mk = gstate->SpawnEntity ();

	mk->epair = self->epair;

	gstate->SetOrigin2 (mk, self->s.origin);
	
	mikiko_init (mk);
}

///////////////////////////////
//	mikiko_spawn
//
//	spawn Mikiko after the level has started
///////////////////////////////

void	mikiko_spawn (userEntity_t *self)
{

	////////////////////////////////////////////////////////////////
	//	add to actor_list, bot_list, monster_bot_list and client_bot_list
	////////////////////////////////////////////////////////////////

	alist_add (actor_list, self);
	alist_add (bot_list, self);
	alist_add (client_bot_list, self);
}