// 
//---------------------------------------------------------------------------
//
// 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"


CUSTOM_CVAR (Bool, gl_lights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL)
{
	if (self) AActor::RecreateAllAttachedLights();
	else AActor::DeleteAllAttachedLights();
}

CVAR (Bool, gl_attachedlights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);

//==========================================================================
//
//==========================================================================
DEFINE_CLASS_PROPERTY(type, S, DynamicLight)
{
	PROP_STRING_PARM(str, 0);
	static const char * ltype_names[]={
		"Point","Pulse","Flicker","Sector","RandomFlicker", "ColorPulse", "ColorFlicker", "RandomColorFlicker", NULL};

	static const int ltype_values[]={
		PointLight, PulseLight, FlickerLight, SectorLight, RandomFlickerLight, ColorPulseLight, ColorFlickerLight, RandomColorFlickerLight };

	int style = MatchString(str, ltype_names);
	if (style < 0) I_Error("Unknown light type '%s'", str);
	defaults->lighttype = ltype_values[style];
}

//==========================================================================
//
// Actor classes
//
// For flexibility all functionality has been packed into a single class
// which is controlled by flags
//
//==========================================================================
IMPLEMENT_CLASS(ADynamicLight, false, false)

DEFINE_FIELD(ADynamicLight, SpotInnerAngle)
DEFINE_FIELD(ADynamicLight, SpotOuterAngle)

static FRandom randLight;

//==========================================================================
//
// Base class
//
//==========================================================================

//==========================================================================
//
//
//
//==========================================================================
void ADynamicLight::Serialize(FSerializer &arc)
{
	Super::Serialize (arc);
	auto def = static_cast<ADynamicLight*>(GetDefault());
	arc("lightflags", lightflags, def->lightflags)
		("lighttype", lighttype, def->lighttype)
		("tickcount", m_tickCount, def->m_tickCount)
		("currentradius", m_currentRadius, def->m_currentRadius)
		("spotinnerangle", SpotInnerAngle, def->SpotInnerAngle)
		("spotouterangle", SpotOuterAngle, def->SpotOuterAngle);

	if (lighttype == PulseLight)
		arc("lastupdate", m_lastUpdate, def->m_lastUpdate)
			("cycler", m_cycler, def->m_cycler);

	// Remap the old flags.
	if (SaveVersion < 4552)
	{
		lightflags = 0;
		if (flags4 & MF4_MISSILEEVENMORE) lightflags |= LF_SUBTRACTIVE;
		if (flags4 & MF4_MISSILEMORE) lightflags |= LF_ADDITIVE;
		if (flags4 & MF4_SEESDAGGERS) lightflags |= LF_DONTLIGHTSELF;
		if (flags4 & MF4_INCOMBAT) lightflags |= LF_ATTENUATE;
		if (flags4 & MF4_STANDSTILL) lightflags |= LF_NOSHADOWMAP;
		if (flags4 & MF4_EXTREMEDEATH) lightflags |= LF_DONTLIGHTACTORS;
		flags4 &= ~(MF4_SEESDAGGERS);	// this flag is dangerous and must be cleared. The others do not matter.
	}
}


void ADynamicLight::PostSerialize()
{
	Super::PostSerialize();
	// The default constructor which is used for creating objects before deserialization will not set this variable.
	// It needs to be true for all placed lights.
	visibletoplayer = true;
	LinkLight();
}

//==========================================================================
//
// [TS]
//
//==========================================================================
void ADynamicLight::BeginPlay()
{
	//Super::BeginPlay();
	ChangeStatNum(STAT_DLIGHT);

	specialf1 = DAngle(double(SpawnAngle)).Normalized360().Degrees;
	visibletoplayer = true;
	mShadowmapIndex = 1024;
}

//==========================================================================
//
// [TS]
//
//==========================================================================
void ADynamicLight::PostBeginPlay()
{
	Super::PostBeginPlay();
	
	if (!(SpawnFlags & MTF_DORMANT))
	{
		Activate (NULL);
	}

	subsector = R_PointInSubsector(Pos());
}


