// vim:ts=4:sw=4:cindent
/***********************************************************************

A script object that can be used on any entity (movable, frobable etc.)
that acts as a holder for other light entities. Examples are candle
holders, oil lamps etc.

This script object just adds a few functions that allow to call script
events on the holder, like LightsOff(), LightsOn() and LightsToggle().

These events cause all bound lights to change state, and this in turn
will allow model and skin changes on the light holder.

Code copied from tdm_frobactions.script. Author: Tels

Note: the delay for noshadows is defined in scripts/tdm_lights.script

***********************************************************************/

#ifndef __DARKMOD_LIGHT_HOLDERS__
#define __DARKMOD_LIGHT_HOLDERS__

object tdm_light_holder
{
	boolean m_bExtinguished;
	// if someone toggles the light, store which entity this was, in case
	// it is also bound to us, to avoid toggling it again, resulting in endless
	// trigger loops
	entity activator;

	void init();
	void LightsOff();
	void LightsOn();
	void LightsToggle();
    void LightsToggleResponse(entity e, entity f); // Obsttorte #3760
    
	// internal routines:
	void toggle_children( float state );
	void toggle_child( entity child, float state, boolean isTarget );
};

void tdm_light_holder::init()
{
	entity child;
	float ind;

	//sys.println(sys.getTime() + ": DEBUG (holder::init): initializing light holder " + getName());
	// grayman #2603 - for the case where the holder is a light, fire initial visual stim
	StimClearIgnoreList (STIM_VISUAL);
	StimEnable (STIM_VISUAL, 1);
    
    // Obsttorte #3760
	ResponseAdd(STIM_TRIGGER);
	ResponseSetAction(STIM_TRIGGER,"LightsToggleResponse");
	ResponseEnable(STIM_TRIGGER,1);
    
	if (getKey("extinguished") == "0")
	{
    	//sys.println(sys.getTime() + ": DEBUG (holder::init): initializing light holder " + getName() + " on");
		m_bExtinguished = false;
	}
	else
	{
     	//sys.println(sys.getTime() + ": DEBUG (holder::init): initializing light holder '" + getName() + "' off");
		LightsOff();		// grayman #2823 - tell yourself and your children to go out

		// grayman #2905 - set self (in case we're a light) and light children that we were spawned off

    	//sys.println(sys.getTime() + ": DEBUG (holder::init): calling setStartedOff for self " + getName());
		setStartedOff();
		for ( ind = 0 ; ind < numBindChildren( ) ; ind++ )
		{
			child = getBindChild( ind );
			if ( child.isLight() )
			{
    			//sys.println(sys.getTime() + ": DEBUG (holder::init): calling setStartedOff for child " + getName());
				child.setStartedOff();
			}
		}
	}

	if (getKey("noshadows_lit") == "1")
	{
		//sys.println(sys.getTime() + ": DEBUG: light holder setting shadows");
		noShadows( !m_bExtinguished );
	}
}

// Helper function for toggling any bound or targetted light objects (like flames)
// or other entities like movers:
void tdm_light_holder::toggle_children( float state )
{
	//sys.println(sys.getTime() + ": DEBUG (holder::toggle_children): state = " + state);
	entity child;
	float ind;

	for( ind = 0; ind < numBindChildren( ); ind++ )
	{
		child = getBindChild( ind );
		//sys.println(sys.getTime() + ": DEBUG (holder::toggle_children): toggling child " + child.getName());
		toggle_child( child, state, false );
		//sys.println(sys.getTime() + ": DEBUG (holder::toggle_children): setting _light_toggled key to 1 on child " + child.getName());
		child.setKey( "_light_toggled", "1");	// mark as done
	}
	// and now do the same for our targets, but ignore any we already did
	for( ind = 0; ind < numTargets( ); ind++ )
	{
		child = getTarget( ind );
		//sys.println(sys.getTime() + ": DEBUG toggle target: " + child.getName() + " state = " + state); 
		toggle_child( child, state, true );
	}
	for( ind = 0; ind < numBindChildren( ); ind++ )
	{
		child = getBindChild( ind );
		//sys.println(sys.getTime() + ": DEBUG (holder::toggle_children): setting _light_toggled key to 0 on child " + child.getName());
		child.setKey( "_light_toggled", 0);	// reset mark
	}
}

