global function ShBurnMeter_Init
global function BurnReward_GetById
global function BurnReward_GetByRef
global function BurnReward_GetRandom
global function GetSelectedBurnCardRef
global function GetBoostSkin
#if SERVER || CLIENT
global function BurnMeterEnabled
global function CanUseWeaponAsBurnCard
global function TryUsingBurnCardWeapon
global function TryUsingBurnCardWeaponInCriticalSection
global function BurnMeterPlayer_CanUseEquipped
global function OnWeaponAttemptOffhandSwitch_burncardweapon
global function BurnMeterPlayer_CanUseReward
global function BurnMeterPlayer_GetRewardOrGoal
global function BurnReward_GetTopInventoryItemBurnCard
global function OnWeaponPrimaryAttack_nuke_eject
global function BurnMeter_HarvesterShieldCanUse
#endif

global function GetAllBoostTurretTypes

global const BURN_METER_SMALL_POINT_VALUE = 1
global const BURN_METER_MID_POINT_VALUE = 2
global const BURN_METER_LARGE_POINT_VALUE = 5
global const BURN_METER_EXTRA_LARGE_POINT_VALUE = 10

global const BURN_METER_RADAR_JAMMER_PULSE_DURATION = 6.0
global const BURN_METER_RADAR_JAMMER_EASE_OFF_TIME = 1.0

global enum eBurnMeterRewardAvailableFor
{
	PILOT_ONLY,
	TITAN_ONLY,
	PILOT_AND_TITAN,
	SPECIAL_PLAYERS_ONLY,
}

global struct BurnReward
{
	int id = -1
	string ref = ""
	string localizedName = ""
	string description = ""
	asset image = $""
	float cost
	int userType = -1
	int skinIndex = 0
	string weaponName = ""
	string extraWeaponMod = ""
	string activationText = ""

	void functionref( entity ) ornull rewardAvailableCallback = null
}

global struct BurnStruct
{
	table< string, BurnReward > burnRewards
	array< BurnReward > allCards //Kinda inefficient to store the same burn cards 3 times (once in burnRewards, once in foil/normal, once in allcards. Prefer speed over memory? )
	array< BurnReward > allowedCards

}

global BurnStruct burn

global table< string, bool functionref( entity ) >  burnMeterCanUseFuncTable

struct
{

	bool shouldCycleOnRelease = false

} file

array<string> turretBurnCards = [
	"burnmeter_ap_turret_weapon",
	"burnmeter_at_turret_weapon",
	"burnmeter_ap_turret_weapon_infinite",
	"burnmeter_at_turret_weapon_infinite",
]

void function ShBurnMeter_Init()
{

	#if SERVER || CLIENT
		//burnMeterCanUseFuncTable[ "burnmeter_random_foil" ] <- BurnMeter_RandomFoilCanUse
		burnMeterCanUseFuncTable[ "burnmeter_emergency_titan" ] <- BurnMeter_EmergencyTitanCanUse
		burnMeterCanUseFuncTable[ "burnmeter_nuke_titan" ] <- BurnMeter_NukeTitanCanUse
		// burnMeterCanUseFuncTable[ "burnmeter_harvester_shield" ] <- BurnMeter_HarvesterShieldCanUse
	#endif



	var dataTable = GetDataTable( $"datatable/burn_meter_rewards.rpak" )

	for ( int row = 0; row < GetDatatableRowCount( dataTable ); row++ )
	{
		BurnReward burnReward
		burnReward.id = row
		burnReward.ref = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_REF_COLUMN_NAME ) )
		burnReward.localizedName = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_NAME_COLUMN_NAME ) )
		burnReward.description = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_DESCRIPTION_COLUMN_NAME ) )
		burnReward.image = GetDataTableAsset( dataTable, row, GetDataTableColumnByName( dataTable, BURN_IMAGE_COLUMN_NAME ) )
		burnReward.cost = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, BURN_COST_COLUMN_NAME ) )
		string userType = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_AVAILABLE_COLUMN_NAME ) )
		burnReward.userType = eBurnMeterRewardAvailableFor[userType]
		burnReward.weaponName = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_WEAPON_COLUMN_NAME ) )
		burnReward.extraWeaponMod = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_EXTRA_WEAPON_MOD_NAME ) )
		burnReward.activationText = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "activationText" ) )
		burnReward.skinIndex = GetDataTableInt( dataTable, row, GetDataTableColumnByName( dataTable, "skinIndex" ) )

		if ( IsDisabledRef( burnReward.ref ) )
			continue

#if SERVER || CLIENT
		if ( burnReward.weaponName != "" )
			PrecacheWeapon( burnReward.weaponName )
		#endif

		burn.burnRewards[burnReward.ref] <- burnReward

		bool selectable 		= GetDataTableBool( dataTable, row, GetDataTableColumnByName( dataTable, "selectable" ) )
		burn.allCards.append( burnReward )
		if ( selectable )
			burn.allowedCards.append( burnReward )
	}

	#if CLIENT
		//Registering here instead of in CreateWeaponData() in _items.nut to remove dependence on HUD script with items. If more weapons other than smart pistol run into this problem, just add a column in burn_meter_rewards to specify we need to do this. ( ticks are fine because they have a damagedef associated with them )
		RegisterWeaponDamageSourceName( "mp_weapon_smart_pistol", expect string( GetWeaponInfoFileKeyField_GlobalNotNull( "mp_weapon_smart_pistol", "shortprintname" ) ) )

		RegisterConCommandTriggeredCallback( "-offhand4", CycleOnRelease )
	#endif
}

