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

/*
   ==========================================================================

   - SPLITMODELS -

   ==========================================================================
 */
// - Adding the View Weapon in split pieces


#include "cg_local.h"

viewweaponinfo_t vweap;


//======================================================================
//						Predict WeaponChange
//======================================================================

static qboolean CG_UseWeapon( int newweapon, qboolean feedback )
{
	gitem_t *item;

	if( cgs.demoPlaying )
		return qfalse;

	if( newweapon < WEAP_GUNBLADE || newweapon >= WEAP_TOTAL )
		return qfalse;

	if (cgs.serverRules.instagib) // disable weapon switching in instagib -> avoid annoying sound
		return qfalse;

	if( newweapon == cg.latched_weapon ||
	   ( newweapon == cg.frame.playerState.stats[STAT_WEAPON_ITEM] && cg.latched_weapon == WEAP_NONE ) )
		return qfalse;

	item = GS_FindItemByTag( newweapon );
	if( !item )
		return qfalse;

	//check if we have the weapon
	if( !cg.frame.playerState.weaponlist[newweapon-1][0] )
	{
		if( feedback )
			trap_S_StartGlobalSound( CG_MediaSfx( cgs.media.sfxWeaponUpNoAmmo ), CHAN_AUTO, cg_volume_effects->value );

		return qfalse;
	}

	//check we have ammo for it
	if( !cg.frame.playerState.weaponlist[newweapon-1][1] &&
		!cg.frame.playerState.weaponlist[newweapon-1][2] &&
	    newweapon != WEAP_GUNBLADE )
	{
		if( feedback )
			trap_S_StartGlobalSound( CG_MediaSfx( cgs.media.sfxWeaponUpNoAmmo ), CHAN_AUTO, cg_volume_effects->value );

		return qfalse;
	}

	cg.last_weapon = ( cg.latched_weapon != WEAP_NONE ? cg.latched_weapon : cg.frame.playerState.stats[STAT_WEAPON_ITEM] );
	cg.latched_weapon = newweapon;
	trap_Cmd_ExecuteText( EXEC_NOW, va( "cmd use %s", item->pickup_name ) );

	return qtrue;
}

//=================
//CG_Cmd_Use_f
//=================
void CG_Cmd_Use_f( void )
{
	gitem_t	*item;

	if( cgs.demoPlaying )
		return;

	if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM ||
	    cg.frame.playerState.pmove.pm_type == PM_DEAD ||
	    cg.frame.playerState.pmove.pm_type == PM_SPECTATOR )
		return;

	if( trap_Cmd_Argc() < 2 )
		return;

	item = GS_FindItemByName( trap_Cmd_Args() );
	if( !item )
	{
		CG_Printf( "unknown item: %s\n", trap_Cmd_Args() );
		return;
	}

	if( !( item->flags & ITFLAG_USABLE ) )
	{
		CG_Printf( "%s is not usable.\n", item->pickup_name );
		return;
	}

	if( item->type & IT_WEAPON )
	{
		CG_UseWeapon( item->tag, qtrue );
		return;
	}

	trap_Cmd_ExecuteText( EXEC_NOW, va( "cmd use %s", item->pickup_name ) );
}

/*
   =================
   CG_WeapLast_f
   =================
 */
void CG_WeapLast_f( void )
{
	if( cg.last_weapon != WEAP_NONE )
		CG_UseWeapon( cg.last_weapon, qtrue );
}

/*
   =================
   CG_WeapPrev_f
   =================
 */
void CG_WeapPrev_f( void )
{
	int tag;
	int selected_weapon;

	if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
	{
		CG_ChasePrev();
		return;
	}

	if( cg.frame.playerState.pmove.pm_type == PM_DEAD )
		return;

	if( cgs.demoPlaying )
		return;

	if( cg.latched_weapon == WEAP_NONE )
		selected_weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM];
	else
		selected_weapon = cg.latched_weapon;

	if( selected_weapon < WEAP_GUNBLADE || selected_weapon >= WEAP_TOTAL )
		selected_weapon = WEAP_GUNBLADE; // don't get stuck to a loop with invalid weapon data

	tag = selected_weapon;
	tag--;
	if( tag < WEAP_GUNBLADE ) tag = WEAP_TOTAL - 1;
	while( tag != selected_weapon )
	{
		if( CG_UseWeapon( tag, qfalse ) )
			return; // successful

		tag--;
		if( tag < WEAP_GUNBLADE )
			tag = WEAP_TOTAL - 1;
	}
}

