#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;

MAX_KILLCAM_START_TIME = 5;

init()
{
	level.killcam = maps\mp\gametypes\_tweakables::getTweakableValue( "game", "allowkillcam" );
}

setCinematicCameraStyle( cameraStyle, leadingActorId, supportingActorId, leadingActorAltId, supportingActorAltId )
{
	self SetClientOmnvar( "cam_scene_name", cameraStyle );
	self SetClientOmnvar( "cam_scene_lead", leadingActorId );
	self SetClientOmnvar( "cam_scene_support", supportingActorId );
	if ( IsDefined( leadingActorAltId ) )
		self SetClientOmnvar( "cam_scene_lead_alt", leadingActorAltId );
	else
		self SetClientOmnvar( "cam_scene_lead_alt", leadingActorId );
	if ( IsDefined( supportingActorAltId ) )
		self SetClientOmnvar( "cam_scene_support_alt", supportingActorAltId );
	else
		self SetClientOmnvar( "cam_scene_support_alt", supportingActorId );
}

// self == victim
setKillCameraStyle( eInflictor, attackerNum, killcamentityindex, weaponName, victim, camtime )
{
	if ( IsDefined( eInflictor ) && IsDefined( eInflictor.agent_type ) )
	{
		if ( eInflictor.agent_type == "dog" )
			self setCinematicCameraStyle( "killcam_dog", eInflictor GetEntityNumber(), self GetEntityNumber() );
		else
			self setCinematicCameraStyle( "killcam_agent", eInflictor GetEntityNumber(), self GetEntityNumber() );
	}
	else if ( IsDefined( victim ) && IsDefined( weaponName ) && weaponName == "orbital_laser_fov_mp" && camtime == MAX_KILLCAM_START_TIME )
	{
		body = -1;
		if ( IsDefined( victim.body ) )
			body = victim.body GetEntityNumber();
		self thread setCinematicCameraStyle( "orbital_laser_killcam", attackerNum, victim GetEntityNumber(), attackerNum, body );
	}
	else if ( killcamentityindex >= 0 )
	{
		// use killcam entity camera rather than a "cinematic camera"
		self setCinematicCameraStyle( "unknown", -1, -1 );
		return false;
	}		
	// make sure this scene name has a corresponding file in "game/share/raw/mp/cinematic_camera/*.txt" and is added to common_mp.csv & ncsLuiStrings.txt
	else if ( level.showingFinalKillcam )
	{
		self setCinematicCameraStyle( "unknown", attackerNum, self GetEntityNumber() );
	}
	else
	{
		self setCinematicCameraStyle( "unknown", attackerNum, -1 );
	}
	
	return true;
}


preKillcamNotify( eInflictor, attacker, camtime, currentWeapon )
{
	if( isPlayer( self ) && IsDefined(attacker) && isPlayer(attacker) )
	{
		weapons = maps\mp\gametypes\_playerlogic::gatherClassWeapons();

		// Wait one frame before doing this to avoid a stack overflow on CG due to a really deep stack when executing the warbird kill streak
		// Waiting one frame shouldn't be a problem because the code that calls this performs several waits right after this function anyways
		offset = getTime();
		
		waitframe();
		if( isPlayer( self ) && IsDefined(attacker) && isPlayer(attacker) )
		{
			offset = ( getTime() - offset ) / 1000;
			self.streamWeapons = self LoadCustomizationPlayerView( attacker, camtime + offset, currentWeapon, weapons );
			self PrecacheKillCamIconForWeapon( currentWeapon );
		}
	}

	// TODO: Could store the attacker spawn time here, before too much time passes?
}

