#include common_scripts\utility;
#include animscripts\combat_utility;
#include animscripts\utility;
#include animscripts\shared;

// Ideally, this will be the only thread *anywhere* that decides what/where to shoot at
// and how to shoot at it.

// This thread keeps three variables updated, and notifies "shoot_behavior_change" when any of them have changed.
// They are:
//  shootEnt - an entity. aim/shoot at this if it's defined.
//  shootPos - a vector. aim/shoot towards this if shootEnt isn't defined. if not defined, stop shooting entirely and return to cover if possible.
//		Whenever shootEnt is defined, shootPos will be defined as its getShootAtPos().
//  shootStyle - how to shoot.
//    "full" (unload on the target),
//    "burst" (occasional groups of shots),
//    "semi" (occasianal single shots),
//    "single" (occasional single shots),
//    "none" (don't shoot, just aim).
// This thread will also notify "return_to_cover" and set self.shouldReturnToCover = true if it's a good idea to do so.
// Notify "stop_deciding_how_to_shoot" to end this thread if no longer trying to shoot.

decideWhatAndHowToShoot( objective )
{
	self endon( "killanimscript" );
	self notify( "stop_deciding_how_to_shoot" );// just in case...
	self endon( "stop_deciding_how_to_shoot" );
	self endon( "death" );

	assert( isdefined( objective ) );// just use "normal" if you don't know what to use

	maps\_gameskill::resetMissTime();
	self.shootObjective = objective;
	// self.shootObjective is always "normal", "suppress", or "ambush"

	self.shootEnt = undefined;
	self.shootPos = undefined;
	self.shootStyle = "none";
	self.fastBurst = false;
	self.shouldReturnToCover = undefined;

	if ( !isdefined( self.changingCoverPos ) )
		self.changingCoverPos = false;

	atCover = isDefined( self.coverNode ) && self.coverNode.type != "Cover Prone" && self.coverNode.type != "Conceal Prone";

	if ( atCover )
	{
		// it's not safe to do some things until the next frame,
		// such as canSuppressEnemy(), which may change the state of
		// self.goodShootPos, which will screw up cover_behavior::main
		// when this is called but then stopped immediately.
		wait .05;
	}

	prevShootEnt = self.shootEnt;
	prevShootPos = self.shootPos;
	prevShootStyle = self.shootStyle;

	if ( !isdefined( self.has_no_ir ) )
	{
		self.a.laserOn = true;
		self animscripts\shared::updateLaserStatus();
	}

	if ( self isSniper() )
		self resetSniperAim();

	// only watch for incoming fire if it will be beneficial for us to return to cover when shot at.
	if ( atCover && ( !self.a.atConcealmentNode || !self canSeeEnemy() ) )
		thread watchForIncomingFire();
	thread runOnShootBehaviorEnd();

	self.ambushEndTime = undefined;

	prof_begin( "decideWhatAndHowToShoot" );

	while ( 1 )
	{
		if ( isdefined( self.shootPosOverride ) )
		{
			if ( !isdefined( self.enemy ) )
			{
				self.shootPos = self.shootPosOverride;
				self.shootPosOverride = undefined;
				WaitABit();
			}
			else
			{
				self.shootPosOverride = undefined;
			}
		}

		assert( self.shootObjective == "normal" || self.shootObjective == "suppress" || self.shootObjective == "ambush" );
		assert( !isdefined( self.shootEnt ) || isdefined( self.shootPos ) );// shootPos must be shootEnt's shootAtPos if shootEnt is defined, for convenience elsewhere
		
		result = undefined;
		if ( self.weapon == "none" )
			noGunShoot();
		else if ( usingStreamgun() )
			result = streamgunShoot();
		else if ( usingRocketLauncher() )
			result = rpgShoot();
		else if ( usingSidearm() )
			result = pistolShoot();
		else if ( usingMinigun() )
			result = rifleShoot();//minigunShoot();
		else
			result = rifleShoot();
		
		if ( isDefined( self.a.specialShootBehavior ) )
			[[self.a.specialShootBehavior]]();

		
		if ( checkChanged( prevShootEnt, self.shootEnt ) || ( !isdefined( self.shootEnt ) && checkChanged( prevShootPos, self.shootPos ) ) || checkChanged( prevShootStyle, self.shootStyle ) )
			self notify( "shoot_behavior_change" );
		prevShootEnt = self.shootEnt;
		prevShootPos = self.shootPos;
		prevShootStyle = self.shootStyle;


		// (trying to prevent many AI from doing lots of work on the same frame)
		if ( !isdefined( result ) )
			WaitABit();
	}

	prof_end( "decideWhatAndHowToShoot" );
}