/*
   =================
   CG_WeapNext_f
   =================
 */
void CG_WeapNext_f( void )
{
	int tag;
	int selected_weapon;

	if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
	{
		CG_ChaseNext();
		return;
	}

	if( cg.frame.playerState.pmove.pm_type == PM_DEAD )
		return;

	if( cgs.demoPlaying )
		return;

	if( cg.latched_weapon == WEAP_NONE )
		selected_weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM];
	else
		selected_weapon = cg.latched_weapon;

	if( selected_weapon < WEAP_GUNBLADE || selected_weapon >= WEAP_TOTAL )
		selected_weapon = WEAP_GUNBLADE; // don't get stuck to a loop with invalid weapon data

	tag = selected_weapon;
	tag++;
	if( tag >= WEAP_TOTAL ) tag = WEAP_GUNBLADE;
	while( tag != selected_weapon )
	{
		if( CG_UseWeapon( tag, qfalse ) )
			return; // successful

		tag++;
		if( tag >= WEAP_TOTAL )
			tag = WEAP_GUNBLADE;
	}
}

void CG_WeaponAutoswitch( int weapon )
{
	int i;

	assert( weapon > WEAP_NONE && weapon < WEAP_TOTAL );

	// we don't want an automatic switch?
	if( !cg_weaponAutoswitch->integer )
		return;

	// weapon autoswitch 2 only switches away from the gunblade
	if( cg_weaponAutoswitch->integer == 2 && cg.frame.playerState.stats[STAT_WEAPON_ITEM] != WEAP_GUNBLADE )
		return;

	// already had it?
	if( cg.oldFrame.playerState.weaponlist[weapon-1][0] )
		return;

	// already had a better weapon?
	// if setting was 2, we only change if we only had the gunblade
	for( i = WEAP_TOTAL - 1; i > ( cg_weaponAutoswitch->integer == 2 ? WEAP_GUNBLADE : weapon ); i-- )
	{
		if( cg.oldFrame.playerState.weaponlist[i-1][0] )
			return;
	}

	CG_UseWeapon( weapon, qfalse );
}