killcamTime( killcamentitystarttime, 
		    sWeapon,
		    predelay,
		    timeUntilRespawn,
		    maxtime, 
		    useStartTime,
		    showingFinalKillcam
		   )
{
	if (getdvar("scr_killcam_time") == "")
	{
		shortWeapon = maps\mp\_utility::strip_suffix( sWeapon, "_lefthand" );

		if ( useStartTime || sWeapon == "artillery_mp" || sWeapon == "stealth_bomb_mp" || sWeapon == "killstreakmahem_mp" )
			camtime = (gettime() - killcamentitystarttime) / 1000 - predelay - .1;
		else if ( showingFinalKillcam || sWeapon == "agent_mp" )
			camtime = 4.0;
		else if ( issubstr( sWeapon, "remotemissile_" ) )
			camtime = 5;
		else if ( !timeUntilRespawn || timeUntilRespawn > 5.0 ) // if we're not going to respawn, we can take more time to watch what happened
			camtime = 5.0;
		else if ( shortWeapon == "frag_grenade_mp" || shortWeapon == "frag_grenade_short_mp" || shortWeapon == "semtex_mp" || shortWeapon == "semtexproj_mp" || shortWeapon == "thermobaric_grenade_mp" || shortWeapon == "frag_grenade_var_mp" || shortWeapon == "contact_grenade_var_mp" || shortWeapon == "semtex_grenade_var_mp" )
			camtime = 4.25; // show long enough to see grenade thrown
		else
			camtime = 2.5;
	}
	else
		camtime = getdvarfloat("scr_killcam_time");
	
	if( useStartTime && (camtime > MAX_KILLCAM_START_TIME) )
		camtime = MAX_KILLCAM_START_TIME;

	if ( IsDefined( maxtime ) )
	{
		if ( camtime > maxtime )
			camtime = maxtime;
		if ( camtime < 0.05 )
			camtime = 0.05;
	}
	
	return camtime;
}

killcamArchiveTime( camTime, aliveTime, preDelay, offsetTime )
{
	if ( camTime > aliveTime )
	{
		camTime = aliveTime;
	}
	
	archiveTime = camTime + preDelay + offsetTime;
	println( "killcamArchiveTime: archiveTime: " + archiveTime + " camTime: " + camTime + " preDelay: " + preDelay + " offsetTime: " + offsetTime );
		   
	return archiveTime;
}

killcamValid( victim, doKillcam )
{
	return doKillcam && 
		level.killcam && 
		!( IsDefined( victim.cancelKillcam ) && victim.cancelKillcam ) && 
		game[ "state" ] == "playing" && 
		!victim isUsingRemote() && 
		!level.showingFinalKillcam &&
		!IsAI(victim);
}

