#include "world.h"
#include "hierarchy.h"
#include "ai_frames.h"
#include "ai_move.h"

///////////////////////////////////////////////////////////////////////////////
//	routines for setting up and controlling hierarchical models
///////////////////////////////////////////////////////////////////////////////

void	hr_init_model_offsets (userEntity_t *master, userEntity_t *legs, userEntity_t *torso,
							   userEntity_t *head, userEntity_t *weapon)
{
	playerHook_t	*hook = (playerHook_t *) master->userHook;
	float			head_height;

	//	FIXME:	spawn head at location of hr_neck origin vertex
	//	WAIT:	I think it does this already... nix switch statement
	switch	(hook->type)
	{
		case	TYPE_CLIENT:
			head_height = 16.5;
			break;
		case	TYPE_MIKIKO:
			head_height = 13.5;
			break;
		case	TYPE_SUPERFLY:
			head_height = 16.5;
			break;
	}

	if (legs)
	{
		//	set up legs
		VectorCopy (zero_vector, legs->s.mins);
		VectorCopy (zero_vector, legs->s.maxs);
		VectorCopy (master->s.origin, legs->s.origin);
		VectorCopy (master->s.angles, legs->s.angles);	

		gstate->LinkEntity (legs);		

		gstate->hr_InitRelative (master, legs, 0);	
		master->hchild = legs;

		legs->s.renderfx = 0;
	}

	if (torso)
	{
		//	set up torso
		VectorCopy (zero_vector, torso->s.mins);
		VectorCopy (zero_vector, torso->s.maxs);
		VectorCopy (master->s.origin, torso->s.origin);
		VectorCopy (master->s.angles, torso->s.angles);	

		gstate->LinkEntity (torso);

		gstate->hr_InitSurface (legs, torso, "hr_waist", 0, HRF_NOYAW);
		legs->hchild = torso;

		torso->s.renderfx = 0;
	}

	if (head)
	{
		//	set up head
		VectorCopy (zero_vector, head->s.mins);
		VectorCopy (zero_vector, head->s.maxs);
		VectorCopy (master->s.origin, head->s.origin);
		head->s.origin [2] += head_height;
		VectorCopy (master->s.angles, head->s.angles);	
		
		gstate->LinkEntity (head);

		//gstate->hr_InitSurface_Old (torso, head, "neck forward", 0, "neck right", 0, HRF_NOYAW);
		gstate->hr_InitSurface (torso, head, "hr_neck", 0, HRF_NOYAW);
		torso->hchild = head;

		head->s.renderfx = 0;
	}

	//	FIXME:	load a weapon
	if (weapon)
	{
		//	set up weapon
		VectorCopy (zero_vector, weapon->s.mins);
		VectorCopy (zero_vector, weapon->s.maxs);
		VectorCopy (master->s.origin, weapon->s.origin);
		VectorCopy (master->s.angles, weapon->s.angles);

		gstate->LinkEntity (weapon);

		//gstate->hr_InitSurface_Old (torso, weapon, "hand forward", 0, "hand right", 0, 0);
		gstate->hr_InitSurface (torso, weapon, "hr_hand", 0, 0);
		head->hchild = weapon;
		weapon->hchild = NULL;

		weapon->s.renderfx = 0;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	hr_init_models
//
//	initializes the hierarchical models for
//	clients and Superfly/Mikiko
///////////////////////////////////////////////////////////////////////////////

void	hr_init_models (userEntity_t *self)
{
	userEntity_t	*head, *torso, *legs, *gun;
	char			*head_mdl, *torso_mdl, *legs_mdl, *gun_mdl;
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook, *torso_hook, *head_hook, *gun_hook;
	float			head_height;

	////////////////////////////////////////////////////////////////
	//	set up correct model names based on client type
	////////////////////////////////////////////////////////////////

	switch	(hook->type)
	{
		case	TYPE_CLIENT:
			head_mdl = "models/h_head/h_head.md2";
			torso_mdl = "models/h_torso/h_torso.md2";
			legs_mdl = "models/h_legs/h_legs.md2";
			head_height = 16.5;
			break;
		case	TYPE_MIKIKO:
			head_mdl = "models/mk_head/mk_head.md2";
			legs_mdl = "models/mk_legs/mk_legs.md2";
			torso_mdl = "models/mk_torso/mk_torso.md2";
			head_height = 13.5;
			break;
		case	TYPE_SUPERFLY:
			head_mdl = "models/sf_head/sf_head.md2";
			torso_mdl = "models/sf_torso/sf_torso.md2";
			legs_mdl = "models/sf_legs/sf_legs.md2";
			head_height = 16.5;
			break;
	}

//	gun_mdl = "models/katana/katana.md2";
	gun_mdl = "models/shotgun/shotgun.md2";
//	gun_mdl = "models/pistol/pistol.md2";

	////////////////////////////////////////////////////////////////
	//	actual self model is NULL because it is just a bounding box
	////////////////////////////////////////////////////////////////

	self->modelName = NULL;
	gstate->SetSize (self, -16.0, -16.0, -24.0, 16.0, 16.0, 32.0);

	////////////////////////////////////////////////////////////////
	//	spawn the legs
	////////////////////////////////////////////////////////////////

	legs = gstate->SpawnEntity ();
	legs->s.modelindex = gstate->ModelIndex (legs_mdl);
	legs->modelName = legs_mdl;

	legs->solid = SOLID_NOT;
	legs->movetype = MOVETYPE_NONE;
	legs->className = "legs";
	
	//	maximum rate at which legs will turn to direction of velocity
	legs->ang_speed [1] = 10.0;	

	//	allocate a hook for the legs so that it can run animation sequences like anything else
	legs->userHook = com->malloc_hook (sizeof (playerHook_t), MEM_HUNK);
	leg_hook = (playerHook_t *) legs->userHook;
	
	//	set up animation variables for legs
	leg_hook->frameInc = 1;
	leg_hook->frameTime = 0.05;
	leg_hook->next_frameTime = gstate->time + leg_hook->frameTime;

	leg_hook->frames = (sequenceTable_t *) com->malloc_hook (sizeof (sequenceTable_t), MEM_MALLOC);

	ai_get_sequences (legs);

	//	self->userHook frames need to emulate leg frames so ai_move animation code doesn't 
	//	need to know this is a hierarchical model
	hook->frames = leg_hook->frames;
	memcpy (hook->sequence_max, leg_hook->sequence_max, sizeof (hook->sequence_max));


	////////////////////////////////////////////////////////////////
	//	spawn the torso
	////////////////////////////////////////////////////////////////

	torso = gstate->SpawnEntity ();
	torso->s.modelindex = gstate->ModelIndex (torso_mdl);
	torso->modelName = torso_mdl;

	torso->solid = SOLID_NOT;
	torso->movetype = MOVETYPE_NONE;
	torso->className = "torso";

	//	allocate a hook for the torso so that it can run animation sequences like anything else
	torso->userHook = com->malloc_hook (sizeof (playerHook_t), MEM_HUNK);
	torso_hook = (playerHook_t *) torso->userHook;
	
	//	set up animation variables for torso
	torso_hook->frameInc = 1;
	torso_hook->frameTime = 0.1;
	torso_hook->next_frameTime = gstate->time + torso_hook->frameTime;

	torso_hook->frames = (sequenceTable_t *) com->malloc_hook (sizeof (sequenceTable_t), MEM_MALLOC);

	ai_get_sequences (torso);

	////////////////////////////////////////////////////////////////
	//	spawn the head
	////////////////////////////////////////////////////////////////

	head = gstate->SpawnEntity ();
	head->s.modelindex = gstate->ModelIndex (head_mdl);
	head->modelName = head_mdl;

	head->solid = SOLID_NOT;
	head->movetype = MOVETYPE_NONE;
	head->className = "head";

	//	allocate a hook for the head so that it can run animation sequences like anything else
	head->userHook = com->malloc_hook (sizeof (playerHook_t), MEM_HUNK);
	head_hook = (playerHook_t *) head->userHook;
	
	//	set up animation variables for head
	head_hook->frameInc = 1;
	head_hook->frameTime = 0.05;
	head_hook->next_frameTime = gstate->time + head_hook->frameTime;

	head_hook->frames = (sequenceTable_t *) com->malloc_hook (sizeof (sequenceTable_t), MEM_MALLOC);

	head->modelName = head_mdl;

	ai_get_sequences (head);

	////////////////////////////////////////////////////////////////
	//	spawn the weapon
	////////////////////////////////////////////////////////////////

	gun = gstate->SpawnEntity ();
	gun->s.modelindex = gstate->ModelIndex (gun_mdl);
	gun->modelName = gun_mdl;

	//	gstate->SetModel (gun, "models/pistol.mdl");
	//	fix origin_z_ofs for scaling
	gstate->SetSize(gun, 0, 0, 0, 0, 0, 0);
	gstate->SetOrigin (gun, torso->s.origin [0], torso->s.origin [1], torso->s.origin [2]);
	com->SetVector (gun->s.angles, 0, 0, 0);

	gun->solid = SOLID_NOT;
	gun->movetype = MOVETYPE_NONE;
	gun->className = "weapon";

	//	allocate a hook for the gun so that it can run animation sequences like anything else
	gun->userHook = com->malloc_hook (sizeof (playerHook_t), MEM_HUNK);
	gun_hook = (playerHook_t *) gun->userHook;
	
	//	set up animation variables for gun
	gun_hook->frameInc = 1;
	gun_hook->frameTime = 0.05;
	gun_hook->next_frameTime = gstate->time + gun_hook->frameTime;

	gun_hook->frames = (sequenceTable_t *) com->malloc_hook (sizeof (sequenceTable_t), MEM_MALLOC);

	gun->modelName = gun_mdl;

	gun->s.renderfx |= RF_NODRAW;

	ai_get_sequences (gun);

	////////////////////////////////////////////////////////////////
	//	if a client, set FL_FAKENTITY so these models can be sent
	//	down to the client executable in the client's first prethink
	////////////////////////////////////////////////////////////////

	self->flags |= FL_FAKEENTITY;

	////////////////////////////////////////////////////////////////
	//	save pointers to parts
	////////////////////////////////////////////////////////////////

	hook->legs = legs;
	com->SetVector (hook->legs->s.render_scale, 1.0, 1.0, 1.0);

	hook->torso = torso;
	com->SetVector (hook->torso->s.render_scale, 1.0, 1.0, 1.0);

	hook->head = head;
	com->SetVector (hook->head->s.render_scale, 1.0, 1.0, 1.0);

	hook->weapon = gun;
	com->SetVector (hook->weapon->s.render_scale, 1.0, 1.0, 1.0);

	//	flag as a hierarchical entity (for teleporting and other stuff)
	hook->dflags |= DFL_HIERARCHICAL;

	hr_init_model_offsets (self, hook->legs, hook->torso, hook->head, hook->weapon);
}

////////////////////////////////
//	hr_yaw_follow
//
//	adjust torso yaw based on head yaw
//	adjust leg yaw based on torso yaw
////////////////////////////////

void	hr_yaw_follow (userEntity_t *master, userEntity_t *slave, float max_twist)
{
	userEntity_t	*legs;
	float			yaw_diff, yaw_dir;
	float			slave_yaw, master_yaw;
 	
	yaw_diff = com->AngleDiff (master->s.angles [1], slave->s.angles [1]);
	if (yaw_diff < 0)
		yaw_dir = -1.0;
	else
		yaw_dir = 1.0;
	yaw_diff = fabs (yaw_diff);

	if (yaw_diff > max_twist)
		slave->s.angles [1] = master->s.angles [1] + max_twist * yaw_dir;
}

////////////////////////////////
//	hr_torso_pitch
//
//	adjust torso pitch based on self pitch
////////////////////////////////


void	hr_torso_pitch (userEntity_t *master, userEntity_t *slave, float max_tilt)
{
/*
	float			pitch_diff, pitch_dir;
	float			master_pitch, slave_pitch;

	pitch_diff = com->AngleDiff (master->s.angles [0] * 3.0, slave->s.angles [0]);
	if (pitch_diff < 0)
		pitch_dir = -1;
	else
		pitch_dir = 1;
	pitch_diff = fabs (pitch_diff);

	if (pitch_diff > max_tilt)
	{
		slave->angle_delta [0] = (master->s.angles [0] * 3.0) + max_tilt * pitch_dir;
	}
*/
	slave->angle_delta [0] = master->s.angles [0];
}

////////////////////////////////
//	hr_head_follow
//
//	adjust head pitch based on self pitch
////////////////////////////////

void	hr_head_follow (userEntity_t *self, userEntity_t *head)
{
	//	head yaw should exactly match self yaw
	head->s.angles [1] = self->s.angles [1];

#ifdef	ASS
	if (self->flags & FL_CLIENT)
		gstate->Con_Printf ("client pitch = %.2f\n", self->s.angles [0]);
	else
		gstate->Con_Printf ("npc pitch = %.2f\n", self->s.angles [0]);
#endif

	//	adjust pitch
	if (self->s.angles [0] > 0)
		head->angle_delta [0] = self->s.angles [0] * 1.75;
	else
		head->angle_delta [0] = self->s.angles [0];
}

////////////////////////////////
//	hr_snap_legs
//
//	turns legs to direction of travel if moving
//	never allow legs to be > 90 degrees right or left
//	of facing yaw (self->s.angles [1])
////////////////////////////////

void	hr_snap_legs (userEntity_t *self, userEntity_t *legs)
{
	Vector			temp;
	float			facing_yaw, vel_yaw, yaw_diff, speed;
	userEntity_t	*arrow;

	temp = Vector (self->velocity);
	temp.z = 0;
	speed = temp.vlen ();
	if (speed == 0)
		return;

	//	adjust ang_speed [1] based on velocity
	legs->ang_speed [YAW] = speed / 12.0;
	
	//	if not on ground, adjust leg yaw to self yaw
	if (!(self->flags & FL_ONGROUND))
	{
		legs->ideal_ang [YAW] = self->s.angles [YAW];
		com->ChangeYaw (legs);

		return;
	}

	facing_yaw = self->s.angles [YAW];

	temp = Vector (self->velocity);
	temp.Normalize ();
	vel_yaw = com->VecToYaw (temp);

	yaw_diff = com->AngleDiff (facing_yaw, vel_yaw);

	if (yaw_diff < -90)
	{
		yaw_diff = -90;
	}
	else if (yaw_diff > 90)
	{
		yaw_diff = 90;
	}

	legs->ideal_ang [1] = facing_yaw + yaw_diff;
	com->ChangeYaw (legs);
}

/* ********************* hierarchical animation routines ******************* */

//////////////////////////////////
//	hr_stand
//
//
//////////////////////////////////

void	hr_stand (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook = (playerHook_t *) hook->legs->userHook;
	playerHook_t	*torso_hook = (playerHook_t *) hook->torso->userHook;
	playerHook_t	*head_hook = (playerHook_t *) hook->head->userHook;
	playerHook_t	*gun_hook = (playerHook_t *) hook->weapon->userHook;

	//	put each part of model in stand frames
	ai_init_sequence (hook->legs, leg_hook->frames->stand [0], SEQUENCE_STAND, 0);
	ai_init_sequence (hook->torso, torso_hook->frames->stand [0], SEQUENCE_STAND, 0);
	ai_init_sequence (hook->head, head_hook->frames->stand [0], SEQUENCE_STAND, 0);
	ai_init_sequence (hook->weapon, gun_hook->frames->stand [0], SEQUENCE_STAND, 0);
}

//////////////////////////////////
//	hr_run
//
//
//////////////////////////////////

void	hr_run (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook = (playerHook_t *) hook->legs->userHook;
	playerHook_t	*torso_hook = (playerHook_t *) hook->torso->userHook;
	playerHook_t	*head_hook = (playerHook_t *) hook->head->userHook;
	playerHook_t	*gun_hook = (playerHook_t *) hook->weapon->userHook;
	
	//	put each part of model in stand frames
	ai_init_sequence (hook->legs, leg_hook->frames->run [0], SEQUENCE_RUN, 0);
	ai_init_sequence (hook->torso, torso_hook->frames->run [0], SEQUENCE_RUN, 0);
	ai_init_sequence (hook->head, head_hook->frames->run [0], SEQUENCE_RUN, 0);
	ai_init_sequence (hook->weapon, gun_hook->frames->run [0], SEQUENCE_RUN, 0);
}

//////////////////////////////////
//	hr_jump
//
//
//////////////////////////////////

void	hr_jump (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook = (playerHook_t *) hook->legs->userHook;
	playerHook_t	*torso_hook = (playerHook_t *) hook->torso->userHook;
	playerHook_t	*head_hook = (playerHook_t *) hook->head->userHook;
	playerHook_t	*gun_hook = (playerHook_t *) hook->weapon->userHook;
	
	//	put each part of model in stand frames
	ai_init_sequence (hook->legs, leg_hook->frames->jump [0], SEQUENCE_JUMP, 0);
	ai_init_sequence (hook->torso, torso_hook->frames->jump [0], SEQUENCE_JUMP, 0);
	ai_init_sequence (hook->head, head_hook->frames->jump [0], SEQUENCE_JUMP, 0);
	ai_init_sequence (hook->weapon, gun_hook->frames->jump [0], SEQUENCE_JUMP, 0);
}

//////////////////////////////////
//	hr_walk
//
//
//////////////////////////////////

void	hr_walk (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	playerHook_t	*leg_hook = (playerHook_t *) hook->legs->userHook;
	playerHook_t	*torso_hook = (playerHook_t *) hook->torso->userHook;
	playerHook_t	*head_hook = (playerHook_t *) hook->head->userHook;
	playerHook_t	*gun_hook = (playerHook_t *) hook->weapon->userHook;
	
	//	put each part of model in stand frames
	ai_init_sequence (hook->legs, leg_hook->frames->walk [0], SEQUENCE_WALK, 0);
	ai_init_sequence (hook->torso, torso_hook->frames->walk [0], SEQUENCE_WALK, 0);
	ai_init_sequence (hook->head, head_hook->frames->walk [0], SEQUENCE_WALK, 0);
	ai_init_sequence (hook->weapon, gun_hook->frames->walk [0], SEQUENCE_WALK, 0);
}

///////////////////////////////////
//	hr_gib
//
//
///////////////////////////////////

#define	MAX_GIBS	3

userEntity_t	*hr_gib (userEntity_t *self)
{
	playerHook_t	*hook = (playerHook_t *) self->userHook;
	userEntity_t	*head;
	int				i;

	//	its possible that we can be gibbed while exiting a teleporter,
	//	when a client is hierarchical, but just not stuck together yet
	//	so make sure each part is a STICKYCHILD before removing it
	if (hook->weapon)
	{
		ai_throw_gib (self, hook->weapon->modelName, Vector (16, 0, 0));

		gstate->RemoveEntity (hook->weapon);
	}
	if (hook->head)
	{
		head = ai_throw_gib (self, hook->head->modelName, Vector (0, 0, self->s.maxs [2] - 8));

		gstate->RemoveEntity (hook->head);
	}
	if (hook->torso)
	{
		ai_throw_gib (self, gib_names [0], Vector (0, 0, self->s.maxs [2] * 0.5));
		ai_throw_gib (self, gib_names [3], Vector (rnd () * 16 - 8, 0, self->s.maxs [2] * 0.5));

		gstate->RemoveEntity (hook->torso);
	}
	if (hook->legs)
	{
		ai_throw_gib (self, gib_names [2], Vector (0, 0, -8));

		gstate->RemoveEntity (hook->legs);
	}

	hook->weapon = hook->head = hook->torso = hook->legs = NULL;

	for (i = 0; i < (int) (gstate->damage_inflicted / 10) && i < MAX_GIBS; i++)
	{
		if (rnd () < 0.5)
			ai_throw_gib (self, gib_names [1], Vector (rnd () * 16 - 8, rnd () * 16 - 8, rnd () * 16 - 8));
		else
			ai_throw_gib (self, gib_names [4], Vector (rnd () * 16 - 8, rnd () * 16 - 8, rnd () * 16 - 8));
	}

	self->s.renderfx |= RF_NODRAW;
	self->s.modelindex2 = 0;
	self->s.modelindex3 = 0;
	self->s.modelindex4 = 0;

	return	head;
}