#include "weapons.h"
#include "c4.h"

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

DllExport	void	weapon_c4viz (userEntity_t *self);

//////////////////////////////////////////////////////////////////////////////
// globals
//////////////////////////////////////////////////////////////////////////////

listHeader_t	*c4_list;

//////////////////////////////////////////////////////////////////////////////
// prototypes
//////////////////////////////////////////////////////////////////////////////

void			c4_select (userEntity_t *self);
userInventory_t	*c4_give (userEntity_t *self);
void			c4_use (userInventory_s *ptr, userEntity_t *self);
void			c4_detonate(userEntity_t *self);

/////////////////////////////////////////////////////////////////////
/// weapon_c4viz
/////////////////////////////////////////////////////////////////////

typedef struct
{
	// first seven fields must be the same as weapon_t
	char	*name;
	void	(*use)(struct userInventory_s *ptr, userEntity_t *user);
	void	*(*command)(struct userInventory_s *ptr, char *commandStr, void *data);
	void	*precache;
	void	*dummy3;
	void	*dummy4;

	ammo_t	*ammo;
} c4_weapon_t;

typedef struct
{
	float			beep_time;		//When should we beep next?
	float			time_to_die;	//How long should we live?
	
	c4_weapon_t		*my_master;		//Who launched me into this world?
	userEntity_t	*owner;

	// Content friction hook
	frictionHook_t	*fhook;
}moduleHook_t;


///////////////////////////////////////////////////////////////////
// standard weapon interface routines
//
// each weapon must have these routines so that they can "plug-in"
// to Daikatana with minimum effort -- modularity is the goal
///////////////////////////////////////////////////////////////////

static	char			*weapon_name;
static	char			*weapon_netname;
static  char            *weapon_model;
static	char			*ammo_name;
static	char			*ammo_netname;
static  char            *ammo_model;

static	int	readyFirst		[1];
static	int	readyLast		[1];

static	int	shootFirst		[1];
static	int	shootLast		[1];

static	int	reloadFirst		[1];
static	int	reloadLast		[1];

static	int	changeFirst		[1];
static	int	changeLast		[1];

static	int	detonateFirst	[1];
static	int	detonateLast	[1];


/////////////////////////////////
// c4_load_init
//
// should be called from dll_Load
/////////////////////////////////

int	c4_load_init (void)
{
	////////////////////////////////
	// set up names for this weapon
	////////////////////////////////

	weapon_name = "weapon_c4viz";
	weapon_netname = "C4 Vizertago";
	weapon_model = "models/c4/c4old.md2";
	ammo_name = "ammo_c4";
	ammo_netname = "C4";
	ammo_model = "models/c4/c4ammo.md2";//Debug for sticky
	
	//Add console commands, which point to in-file functions
	gstate->AddCommand ("select_c4viz", c4_select);
	gstate->AddCommand (weapon_name, c4_give);
	gstate->AddCommand ("c4_detonate", c4_detonate);

	//Add Inventory item "spaces"
	gstate->InventoryPrecache(weapon_name, "needicon", weapon_model);
	gstate->InventoryPrecache(ammo_name, "needicon", ammo_model);

	return	sizeof (userEntity_t) + sizeof (serverState_t);
}

/////////////////////////////////
// c4_level_init
//
// should be called from dll_LoadLevel
/////////////////////////////////