WaitABit()
{
	self endon( "enemy" );
	self endon( "done_changing_cover_pos" );
	self endon( "weapon_position_change" );
	self endon( "enemy_visible" );

	if ( isdefined( self.shootEnt ) )
	{
		self.shootEnt endon( "death" );

		self endon( "do_slow_things" );

		// (want to keep self.shootPos up to date)
		wait .05;
		while ( isdefined( self.shootEnt ) )
		{
			self.shootPos = self.shootEnt getShootAtPos();
			wait .05;
		}
	}
	else
	{
		self waittill( "do_slow_things" );
	}
}

noGunShoot()
{
	 /#
	println( "^1Warning: AI at " + self.origin + ", entnum " + self getEntNum() + ", export " + self.export + " trying to shoot but has no gun" );
	#/
	self.shootEnt = undefined;
	self.shootPos = undefined;
	self.shootStyle = "none";
	self.shootObjective = "normal";
}

shouldSuppress()
{
	return !self isSniper() && !isShotgun( self.weapon );
}

shouldShootEnemyEnt()
{
	assert( isDefined ( self ) );
	
	if ( !self canSeeEnemy() )
		return false;

	// When not in cover, check if we can shoot at our current enemy as well
	if ( !isDefined( self.coverNode ) && !self canShootEnemy() )
		return false;

	return true;
}


rifleShootObjectiveNormal()
{
	if ( !shouldShootEnemyEnt() )
	{
		// enemy disappeared!
		if ( self isSniper() )
			self resetSniperAim();

		if ( self.doingAmbush )
		{
			self.shootObjective = "ambush";
			return "retry";
		}

		if ( !isdefined( self.enemy ) )
		{
			haveNothingToShoot();
		}
		else
		{
			markEnemyPosInvisible();

			if ( ( self.provideCoveringFire || randomint( 5 ) > 0 ) && shouldSuppress() )
				self.shootObjective = "suppress";
			else
				self.shootObjective = "ambush";
			return "retry";
		}
	}
	else
	{
		setShootEntToEnemy();
		self setShootStyleForVisibleEnemy();
	}
}


rifleShootObjectiveSuppress( enemySuppressable )
{
	if ( !enemySuppressable )
	{
		haveNothingToShoot();
	}
	else
	{
		self.shootEnt = undefined;
		self.shootPos = getEnemySightPos();

		self setShootStyleForSuppression();		
	}
}

rifleShootObjectiveAmbush( enemySuppressable )
{
	assert( self.shootObjective == "ambush" );

	self.shootStyle = "none";
	self.shootEnt = undefined;
	
	if ( !enemySuppressable )
	{
		getAmbushShootPos();

		if ( shouldStopAmbushing() )
		{
			self.ambushEndTime = undefined;
			self notify( "return_to_cover" );
			self.shouldReturnToCover = true;
		}	
	}
	else
	{
		self.shootPos = getEnemySightPos();

		if ( self shouldStopAmbushing() )
		{
			self.ambushEndTime = undefined;

			if ( shouldSuppress() )
				self.shootObjective = "suppress";
				
			if ( randomint( 3 ) == 0 )
			{
				self notify( "return_to_cover" );
				self.shouldReturnToCover = true;
			}
			return "retry";
		}
	}
}


