/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod Source Code is free software: you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

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

#ifndef __DARKMOD_LIGHT_HOLDERS__
#define __DARKMOD_LIGHT_HOLDERS__

/**
 * 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
 */
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 );
};

void tdm_light_holder::init()
{
	entity child;
	float ind;
    sys.waitFrame();
	//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 );
		//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 );
	}
	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 )
{
	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");
				// 2011-01-16 Tels: Fix bug with normal lights bound to ourselves not having "frob_extinguish"
				// and thus not getting turned On/Off:
				// So far I am not able to determine whether an event is available, so we simply try both here:
				//sys.println(sys.getTime() + ": DEBUG (holder::toggle_child): child " + child.getName() + " using On()");
				child.Open();	// in case of frobmovers, call Open() (#2676)
				child.On();		// for lights: simply turn on
			}
		}

		// #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()");
				// 2011-01-16 Tels: Fix bug with normal lights bound to ourselves not having "frob_extinguish"
				// and thus not getting turned On/Off:
				// So far I am not able to determine whether an event is available, so we simply try both here:
				child.Close();	// in case of frobmovers, call Close() (#2676)
				child.Off();	// simply turn off
			}
		}
		// #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__