void c4_level_init (void)
{
	userEntity_t	*temp;
	gstate->Precache_Sound("weapons/c4/c4fire.wav");
	gstate->Precache_Sound("weapons/c4/c4stick.wav");
	gstate->Precache_Sound("weapons/c4/c4cock.wav");
	gstate->Precache_Sound("weapons/c4/c4beep.wav");
	gstate->Precache_Sound("explosions/explosion1.wav");
	gstate->Precache_Model(weapon_model);
	gstate->Precache_Model(ammo_model);

	////////////////////////////////////////////////////////////////////
	// spawn a temp entity to pull out the frame sequence data
	////////////////////////////////////////////////////////////////////

	temp = gstate->SpawnEntity ();
	gstate->SetModel (temp, weapon_model);
	
	com->GetFrames (temp, "goaway", &changeFirst [0], &changeLast [0]);
	com->GetFrames (temp, "reload", &reloadFirst [0], &reloadLast [0]);
	com->GetFrames (temp, "shoot", &shootFirst [0], &shootLast [0]);
	com->GetFrames (temp, "ready", &readyFirst [0], &readyLast [0]);
	com->GetFrames (temp, "detonate", &detonateFirst [0], &detonateLast [0]);
	
	gstate->RemoveEntity (temp);

	c4_list = com->list_init (c4_list);
}


//////////////////////////////////
// c4_command
//////////////////////////////////

static	void *c4_command (struct userInventory_s *ptr, char *commandStr, void *data)
{
	char *buf;
	if (!stricmp (commandStr, "death_msg"))
	{
		killerInfo_t	*ki = (killerInfo_t *) data;
		char			msg [80];

		if (ki->victim->netname && ki->attacker->netname)
		{
			if (ki->attacker == ki->victim)
				sprintf (msg, "%s blows himself apart!\n", ki->attacker->netname, ki->victim->netname);
			else
				sprintf (msg, "%s is shredded to pieces by %s's C4 explosion!\n", ki->victim->netname, ki->attacker->netname);

			gstate->bprint (msg);
		}
	}
	else if (!stricmp (commandStr, "rating"))
	{
		// return a value between 0 and 1000
		*(int *) data = 275;
	}
	else if (!stricmp (commandStr, "c4viz"))
	{
		//Return the value of c4viz so we know we have it readied
		buf = (char*) data;
		sprintf(buf,"c4viz");
	}
	else if (!stricmp (commandStr, "change"))
	{
		//	play goaway sequence
		weapon_animate ((userEntity_t *)data, changeFirst [0], changeLast [0], 0.05);
	}
	else if (!stricmp (commandStr, "change_time"))
	{
		//	return length of time to play putaway sequence
		*(float *) data = (changeLast [0] - changeFirst [0]) * 0.05;
	}


	return NULL;
}

//////////////////////////////////
// ammo_command
//
//  make sure you use standard 
//  command names when modifying
//  or adding weapons:
//		give_ammo
//		take_ammo
//		count
//////////////////////////////////
static	void	*ammo_command (userInventory_t *ptr, char *commandStr, void *data)
{
	ammo_t	*ammo = (ammo_t *) ptr;

	if (!stricmp (commandStr, "give_ammo"))
		ammo->count = ammo->count + *(int *) data;
	else if (!stricmp (commandStr, "take_ammo"))
	{
		if (ammo->count > 0)
		{
			ammo->count = ammo->count - *(int *) data;
			return (void *) 1;
		}
		else 
			return NULL;
	}
	else if (!stricmp (commandStr, "take_ammo"))
	{
		if (ammo->count > 0)
		{
			ammo->count = ammo->count - *(int *) data;
			return (void *) 1;
		}
		else 
			return NULL;
	}
	else if (!stricmp (commandStr, "use_ammo"))
	{
		if (ammo->count > 0)
		{
			ammo->count--;
			return (void *) 1;
		}
		else 
			return NULL;
	}
	else if (!stricmp (commandStr, "count"))
	{
		// returns ammount of ammo in data
		*(int *) data = ammo->count;
		return (void *) 1;
	}
	
	return NULL;
}

//////////////////////////////////
//ammo_create
//
//creates an inventory item of ammo type
//////////////////////////////////

static ammo_t *ammo_create (void)
{
	ammo_t	*ammo;

	ammo = (ammo_t *) com->malloc (sizeof (ammo_t), MEM_MALLOC);
	memset (ammo, 0x00, sizeof (ammo_t));

	ammo->name = strdup (ammo_name);
	ammo->use  = NULL;
	ammo->command = ammo_command;
	ammo->count = 0;

	return ammo;
}