getAmbushShootPos()
{
	if ( isdefined( self.enemy ) && self cansee( self.enemy ) ) 
	{
		setShootEntToEnemy();
		return;
	}
	
	likelyEnemyDir = self getAnglesToLikelyEnemyPath();
	
	if ( !isdefined( likelyEnemyDir ) )
	{
		if ( isDefined( self.coverNode ) )
			likelyEnemyDir = self.coverNode.angles;
		else if ( isdefined( self.ambushNode ) )
			likelyEnemyDir = self.ambushNode.angles;
		else if ( isdefined( self.enemy ) )
			likelyEnemyDir = vectorToAngles( self lastKnownPos( self.enemy ) - self.origin );
		else 
			likelyEnemyDir = self.angles;
	}

	dist = 1024;
	if ( isdefined( self.enemy ) )
		dist = distance( self.origin, self.enemy.origin );

	newShootPos = self getEye() + anglesToForward( likelyEnemyDir ) * dist;	
	
	if ( !isdefined( self.shootPos ) || distanceSquared( newShootPos, self.shootPos ) > 5 * 5 )// avoid frequent "shoot_behavior_change" notifies
		self.shootPos = newShootPos;
}


rifleShoot()
{
	if ( self.shootObjective == "normal" )
	{
		rifleShootObjectiveNormal();
	}
	else
	{
		if ( shouldShootEnemyEnt() )// later, maybe we can be more realistic than just shooting at the enemy the instant he becomes visible
		{
			self.shootObjective = "normal";
			self.ambushEndTime = undefined;
			return "retry";
		}

		markEnemyPosInvisible();

		if ( self isSniper() )
			self resetSniperAim();
			
		enemySuppressable = canSuppressEnemy();
			
		if ( self.shootObjective == "suppress" || ( self.team == "allies" && !isdefined( self.enemy ) && !enemySuppressable ) )
			rifleShootObjectiveSuppress( enemySuppressable );
		else
			rifleShootObjectiveAmbush( enemySuppressable );
	}
}

streamgunShoot()
{
	if ( shouldShootEnemyEnt() )
	{
		setShootEntToEnemy();
		setShootStyle( "continuous", false );
	}
}

shouldStopAmbushing()
{
	if ( !isdefined( self.ambushEndTime ) )
	{
		if ( self isBadGuy() )
			self.ambushEndTime = gettime() + randomintrange( 10000, 60000 );
		else
			self.ambushEndTime = gettime() + randomintrange( 4000, 10000 );
	}
	return self.ambushEndTime < gettime();
}

rpgShoot()
{
	if ( !shouldShootEnemyEnt() )
	{
		markEnemyPosInvisible();

		haveNothingToShoot();
		return;
	}

	setShootEntToEnemy();
	self.shootStyle = "single";

	distSqToShootPos = lengthsquared( self.origin - self.shootPos );
	// too close for RPG
	if ( distSqToShootPos < squared( 512 ) )
	{
		self notify( "return_to_cover" );
		self.shouldReturnToCover = true;
		return;
	}
}


pistolShoot()
{
	if ( self.shootObjective == "normal" )
	{
		if ( !shouldShootEnemyEnt() )
		{
			// enemy disappeared!
			if ( !isdefined( self.enemy ) )
			{
				haveNothingToShoot();
				return;
			}
			else
			{
				markEnemyPosInvisible();

				self.shootObjective = "ambush";
				return "retry";
			}
		}
		else
		{
			setShootEntToEnemy();
			self.shootStyle = "single";
		}
	}
	else
	{
		if ( shouldShootEnemyEnt() )// later, maybe we can be more realistic than just shooting at the enemy the instant he becomes visible
		{
			self.shootObjective = "normal";
			self.ambushEndTime = undefined;
			return "retry";
		}

		markEnemyPosInvisible();

		self.shootEnt = undefined;
		self.shootStyle = "none";
		self.shootPos = getEnemySightPos();

		// stop ambushing after a while
		if ( !isdefined( self.ambushEndTime ) )
			self.ambushEndTime = gettime() + randomintrange( 4000, 8000 );

		if ( self.ambushEndTime < gettime() )
		{
			self.shootObjective = "normal";
			self.ambushEndTime = undefined;
			return "retry";
		}
	}
}