//==========================================================================
//
// [TS]
//
//==========================================================================
void ADynamicLight::Activate(AActor *activator)
{
	//Super::Activate(activator);
	flags2&=~MF2_DORMANT;	

	m_currentRadius = float(args[LIGHT_INTENSITY]);
	m_tickCount = 0;

	if (lighttype == PulseLight)
	{
		float pulseTime = float(specialf1 / TICRATE);
		
		m_lastUpdate = level.maptime;
		if (!swapped) m_cycler.SetParams(float(args[LIGHT_SECONDARY_INTENSITY]), float(args[LIGHT_INTENSITY]), pulseTime);
		else m_cycler.SetParams(float(args[LIGHT_INTENSITY]), float(args[LIGHT_SECONDARY_INTENSITY]), pulseTime);
		m_cycler.ShouldCycle(true);
		m_cycler.SetCycleType(CYCLE_Sin);
		m_currentRadius = float(m_cycler.GetVal());
	}
	if (m_currentRadius <= 0) m_currentRadius = 1;
}


//==========================================================================
//
// [TS]
//
//==========================================================================
void ADynamicLight::Deactivate(AActor *activator)
{
	//Super::Deactivate(activator);
	flags2|=MF2_DORMANT;	
}


//==========================================================================
//
// [TS]
//
//==========================================================================
void ADynamicLight::Tick()
{
	if (IsOwned())
	{
		if (!target || !target->state)
		{
			this->Destroy();
			return;
		}
		if (target->flags & MF_UNMORPHED) return;
		visibletoplayer = target->IsVisibleToPlayer();	// cache this value for the renderer to speed up calculations.
	}

	// Don't bother if the light won't be shown
	if (!IsActive()) 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();
		float pct = float(specialf1 / 360.f);
		
		m_currentRadius = float(args[LIGHT_INTENSITY + (rnd >= pct * 255)]);
		break;
	}

	case RandomFlickerLight:
	{
		int flickerRange = args[LIGHT_SECONDARY_INTENSITY] - args[LIGHT_INTENSITY];
		float amt = randLight() / 255.f;
		
		if (m_tickCount > specialf1)
		{
			m_tickCount = 0;
		}
		if (m_tickCount++ == 0 || m_currentRadius > args[LIGHT_SECONDARY_INTENSITY])
		{
			m_currentRadius = float(args[LIGHT_INTENSITY] + (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 = args[LIGHT_SECONDARY_INTENSITY] - args[LIGHT_INTENSITY];
		float amt = randLight() / 255.f;
		
		m_tickCount++;
		
		if (m_tickCount > specialf1)
		{
			m_currentRadius = args[LIGHT_INTENSITY] + (amt * flickerRange);
			m_tickCount = 0;
		}
		break;
	}
#endif

	case SectorLight:
	{
		float intensity;
		float scale = args[LIGHT_SCALE] / 8.f;
		
		if (scale == 0.f) scale = 1.f;
		
		intensity = Sector->lightlevel * scale;
		intensity = clamp<float>(intensity, 0.f, 1024.f);
		
		m_currentRadius = intensity;
		break;
	}

	case PointLight:
		m_currentRadius = float(args[LIGHT_INTENSITY]);
		break;
	}
	if (m_currentRadius <= 0) m_currentRadius = 1;
	UpdateLocation();
}




//==========================================================================
//
//
//
//==========================================================================
void ADynamicLight::UpdateLocation()
{
	double oldx= X();
	double oldy= Y();
	double oldradius= radius;
	float intensity;

	if (IsActive())
	{
		if (target)
		{
			DAngle angle = target->Angles.Yaw;
			double s = angle.Sin();
			double c = angle.Cos();

			DVector3 pos = target->Vec3Offset(m_off.X * c + m_off.Y * s, m_off.X * s - m_off.Y * c, m_off.Z + target->GetBobOffset());
			SetXYZ(pos); // attached lights do not need to go into the regular blockmap
			Prev = target->Pos();
			subsector = R_PointInSubsector(Prev);
			Sector = subsector->sector;

			// 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.
			if (Z() < target->floorz + 5.) 	SetZ(target->floorz + 5.);
			else if (Z() > target->ceilingz - 5.) 	SetZ(target->ceilingz - 5.);
		}
		else
		{
			if (Z() < floorz + 5.) 	SetZ(floorz + 5.);
			else if (Z() > ceilingz - 5.) 	SetZ(ceilingz - 5.);
		}


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

		if (lighttype == FlickerLight || lighttype == RandomFlickerLight || lighttype == PulseLight)
		{
			intensity = float(MAX(args[LIGHT_INTENSITY], args[LIGHT_SECONDARY_INTENSITY]));
		}
		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();
		}
	}
}


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

void ADynamicLight::SetOrigin(double x, double y, double z, bool moving)
{
	Super::SetOrigin(x, y, z, moving);
	LinkLight();
}

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

void ADynamicLight::SetOffset(const DVector3 &pos)
{
	m_off = pos;
	UpdateLocation();
}