killcam(
	eInflictor, // entity of the inflictor, can be the attacker
	attackerNum, // entity number of the attacker
	killcamentityindex, // entity number of the entity to view (grenade, airstrike, etc)
	killcamentitystarttime, // time at which the killcamentity came into being
	sWeapon, // killing weapon
	predelay, // time between player death and beginning of killcam
	offsetTime, // something to do with how far back in time the killer was seeing the world when he made the kill; latency related, sorta
	timeUntilRespawn, // will the player be allowed to respawn after the killcam?
	maxtime, // time remaining until map ends; the killcam will never last longer than this. undefined = no limit
	attacker, // entity object of attacker
	victim, // entity object of the victim
	sMeansOfDeath, // the means of death
	type, // "normal" or "score"; Allow final kill cams that show winning game event that is not a kill
	aliveTime, // the time the attacker had been alive at the time the victim died
	useStartTime
)
{
	// monitors killcam and hides HUD elements during killcam session
	//if ( !level.splitscreen )
	//	self thread killcam_HUD_off();
	
	self endon( "disconnect" );
	self endon( "spawned" );
	level endon( "game_ended" );
	
	if ( attackerNum < 0 || !IsDefined( attacker ) )
		return;

	//this is to track and amortize number of killcams per frame
	level.numPlayersWaitingToEnterKillcam++;
	totalKillcamWait = level.numPlayersWaitingToEnterKillcam * 0.05;
	if ( level.numPlayersWaitingToEnterKillcam > 1 )
	{
		/#
			println( "more than one client entering killcam this frame: " + level.numPlayersWaitingToEnterKillcam );
		#/
			
		wait 0.05 * ( level.numPlayersWaitingToEnterKillcam - 1 );
	}
	
	wait 0.05;	// Allow all players entering killcam to increment 'level.numPlayersWaitingToEnterKillcam'
	prof_begin( "killcam" );
	level.numPlayersWaitingToEnterKillcam--;
	
	// length from killcam start to killcam end
	camtime = killcamTime( killcamentitystarttime, sWeapon, predelay, timeUntilRespawn, maxtime, useStartTime, level.showingFinalKillcam );
		
	// time after player death that killcam continues for
	if (getdvar("scr_killcam_posttime") == "")
		postdelay = 2;
	else {
		postdelay = getdvarfloat("scr_killcam_posttime");
		if (postdelay < 0.05)
			postdelay = 0.05;
	}
	
	/* timeline:
	
	|        camtime       |      postdelay      |
	|                      |   predelay    |
	
	^ killcam start        ^ player death        ^ killcam end
	                                       ^ player starts watching killcam
	
	*/
	
	killcamlength = camtime + postdelay;
	
	// don't let the killcam last past the end of the round.
	if (isdefined(maxtime) && killcamlength > maxtime)
	{
		// first trim postdelay down to a minimum of 1 second.
		// if that doesn't make it short enough, trim camtime down to a minimum of 1 second.
		// if that's still not short enough, cancel the killcam.
		if ( maxtime < 2 )
		{
			prof_end( "killcam" );
			return;
		}

		if (maxtime - camtime >= 1) {
			// reduce postdelay so killcam ends at end of match
			postdelay = maxtime - camtime;
		}
		else {
			// distribute remaining time over postdelay and camtime
			postdelay = 1;
			camtime = maxtime - 1;
		}
		
		// recalc killcamlength
		killcamlength = camtime + postdelay;
	}

	// LUA: we need to reset this dvar so the killcam will reset if we come in here for the final killcam while you're watching a killcam
	self SetClientOmnvar( "ui_killcam_end_milliseconds", 0 );
	// END LUA
	
	if ( IsAgent(attacker) && !IsDefined(attacker.isActive) )
	{
		prof_end( "killcam" );
		return;	// Agent attacker has disconnected
	}

	// LUA: we need to show attacker info in the lua killcam
	assert( IsGameParticipant( attacker ) );
	
	if( IsPlayer( attacker ) )
	{
		self SetClientOmnvar( "ui_killcam_killedby_id", attacker GetEntityNumber() );
	}
	else if ( IsAgent( attacker ) )
	{
		self SetClientOmnvar( "ui_killcam_killedby_id", -1 );
	}
	
	if ( isKillstreakWeapon( sWeapon ) )
	{
		AssertEx( IsDefined( level.killstreakWieldWeapons[sWeapon] ), "Killstreak weapon needs to be added to weapons array: " + sWeapon );
		killstreakRowIdx = getKillstreakRowNum( level.killstreakWieldWeapons[ sWeapon ] );
		self SetClientOmnvar( "ui_killcam_killedby_killstreak", killstreakRowIdx );
		self SetClientOmnvar( "ui_killcam_killedby_weapon", -1 );
		self SetClientOmnvar( "ui_killcam_killedby_attachment1", -1 );
		self SetClientOmnvar( "ui_killcam_killedby_attachment2", -1 );
		self SetClientOmnvar( "ui_killcam_killedby_attachment3", -1 );
		self SetClientOmnvar( "ui_killcam_copycat", false );
	}
	else
	{
		attachments = [];
		
		weaponName = GetWeaponBaseName( sWeapon );
		if(IsDefined(weaponName))
		{
			// set to knife only weapon to get knife image for lua
			if( isMeleeMOD(sMeansOfDeath) && !maps\mp\gametypes\_weapons::isRiotShield( sWeapon ) )
			{
				weaponName = "iw5_combatknife";
			}
			else
			{
				weaponName = maps\mp\_utility::strip_suffix( weaponName, "_lefthand" );
				weaponName = maps\mp\_utility::strip_suffix( weaponName, "_mp" );
			}
			weaponRowIdx = TableLookupRowNum( "mp/statsTable.csv", 4, weaponName );
			self SetClientOmnvar( "ui_killcam_killedby_weapon", weaponRowIdx );
			self SetClientOmnvar( "ui_killcam_killedby_killstreak", -1 );
			
			if( weaponName != "iw5_combatknife" )
				attachments = GetWeaponAttachments( sWeapon );
			
			// Determine if we should show the 'copycat' item
			if ( !level.showingFinalKillcam && practiceRoundGame() && IsPlayer( attacker ) && !IsBot( self ) && !IsAgent( self ) && maps\mp\gametypes\_class::loadoutValidForCopycat( attacker ) )
			{
				self SetClientOmnvar( "ui_killcam_copycat", true );
				self thread waitCopycatKillcamButton( attacker );
			}
			else
			{
				self SetClientOmnvar( "ui_killcam_copycat", false );
			}
		}
		else
		{
			self SetClientOmnvar( "ui_killcam_killedby_weapon", -1 );
			self SetClientOmnvar( "ui_killcam_killedby_killstreak", -1 );
			self SetClientOmnvar( "ui_killcam_copycat", false );
		}
		
		for( i = 0; i < 3; i++ )
		{
			if( IsDefined( attachments[ i ] ) )
			{
				attachmentRowIdx = TableLookupRowNum( "mp/attachmentTable.csv", 3, attachmentMap_toBase( attachments[ i ] ) );
				self SetClientOmnvar( "ui_killcam_killedby_attachment" + ( i + 1 ), attachmentRowIdx );
			}
			else
			{
				self SetClientOmnvar( "ui_killcam_killedby_attachment" + ( i + 1 ), -1 );
			}
		}
	}

	if ( timeUntilRespawn && !level.gameEnded || ( isDefined( self ) && isDefined(self.battleBuddy) && !level.gameEnded ) )
	{
		//setLowerMessage( "kc_info", &"PLATFORM_PRESS_TO_SKIP", undefined, undefined, undefined, undefined, undefined, undefined, true );
		self SetClientOmnvar( "ui_killcam_text", "skip" );
	}
	else if ( !level.gameEnded )
	{
		//setLowerMessage( "kc_info", &"PLATFORM_PRESS_TO_RESPAWN", undefined, undefined, undefined, undefined, undefined, undefined, true );
		self SetClientOmnvar( "ui_killcam_text", "respawn" );	
	}
	else
	{
		self SetClientOmnvar( "ui_killcam_text", "none" );	
	}
	
	switch(type)
	{
		case "score":
			self SetClientOmnvar( "ui_killcam_type", 1 );
			break;
		case "normal":
		default:
			self SetClientOmnvar( "ui_killcam_type", 0 );
			break;	
	}
	// END LUA

	killcamoffset = camtime + predelay + totalKillcamWait;
	
	startTime = getTime();
	self notify ( "begin_killcam", startTime );

	if ( !isAgent(attacker) && isDefined( attacker ) && IsPlayer(victim) ) // attacker may have disconnected
		attacker visionsyncwithplayer( victim );
	
	self updateSessionState( "spectator" );
	self.spectatekillcam = true;
	
	if( IsAgent( attacker ) )
		attackerNum =  victim GetEntityNumber();

	self OnlyStreamActiveWeapon( false );
	
	self.forcespectatorclient = attackerNum;
	self.killcamentity = -1;
	
	usingCinematicKillCam = self setKillCameraStyle( eInflictor, attackerNum, killcamentityindex, sWeapon, victim, camtime );
	
	if ( !usingCinematicKillCam )
	{
		self thread setKillCamEntity( killcamentityindex, killcamoffset, killcamentitystarttime );
	}

	if ( killcamoffset > aliveTime )
		killcamoffset = aliveTime;

	self.archivetime = killcamoffset;
	self.killcamlength = killcamlength;
	self.psoffsettime = offsetTime;
	
	//if ( killcamentitystarttime < attacker.lastSpawnTime )
	//	killcamentitystarttime = attacker.lastSpawnTime;
	
	// ignore spectate permissions
	self allowSpectateTeam("allies", true);
	self allowSpectateTeam("axis", true);
	self allowSpectateTeam("freelook", true);
	self allowSpectateTeam("none", true);
	if( level.multiTeamBased )
	{
		foreach( teamname in level.teamNameList )
		{
			self allowSpectateTeam( teamname, true );
		}
	}


	{
		foreach( teamname in level.teamNameList )
		{
			self allowSpectateTeam( teamname, true );
		}
	}

	self thread endedKillcamCleanup();

	prof_end( "killcam" );
	// wait till the next server frame to allow code a chance to update archivetime if it needs trimming
	wait 0.05;	
	
	if( !isDefined( self ) )
		return;
	
	prof_begin( "killcam2" );
	assertex( self.archivetime <= killcamoffset + 0.0001, "archivetime: " + self.archivetime + ", killcamoffset: " + killcamoffset );
	if ( self.archivetime < killcamoffset )
		println( "WARNING: Code trimmed killcam time by " + (killcamoffset - self.archivetime) + " seconds because it doesn't have enough game time recorded!" );
	
	camtime = self.archivetime - .05 - predelay;
	killcamlength = camtime + postdelay;
	self.killcamlength = killcamlength;

	if ( camtime <= 0 ) // if we're not looking back in time far enough to even see the death, cancel
	{
		println( "Cancelling killcam because we don't even have enough recorded to show the death." );
		
		self updateSessionState( "dead" );
		self ClearKillcamState();
		
		self notify ( "killcam_ended" );
		
		prof_end( "killcam2" );
		return;
	}
	
	// LUA: we need to show timer info in the lua killcam, this will tell the killcam to display and eveything else to hide
	self SetClientOmnvar( "ui_killcam_end_milliseconds", int( killcamlength * 1000 ) + GetTime() );
	// END LUA

	if ( level.showingFinalKillcam )
		thread doFinalKillCamFX( camtime, killcamentityindex );
	
	self.killcam = true;

	if ( isDefined( self.battleBuddy ) && !level.gameEnded )
	{
		self.battleBuddyRespawnTimeStamp = GetTime();
	}
	
	self thread spawnedKillcamCleanup();
	
	self.skippedKillCam = false;
	self.killcamStartedTimeDeciSeconds = getTimePassedDeciSecondsIncludingRounds();
	
	if ( !level.showingFinalKillcam )
		self thread waitSkipKillcamButton( timeUntilRespawn );
	else
		self notify ( "showing_final_killcam" );
	
	self thread endKillcamIfNothingToShow();
	
	prof_end( "killcam2" );
	self waittillKillcamOver();
	
	if ( level.showingFinalKillcam )
	{
		if( self == attacker )
			attacker maps\mp\gametypes\_missions::processChallenge( "ch_precision_moviestar" );
		
		self thread maps\mp\gametypes\_playerlogic::spawnEndOfGame();
		return;
	}
	
	self thread killcamCleanup( true );
}