markEnemyPosInvisible()
{
	if ( isdefined( self.enemy ) && !self.changingCoverPos && self.script != "combat" )
	{
		// make sure they're not just hiding
		if ( isAI( self.enemy ) && isdefined( self.enemy.script ) && ( self.enemy.script == "cover_stand" || self.enemy.script == "cover_crouch" ) )
		{
			if ( isdefined( self.enemy.a.coverMode ) && self.enemy.a.coverMode == "hide" )
				return;
		}

		self.couldntSeeEnemyPos = self.enemy.origin;
	}
}

watchForIncomingFire()
{
	self endon( "killanimscript" );
	self endon( "stop_deciding_how_to_shoot" );

	while ( 1 )
	{
		self waittill( "suppression" );

		if ( self.suppressionMeter > self.suppressionThreshold )
		{
			if ( self readyToReturnToCover() )
			{
				self notify( "return_to_cover" );
				self.shouldReturnToCover = true;
			}
		}
	}
}

readyToReturnToCover()
{
	if ( self.changingCoverPos )
		return false;

	assert( isdefined( self.coverPosEstablishedTime ) );

	if ( !isdefined( self.enemy ) || !self canSee( self.enemy ) )
		return true;

	if ( gettime() < self.coverPosEstablishedTime + 800 )
	{
		// don't return to cover until we had time to fire a couple shots;
		// better to look daring than indecisive
		return false;
	}

	if ( isPlayer( self.enemy ) && self.enemy.health < self.enemy.maxHealth * .5 )
	{
		// give ourselves some time to take them down
		if ( gettime() < self.coverPosEstablishedTime + 3000 )
			return false;
	}

	return true;
}

runOnShootBehaviorEnd()
{
	self endon( "death" );

	self waittill_any( "killanimscript", "stop_deciding_how_to_shoot"/*, "return_to_cover"*/ );

	self.a.laserOn = false;
	self animscripts\shared::updateLaserStatus();
}

checkChanged( prevval, newval )
{
	if ( isdefined( prevval ) != isdefined( newval ) )
		return true;
	if ( !isdefined( newval ) )
	{
		assert( !isdefined( prevval ) );
		return false;
	}
	return prevval != newval;
}

setShootEntToEnemy()
{
	self.shootEnt = self.enemy;
	self.shootPos = self.shootEnt getShootAtPos();
}

haveNothingToShoot()
{
	self.shootEnt = undefined;
	self.shootPos = undefined;
	self.shootStyle = "none";

	if ( self.doingAmbush )
		self.shootObjective = "ambush";

	if ( !self.changingCoverPos )
	{
		self notify( "return_to_cover" );
		self.shouldReturnToCover = true;
	}
}

shouldBeAJerk()
{
	return level._gameskill == 3 && isPlayer( self.enemy );// && self shouldDoSemiForVariety();
}

fullAutoRangeSq = 250 * 250;
burstRangeSq = 900 * 900;
singleShotRangeSq = 1600 * 1600;