//==========================================================================
//
// The target pointer in dynamic lights should never be substituted unless 
// notOld is NULL (which indicates that the object was destroyed by force.)
//
//==========================================================================
size_t ADynamicLight::PointerSubstitution (DObject *old, DObject *notOld)
{
	AActor *saved_target = target;
	size_t ret = Super::PointerSubstitution(old, notOld);
	if (notOld != NULL) target = saved_target;
	return ret;
}

//=============================================================================
//
// These have been copied from the secnode code and modified for the light links
//
// P_AddSecnode() searches the current list to see if this sector is
// already there. If not, it adds a sector node at the head of the list of
// sectors this object appears in. This is called when creating a list of
// nodes that will get linked in later. Returns a pointer to the new node.
//
//=============================================================================

FLightNode * AddLightNode(FLightNode ** thread, void * linkto, ADynamicLight * light, FLightNode *& nextnode)
{
	FLightNode * node;

	node = nextnode;
	while (node)
    {
		if (node->targ==linkto)   // Already have a node for this sector?
		{
			node->lightsource = light; // Yes. Setting m_thing says 'keep it'.
			return(nextnode);
		}
		node = node->nextTarget;
    }

	// Couldn't find an existing node for this sector. Add one at the head
	// of the list.
	
	node = new FLightNode;
	
	node->targ = linkto;
	node->lightsource = light; 

	node->prevTarget = &nextnode; 
	node->nextTarget = nextnode;

	if (nextnode) nextnode->prevTarget = &node->nextTarget;
	
	// Add new node at head of sector thread starting at s->touching_thinglist
	
	node->prevLight = thread;  	
	node->nextLight = *thread; 
	if (node->nextLight) node->nextLight->prevLight=&node->nextLight;
	*thread = node;
	return(node);
}


//=============================================================================
//
// P_DelSecnode() deletes a sector node from the list of
// sectors this object appears in. Returns a pointer to the next node
// on the linked list, or NULL.
//
//=============================================================================

static FLightNode * DeleteLightNode(FLightNode * node)
{
	FLightNode * tn;  // next node on thing thread
	
	if (node)
    {
		
		*node->prevTarget = node->nextTarget;
		if (node->nextTarget) node->nextTarget->prevTarget=node->prevTarget;

		*node->prevLight = node->nextLight;
		if (node->nextLight) node->nextLight->prevLight=node->prevLight;
		
		// Return this node to the freelist
		tn=node->nextTarget;
		delete node;
		return(tn);
    }
	return(NULL);
}                             // phares 3/13/98



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