//////////////////////////////////
//	c4_create	
//////////////////////////////////
static c4_weapon_t *c4_create (void)
{
	c4_weapon_t		*weapon;
	weapon = (c4_weapon_t *) com->malloc (sizeof (c4_weapon_t), MEM_MALLOC);
	memset (weapon, 0x00, sizeof (c4_weapon_t));

	weapon->name = strdup (weapon_name);
	weapon->use  = c4_use;
	weapon->command = c4_command;
	
	return weapon;		
}
 

//////////////////////////////////
//	c4_give 
//////////////////////////////////

userInventory_t *c4_give (userEntity_t *self)
{
	c4_weapon_t		*weapon;
	ammo_t			*ammo;
	userInventory_t	*invItem;
	int				ammo_count;

	/////////////////////////////
	// add weapon to inventory
	/////////////////////////////

	// make sure weapon is not already in inventory	
	invItem = gstate->InventoryFindItem (self->inventory, weapon_name);

	if (invItem)
	{
		gstate->Con_Printf ("You already have a %s.\n", weapon_netname);
		return	invItem;
	}

	// create new inventory item
	if ((weapon = c4_create ()) == NULL)
	{
		gstate->Con_Printf("Unable to malloc %s.\n", weapon_netname);
		return	NULL;
	}

	// add the weapon to the player's inventory list
	if (!gstate->InventoryAddItem (self->inventory, (userInventory_t *) weapon))
	{
		gstate->Con_Printf ("Could not add %s to inventory.\n", weapon_netname);
		return	NULL;
	}

	///////////////////////////////////
	// add ammo to inventory
	///////////////////////////////////

	// ammount of ammo weapon starts with
	ammo_count = 30;

	// make sure that this ammo type isn't already in inventory
	ammo = (ammo_t *) gstate->InventoryFindItem (self->inventory, ammo_name);
	
	if (!ammo)
	{
		// ammo type has not been created so go create an ammo type in the inventory
		if ((ammo = ammo_create ()) == NULL)
		{
			gstate->Con_Printf ("Unable to malloc %s.\n", ammo_netname);
			return	NULL;
		}

		// ammo type already exists, add the ammo to the player's inventory list
		if (!gstate->InventoryAddItem (self->inventory, (userInventory_t *) ammo))
		{
			gstate->Con_Printf ("Could not add %s to inventory.\n", ammo_netname);
			return	NULL;
		}
	}

	// C4 Modules are in inventory, pointed to by invItem so add ammo to them
	ammo->command ((userInventory_t *) ammo, "give_ammo", &ammo_count);
	weapon->ammo = ammo;

	gstate->sprint (self, "You got the %s!\n", weapon_netname);

	return	(userInventory_t *) weapon;
}

//////////////////////////////////////////////////////////////////////////////
// end of weapon interface routines
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////
//	module_explode
//////////////////////////////////

void	module_explode (userEntity_t *self)
{
	moduleHook_t	*hook = (moduleHook_t *) self->userHook;

	// do the damage
	com->RadiusDamage (self, self->owner, self, 75, DAMAGE_EXPLOSION);						//Damage!
	gstate->sound (self, CHAN_WEAPON, "explosions/explosion1.wav", 255, ATTN_NORM);	//Sound off!

	//Generate explosion sprite
	spawn_sprite_explosion (self, self->s.origin, FALSE);

	// get rid of the friction hook memory if it is there
	com->friction_remove (hook->fhook);

	// remove this c4 module from the list of active c4 modules
	com->list_remove_node (self, c4_list);

	com->free (self->userHook);

	gstate->RemoveEntity (self);
}

//////////////////////////////////
//	module_detonate
//////////////////////////////////