doFinalKillCamFX( camTime, killcamentityindex )
{
	if ( isDefined( level.doingFinalKillcamFx ) )
		return;
	level.doingFinalKillcamFx = true;
	
	intoSlowMoTime = camTime;
	if ( intoSlowMoTime > 1.0 )
	{
		intoSlowMoTime = 1.0;
		wait( camTime - 1.0 );
	}
	
	setSlowMotion( 1.0, 0.25, intoSlowMoTime ); // start timescale, end timescale, lerp duration
	wait( intoSlowMoTime + .5 );
	setSlowMotion( 0.25, 1, 1.0 );
	
	level.doingFinalKillcamFx = undefined;
}

waittillKillcamOver()
{
	self endon("abort_killcam");
	
	wait(self.killcamlength - 0.05);
}

setKillCamEntity( killcamentityindex, killcamoffset, starttime )
{
	self endon("disconnect");
	self endon("killcam_ended");
	
	killcamtime = (gettime() - killcamoffset * 1000);
	
	if ( starttime > killcamtime )
	{
		wait .05;
		// code may have trimmed archivetime after the first frame if we couldn't go back in time as far as requested.
		killcamoffset = self.archivetime;
		killcamtime = (gettime() - killcamoffset * 1000);
		
		if ( starttime > killcamtime )
			wait (starttime - killcamtime) / 1000;
	}
	self.killcamentity = killcamentityindex;
}