double ADynamicLight::DistToSeg(const DVector3 &pos, seg_t *seg)
{
	double u, px, py;

	double seg_dx = seg->v2->fX() - seg->v1->fX();
	double seg_dy = seg->v2->fY() - seg->v1->fY();
	double seg_length_sq = seg_dx * seg_dx + seg_dy * seg_dy;

	u = (((pos.X - seg->v1->fX()) * seg_dx) + (pos.Y - seg->v1->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 = seg->v1->fX() + (u * seg_dx);
	py = seg->v1->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
{
	subsector_t *sub;
	DVector3 pos;
};
static TArray<LightLinkEntry> collected_ss;

void ADynamicLight::CollectWithinRadius(const DVector3 &opos, subsector_t *subSec, float radius)
{
	if (!subSec) return;
	collected_ss.Clear();
	collected_ss.Push({ subSec, opos });
	subSec->validcount = ::validcount;

	bool hitonesidedback = false;
	for (unsigned i = 0; i < collected_ss.Size(); i++)
	{
		subSec = collected_ss[i].sub;

		touching_subsectors = AddLightNode(&subSec->lighthead, subSec, this, touching_subsectors);
		if (subSec->sector->validcount != ::validcount)
		{
			touching_sector = AddLightNode(&subSec->render_sector->lighthead, subSec->sector, this, touching_sector);
			subSec->sector->validcount = ::validcount;
		}

		for (unsigned int j = 0; j < subSec->numlines; ++j)
		{
			auto &pos = collected_ss[i].pos;
			seg_t *seg = subSec->firstline + j;

			// 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, seg) <= radius)
			{
				if (seg->sidedef && seg->linedef && seg->linedef->validcount != ::validcount)
				{
					// light is in front of the seg
					if ((pos.Y - seg->v1->fY()) * (seg->v2->fX() - seg->v1->fX()) + (seg->v1->fX() - pos.X) * (seg->v2->fY() - seg->v1->fY()) <= 0)
					{
						seg->linedef->validcount = validcount;
						touching_sides = AddLightNode(&seg->sidedef->lighthead, seg->sidedef, this, touching_sides);
					}
					else if (seg->linedef->sidedef[0] == seg->sidedef && seg->linedef->sidedef[1] == nullptr)
					{
						hitonesidedback = true;
					}
				}
				if (seg->linedef)
				{
					FLinePortal *port = seg->linedef->getPortal();
					if (port && port->mType == PORTT_LINKED)
					{
						line_t *other = port->mDestination;
						if (other->validcount != ::validcount)
						{
							subsector_t *othersub = R_PointInSubsector(other->v1->fPos() + other->Delta() / 2);
							if (othersub->validcount != ::validcount)
							{
								othersub->validcount = ::validcount;
								collected_ss.Push({ othersub, PosRelative(other) });
							}
						}
					}
				}

				seg_t *partner = seg->PartnerSeg;
				if (partner)
				{
					subsector_t *sub = partner->Subsector;
					if (sub != NULL && sub->validcount != ::validcount)
					{
						sub->validcount = ::validcount;
						collected_ss.Push({ sub, pos });
					}
				}
			}
		}
		sector_t *sec = subSec->sector;
		if (!sec->PortalBlocksSight(sector_t::ceiling))
		{
			line_t *other = subSec->firstline->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 = R_PointInSubsector(refpos);
				if (othersub->validcount != ::validcount)
				{
					othersub->validcount = ::validcount;
					collected_ss.Push({ othersub, PosRelative(othersub->sector) });
				}
			}
		}
		if (!sec->PortalBlocksSight(sector_t::floor))
		{
			line_t *other = subSec->firstline->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 = R_PointInSubsector(refpos);
				if (othersub->validcount != ::validcount)
				{
					othersub->validcount = ::validcount;
					collected_ss.Push({ othersub, PosRelative(othersub->sector) });
				}
			}
		}
	}
	shadowmapped = hitonesidedback && !(lightflags & LF_NOSHADOWMAP);
}

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

void ADynamicLight::LinkLight()
{
	// mark the old light nodes
	FLightNode * node;
	
	node = touching_sides;
	while (node)
    {
		node->lightsource = NULL;
		node = node->nextTarget;
    }
	node = touching_subsectors;
	while (node)
    {
		node->lightsource = NULL;
		node = node->nextTarget;
    }
	node = touching_sector;
	while (node)
	{
		node->lightsource = NULL;
		node = node->nextTarget;
	}

	if (radius>0)
	{
		// passing in radius*radius allows us to do a distance check without any calls to sqrt
		subsector_t * subSec = R_PointInSubsector(Pos());
		::validcount++;
		CollectWithinRadius(Pos(), subSec, float(radius*radius));

	}
		
	// Now delete any nodes that won't be used. These are the ones where
	// m_thing is still NULL.
	
	node = touching_sides;
	while (node)
	{
		if (node->lightsource == NULL)
		{
			node = DeleteLightNode(node);
		}
		else
			node = node->nextTarget;
	}

	node = touching_subsectors;
	while (node)
	{
		if (node->lightsource == NULL)
		{
			node = DeleteLightNode(node);
		}
		else
			node = node->nextTarget;
	}

	node = touching_sector;
	while (node)
	{
		if (node->lightsource == NULL)
		{
			node = DeleteLightNode(node);
		}
		else
			node = node->nextTarget;
	}
}


//==========================================================================
//
// Deletes the link lists
//
//==========================================================================
void ADynamicLight::UnlinkLight ()
{
	if (owned && target != NULL)
	{
		// Delete reference in owning actor
		for(int c=target->AttachedLights.Size()-1; c>=0; c--)
		{
			if (target->AttachedLights[c] == this)
			{
				target->AttachedLights.Delete(c);
				break;
			}
		}
	}
	while (touching_sides) touching_sides = DeleteLightNode(touching_sides);
	while (touching_subsectors) touching_subsectors = DeleteLightNode(touching_subsectors);
	while (touching_sector) touching_sector = DeleteLightNode(touching_sector);
	shadowmapped = false;
}

void ADynamicLight::OnDestroy()
{
	UnlinkLight();
	Super::OnDestroy();
}


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

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

	if (count < AttachedLights.Size()) 
	{
		light = barrier_cast<ADynamicLight*>(AttachedLights[count]);
		assert(light != NULL);
	}
	else
	{
		light = Spawn<ADynamicLight>(Pos(), NO_REPLACE);
		light->target = this;
		light->owned = true;
		light->ObjectFlags |= OF_Transient;
		//light->lightflags |= LF_ATTENUATE;
		AttachedLights.Push(light);
	}
	light->flags2&=~MF2_DORMANT;
	lightdef->ApplyProperties(light);
}

