//=============================================================================
// KFLightPool
//=============================================================================
// A client pool to dynamical scale active lights spawned during gameplay based
// on scene relevance and priority.  Unlike other pools this doesn't have a list
// of blank components to re-use, because there is no light template system.
// Instead, expect the caller to handle component management.
//
// We can categorize lights based on lifetime in the world.  On one end you have
// very brief lights that work well with bDropDetail, but on the other we have
// environment lights that work better with GFX settings such as detail mode.
// 1. Flash / Burst (e.g. explosion, muzzle flash)
// 2. Lifespan (e.g. projectile)
// 3. Indefinite / No-Lifespan (e.g. character, weapon)
// 4. Persistent (e.g. environment)
//=============================================================================
// Killing Floor 2
// Copyright (C) 2016 Tripwire Interactive LLC
// - Andrew "Strago" Ladenberger
//=============================================================================

class KFLightPool extends Actor
	native(Effect);

struct native LightPoolInfo
{
	var PointLightComponent Component;
	var int Priority;
	var bool bAllowReenable;
};

enum LightPoolPriority
{
	LPP_Low,
	LPP_High,
	LPP_GameplayUsed
};

/** components currently active */
var private array<LightPoolInfo> ActiveComponents;
/** maximum allowed active components */
var() int MaxOnScreenLights;

/** Save values from last frame to use when new lights are registered */
var transient int CurrentOnScreenLights;
var transient int CurrentLowestPriority;

cpptext
{
	void TickSpecial(FLOAT DeltaTime);
	UBOOL ReenableLight(UPointLightComponent* LightComp, FVector& ViewLocation);
}

//
// Called immediately before gameplay begins.
//
event PreBeginPlay()
{
	switch( WorldInfo.GetDetailMode() )
	{
		case DM_Low:
			MaxOnScreenLights = 5;
			break;
		case DM_Medium:
			MaxOnScreenLights = 10;
			break;
		case DM_High:
			MaxOnScreenLights = 20;
			break;
	}

	Super.PreBeginPlay();
}

/**
 * Register a light with the pool so it can be managed relative to other lights
 */
function bool RegisterPointLight(PointLightComponent LightComp, optional LightPoolPriority PriorityType=LPP_Low)
{
	local LightPoolInfo NewLight;
	local int i;

	// Requires attachment before calling (for now), so that we can get info on the owner.  
	// Maybe later we want to pass the owner as a function param?
	if ( LightComp.Owner == None || !LightComp.bAttached )
	{
		`log(GetFuncName()@"failed because light has not been attached");
		LightComp.SetEnabled(false);
		return false;
	}

	if ( WorldInfo.NetMode == NM_DedicatedServer )
	{
		`log(GetFuncName()@"perf warning: dedicated server shouldn't be playing with lights on"@LightComp.Owner);
		LightComp.DetachFromAny();
		return false;
	}

	NewLight.Component = LightComp;
	NewLight.Priority = 100 * PriorityType;
	NewLight.bAllowReenable = LightComp.bEnabled && (Owner.LifeSpan == 0 || PriorityType == LPP_GameplayUsed);
	if ( LightComp.Owner.Instigator != None && LightComp.Owner.Instigator.IsLocallyControlled() )
	{
		NewLight.Priority += 50.f;
	}

	// If the list is full start the light disabled
	if ( CurrentOnScreenLights >= MaxOnScreenLights && NewLight.Priority < CurrentLowestPriority )
	{
		`log(GetFuncName()@"disabling incoming light for Owner:"$LightComp.Owner@"Priority:"$NewLight.Priority@"Total:"$ActiveComponents.Length,bDebug);

		LightComp.SetEnabled(false);
		// If the lifespan is too short to re-enable, don't bother tracking it
		if ( !NewLight.bAllowReenable )
		{
			LightComp.DetachFromAny();
			return false;
		}
	}

	// insert into sorted list
	for (i = 0; i < ActiveComponents.Length; i++)
	{
		if ( NewLight.Priority >= ActiveComponents[i].Priority)
		{
			break;
		}
	}

	`log(GetFuncName()@"adding light for Owner:"$LightComp.Owner@"Priority:"$NewLight.Priority@"Total:"$ActiveComponents.Length,bDebug);

	// Add to the end
	ActiveComponents.InsertItem(i, NewLight);
	SetTickIsDisabled(false);
	return TRUE;
}

defaultproperties
{
	//bDebug=true

	TickGroup=TG_DuringAsyncWork
	bTickIsDisabled=true
	MaxOnScreenLights=20
}