waitSkipKillcamButton( timeUntilRespawn )
{
	self endon("disconnect");
	self endon("killcam_ended");
	
	/*
	self NotifyOnPlayerCommand( "kc_respawn", "+usereload" );
	self waittill("kc_respawn");
	*/
	
	while(self useButtonPressed())
		wait .05;

	while(!(self useButtonPressed()))
		wait .05;
	
	self.skippedKillCam = true;

	if( isDefined( self.pers["totalKillcamsSkipped"] ) )
		self.pers["totalKillcamsSkipped"]++;

	if ( timeUntilRespawn <= 0 )
		clearLowerMessage( "kc_info" );
	
	self notify("abort_killcam");
}

waitCopycatKillcamButton( attacker )
{
	self endon("disconnect");
	self endon("killcam_ended");
	
	assert( IsPlayer( attacker ) );
		
	self NotifyOnPlayerCommand( "KillCamCopyCat", "weapnext" );
	
	self waittill( "KillCamCopyCat" );
	
	// Turn off the message
	self SetClientOmnvar( "ui_killcam_copycat", false );
	
	self playSound( "copycat_steal_class" );
	maps\mp\gametypes\_class::setCopyCatLoadout( attacker );
}

endKillcamIfNothingToShow()
{
	self endon("disconnect");
	self endon("killcam_ended");
	
	while(1)
	{
		// code may trim our archivetime to zero if there is nothing "recorded" to show.
		// this can happen when the person we're watching in our killcam goes into killcam himself.
		// in this case, end the killcam.
		if ( self.archivetime <= 0 )
			break;
		wait .05;
	}
	
	self notify("abort_killcam");
}