#if SERVER || CLIENT
bool function BurnMeterEnabled()
{
	return Riff_BoostAvailability() != eBoostAvailability.Disabled
}
#endif

BurnReward function BurnReward_GetById( int id )
{
	return burn.allCards[ id ]
}


BurnReward function BurnReward_GetByRef( string ref )
{
	Assert( ref in burn.burnRewards )
	return burn.burnRewards[ref]
}

int function GetBoostSkin( string ref )
{
	BurnReward reward = BurnReward_GetByRef( ref )
	//printt( "ref: " + ref + ", Boost skin: " + reward.skinIndex )
	return reward.skinIndex
}


BurnReward function BurnReward_GetRandom()
{
	return burn.allowedCards.getrandom()
}

string function GetSelectedBurnCardRef( entity player )
{
	int burnCardID = expect int ( player.GetPersistentVar( "burnmeterSlot" ) )
	BurnReward burnCard = burn.allCards[ burnCardID ]
	string ref = burnCard.ref

	#if SERVER
	if ( GetItemDisplayData( ref ).hidden )
		ClientCommand( player, "disconnect" )
	#endif

	#if SERVER || CLIENT

	if ( Riff_TitanAvailability() == eTitanAvailability.Never )
	{
		//JFS: Batteries and Titan turrets are useless in PvP, turn them into amped weapons instead. Not an elegant solution at all. Fix next game. (Make boosts editable mid-match? )
		if ( ref == "burnmeter_emergency_battery" || ref == "burnmeter_at_turret_weapon"  )
			ref = "burnmeter_amped_weapons"
	}

	if ( GetCurrentPlaylistVarInt( "featured_mode_all_ticks", 0 ) >= 1 )
	{
		if ( ref == "burnmeter_ticks" || ref == "burnmeter_random_foil"  )
			ref = "burnmeter_amped_weapons"
	}
	#endif

	return ref
}


#if SERVER || CLIENT
bool function CanUseWeaponAsBurnCard( entity weapon, entity ownerPlayer )
{
	Assert( weapon.HasMod( "burn_card_weapon_mod" ) )

	if ( !BurnMeterPlayer_CanUseEquipped( ownerPlayer ) )
	{
		weapon.EmitWeaponSound( "CoOp_SentryGun_DeploymentDeniedBeep" )
		return false
	}

	return true
}

bool function TryUsingBurnCardWeapon( entity weapon, entity ownerPlayer )
{
	Assert( weapon.HasMod( "burn_card_weapon_mod" ) )

	if ( !CanUseWeaponAsBurnCard( weapon, ownerPlayer ) )
		return false

	#if SERVER
		UseBurnCardWeapon( weapon, ownerPlayer )
	#endif

	return true
}

bool function TryUsingBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer )
{
	Assert( weapon.HasMod( "burn_card_weapon_mod" ) )

	if ( !CanUseWeaponAsBurnCard( weapon, ownerPlayer ) )
		return false

	#if SERVER
		UseBurnCardWeaponInCriticalSection( weapon, ownerPlayer )
	#endif

	return true
}



bool function BurnMeterPlayer_CanUseReward( entity player, BurnReward burnReward )
{
	if ( !BurnMeterPlayer_CanUseGlobal( player ) )
		return false

	switch( burnReward.userType )
	{
		case eBurnMeterRewardAvailableFor.PILOT_ONLY:
			return !player.IsTitan()

		case eBurnMeterRewardAvailableFor.TITAN_ONLY:
			return player.IsTitan()

		case eBurnMeterRewardAvailableFor.PILOT_AND_TITAN:
			return true

		case eBurnMeterRewardAvailableFor.SPECIAL_PLAYERS_ONLY:
			return BurnMeterPlayer_CanUseSpecial( player, burnReward ) //TODO: Note that for the case of reaperfall we also send the message to the client if they can't summon reaper in the validation function. Not ideal...
	}

	unreachable

}


bool function BurnMeterPlayer_CanUseEquipped( entity player )
{
	BurnReward burnReward = BurnReward_GetTopInventoryItemBurnCard( player )
	return BurnMeterPlayer_CanUseReward( player, burnReward )
}


EarnObject function BurnMeterPlayer_GetRewardOrGoal( entity player )
{
	if ( PlayerEarnMeter_IsRewardEnabled( player ) ) // TODO: more robust
		return PlayerEarnMeter_GetReward( player )
	else
		return PlayerEarnMeter_GetGoal( player )

	unreachable
}