//=================
//CG_NoAmmoWeaponChange
//
// Called only from the ViewWeapon events filter
//=================
void CG_NoAmmoWeaponChange( void )
{
	int weaptag;
	int newweapon = WEAP_GUNBLADE;

	if( cg.frame.playerState.pmove.pm_type == PM_DEAD )
		return;

	// check if we're already changing weapon
	if( cgs.demoPlaying || cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
	{
		if( cg.changing_weapon )
			return;
	}
	else
	{
		if( cg.latched_weapon != WEAP_NONE )
			return;
	}

	trap_S_StartGlobalSound( CG_MediaSfx( cgs.media.sfxWeaponUpNoAmmo ), CHAN_AUTO, cg_volume_effects->value );

	if( cgs.demoPlaying || cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
	{
		cg.changing_weapon = qtrue;
		return;
	}

	// in playerstate, GUNBLADE is 0
	// we could ignore the gunblade here, but I prefer the 'if' being visible
	for( weaptag = WEAP_TOTAL-1; weaptag > WEAP_NONE; weaptag-- )
	{
		if( !cg.frame.playerState.weaponlist[weaptag-1][0] )  // has the weapon?
			continue;

		if( !cg.frame.playerState.weaponlist[weaptag-1][1] )  // has strong ammo?
			continue;

		if( weaptag != WEAP_GUNBLADE )
		{                               // gunblade is ignored in strong list
			newweapon = weaptag;
			goto found;
		}
	}

	for( weaptag = WEAP_TOTAL-1; weaptag > WEAP_NONE; weaptag-- )
	{
		if( !cg.frame.playerState.weaponlist[weaptag-1][0] )  // has the weapon?
			continue;

		if( weaptag != WEAP_GUNBLADE )
		{
			if( !cg.frame.playerState.weaponlist[weaptag-1][2] )  // has weak ammo?
				continue;
		}

		newweapon = weaptag;
		goto found;
	}
found:
	{
		CG_UseWeapon( newweapon, qfalse );
	}
}

void CG_CheckWeaponState( void )
{
	if( cg.frame.playerState.pmove.pm_type == PM_CHASECAM || cg.frame.playerState.pmove.pm_type == PM_DEAD )
	{
		static int last_weapon = WEAP_NONE;

		if( cg.changing_weapon && last_weapon != cg.frame.playerState.stats[STAT_WEAPON_ITEM] )
			cg.changing_weapon = qfalse;

		last_weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM];
		cg.latched_weapon = WEAP_NONE;
		cg.last_weapon = WEAP_NONE;

		return;
	}

	// check if the weapon change is complete
	if( cg.latched_weapon == cg.frame.playerState.stats[STAT_WEAPON_ITEM] )
		cg.latched_weapon = WEAP_NONE;

	// check if we don't have the weapon/ammo we're changing to anymore
	if( cg.latched_weapon != WEAP_NONE )
	{
		if( !cg.frame.playerState.weaponlist[cg.latched_weapon-1][0] ||
		   ( !cg.frame.playerState.weaponlist[cg.latched_weapon-1][1] &&
		     !cg.frame.playerState.weaponlist[cg.latched_weapon-1][2] && cg.latched_weapon != WEAP_GUNBLADE ) )
			cg.latched_weapon = WEAP_NONE;
	}
}

//======================================================================
//						ViewWeapon
//======================================================================

static entity_t	gun;    // hand model

//===============
//CG_ViewWeapon_UpdateProjectionSource
//===============
static void CG_ViewWeapon_UpdateProjectionSource( vec3_t hand_origin, vec3_t hand_axis[3], vec3_t weap_origin, vec3_t weap_axis[3] )
{
	orientation_t *tag_result = &vweap.projectionSource;
	orientation_t tag_weapon;
	weaponinfo_t *weaponInfo;

	VectorCopy( vec3_origin, tag_weapon.origin );
	Matrix_Copy( axis_identity, tag_weapon.axis );

	// move to tag_weapon
	CG_MoveToTag( tag_weapon.origin, tag_weapon.axis,
	              hand_origin, hand_axis,
	              weap_origin, weap_axis );

	weaponInfo = CG_GetWeaponInfo( vweap.weapon );

	// move to projectionSource tag
	if( weaponInfo )
	{
		VectorCopy( vec3_origin, tag_result->origin );
		Matrix_Copy( axis_identity, tag_result->axis );
		CG_MoveToTag( tag_result->origin, tag_result->axis,
		              tag_weapon.origin, tag_weapon.axis,
		              weaponInfo->tag_projectionsource.origin, weaponInfo->tag_projectionsource.axis );
		return;
	}

	// fall back: copy gun origin and move it front by 16 units and 8 up
	VectorCopy( tag_weapon.origin, tag_result->origin );
	Matrix_Copy( tag_weapon.axis, tag_result->axis );
	VectorMA( tag_result->origin, 16, tag_result->axis[0], tag_result->origin );
	VectorMA( tag_result->origin, 8, tag_result->axis[2], tag_result->origin );
}


unsigned int CG_ViewWeapon_GetTotalReloadTime( firedef_t *firedef )
{
	return firedef->reload_time + firedef->cooldown_time;
}

//===============
//CG_ViewWeapon_StartShootEffect
//===============
void CG_ViewWeapon_StartShootEffect( int fireMode )
{
	weaponinfo_t *weaponInfo;
	weapon_info_t *weap_firedef = &( gs_weaponInfos[vweap.weapon] );

	weaponInfo = CG_GetWeaponInfo( vweap.weapon );
	if( !weaponInfo )
		return;

	if( fireMode == FIRE_MODE_WEAK && vweap.newAnim < VWEAP_ATTACK_WEAK )
	{
		vweap.newAnim = VWEAP_ATTACK_WEAK;
		if( vweap.weapon != WEAP_GUNBLADE )
		{                             // gunblade knife doesn't flash
			if( cg_weaponFlashes->integer == 2 )
				vweap.pweapon.flashtime = cg.time + weaponInfo->frametime[VWEAP_ATTACK_WEAK];
		}
		vweap.pweapon.barreltime = cg.time + weaponInfo->frametime[VWEAP_ATTACK_STRONG];
	}
	else if( fireMode == FIRE_MODE_STRONG && vweap.newAnim < VWEAP_ATTACK_STRONG )
	{
		vweap.newAnim = VWEAP_ATTACK_STRONG;
		if( cg_weaponFlashes->integer == 2 )
			vweap.pweapon.flashtime = cg.time + weaponInfo->frametime[VWEAP_ATTACK_STRONG];

		// gunblade is special
		if( vweap.weapon != WEAP_GUNBLADE )
			vweap.pweapon.barreltime = cg.time + weaponInfo->frametime[VWEAP_ATTACK_STRONG];

		if( vweap.weapon == WEAP_INSTAGUN )
			vweap.pweapon.reloadedtime = cg.time + CG_ViewWeapon_GetTotalReloadTime( weap_firedef->firedef );

	}

	//eject brass-debris
	if( cg_ejectBrass->integer && cg_ejectBrass->integer < 3 && weaponInfo && cg.view.drawWeapon )
	{
		vec3_t origin;
		VectorCopy( cg.view.refdef.vieworg, origin );

		//move it a bit forward and up
		VectorMA( origin, 16, cg.view.axis[FORWARD], origin );
		VectorMA( origin, 4, cg.view.axis[UP], origin );
		if( cgs.clientInfo[cg.view.POVent-1].hand == 0 )
			VectorMA( origin, 8, cg.view.axis[RIGHT], origin );
		else if( cgs.clientInfo[cg.view.POVent-1].hand == 1 )
			VectorMA( origin, -4, cg.view.axis[RIGHT], origin );
	}
}

//==============
//CG_ViewWeapon_AddAngleEffects
//==============
static void CG_ViewWeapon_AddAngleEffects( vec3_t angles )
{
	int i;
	float delta;

	if( !cg.view.drawWeapon )
		return;

	if( cg_gunbob->integer )
	{
		// gun angles from bobbing
		if( cg.bobCycle & 1 )
		{
			angles[ROLL] -= cg.xyspeed * cg.bobFracSin * 0.012;
			angles[YAW] -= cg.xyspeed * cg.bobFracSin * 0.006;
		}
		else
		{
			angles[ROLL] += cg.xyspeed * cg.bobFracSin * 0.012;
			angles[YAW] += cg.xyspeed * cg.bobFracSin * 0.006;
		}
		angles[PITCH] += cg.xyspeed * cg.bobFracSin * 0.012;

		// gun angles from delta movement
		for( i = 0; i < 3; i++ )
		{
			delta = ( cg.oldFrame.playerState.viewangles[i] - cg.frame.playerState.viewangles[i] ) * cg.lerpfrac;
			if( delta > 180 )
				delta -= 360;
			if( delta < -180 )
				delta += 360;
			clamp( delta, -45, 45 );


			if( i == YAW )
				angles[ROLL] += 0.001 * delta;
			angles[i] += 0.002 * delta;
		}
	}

	// gun angles from kicks
	if( !cg_damage_kick->integer )
		CG_AddKickAngles( angles );
}

//==============
//CG_ViewWeapon_StartFallKickEff
//==============
void CG_ViewWeapon_StartFallKickEff( int parms )
{
	int bouncetime;

	if( !cg_gunbob->integer )
		return;

	if( vweap.fallEff_Time > cg.time )
		vweap.fallEff_rebTime = 0;

	bouncetime = ( ( parms + 5 )*10 )+150;
	vweap.fallEff_Time = cg.time + bouncetime;
	if( vweap.fallEff_rebTime )
		vweap.fallEff_rebTime = cg.time - ( ( cg.time - vweap.fallEff_rebTime ) * 0.5 );
	else
		vweap.fallEff_rebTime = cg.time;
}

//===============
//CG_ViewWeapon_Position
//===============
static void CG_ViewWeapon_Position( vec3_t origin, vec3_t axis[3] )
{
	vec3_t gun_angles;
	float gunx, guny, gunz;
	float fallfrac, fallkick;

	// set up gun position
	VectorCopy( cg.view.refdef.vieworg, origin );
	VectorCopy( cg.view.refdef.viewangles, gun_angles );

	//offset by client cvars
	gunx = cg_gunx->value;
	guny = cg_guny->value;
	gunz = cg_gunz->value;

	//move hand to the left/center
	if( cgs.demoPlaying )
	{
		if( hand->integer == 0 )
			gunx += cg_handOffset->value;
		else if( hand->integer == 1 )
			gunx -= cg_handOffset->value;
	}
	else
	{
		if( cgs.clientInfo[cg.view.POVent-1].hand == 0 )
			gunx += cg_handOffset->value;
		else if( cgs.clientInfo[cg.view.POVent-1].hand == 1 )
			gunx -= cg_handOffset->value;
	}

	//Add fallkick effect
	if( vweap.fallEff_Time > cg.time )
	{
		fallfrac = (float)( cg.time - vweap.fallEff_rebTime ) / (float)( vweap.fallEff_Time - vweap.fallEff_rebTime );
		fallkick = sin( DEG2RAD( fallfrac*180 ) ) * ( ( vweap.fallEff_Time - vweap.fallEff_rebTime ) * 0.01f );
	}
	else
	{
		vweap.fallEff_Time = vweap.fallEff_rebTime = 0;
		fallkick = fallfrac = 0;
	}

	guny -= fallkick;

	//Move the gun
	VectorMA( origin, gunx, cg.view.axis[RIGHT], origin );
	VectorMA( origin, gunz, cg.view.axis[FORWARD], origin );
	VectorMA( origin, guny, cg.view.axis[UP], origin );

	//add bobbing
	CG_ViewWeapon_AddAngleEffects( gun_angles );
	AnglesToAxis( gun_angles, axis );

	if( cg_gun_fov->integer && !( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_ZOOM ) )
	{
		float fracWeapFOV = ( 1.0f / cg.view.fracDistFOV ) *tan( cg_gun_fov->integer *( M_PI/180 ) *0.5f );
		VectorScale( axis[0], fracWeapFOV, axis[0] );
	}
}

//===============
//CG_ViewWeapon_UpdateAnimation
//===============
static void CG_ViewWeapon_UpdateAnimation( void )
{
	weaponinfo_t *weaponInfo;

	if( cg.time <= vweap.nextframetime )
	{
		vweap.backlerp = 1.0 - ( (double)( cg.time - vweap.prevframetime )/(double)( vweap.nextframetime - vweap.prevframetime ) );
		clamp( vweap.backlerp, 0, 1 );
		return;
	}

	weaponInfo = CG_GetWeaponInfo( vweap.weapon );

	vweap.oldframe = vweap.frame;
	vweap.frame++;

	//looping
	if( vweap.frame > weaponInfo->lastframe[vweap.currentAnim] )
	{
		if( weaponInfo->loopingframes[vweap.currentAnim] )
			vweap.frame = ( weaponInfo->lastframe[vweap.currentAnim] - ( weaponInfo->loopingframes[vweap.currentAnim] - 1 ) );
		else if( !vweap.newAnim )
			vweap.newAnim = VWEAP_STANDBY; // always return to standby if no animation was fed
	}

	// if changed POV
	if( vweap.weapon != cg.frame.playerState.stats[STAT_WEAPON_ITEM]
	    && vweap.newAnim != VWEAP_WEAPONUP )
	{
		vweap.newAnim = 0;
		vweap.currentAnim = VWEAP_STANDBY;
		vweap.weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM];
		weaponInfo = CG_GetWeaponInfo( vweap.weapon );
		vweap.frame = vweap.oldframe = weaponInfo->firstframe[vweap.currentAnim];
	}

	// is there a pending new animation?
	if( vweap.newAnim )
	{
		vweap.currentAnim = vweap.newAnim;
		// pending weapon change?
		if( vweap.newAnim == VWEAP_WEAPONUP )
		{
			vweap.weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM];
			weaponInfo = CG_GetWeaponInfo( vweap.weapon );
			vweap.oldframe = weaponInfo->firstframe[vweap.currentAnim];
		}
		vweap.frame = weaponInfo->firstframe[vweap.currentAnim];
		vweap.newAnim = 0;
	}

	vweap.prevframetime = cg.time;
	vweap.nextframetime = cg.time + weaponInfo->frametime[vweap.currentAnim];
	vweap.backlerp = 1.0f;
}