spawnedKillcamCleanup()
{
	self endon("disconnect");
	self endon("killcam_ended");
	
	self waittill("spawned");
	self thread killcamCleanup( false );
}

endedKillcamCleanup()
{
	self endon("disconnect");
	self endon("killcam_ended");
	
	level waittill("game_ended");

	self thread killcamCleanup( true );
}

killcamCleanup( clearState )
{
	// LUA: we need to reset this dvar so the killcam will reset
	self SetClientOmnvar( "ui_killcam_end_milliseconds", 0 );
	// END LUA

	self setCinematicCameraStyle( "unknown", -1, -1 );
	self.killcam = undefined;

	if( isDefined( self.killcamStartedTimeDeciSeconds )  && isPlayer( self ) && maps\mp\_matchdata::canLogLife( self.lifeId ) ) 
	{
		currTime = getTimePassedDeciSecondsIncludingRounds();
		setMatchData( "lives", self.lifeId, "killcamWatchTimeDeciSeconds", clampToByte( currTime - self.killcamStartedTimeDeciSeconds ) );		
	}

	if ( !level.gameEnded )
		self clearLowerMessage( "kc_info" );
	
	self thread maps\mp\gametypes\_spectating::setSpectatePermissions();
	
	self notify("killcam_ended"); // do this last, in case this function was called from a thread ending on it

	if ( !clearState )
		return;
			
	self updateSessionState( "dead" );
	self ClearKillcamState();
}



cancelKillCamOnUse()
{
	self.cancelKillcam = false;
	self thread cancelKillCamOnUse_specificButton( ::cancelKillCamUseButton, ::cancelKillCamCallback );
	//self thread cancelKillCamOnUse_specificButton( ::cancelKillCamSafeSpawnButton, ::cancelKillCamSafeSpawnCallback );
}

cancelKillCamUseButton()
{
	return self useButtonPressed();
}
cancelKillCamSafeSpawnButton()
{
	return self fragButtonPressed();
}
cancelKillCamCallback()
{
	self.cancelKillcam = true;
}
cancelKillCamSafeSpawnCallback()
{
	self.cancelKillcam = true;
	self.wantSafeSpawn = true;
}

cancelKillCamOnUse_specificButton( pressingButtonFunc, finishedFunc )
{
	self endon ( "death_delay_finished" );
	self endon ( "disconnect" );
	level endon ( "game_ended" );
	
	for ( ;; )
	{
		if ( !self [[pressingButtonFunc]]() )
		{
			wait ( 0.05 );
			continue;
		}
		
		buttonTime = 0;
		while( self [[pressingButtonFunc]]() )
		{
			buttonTime += 0.05;
			wait ( 0.05 );
		}
		
		if ( buttonTime >= 0.5 )
			continue;
		
		buttonTime = 0;
		
		while ( !self [[pressingButtonFunc]]() && buttonTime < 0.5 )
		{
			buttonTime += 0.05;
			wait ( 0.05 );
		}
		
		if ( buttonTime >= 0.5 )
			continue;
			
		self [[finishedFunc]]();
		return;
	}	
}