bool function BurnMeterPlayer_CanUseGlobal( entity player )
{
	if ( player.IsBot() )
		return false

	if ( !IsAlive( player ) )
		return false

	if ( player.IsPhaseShifted() )
		return false

	if ( player.ContextAction_IsInVehicle() ) //no rewards when in dropship
		return false

	if ( !IsBurnMeterRewardAvailableForGameState() )
		return false

	return true
}


bool function IsBurnMeterRewardAvailableForGameState() //Based off IsReplacementTitanAvailable
{
	int currentGameState = GetGameState()

	switch ( currentGameState ) //need to add a new entry in here for every new game state we make
	{
		case eGameState.WaitingForCustomStart:
		case eGameState.WaitingForPlayers:
		case eGameState.PickLoadout:
		case eGameState.Prematch:
		case eGameState.SwitchingSides:
		case eGameState.Postmatch:
			return false

		case eGameState.Playing:
		case eGameState.SuddenDeath:
			return true

		case eGameState.WinnerDetermined:
		case eGameState.Epilogue:
		{
			if ( IsRoundBased() )
			{
				// TODO: make this work on the client
				#if SERVER
				if ( !IsRoundBasedGameOver() )
					return false

				if ( !ShouldRunEvac() )
					return false
				#endif
			}

			return true
		}

		default:
			Assert( false, "Unknown Game State: " + currentGameState )
			return false
	}

	unreachable
}


bool function OnWeaponAttemptOffhandSwitch_burncardweapon( entity weapon )
{
	entity ownerPlayer = weapon.GetWeaponOwner()
	Assert( ownerPlayer.IsPlayer() )

	if ( ownerPlayer.IsUsingOffhandWeapon() )
		return false

	if ( weapon.HasMod( "burn_card_weapon_mod" ) )
	{
		bool canUse = BurnMeterPlayer_CanUseEquipped( ownerPlayer )

		return canUse
	}
	else
		return true

	unreachable
}

BurnReward function BurnReward_GetTopInventoryItemBurnCard( entity player )
{
	int burnRewardID = player.GetPlayerNetInt( TOP_INVENTORY_ITEM_BURN_CARD_ID )
	return BurnReward_GetById( burnRewardID )
}

bool function BurnMeterPlayer_CanUseSpecial( entity player, BurnReward burnReward )
{
	Assert( burnReward.ref in burnMeterCanUseFuncTable )
	return burnMeterCanUseFuncTable[ burnReward.ref ]( player )
}

bool function BurnMeter_HarvesterShieldCanUse( entity player )
{
//	entity harvester = GetGlobalNetEnt( "FD_activeHarvester" )
	bool canUse = GetGlobalNetTime( "FD_harvesterInvulTime" ) < Time()
	// harvester.GetShieldHealth() < harvester.GetShieldHealthMax()

	if ( !canUse )
	{
		#if CLIENT
		AddPlayerHint( 3.0, 0.5, $"", "#HINT_HARVESTER_BOOST_CANT_USE" )
		#endif
	}

	return canUse
}

bool function BurnMeter_NukeTitanCanUse( entity player )
{
	bool canUse = BurnMeter_EmergencyTitanCanUse( player )

	if ( !canUse )
	{
		#if CLIENT
		AddPlayerHint( 5.0, 0.5, $"", "#HINT_NUKE_TITAN_CANT_USE" )
		#endif
	}

	return canUse
}

bool function BurnMeter_EmergencyTitanCanUse( entity player )
{
	if ( IsAlive( player.GetPetTitan() ) )
		return false

	#if SERVER
		if ( !IsReplacementTitanAvailableForGameState() )
			return false

		if ( player.isSpawning )
			return false

		return true
	#endif

	#if CLIENT
		return true
	#endif
}

bool function BurnMeter_SummonReaperCanUse( entity player )
{
	array< entity > allReapers = GetNPCArrayByClass( "npc_super_spectre" )

	int playerTeam = player.GetTeam()

	foreach( reaper in allReapers )
	{
		if ( reaper.GetTeam() == playerTeam )
		{
			#if SERVER
				MessageToPlayer( player, eEventNotifications.BurnMeter_CantSummonReaper )
			#endif

			return false
		}
	}
	return true
}

var function OnWeaponPrimaryAttack_nuke_eject( entity weapon, WeaponPrimaryAttackParams attackParams )
{
	#if CLIENT
		entity weaponOwner = weapon.GetWeaponOwner()
		if ( weaponOwner.IsPlayer() && IsValid( weaponOwner.GetCockpit() ) )
		{
			weaponOwner.ClientCommand( "TitanEject " + 3 )
			PlayerEjects( weaponOwner, weaponOwner.GetCockpit() ) //Note that this can be run multiple times in a frame, e.g. get damaged by 4 pellets of a shotgun that brings the Titan into a doomed state with auto eject. Not ideal
		}
	#endif
	return 0
}

#endif

#if CLIENT
void function CycleOnRelease( entity player )
{
	if ( file.shouldCycleOnRelease )
	{
		CycleInventory( player )
		file.shouldCycleOnRelease = false
	}
}
#endif

array<string> function GetAllBoostTurretTypes()
{
	return turretBurnCards
}