//==============
//CG_CalcViewWeapon
//==============
void CG_CalcViewWeapon( void )
{
	orientation_t tag, *handposition;
	weaponinfo_t *weaponInfo;

	CG_ViewWeapon_UpdateAnimation();
	CG_ViewWeapon_Position( vweap.origin, vweap.axis );

	weaponInfo = CG_GetWeaponInfo( vweap.weapon );

	// offset the view weapon to the tag_position in the model *_handposition
	handposition = &weaponInfo->tag_handposition;
	VectorCopy( vec3_origin, tag.origin );
	Matrix_Copy( axis_identity, tag.axis );

	// move the empty tag to the handposition tag in vweap space
	CG_MoveToTag( tag.origin, tag.axis,
	              vweap.origin, vweap.axis,
	              handposition->origin, handposition->axis );

	VectorCopy( tag.origin, vweap.origin );
	Matrix_Copy( tag.axis, vweap.axis );

	//hand entity
	memset( &gun, 0, sizeof( gun ) );
	gun.model = weaponInfo->model[HAND];

	//if the player doesn't want to view the weapon we still have to build the projection source
	if( CG_GrabTag( &tag, &gun, "tag_weapon" ) )
		CG_ViewWeapon_UpdateProjectionSource( vweap.origin, vweap.axis, tag.origin, tag.axis );
	else
		CG_ViewWeapon_UpdateProjectionSource( vweap.origin, vweap.axis, vec3_origin, axis_identity );
}