void	module_detonate (userEntity_t *owner)
{
	float			i;
	listNode_t		*cur_node = c4_list->head;
	userEntity_t	*cur_ent;
	moduleHook_t	*cur_entHook;

	i = 1;
	while (cur_node)
	{
		cur_ent = (userEntity_t *) com->list_ptr (cur_node);
		cur_entHook = (moduleHook_t *) cur_ent->userHook;
		
		// only detonate if owner detonates me
		if (cur_entHook->owner == owner)
		{
			cur_entHook->time_to_die = gstate->time + ((i * 0.20) + 0.05);
			i++;
		}

		// go to the next node
		cur_node = com->list_node_next (cur_node);
	}
}

//////////////////////////////////
//	module_think
//////////////////////////////////

void	module_think (userEntity_t *self)
{
	moduleHook_t	*hook = (moduleHook_t *) self->userHook;
	
	// are we sticking to something?
	if (!(self->flags & FL_HCHILD))
	{
		weapon_face_velocity (self);
		com->friction_apply (self, hook->fhook);
	}

	if(hook->beep_time < gstate->time)
	{
		gstate->sound (self, CHAN_WEAPON, "weapons/c4/c4beep.wav", 128, ATTN_NORM);
		hook->beep_time = gstate->time + 16.0;
	}
	if (hook->time_to_die <= gstate->time)
	{

		self->think = module_explode;
		self->nextthink = gstate->time + 0.05;
		return;
	}

	self->think = module_think;
	self->nextthink = gstate->time + 0.05;
}

//////////////////////////////////
//	module_touch
//////////////////////////////////

void	module_touch (userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
	moduleHook_t	*hook = (moduleHook_t *) self->userHook;
	int				pc;

	if (self->owner == other)
		return;

	pc = gstate->PointContents (self->s.origin);
	if (pc == CONTENTS_WATER)           //CONTENTS_SKY)      mdm q2fix
		return;

	// Make sticking noise
	gstate->sound (self, CHAN_WEAPON, "weapons/c4/c4stick.wav", 255, ATTN_NORM);

	// if other is not the world, then set up sticking
	if (other != gstate->FirstEntity ())
	{
		gstate->hr_InitRelative (other, self, 0);
	}
	else
		self->movetype = MOVETYPE_NONE;

	// if too many c4 modules are sticking, start a detonation sequence
	if (c4_list->count > 16)
		module_detonate (self->owner);
}

//////////////////////////////////
//	module_launch
//////////////////////////////////

void	module_launch (userEntity_t *self)
{
	moduleHook_t	*hook = (moduleHook_t *) self->userHook;
	Vector			vel, origin;

	// make the model visible
	gstate->SetModel (self, "models/c4/c4ammo.md2");
//	self->origin_z_ofs = 0;
	gstate->SetSize (self, 0, 0, 0, 0, 0, 0);

	self->movetype = MOVETYPE_TOSS;

	// calculate the velocity of the projectile
	vel = Vector (self->owner->client->v_angle);
	vel.AngleVectors (forward, right, up);
	
	vel = forward * 800;
	vel.z += 200;

	vel.set (self->velocity);

	// place the projectile in the world at the correct location
	origin = Vector (self->owner->s.origin) + forward * 16;
	origin.z += 16.0;
	gstate->SetOrigin2 (self, origin.vec ());

	// set max detonation time and beep time
	hook->time_to_die = gstate->time + 120.0;
	hook->beep_time = gstate->time + 16.0;

	self->think = module_think;
	self->nextthink = gstate->time + 0.05;
	self->touch = (touch_t)module_touch;
}

//////////////////////////////////
//	c4_use
//////////////////////////////////