//==========================================================================
//
// per-state light adjustment
//
//==========================================================================
extern TArray<FLightDefaults *> StateLights;

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

	if (state == NULL) return;
	if (LightAssociations.Size() > 0)
	{
		ADynamicLight *lights, *tmpLight;
		unsigned int i;

		lights = tmpLight = NULL;

		for (i = 0; i < LightAssociations.Size(); i++)
		{
			if (LightAssociations[i]->Sprite() == sprite &&
				(LightAssociations[i]->Frame()==frame || LightAssociations[i]->Frame()==-1))
			{
				AttachLight(count++, LightAssociations[i]->Light());
			}
		}
	}
	if (count == 0 && state->Light > 0)
	{
		for(int i= state->Light; StateLights[i] != NULL; i++)
		{
			if (StateLights[i] != (FLightDefaults*)-1)
			{
				AttachLight(count++, StateLights[i]);
			}
		}
	}

	for(;count<AttachedLights.Size();count++)
	{
		AttachedLights[count]->flags2 |= MF2_DORMANT;
		memset(AttachedLights[count]->args, 0, 3*sizeof(args[0]));
	}
}

//==========================================================================
//
// Needed for garbage collection
//
//==========================================================================

size_t AActor::PropagateMark()
{
	for (unsigned i = 0; i<AttachedLights.Size(); i++)
	{
		GC::Mark(AttachedLights[i]);
	}
	return Super::PropagateMark();
}

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

void AActor::DeleteAllAttachedLights()
{
	TThinkerIterator<AActor> it;
	AActor * a;
	ADynamicLight * l;

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

	TThinkerIterator<ADynamicLight> it2;

	l=it2.Next();
	while (l) 
	{
		ADynamicLight * ll = it2.Next();
		if (l->owned) l->Destroy();
		l=ll;
	}
}

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

void AActor::RecreateAllAttachedLights()
{
	TThinkerIterator<AActor> it;
	AActor * a;

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

//==========================================================================
//
// CCMDs
//
//==========================================================================

CCMD(listlights)
{
	int walls, sectors, subsecs;
	int allwalls=0, allsectors=0, allsubsecs = 0;
	int i=0, shadowcount = 0;
	ADynamicLight * dl;
	TThinkerIterator<ADynamicLight> it;

	while ((dl=it.Next()))
	{
		walls=0;
		sectors=0;
		subsecs = 0;
		Printf("%s at (%f, %f, %f), color = 0x%02x%02x%02x, radius = %f %s %s",
			dl->target? dl->target->GetClass()->TypeName.GetChars() : dl->GetClass()->TypeName.GetChars(),
			dl->X(), dl->Y(), dl->Z(), dl->args[LIGHT_RED], 
			dl->args[LIGHT_GREEN], dl->args[LIGHT_BLUE], dl->radius, (dl->lightflags & LF_ATTENUATE)? "attenuated" : "", dl->shadowmapped? "shadowmapped" : "");
		i++;
		shadowcount += dl->shadowmapped;

		if (dl->target)
		{
			FTextureID spr = sprites[dl->target->sprite].GetSpriteFrame(dl->target->frame, 0, 0., nullptr);
			Printf(", frame = %s ", TexMan[spr]->Name.GetChars());
		}


		FLightNode * node;

		node=dl->touching_sides;

		while (node)
		{
			walls++;
			allwalls++;
			node = node->nextTarget;
		}

		node=dl->touching_subsectors;

		while (node)
		{
			allsubsecs++;
			subsecs++;
			node = node->nextTarget;
		}

		node = dl->touching_sector;

		while (node)
		{
			allsectors++;
			sectors++;
			node = node->nextTarget;
		}
		Printf("- %d walls, %d subsectors, %d sectors\n", walls, subsecs, sectors);

	}
	Printf("%i dynamic lights, %d shadowmapped, %d walls, %d subsectors, %d sectors\n\n\n", i, shadowcount, allwalls, allsubsecs, allsectors);
}

CCMD(listsublights)
{
	for(auto &sub : level.subsectors)
	{
		int lights = 0;

		FLightNode * node = sub.lighthead;
		while (node != NULL)
		{
			lights++;
			node = node->nextLight;
		}

		Printf(PRINT_LOG, "Subsector %d - %d lights\n", sub.Index(), lights);
	}
}