// Helper function for toggling one bound or targeted object (like flames or movers)
void tdm_light_holder::toggle_child( entity child, float state, boolean isTarget )
{
	if ( child == $null_entity )
	{
		return;
	}
	//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): child " + child.getName() + " starting ...");
	// #3048: avoid endless loops when this child is a mover bound to us, and linked back to us
	if (child.getIntKey("_light_toggled") == 1)	// marked as done?
	{
		//sys.println(sys.getTime() + ": DEBUG child " + child.getName() + " already toggled");
		return;
	}

	if (child.getIntKey("noactivate") != 0)	// or as noactivate?
	{
		//sys.println(sys.getTime() + ": DEBUG child has noactivate");
		return;
	}

	//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): " + child.getName() + " state = " + state); 
	string unlit_skin = child.getKey( "skin_unlit" );
	string lit_skin = child.getKey( "skin_lit" );
	string cur_skin = child.getKey( "skin" );
	//sys.println(sys.getTime() + ": DEBUG skin: " + cur_skin + " skin_unlit: " + unlit_skin + " lit_skin: " + lit_skin);

	if ( state > 0) // 1 = turn on 
	{
		//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): child " + child.getName() + " will be relit");

		// tels: call this in a sep. thread, otherwise it doesn't return until the 
		// ext. animation+particle have finished, which can be several seconds.
		// this poses problems when you ignite/extinguish more than one bound flame:
		if (child.hasFunction("frob_ignite") )
		{
			//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): child " + child.getName() + " using frob_ignite() in a new thread");
			thread callFunctionOn( "frob_ignite", child);
		}
		else
	   	{
			if (child.hasFunction("LightsOn") )
			{
				// child is a light holder?
				//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): child " + child.getName() + " using LightsOn() because the child is a light holder, in a new thread");
				thread callFunctionOn( "LightsOn", child);
			} else
			{
				//sys.println(sys.getTime() + ": DEBUG light on");
				if ( child.isLight() ) child.On();
				else if (isTarget) child.activate(self);
			}
		}

		// #3109: in case the spawnarg is set and the current skin is different,
		// the swap the skin, too. For lights, or light holder, the skin might
		// already have been swapped, so skip this step:
		if (lit_skin)
		   	{
			if (cur_skin != lit_skin)
				{
				//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): setting lit skin");
				child.setSkin( lit_skin );
				// store for later retrival by scripts
				child.setKey( "skin", lit_skin );
				}
			}
	}
	else // 0 = turn off
	{
		//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): child " + child.getName() + " will be extinguished");
		if (child.hasFunction("frob_extinguish") )
		{
			//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): " + child.getName() + ".frob_extinguish() is being called in a new thread");
			thread callFunctionOn( "frob_extinguish", child );
		}
		else
	   	{
			if (child.hasFunction("LightsOff") )
			{
				// child is a light holder?
				thread callFunctionOn( "LightsOff", child);
			} else
			{
				//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): " + child.getName() + ".Off()");
				if ( child.isLight() ) child.Off();
				else if (isTarget) child.activate(self);
			}
		}
		// #3109: in case the spawnarg is set and the current skin is different,
		// then swap the skin, too. For lights, or light holder, the skin might
		// already have been swapped, so skip this step:
		if (unlit_skin)
		{
			if (cur_skin != unlit_skin)
			{
				//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): setting unlit skin");
				child.setSkin( unlit_skin );
				// store for later retrival by scripts
				child.setKey( "skin", unlit_skin );
			}
		}
	}
	// grayman #2603 - We have changed state, so entities that noticed us last time should notice us again
	// grayman #4392 - But only if we're a light.
	if ( child.isLight() )
	{
		//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): child " + child.getName() + " clearing ignore stim list and enabling visual stim");
		child.StimClearIgnoreList (STIM_VISUAL);
		child.StimEnable (STIM_VISUAL, 1);
	}
}