void	c4_use (struct userInventory_s *ptr, userEntity_t *self)
{
	playerHook_t	*phook = (playerHook_t *) self->userHook;
	moduleHook_t	*hook;
	Vector			org;
	userEntity_t	*module;

	// have we finished our last attack?
	if (phook->attack_finished >= gstate->time)
		return;

	// do we have any ammo left?
	if (!weapon_check_ammo (self, ammo_name, 1))
		return;

	// start the firing frame sequence
	weapon_animate (self, shootFirst [0], shootLast [0], 0.05);
	phook->attack_finished = gstate->time + (shootLast [0] - shootFirst [0] + 1) * 0.05;
	
	/////////////////////////////////////////////////////////////////
	// create projectile
	/////////////////////////////////////////////////////////////////

	// spawn a new c4 module
	org.x = 0;
	org.y = 16.0;
	org.z = 16.0;

	module = weapon_spawn_projectile (self, org, 800.0, 200.0, "projectile_c4");
	// don't move until module_launch is called
	module->movetype = MOVETYPE_NONE;
	module->modelName = NULL;

	// set up the userHook for the c4 module
	module->userHook = com->malloc_hook (sizeof (moduleHook_t), MEM_MALLOC);
	hook = (moduleHook_t *) module->userHook;

	hook->owner = self;

	// set up frictionHook
	hook->fhook = com->friction_init (self, hook->fhook);
	com->friction_set_physics (self, hook->fhook, 0.5, 1.5, 1000.0, FR_SPIRAL);

	// set up touch and think functions for the module
	module->touch = NULL;
	module->think = module_launch;
	module->nextthink = gstate->time + 0.30;

	// add this module to the global list of c4 modules
	com->list_add_node (module, c4_list, 0);
}

//////////////////////////////////
//	c4_detonate
//////////////////////////////////

void	c4_detonate (userEntity_t *self)
{
	module_detonate (self);
}

//////////////////////////////////
//	c4_select_finish	
//////////////////////////////////

void	c4_select_finish (userEntity_t *self)
{
	playerHook_t		*hook = (playerHook_t *) self->userHook;
	userInventory_t		*invItem;

	invItem = gstate->InventoryFindItem (self->inventory, weapon_name);

	if (!invItem)
		return;

	weapon_animate (self, readyFirst [0], readyLast [0], 0.05);

	self->curWeapon = invItem;
	// this must be here to stop the sling from screwing up the firing of other weapons
	self->flags = self->flags - (self->flags & FL_ATTACKHOLD);

	gstate->sound (self, CHAN_WEAPON, "weapons/c4/c4cock.wav", 200.0, ATTN_NORM);
}

///////////////////////////////
//	c4_select
//
//	set the kineticore as the players current weapon if it is in the 
//	inventory
///////////////////////////////

static	void	c4_select (userEntity_t *self)
{
	playerHook_t		*hook = (playerHook_t *) self->userHook;
	weapon_t			*weapon;
	userInventory_t		*invItem;
	float				seq_time;

	invItem = gstate->InventoryFindItem (self->inventory, weapon_name);

	if (!invItem)
		return;
	//	tell the user's current weapon to go away
	weapon = (weapon_t *) self->curWeapon;
	if (weapon)
	{
		weapon->command (self->curWeapon, "change", self);
		weapon->command (self->curWeapon, "change_time", &seq_time);

		//	make sure the client's frame flags tell us to go to next weapon
		hook->vm_frameFlags |= FRAME_WEAPONSWITCH;
	
		//	set client's next weapon to this weapon
		hook->weapon_next = c4_select_finish;

		//	make sure we can't shoot!
		hook->attack_finished = gstate->time + seq_time;
	}
	else
		c4_select_finish (self);
}

//////////////////////////////////////////////////////
// ammo_touch
//
// generic touch function for all standard ammo types
//////////////////////////////////////////////////////