//==============
//CG_AddViewWeapon
//==============
void CG_AddViewWeapon( void )
{
	orientation_t tag;
	weaponinfo_t *weaponInfo;

	weaponInfo = CG_GetWeaponInfo( vweap.weapon );

	//hand entity
	gun.model = weaponInfo->model[HAND];

	//update the position
	VectorCopy( vweap.origin, gun.origin );
	VectorCopy( vweap.origin, gun.origin2 );
	VectorCopy( cg.lightingOrigin, gun.lightingOrigin );
	Matrix_Copy( vweap.axis, gun.axis );

	gun.renderfx = RF_MINLIGHT|RF_WEAPONMODEL;
	gun.scale = 1.0f;
	gun.frame = vweap.frame;
	gun.oldframe = vweap.oldframe;
	gun.backlerp = vweap.backlerp;

	if( cg.view.drawWeapon && vweap.weapon != WEAP_NONE )
		CG_AddColoredOutLineEffect( &gun, cg.effects, 0, 0, 0, 255 );

	CG_AddEntityToScene( &gun );

	// weapon disabled
	if( !cg.view.drawWeapon || vweap.weapon == WEAP_NONE )
		return;

	CG_AddShellEffects( &gun, cg.effects );

	// add attached weapon
	if( CG_GrabTag( &tag, &gun, "tag_weapon" ) )
		CG_AddWeaponOnTag( &gun, &tag, vweap.weapon, &vweap.pweapon, cg.effects|EF_OUTLINE, NULL );
}
