// 
//---------------------------------------------------------------------------
//
// Copyright(C) 2004-2016 Christoph Oelckers
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
/*
** a_dynlight.cpp
** Implements actors representing dynamic lights (hardware independent)
**
**
** all functions marked with [TS] are licensed under
**---------------------------------------------------------------------------
** Copyright 2003 Timothy Stump
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

#include "c_dispatch.h"
#include "thingdef.h"
#include "r_utility.h"
#include "doomstat.h"
#include "serializer.h"
#include "g_levellocals.h"
#include "a_dynlight.h"
#include "actorinlines.h"
#include "memarena.h"

EXTERN_CVAR(Bool, lm_dynlights);

static FMemArena DynLightArena(sizeof(FDynamicLight) * 200);
static TArray<FDynamicLight*> FreeList;
static FCRandom randLight;

extern TArray<FLightDefaults *> StateLights;


static void MarkTilesForUpdate(FLevelLocals * Level, FLightNode * touching_sides, FLightNode * touching_sector)
{
	if(Level->levelMesh)
	{
		while(touching_sides)
		{
			LevelMeshUpdater->SideLightListChanged(touching_sides->targLine);
			touching_sides = touching_sides->nextTarget;
		}
	
		while(touching_sector)
		{
			LevelMeshUpdater->SectorLightListChanged(touching_sector->targSection->sector);
			touching_sector = touching_sector->nextTarget;
		}
	}
}

//==========================================================================
//
//
//
//==========================================================================

static FDynamicLight *GetLight(FLevelLocals *Level)
{
	FDynamicLight *ret;
	if (FreeList.Size())
	{
		FreeList.Pop(ret);
	}
	else ret = (FDynamicLight*)DynLightArena.Alloc(sizeof(FDynamicLight));
	memset(ret, 0, sizeof(*ret));
	ret = new(ret)FDynamicLight();
	ret->m_cycler.m_increment = true;
	ret->next = Level->lights;
	Level->lights = ret;
	if (ret->next) ret->next->prev = ret;
	ret->visibletoplayer = true;
	ret->mShadowmapIndex = 1024;
	ret->Level = Level;
	ret->Pos.X = -10000000;	// not a valid coordinate.

	return ret;
}


float LightCalcStrength(float radius)
{
	radius *= 2; // radius in shaders is actualy diameter, so multiply it here to match
	return std::min(1500.0f, (radius * radius) / 10);
}

//==========================================================================
//
// Attaches a dynamic light descriptor to a dynamic light actor.
// Owned lights do not use this function.
//
//==========================================================================

void AttachLight(AActor *self)
{
	auto light = GetLight(self->Level);

	light->pSpotInnerAngle = &self->AngleVar(NAME_SpotInnerAngle);
	light->pSpotOuterAngle = &self->AngleVar(NAME_SpotOuterAngle);
	light->lightDefIntensity = 1.0;
	light->pPitch = &self->Angles.Pitch;
	light->pLightFlags = (LightFlags*)&self->IntVar(NAME_lightflags);
	light->shadowMinQuality = std::clamp(self->IntVar(NAME_shadowMinQuality), 0, 4);
	light->pArgs = self->args;
	light->pSoftShadowRadius = &self->FloatVar(NAME_SoftShadowRadius);
	light->pLinearity = &self->FloatVar(NAME_LightLinearity);

	light->lightStrength = LightCalcStrength(light->pArgs[LIGHT_INTENSITY]);
	

	light->specialf1 = DAngle::fromDeg(double(self->SpawnAngle)).Normalized360().Degrees();
	light->Sector = self->Sector;
	light->target = self;
	light->mShadowmapIndex = 1024;
	light->m_active = false;
	light->wasactive = false;
	light->visibletoplayer = true;
	light->lighttype = (uint8_t)self->IntVar(NAME_lighttype);
	self->AttachedLights.Push(light);

	// Disable postponed processing of dynamic light because its setup has been completed by this function
	self->flags8 &= ~MF8_RECREATELIGHTS;
}

DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, AttachLight, AttachLight)
{
	PARAM_SELF_PROLOGUE(AActor);
	AttachLight(self);
	return 0;
}

//==========================================================================
//
//
//
//==========================================================================

void ActivateLight(AActor *self)
{
	for (auto l : self->AttachedLights) l->Activate();
}

DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, ActivateLight, ActivateLight)
{
	PARAM_SELF_PROLOGUE(AActor);
	ActivateLight(self);
	return 0;
}


//==========================================================================
//
//
//
//==========================================================================

void DeactivateLight(AActor *self)
{
	for (auto l : self->AttachedLights) l->Deactivate();
}

DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, DeactivateLight, DeactivateLight)
{
	PARAM_SELF_PROLOGUE(AActor);
	DeactivateLight(self);
	return 0;
}

//==========================================================================
//
//
//
//==========================================================================

static void SetOffset(AActor *self, double x, double y, double z)
{
	for (auto l : self->AttachedLights)
	{
		l->SetOffset(DVector3(x, y, z));
	}
}

DEFINE_ACTION_FUNCTION_NATIVE(ADynamicLight, SetOffset, SetOffset)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_FLOAT(x);
	PARAM_FLOAT(y);
	PARAM_FLOAT(z);
	SetOffset(self, x, y, z);
	return 0;
}

//==========================================================================
//
//
//
//==========================================================================

void FDynamicLight::ReleaseLight()
{
	assert(prev != nullptr || this == Level->lights);
	if (prev != nullptr) prev->next = next;
	else Level->lights = next;
	if (next != nullptr) next->prev = prev;
	next = prev = nullptr;
	this->~FDynamicLight();
	FreeList.Push(this);
}

//==========================================================================
//
// [TS]
//
//==========================================================================
void FDynamicLight::Activate()
{
	m_active = true;
	m_currentRadius = float(GetIntensity());
	m_tickCount = 0;

	if (lighttype == PulseLight)
	{
		float pulseTime = float(specialf1 / TICRATE);

		m_lastUpdate = Level->maptime;
		if (!swapped) m_cycler.SetParams(float(GetSecondaryIntensity()), float(GetIntensity()), pulseTime);
		else m_cycler.SetParams(float(GetIntensity()), float(GetSecondaryIntensity()), pulseTime);
		m_cycler.ShouldCycle(true);
		m_cycler.SetCycleType(CYCLE_Sin);
		m_currentRadius = float(m_cycler.GetVal());
	}
	if (m_currentRadius <= 0) m_currentRadius = 1;

	lightStrength = LightCalcStrength(m_currentRadius);
}



//==========================================================================
//
// [TS]
//
//==========================================================================
void FDynamicLight::Tick()
{
	if (!target)
	{
		// How did we get here? :?
		UnlinkLight();
		ReleaseLight();
		return;
	}

	int oldradius = m_currentRadius;

	if (owned)
	{
		if (!target->state || !target->ShouldRenderLocally() || (target->flags & MF_UNMORPHED))
		{
			m_active = false;
		}
		else
		{
			visibletoplayer = target->IsVisibleToPlayer();	// cache this value for the renderer to speed up calculations.
		}
	}

	bool markTiles = ((Trace() || lm_dynlights) && Level->levelMesh);

	// Don't bother if the light won't be shown
	if (!IsActive())
	{
		if(markTiles && m_active != wasactive)
		{
			MarkTilesForUpdate(Level, touching_sides, touching_sector);
		}
		wasactive = m_active;
		return;
	}
	
	// I am doing this with a type field so that I can dynamically alter the type of light
	// without having to create or maintain multiple objects.
	switch(lighttype)
	{
	case PulseLight:
	{
		float diff = (Level->maptime - m_lastUpdate) / (float)TICRATE;
		
		m_lastUpdate = Level->maptime;
		m_cycler.Update(diff);
		m_currentRadius = float(m_cycler.GetVal());
		break;
	}

	case FlickerLight:
	{
		int rnd = randLight(360);
		m_currentRadius = float((rnd < int(specialf1))? GetIntensity() : GetSecondaryIntensity());
		break;
	}

	case RandomFlickerLight:
	{
		int flickerRange = GetSecondaryIntensity() - GetIntensity();
		float amt = randLight() / 255.f;
		
		if (m_tickCount > specialf1)
		{
			m_tickCount = 0;
		}
		if (m_tickCount++ == 0 || m_currentRadius > GetSecondaryIntensity())
		{
			m_currentRadius = float(GetIntensity() + (amt * flickerRange));
		}
		break;
	}

#if 0
	// These need some more work elsewhere
	case ColorFlickerLight:
	{
		int rnd = randLight();
		float pct = specialf1/360.f;
		
		m_currentRadius = m_Radius[rnd >= pct * 255];
		break;
	}

	case RandomColorFlickerLight:
	{
		int flickerRange = GetSecondaryIntensity() - GetIntensity();
		float amt = randLight() / 255.f;
		
		m_tickCount++;
		
		if (m_tickCount > specialf1)
		{
			m_currentRadius = GetIntensity() + (amt * flickerRange);
			m_tickCount = 0;
		}
		break;
	}
#endif

	case SectorLight:
	{
		float intensity;
		float scale = GetIntensity() / 8.f;
		
		if (scale == 0.f) scale = 1.f;
		
		intensity = Sector? Sector->lightlevel * scale : 0;
		intensity = clamp<float>(intensity, 0.f, 255.f);

		m_currentRadius = intensity;

		break;
	}

	case PointLight:
		m_currentRadius = float(GetIntensity());
		break;
	}
	if (m_currentRadius <= 0) m_currentRadius = 1;


	if(oldradius != m_currentRadius)
	{
		lightStrength = LightCalcStrength(m_currentRadius);
	}

	updated = UpdateLocation();

	if(!updated && (m_active != wasactive || oldred != GetRed() || oldblue != GetBlue() || oldgreen != GetGreen() || oldradius != m_currentRadius))
	{
		if(markTiles)
		{
			MarkTilesForUpdate(Level, touching_sides, touching_sector);
		}

		updated = true;
	}

	if(TraceActors())
	{
		if(updated)
		{
			ActorList.Clear();
			ActorResult.Clear();
		}
		else if(ActorList.Size() > 0)
		{
			unsigned i = ActorList.Size();
			while(i > 0)
			{
				const unsigned index = i - 1u;
				if(ActorList[index]->ObjectFlags & OF_EuthanizeMe)
				{
					ActorList.Delete(index);
					ActorResult.Delete(index);
				}
				i--;
			}
		}
	}

	wasactive = m_active;
	oldred = GetRed();
	oldblue = GetBlue();
	oldgreen = GetGreen();
}




//==========================================================================
//
//
//
//==========================================================================
bool FDynamicLight::UpdateLocation()
{
	double oldx= X();
	double oldy= Y();
	float oldradius = radius;

	if (IsActive())
	{
		AActor *target = this->target;	// perform the read barrier only once.

		// Offset is calculated in relation to the owning actor.
		DAngle angle = target->Angles.Yaw;
		double s = angle.Sin();
		double c = angle.Cos();

		Pos = target->Vec3Offset(m_off.X * c + m_off.Y * s, m_off.X * s - m_off.Y * c, m_off.Z + target->GetBobOffset());
		Sector = target->subsector->sector;	// Get the render sector. target->Sector is the sector according to play logic.

		if (!(target->flags5 & MF5_NOINTERACTION))
		{
			// Some z-coordinate fudging to prevent the light from getting too close to the floor or ceiling planes. With proper attenuation this would render them invisible.
			// A distance of 5 is needed so that the light's effect doesn't become too small.
			// [SP] don't do this if +NOINTERACTION is set, since the object can fly right through floors and ceilings with that flag
			if (Z() < target->floorz + 5.) Pos.Z = target->floorz + 5.;
			else if (Z() > target->ceilingz - 5.) Pos.Z = target->ceilingz - 5.;
		}

		// The radius being used here is always the maximum possible with the
		// current settings. This avoids constant relinking of flickering lights

		float intensity;

		if (lighttype == FlickerLight || lighttype == RandomFlickerLight || lighttype == PulseLight)
		{
			intensity = float(max(GetIntensity(), GetSecondaryIntensity()));
		}
		else
		{
			intensity = m_currentRadius;
		}
		radius = intensity * 2.0f;
		if (radius < m_currentRadius * 2) radius = m_currentRadius * 2;

		if (X() != oldx || Y() != oldy || radius != oldradius)
		{
			//Update the light lists
			LinkLight();
			return true;
		}
	}

	return false;
}

//=============================================================================
//
// Attempts to emplace the light node in the TMap
//
//=============================================================================

void FDynamicLight::AddLightNode(FSection *section, side_t *sidedef)
{
	auto updateFlatTList = [&](FSection *sec)
	{
		touchlists.flat_tlist.TryEmplace(sec, sec);
	};
	auto updateWallTList = [&](side_t *sidedef)
	{
		touchlists.wall_tlist.TryEmplace(sidedef, sidedef);
	};

	if (section)
	{
		auto flatLightList = Level->lightlists.flat_dlist.CheckKey(section);
		if (flatLightList)
		{
			if (!flatLightList->CheckKey(this))
			{
				FLightNode * node = new FLightNode;
				node->lightsource = this;
				node->targ = section;

				flatLightList->TryEmplace(this, node);
				updateFlatTList(section);
			}
		}
		else
		{
			FLightNode * node = new FLightNode;
			node->lightsource = this;
			node->targ = section;

			TMap<FDynamicLight *, std::unique_ptr<FLightNode>> u;
			u.TryEmplace(this, node);
			Level->lightlists.flat_dlist.TryEmplace(section, std::move(u));
			updateFlatTList(section);
		}
	}
	else if (sidedef)
	{
		auto wallLightList = Level->lightlists.wall_dlist.CheckKey(sidedef);
		if (wallLightList)
		{
			if (!wallLightList->CheckKey(this))
			{
				FLightNode * node = new FLightNode;
				node->lightsource = this;
				node->targ = sidedef;

				wallLightList->TryEmplace(this, node);
				updateWallTList(sidedef);
			}
		}
		else
		{
			FLightNode * node = new FLightNode;
			node->lightsource = this;
			node->targ = sidedef;

			TMap<FDynamicLight *, std::unique_ptr<FLightNode>> u;
			u.TryEmplace(this, node);
			Level->lightlists.wall_dlist.TryEmplace(sidedef, std::move(u));
			updateWallTList(sidedef);
		}
	}
}




//==========================================================================
//
// Gets the light's distance to a line
//
//==========================================================================

double FDynamicLight::DistToSeg(const DVector3 &pos, vertex_t *start, vertex_t *end)
{
	double u, px, py;

	double seg_dx = end->fX() - start->fX();
	double seg_dy = end->fY() - start->fY();
	double seg_length_sq = seg_dx * seg_dx + seg_dy * seg_dy;

	u = (((pos.X - start->fX()) * seg_dx) + (pos.Y - start->fY()) * seg_dy) / seg_length_sq;
	if (u < 0.) u = 0.; // clamp the test point to the line segment
	else if (u > 1.) u = 1.;

	px = start->fX() + (u * seg_dx);
	py = start->fY() + (u * seg_dy);

	px -= pos.X;
	py -= pos.Y;

	return (px*px) + (py*py);
}


//==========================================================================
//
// Collect all touched sidedefs and subsectors
// to sidedefs and sector parts.
//
//==========================================================================
struct LightLinkEntry
{
	FSection *sect;
	DVector3 pos;
};
static TArray<LightLinkEntry> collected_ss;

void FDynamicLight::CollectWithinRadius(const DVector3 &opos, FSection *section, float radius)
{
	if (!section) return;
	collected_ss.Clear();
	collected_ss.Push({ section, opos });
	section->validcount = dl_validcount;

	bool markTiles = ((Trace() || lm_dynlights) && Level->levelMesh);

	bool hitonesidedback = false;
	for (unsigned i = 0; i < collected_ss.Size(); i++)
	{
		auto pos = collected_ss[i].pos;
		section = collected_ss[i].sect;

		if (markTiles)
			LevelMeshUpdater->SectorLightListChanged(section->sector);

		AddLightNode(section, NULL);

		auto processSide = [&](side_t *sidedef, const vertex_t *v1, const vertex_t *v2)
		{
			auto linedef = sidedef->linedef;
			if (linedef && linedef->validcount != ::validcount)
			{
				// light is in front of the seg
				if ((pos.Y - v1->fY()) * (v2->fX() - v1->fX()) + (v1->fX() - pos.X) * (v2->fY() - v1->fY()) <= 0)
				{
					linedef->validcount = ::validcount;

					if (markTiles)
						LevelMeshUpdater->SideLightListChanged(sidedef);

					AddLightNode(NULL, sidedef);
				}
				else if (linedef->sidedef[0] == sidedef && linedef->sidedef[1] == nullptr)
				{
					hitonesidedback = true;
				}
			}
			if (linedef)
			{
				FLinePortal *port = linedef->getPortal();
				if (port && port->mType == PORTT_LINKED)
				{
					line_t *other = port->mDestination;
					if (other->validcount != ::validcount)
					{
						subsector_t *othersub = Level->PointInRenderSubsector(other->v1->fPos() + other->Delta() / 2);
						FSection *othersect = othersub->section;
						if (othersect->validcount != ::validcount)
						{
							othersect->validcount = ::validcount;
							collected_ss.Push({ othersect, PosRelative(other->frontsector->PortalGroup) });
						}
					}
				}
			}
		};

		for (auto &segment : section->segments)
		{
			// check distance from x/y to seg and if within radius add this seg and, if present the opposing subsector (lather/rinse/repeat)
			// If out of range we do not need to bother with this seg.
			if (DistToSeg(pos, segment.start, segment.end) <= radius)
			{
				auto sidedef = segment.sidedef;
				if (sidedef)
				{
					processSide(sidedef, segment.start, segment.end);
				}

				auto partner = segment.partner;
				if (partner)
				{
					FSection *sect = partner->section;
					if (sect != nullptr && sect->validcount != dl_validcount)
					{
						sect->validcount = dl_validcount;
						collected_ss.Push({ sect, pos });
					}
				}
			}
		}
		for (auto side : section->sides)
		{
			auto v1 = side->V1(), v2 = side->V2();
			if (DistToSeg(pos, v1, v2) <= radius)
			{
				processSide(side, v1, v2);
			}
		}
		sector_t *sec = section->sector;
		if (!sec->PortalBlocksSight(sector_t::ceiling))
		{
			line_t *other = section->segments[0].sidedef->linedef;
			if (sec->GetPortalPlaneZ(sector_t::ceiling) < Z() + radius)
			{
				DVector2 refpos = other->v1->fPos() + other->Delta() / 2 + sec->GetPortalDisplacement(sector_t::ceiling);
				subsector_t *othersub = Level->PointInRenderSubsector(refpos);
				FSection *othersect = othersub->section;
				if (othersect->validcount != dl_validcount)
				{
					othersect->validcount = dl_validcount;
					collected_ss.Push({ othersect, PosRelative(othersub->sector->PortalGroup) });
				}
			}
		}
		if (!sec->PortalBlocksSight(sector_t::floor))
		{
			line_t *other = section->segments[0].sidedef->linedef;
			if (sec->GetPortalPlaneZ(sector_t::floor) > Z() - radius)
			{
				DVector2 refpos = other->v1->fPos() + other->Delta() / 2 + sec->GetPortalDisplacement(sector_t::floor);
				subsector_t *othersub = Level->PointInRenderSubsector(refpos);
				FSection *othersect = othersub->section;
				if (othersect->validcount != dl_validcount)
				{
					othersect->validcount = dl_validcount;
					collected_ss.Push({ othersect, PosRelative(othersub->sector->PortalGroup) });
				}
			}
		}
	}
	shadowmapped = (hitonesidedback || gl_light_shadows > 1) && !DontShadowmap() && shadowMinQuality <= gl_light_shadow_max_quality;
}

//==========================================================================
//
// Link the light into the world
//
//==========================================================================

void FDynamicLight::LinkLight()
{
	UnlinkLight();

	if (radius>0)
	{
		// passing in radius*radius allows us to do a distance check without any calls to sqrt
		FSection *sect = Level->PointInRenderSubsector(Pos)->section;

		dl_validcount++;
		::validcount++;
		CollectWithinRadius(Pos, sect, float(radius*radius));
	}
}


//==========================================================================
//
// Deletes the link lists
//
//==========================================================================
void FDynamicLight::UnlinkLight ()
{
	TMap<side_t *, side_t *>::Iterator wit(touchlists.wall_tlist);
	TMap<side_t *, side_t *>::Pair *wpair;
	while (wit.NextPair(wpair))
	{
		auto sidedef = wpair->Value;
		if (!sidedef) continue;
		
		auto wallLightList = Level->lightlists.wall_dlist.CheckKey(sidedef);
		if (wallLightList)
		{
			LevelMeshUpdater->SideLightListChanged(sidedef);
			wallLightList->Remove(this);
		}
	}

	TMap<FSection *, FSection *>::Iterator fit(touchlists.flat_tlist);
	TMap<FSection *, FSection *>::Pair *fpair;
	while (fit.NextPair(fpair))
	{
		auto sec = fpair->Value;
		if (!sec) continue;
		
		auto flatLightList = Level->lightlists.flat_dlist.CheckKey(sec);
		if (flatLightList)
		{
			LevelMeshUpdater->SectorLightListChanged(sec->sector);
			flatLightList->Remove(this);
		}
	}

	touchlists.flat_tlist.Clear();
	touchlists.wall_tlist.Clear();

	shadowmapped = false;
}

//==========================================================================
//
//
//
//==========================================================================

void AActor::AttachLight(unsigned int count, const FLightDefaults *lightdef)
{
	FDynamicLight *light;

	if (count < AttachedLights.Size()) 
	{
		light = AttachedLights[count];
		assert(light != nullptr);
	}
	else
	{
		light = GetLight(Level);
		light->SetActor(this, true);
		AttachedLights.Push(light);
	}
	lightdef->ApplyProperties(light);
	light->UpdateLocation();
}

//==========================================================================
//
// per-state light adjustment
//
//==========================================================================

void AActor::SetDynamicLights()
{
	TArray<FInternalLightAssociation *> & LightAssociations = GetInfo()->LightAssociations;
	unsigned int count = 0;

	if (state == nullptr) return;

	for (const auto def : UserLights)
	{
		AttachLight(count++, def);
	}

	for (const auto asso : LightAssociations)
	{
		if (asso->Sprite() == sprite && (asso->Frame() == frame || asso->Frame() == -1))
		{
			AttachLight(count++, asso->Light());
		}
	}
	if (count == 0 && state->Light > 0)
	{
		for(int i= state->Light; StateLights[i] != nullptr; i++)
		{
			if (StateLights[i] != (FLightDefaults*)-1)
			{
				AttachLight(count++, StateLights[i]);
			}
		}
	}

	for(;count<AttachedLights.Size();count++)
	{
		AttachedLights[count]->Deactivate();
	}
}

//==========================================================================
//
//
//
//==========================================================================

void AActor::DeleteAttachedLights()
{
	for (auto l : AttachedLights)
	{
		l->UnlinkLight();
		l->ReleaseLight();
	}
	AttachedLights.Clear();
}

//==========================================================================
//
//
//
//==========================================================================
extern TDeletingArray<FLightDefaults *> LightDefaults;

unsigned FindUserLight(AActor *self, FName id, bool create = false)
{
	for (unsigned i = 0; i < self->UserLights.Size(); i++ )
	{
		if (self->UserLights[i]->GetName() == id) return i;
	}
	if (create)
	{
		auto li = new FLightDefaults(id);
		return self->UserLights.Push(li);
	}
	return ~0u;
}

//==========================================================================
//
//
//
//==========================================================================

int AttachLightDef(AActor *self, int _lightid, int _lightname)
{
	FName lightid = FName(ENamedName(_lightid));
	FName lightname = FName(ENamedName(_lightname));
	
	// Todo: Optimize. This may be too slow.
	auto lightdef = LightDefaults.FindEx([=](const auto &a) {
		return a->GetName() == lightname;
	});
	if (lightdef < LightDefaults.Size())
	{
		auto userlight = self->UserLights[FindUserLight(self, lightid, true)];
		userlight->CopyFrom(*LightDefaults[lightdef]);
		self->flags8 |= MF8_RECREATELIGHTS;
		self->Level->flags3 |= LEVEL3_LIGHTCREATED;
		return 1;
	}
	return 0;
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_AttachLightDef, AttachLightDef)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_NAME(lightid);
	PARAM_NAME(lightname);
	ACTION_RETURN_BOOL(AttachLightDef(self, lightid.GetIndex(), lightname.GetIndex()));
}

//==========================================================================
//
//
//
//==========================================================================

int AttachLightDirect(AActor *self, int _lightid, int type, int color, int radius1, int radius2, int flags, double ofs_x, double ofs_y, double ofs_z, double param, double spoti, double spoto, double spotp, double intensity, double softshadowradius)
{
	FName lightid = FName(ENamedName(_lightid));
	auto userlight = self->UserLights[FindUserLight(self, lightid, true)];
	userlight->SetType(ELightType(type));
	userlight->SetArg(LIGHT_RED, RPART(color));
	userlight->SetArg(LIGHT_GREEN, GPART(color));
	userlight->SetArg(LIGHT_BLUE, BPART(color));
	userlight->SetArg(LIGHT_INTENSITY, radius1);
	userlight->SetArg(LIGHT_SECONDARY_INTENSITY, radius2);
	userlight->SetFlags(LightFlags::FromInt(flags));
	float of[] = { float(ofs_x), float(ofs_z), float(ofs_y)};
	userlight->SetOffset(of);
	userlight->SetParameter(type == PulseLight? param*TICRATE : param*360.);
	userlight->SetSpotInnerAngle(spoti);
	userlight->SetSpotOuterAngle(spoto);
	userlight->SetLightDefIntensity(intensity);
	if (spotp >= -90. && spotp <= 90.)
	{
		userlight->SetSpotPitch(spotp);
	}
	else
	{
		userlight->UnsetSpotPitch();
	}
	userlight->SetSoftShadowRadius(softshadowradius);
	self->flags8 |= MF8_RECREATELIGHTS;
	self->Level->flags3 |= LEVEL3_LIGHTCREATED;
	return 1;
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_AttachLight, AttachLightDirect)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_NAME(lightid);
	PARAM_INT(type);
	PARAM_INT(color);
	PARAM_INT(radius1);
	PARAM_INT(radius2);
	PARAM_INT(flags);
	PARAM_FLOAT(ofs_x);
	PARAM_FLOAT(ofs_y);
	PARAM_FLOAT(ofs_z);
	PARAM_FLOAT(parami);
	PARAM_FLOAT(spoti);
	PARAM_FLOAT(spoto);
	PARAM_FLOAT(spotp);
	PARAM_FLOAT(intensity);
	PARAM_FLOAT(softshadowradius);
	ACTION_RETURN_BOOL(AttachLightDirect(self, lightid.GetIndex(), type, color, radius1, radius2, flags, ofs_x, ofs_y, ofs_z, parami, spoti, spoto, spotp, intensity, softshadowradius));
}

//==========================================================================
//
//
//
//==========================================================================

int RemoveLight(AActor *self, int _lightid)
{
	FName lightid = FName(ENamedName(_lightid));
	auto userlight = FindUserLight(self, lightid, false);
	if (userlight < self->UserLights.Size())
	{
		delete self->UserLights[userlight];
		self->UserLights.Delete(userlight);
		self->flags8 |= MF8_RECREATELIGHTS;
		self->Level->flags3 |= LEVEL3_LIGHTCREATED;
		return 1;
	}
	return 0;
}

DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_RemoveLight, RemoveLight)
{
	PARAM_SELF_PROLOGUE(AActor);
	PARAM_NAME(lightid);
	ACTION_RETURN_BOOL(RemoveLight(self, lightid.GetIndex()));
}


//==========================================================================
//
//
//
//==========================================================================

//==========================================================================
//
// This is called before saving the game
//
//==========================================================================

void FLevelLocals::DeleteAllAttachedLights()
{
	auto it = GetThinkerIterator<AActor>();
	AActor * a;

	while ((a=it.Next())) 
	{
		a->DeleteAttachedLights();
	}
}

//==========================================================================
//
//
//
//==========================================================================

void FLevelLocals::RecreateAllAttachedLights()
{
	auto it = GetThinkerIterator<AActor>();
	AActor * a;

	while ((a=it.Next())) 
	{
		if (!a->IsKindOf(NAME_DynamicLight))
		{
			a->SetDynamicLights();
		}
		else if (a->AttachedLights.Size() == 0)
		{
			::AttachLight(a);
			if (!(a->flags2 & MF2_DORMANT))
			{
				::ActivateLight(a);
			}
		}
	}
}