static	void	ammo_touch (userEntity_t *self, userEntity_t *other,cplane_t *plane, csurface_t *surf)
{
	playerHook_t	*ohook = (playerHook_t *) other->userHook;
	userInventory_t	*invItem;
	int				ammo_count;
	ammo_t			*ammo;

	if (!com->ValidTouch (self, other))
		return;

	///////////////////////////////////
	// add ammo to inventory
	///////////////////////////////////

	// ammount of ammo
	ammo_count = self->health;

	// make sure that this ammo type isn't already in inventory
	ammo = (ammo_t *) gstate->InventoryFindItem (other->inventory, self->className);
	
	if (!ammo)
	{
		// ammo type is not already in inventory, so create one create ammo
		if ((ammo = ammo_create ()) == NULL)
		{
			gstate->Con_Printf ("Unable to malloc %s.\n", self->className);
			return;
		}

		// add the ammo to the player's inventory list
		if (!gstate->InventoryAddItem (other->inventory, (userInventory_t *) ammo))
		{
			gstate->Con_Printf ("Could not add %s to inventory.\n", self->className);
			return;
		}
	}

	// ammo is in inventory, pointed to by ammo so add ammo to them
	ammo->command ((userInventory_t *) ammo, "give_ammo", &ammo_count);

	com->SetRespawn (self, 30, FALSE, 0);
	
	if (other->flags & FL_CLIENT)
		gstate->sprint (other, "You picked up some %s.\n", self->netname);
}

////////////////////////////////////////
// ammo_c4
////////////////////////////////////////

void	ammo_c4 (userEntity_t *self)
{
	int				i;
	userFrameInfo_t frameInfo;

	self->solid = SOLID_TRIGGER;
	self->movetype = MOVETYPE_FLY;

	gstate->Precache_Model ("models/c4/ammobox.md2");	
	gstate->SetModel (self, "models/c4/ammobox.md2");
	gstate->SetSize (self, -16, -16, -24, 16, 16, 32);
	gstate->SetOrigin (self, self->s.origin [0], self->s.origin [1], self->s.origin [2]);

	self->netname = "c4 modules";
	self->className = "ammo_c4";
	self->health = 4;
	self->touch = (touch_t)ammo_touch;
	com->SetVector (self->avelocity, 100, 50, 100);
}

#if 0
///////////////////////////////////
// weapon_c4viz
///////////////////////////////////

void	weapon_c4viz (userEntity_t *self)
{
	gstate->Precache_Model("models/c4/g_rock2.md2");
	gstate->SetModel(self, "models/c4/g_rock2.md2");
	
	self->touch   = (touch_t)weapon_touch;
	self->netname = weapon_netname;
	self->className = weapon_name;
   self->client->ps.gunindex = gstate->ModelIndex(weapon_model);

	gstate->SetSize(self, -16.0, -16.0, 0.0, 16.0, 16.0, 56.0);

	self->flags    = FL_ITEM;
	self->solid    = SOLID_TRIGGER;
	self->movetype = MOVETYPE_TOSS;

	// set this so weapon_touch knows what select function to call
	// when the weapon is picked up
	self->use = (use_t)c4_give;
	self->die = (die_t)c4_select;
}
#endif

/////////////////////////////////////
// c4_exit_level
//
// clean-up routines called when a
// level ends to reclaim all malloc'd
// memory
/////////////////////////////////////

void	c4_exit_level (userEntity_t *self)
{
	listNode_t		*cur_node, *last_node;
	userEntity_t	*module;
	moduleHook_t	*mhook;

	// traverse the list of c4 modules and remove all of their userhooks,
	// friction hooks and sticky hooks

	cur_node = c4_list->head;

	while (cur_node)
	{
		module = (userEntity_t *) cur_node->ptr;
		mhook = (moduleHook_t *) module->userHook;

		// free the friction hook
		com->friction_remove (mhook->fhook);

		gstate->RemoveEntity (self);

		// go to the next node
		last_node = cur_node;
		cur_node = com->list_node_next (cur_node);
		
		// now remove the node
		com->list_delete_node (c4_list, last_node);
	}
}