// Tels: to be used on f.i. candles, extinguishes the bound flame(s)
// and these in turn will set the unlit skin on us (the holder):
void tdm_light_holder::LightsOff()
{
	//sys.println(sys.getTime() + ": DEBUG (holder::LightsOff): " + getName());

	// nothing to do
	if (m_bExtinguished)
	{
		//sys.println(sys.getTime() + ": DEBUG (holder::LightsOff): " + getName() + " - already extinguished, nothing to do");
	   return;
	}

	// do this as first step to prevent endless recursion
	m_bExtinguished = true;

	//sys.println(sys.getTime() + ": DEBUG (holder::LightsOff): " + getName() + " calling toggle_children(OFF)");
	toggle_children( 0 );

	//sys.println(sys.getTime() + ": DEBUG (holder::LightsOff): " + getName() + " calling Off() on itself");
	// in case the light holder itself is a light?
	Off();
	//fadeOutLight(0.5);

	// if requested, toggle our shadows on (after the light is off!)
	if ( getIntKey("noshadows_lit") == 1 )
	{
		// delay turning on the shadow a bit to give the light a chance to fade out
		if ( getKey("lightType") == "AIUSE_LIGHTTYPE_TORCH" )
		{
			//sys.println(sys.getTime() + ": DEBUG: LightsOff - torch shadows ON on " + getName());
			noShadowsDelayed( false, SHADOW_SWITCH_DELAY_TORCH );
		}
		else
		{
			//sys.println(sys.getTime() + ": DEBUG: LightsOff - electric shadows ON on " + getName());
			noShadowsDelayed( false, SHADOW_SWITCH_DELAY_ELECTRIC );
		}
	}

	// only if the spawnarg is set
	string skin = getKey( "skin_unlit" );
	if (skin)
	{
		setSkin( skin );
		// store for later retrieval by scripts
		setKey( "skin", skin );
	}

	// grayman #2603 - for the case where the holder is a light
	// We have changed state, so entities that noticed us last time should notice us again
	StimClearIgnoreList (STIM_VISUAL);
	StimEnable (STIM_VISUAL, 1);
}

void tdm_light_holder::LightsOn()
{
	// sys.println(sys.getTime() + ": DEBUG (holder::LightsOn): " + getName());
	// nothing to do
	if (!m_bExtinguished)
	{
		//sys.println(sys.getTime() + ": DEBUG (holder::LightsOn): " + getName() + " - already lit, nothing to do");
		return;
	}

	m_bExtinguished = false;

	//sys.println(sys.getTime() + ": DEBUG (holder::LightsOn): " + getName() + " calling toggle_children(ON)");
	toggle_children( 1 );

	// if requested, toggle our shadows off before turning the light on
	// do so immediately, otherwise the light might cast briefly a shadow
	if (getIntKey("noshadows_lit") == 1)
	{
		noShadows(true);
	}

	// in case the light holder itself is a light?
	On();
	// TODO: make this configurable via spawnarg:
	// fadeInLight(0.5);

	// only if the spawnarg is set
	string skin = getKey( "skin_lit" );
	if (skin)
	{
		setSkin( skin );
		// store for later retrieval by scripts
		setKey( "skin", skin );
	}

	// grayman #2603 - for the case where the holder is a light
	// We have changed state, so entities that noticed us last time should notice us again
	StimClearIgnoreList (STIM_VISUAL);
	StimEnable (STIM_VISUAL, 1);
}

// Obsttorte #3760
void tdm_light_holder::LightsToggleResponse(entity e, entity f)
{
	LightsToggle();
}

// Tels: this one toggles bound flames on/off, depending on the
// state the flame is currently in.
void tdm_light_holder::LightsToggle()
{
	if (m_bExtinguished)
	{
		// turn on
		//sys.println(sys.getTime() + ": DEBUG (holder::LightsToggle): on " + getKey("name") + " calling LightsOn()");
		LightsOn();
	} else
	{
		// turn off
		//sys.println(sys.getTime() + ": DEBUG (holder::LightsToggle): off " + getKey("name") + " calling LightsOff()");
		LightsOff();
	}
}

#endif //__DARKMOD_LIGHT_HOLDERS__