setShootStyleForVisibleEnemy()
{
	assert( isdefined( self.shootPos ) );
	assert( isdefined( self.shootEnt ) );

	if ( isdefined( self.shootEnt.enemy ) && isdefined( self.shootEnt.enemy.syncedMeleeTarget ) )
		return setShootStyle( "single", false );

	if ( self isSniper() )
		return setShootStyle( "single", false );

	if ( isStreamgun( self.weapon ) )
	{
		return setShootStyle( "continuous", false );
	}
	
	if ( isShotgun( self.weapon ) )
	{
		if ( weapon_pump_action_shotgun() )
			return setShootStyle( "single", false );
		else
			return setShootStyle( "semi", false );
	}

	if ( weaponBurstCount( self.weapon ) > 0 )
		return setShootStyle( "burst", false );

	distanceSq = distanceSquared( self getShootAtPos(), self.shootPos );

	isMG = weaponClass( self.weapon ) == "mg";

	if ( self.provideCoveringFire && isMG )
		return setShootStyle( "full", false );

	if ( distanceSq < fullAutoRangeSq )
	{
		if ( isdefined( self.shootEnt ) && isdefined( self.shootEnt.magic_bullet_shield ) )
			return setShootStyle( "single", false );
		else
			return setShootStyle( "full", false );
	}
	else if ( distanceSq < burstRangeSq || shouldBeAJerk() )
	{
		if ( weaponIsSemiAuto( self.weapon ) || shouldDoSemiForVariety() )
			return setShootStyle( "semi", true );
		else
			return setShootStyle( "burst", true );
	}
	else if ( self.provideCoveringFire || isMG || distanceSq < singleShotRangeSq )
	{
		if ( shouldDoSemiForVariety() )
			return setShootStyle( "semi", false );
		else
			return setShootStyle( "burst", false );
	}

	return setShootStyle( "single", false );
}

setShootStyleForSuppression()
{
	assert( isdefined( self.shootPos ) );

	distanceSq = distanceSquared( self getShootAtPos(), self.shootPos );

	assert( !self isSniper() );// snipers shouldn't be suppressing!
	assert( !isShotgun( self.weapon ) );// shotgun users shouldn't be suppressing!
	assert( !isStreamgun( self.weapon ) ); // streamguns shouldn't be suppressing!

	if ( weaponIsSemiAuto( self.weapon ) )
	{
		if ( distanceSq < singleShotRangeSq )
			return setShootStyle( "semi", false );
		return setShootStyle( "single", false );
	}

	if ( weaponClass( self.weapon ) == "mg" )
		return setShootStyle( "full", false );

	if ( self.provideCoveringFire || distanceSq < singleShotRangeSq )
	{
		if ( shouldDoSemiForVariety() )
			return setShootStyle( "semi", false );
		else
			return setShootStyle( "burst", false );
	}

	return setShootStyle( "single", false );
}

setShootStyle( style, fastBurst )
{
	self.shootStyle = style;
	self.fastBurst = fastBurst;
}

shouldDoSemiForVariety()
{
	if ( weaponClass( self.weapon ) != "rifle" )
		return false;

	if ( self.team != "allies" )
		return false;

	// true randomness isn't safe, because that will cause frequent shoot_behavior_change notifies.
	// fake the randomness in a way that won't change frequently.
	changeFrequency = safemod( int( self.origin[ 1 ] ), 10000 ) + 2000;
	fakeTimeValue = int( self.origin[ 0 ] ) + gettime();

	return fakeTimeValue %( 2 * changeFrequency ) > changeFrequency;
}

resetSniperAim()
{
	assert( self isSniper() );
	self.sniperShotCount = 0;
	self.sniperHitCount = 0;
	
	thread sniper_glint_behavior();
}

sniper_glint_behavior()
{
	self endon( "killanimscript" );
	self endon( "enemy" );
	self endon( "return_to_cover" );
	self notify( "new_glint_thread" );
	self endon( "new_glint_thread" );
	
	assertex( self isSniper(), "Not a sniper!" );
	if ( !isdefined( level._effect[ "sniper_glint" ] ) )
	{
		println( "^3Warning, sniper glint is not setup for sniper with classname " + self.classname );
		return;
	}
	
	if ( !isAlive( self.enemy ) )
		return;
	
	//if ( !isPlayer( self.enemy ) )
	//	return;
		
	fx = getfx( "sniper_glint" );
	
	wait 0.2;
		
	for ( ;; )
	{
		if ( self.weapon == self.primaryweapon && player_sees_my_scope() )
		{
			if ( distanceSquared( self.origin, self.enemy.origin ) > 256 * 256 )
				PlayFXOnTag( fx, self, "tag_flash" );
				
			timer = randomfloatrange( 3, 5 );
			wait( timer );
		}
		wait( 0.2 );
	}
}

