package com.planet_ink.coffee_mud.Exits;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.interfaces.EachApplicable.ApplyAffectPhyStats;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.Basic.StdItem;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.util.*;

/*
   Copyright 2001-2022 Bo Zimmerman

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

	   http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
public class StdExit implements Exit
{
	@Override
	public String ID()
	{
		return "StdExit";
	}

	protected PhyStats	phyStats		= (PhyStats) CMClass.getCommon("DefaultPhyStats");
	protected PhyStats	basePhyStats	= (PhyStats) CMClass.getCommon("DefaultPhyStats");
	protected boolean	isOpen			= true;
	protected boolean	isLocked		= false;
	protected String	miscText		= "";
	protected String	cachedImageName	= null;
	protected String	rawImageName	= null;
	protected boolean	amDestroyed		= false;
	protected short		usage			= 0;

	protected String				lastRoomID	= "";
	protected CList<Ability>		affects		= null;
	protected CList<Behavior>		behaviors	= null;
	protected CList<ScriptingEngine>scripts		= null;
	protected Exit					me			= this;

	protected ApplyAffectPhyStats<Ability>	affectPhyStats = new ApplyAffectPhyStats<Ability>(this);

	public StdExit()
	{
		super();
		//CMClass.bumpCounter(this,CMClass.CMObjectType.EXIT);
		isOpen=!defaultsClosed();
		isLocked=defaultsLocked();
	}

	/*
	protected void finalize()
	{
		CMClass.unbumpCounter(this, CMClass.CMObjectType.EXIT);
	}// removed for mem & perf
	*/

	@Override
	public void initializeClass()
	{
	}

	@Override
	public String Name()
	{
		return "a walkway";
	}

	@Override
	public boolean hasADoor()
	{
		return false;
	}

	@Override
	public boolean hasALock()
	{
		return false;
	}

	@Override
	public boolean defaultsLocked()
	{
		return false;
	}

	@Override
	public boolean defaultsClosed()
	{
		return false;
	}

	@Override
	public String displayText()
	{
		return "";
	}

	@Override
	public String description()
	{
		return "";
	}

	@Override
	public String description(final MOB viewerMob)
	{
		return description();
	}

	@Override
	public String doorName()
	{
		return "door";
	}

	@Override
	public String closedText()
	{
		return "a closed door";
	}

	@Override
	public String closeWord()
	{
		return "close";
	}

	@Override
	public String openWord()
	{
		return "open";
	}

	@Override
	public String displayText(final MOB viewerMob)
	{
		return displayText();
	}

	@Override
	public String name(final MOB viewerMob)
	{
		return name();
	}

	@Override
	public int getTickStatus()
	{
		return Tickable.STATUS_NOT;
	}

	@Override
	public short exitUsage(final short change)
	{
		if(change<0)
		{
			if((-change)>usage)
				usage=0;
			else
				usage+=change;
		}
		else
		if(Short.MAX_VALUE-change>usage)
			usage+=change;
		return usage;
	}

	@Override
	public void setName(final String newName)
	{
	}

	@Override
	public String name()
	{
		if(phyStats().newName()!=null)
			return phyStats().newName();
		return Name();
	}

	@Override
	public PhyStats phyStats()
	{
		return phyStats;
	}

	@Override
	public PhyStats basePhyStats()
	{
		return basePhyStats;
	}

	@Override
	public void recoverPhyStats()
	{
		basePhyStats.copyInto(phyStats);
		eachEffect(affectPhyStats);
	}

	@Override
	public void setBasePhyStats(final PhyStats newStats)
	{
		basePhyStats=(PhyStats)newStats.copyOf();
	}

	@Override
	public void destroy()
	{
		CMLib.map().registerWorldObjectDestroyed(null,null,this);
		CMLib.threads().deleteTick(this,-1);
		affects=null;
		rawImageName=null;
		cachedImageName=null;
		behaviors=null;
		scripts=null;
		miscText=null;
		amDestroyed=true;
	}

	@Override
	public boolean amDestroyed()
	{
		return amDestroyed;
	}

	@Override
	public boolean isSavable()
	{
		return !amDestroyed && CMLib.flags().isSavable(this);
	}

	@Override
	public void setSavable(final boolean truefalse)
	{
		CMLib.flags().setSavable(this, truefalse);
	}

	@Override
	public String image()
	{
		if(cachedImageName==null)
		{
			if((rawImageName!=null)&&(rawImageName.length()>0))
				cachedImageName=rawImageName;
			else
				cachedImageName=CMLib.protocol().getDefaultMXPImage(this);
		}
		return cachedImageName;
	}

	@Override
	public String rawImage()
	{
		if(rawImageName==null)
			return "";
		return rawImageName;
	}

	@Override
	public void setImage(final String newImage)
	{
		if((newImage==null)||(newImage.trim().length()==0))
			rawImageName=null;
		else
			rawImageName=newImage;
		if((cachedImageName!=null)&&(!cachedImageName.equals(newImage)))
			cachedImageName=null;
	}

	@Override
	public CMObject newInstance()
	{
		try
		{
			return this.getClass().newInstance();
		}
		catch(final Exception e)
		{
			Log.errOut(ID(),e);
		}
		return new StdExit();
	}

	@Override
	public boolean isGeneric()
	{
		return false;
	}

	protected void cloneFix(final Exit X)
	{
		me=this;
		basePhyStats=(PhyStats)X.basePhyStats().copyOf();
		phyStats=(PhyStats)X.phyStats().copyOf();

		affectPhyStats = new ApplyAffectPhyStats<Ability>(this);

		affects=null;
		behaviors=null;
		scripts=null;
		Ability A;
		for(final Enumeration<Ability> a=X.effects();a.hasMoreElements();)
		{
			A=a.nextElement();
			if(A!=null)
			{
				A=(Ability)A.copyOf();
				addEffect(A);
				if(A.canBeUninvoked())
				{
					A.unInvoke();
					delEffect(A);
				}
			}
		}
		for(final Enumeration<Behavior> e=X.behaviors();e.hasMoreElements();)
		{
			final Behavior B=e.nextElement();
			if(B!=null)
				addBehavior((Behavior)B.copyOf());
		}
		for(final Enumeration<ScriptingEngine> e=X.scripts();e.hasMoreElements();)
		{
			final ScriptingEngine SE=e.nextElement();
			if(SE!=null)
				addScript((ScriptingEngine)SE.copyOf());
		}
	}

	@Override
	public CMObject copyOf()
	{
		try
		{
			final StdExit E=(StdExit)this.clone();
			//CMClass.bumpCounter(this,CMClass.CMObjectType.EXIT);//removed for mem & perf
			E.cloneFix(this);
			return E;

		}
		catch(final CloneNotSupportedException e)
		{
			return this.newInstance();
		}
	}

	@Override
	public void setMiscText(final String newMiscText)
	{
		miscText = newMiscText;
	}

	@Override
	public String text()
	{
		return miscText;
	}

	@Override
	public String miscTextFormat()
	{
		return CMParms.FORMAT_UNDEFINED;
	}

	@Override
	public long expirationDate()
	{
		return 0;
	}

	@Override
	public void setExpirationDate(final long time)
	{
	}

	@Override
	public void setDisplayText(final String newDisplayText)
	{
	}

	@Override
	public void setDescription(final String newDescription)
	{
	}

	@Override
	public int maxRange()
	{
		return Integer.MAX_VALUE;
	}

	@Override
	public int minRange()
	{
		return Integer.MIN_VALUE;
	}

	protected final String closeWordPastTense()
	{
		return CMLib.english().makePastTense(closeWord(), "closed");
	}

	protected final String openWordPastTense()
	{
		return CMLib.english().makePastTense(openWord(), "opened");
	}

	@Override
	public boolean okMessage(final Environmental myHost, final CMMsg msg)
	{
		MsgListener N=null;
		for(int b=0;b<numBehaviors();b++)
		{
			N=fetchBehavior(b);
			if((N!=null)&&(!N.okMessage(this,msg)))
				return false;
		}
		for(int s=0;s<numScripts();s++)
		{
			N=fetchScript(s);
			if((N!=null)&&(!N.okMessage(this,msg)))
				return false;
		}
		for(final Enumeration<Ability> a=effects();a.hasMoreElements();)
		{
			N=a.nextElement();
			if((N!=null)&&(!N.okMessage(this,msg)))
				return false;
		}

		final MOB mob=msg.source();
		if((!msg.amITarget(this))&&(msg.tool()!=this))
			return true;
		else
		if(msg.targetMinor()==CMMsg.NO_EFFECT)
			return true;
		else
		switch(msg.targetMinor())
		{
		case CMMsg.TYP_LOOK:
		case CMMsg.TYP_EXAMINE:
		case CMMsg.TYP_READ:
		case CMMsg.TYP_WASREAD:
		case CMMsg.TYP_OK_VISUAL:
		case CMMsg.TYP_KNOCK:
		case CMMsg.TYP_OK_ACTION:
			return true;
		case CMMsg.TYP_ENTER:
			if(msg.target() instanceof Room)
				lastRoomID=CMLib.map().getExtendedRoomID((Room)msg.target());
			if((hasADoor())&&(!isOpen())&&(mob.phyStats().height()>=0))
			{
				if(!CMLib.flags().canBeSeenBy(this,mob))
					mob.tell(L("You can't go that way."));
				else
					mob.tell(L("The @x1 is @x2.",doorName(),closeWordPastTense()));
				return false;
			}
			if((CMLib.flags().isFlying(this))
			&&(!CMLib.flags().isInFlight(mob))
			&&(!CMLib.flags().isFalling(mob)))
			{
				mob.tell(L("You can't fly."));
				return false;
			}
			if((CMLib.flags().isClimbing(this))
			&&(!CMLib.flags().isFalling(this))
			&&(!CMLib.flags().isClimbing(mob))
			&&(!CMLib.flags().isInFlight(mob)))
			{
				Rideable ladder=null;
				if(msg.target() instanceof Room)
					ladder=CMLib.tracking().findALadder(mob,(Room)msg.target());
				if(ladder!=null)
					CMLib.tracking().postMountLadder(mob,ladder);
				if((!CMLib.flags().isClimbing(mob))
				&&(!CMLib.flags().isFalling(mob)))
				{
					mob.tell(L("You need to climb that way, if you know how."));
					return false;
				}
			}
			return true;
		case CMMsg.TYP_LEAVE:
		case CMMsg.TYP_FLEE:
			return true;
		case CMMsg.TYP_CLOSE:
		{
			if(closeWord().length()==0)
				setExitParams(doorName(),openWord(),"close",closedText());
			if(isOpen())
			{
				if(!hasADoor())
				{
					mob.tell(L("There is nothing to @x1!",closeWord()));
					return false;
				}
				return true;
			}
			mob.tell(L("The @x1 is already @x2.",doorName(),closeWordPastTense()));
			return false;
		}
		case CMMsg.TYP_OPEN:
		{
			if(openWord().length()==0)
				setExitParams(doorName(),"open",closeWord(),closedText());
			if(!hasADoor())
			{
				mob.tell(L("There is nothing to @x1 that way!",openWord()));
				return false;
			}
			if(isOpen())
			{
				mob.tell(L("The @x1 is already @x2!",doorName(),openWordPastTense()));
				return false;
			}
			if(isLocked()&&hasALock())
			{
				mob.tell(L("The @x1 is locked.",doorName()));
				return false;
			}
			return true;
		}
		case CMMsg.TYP_PUSH:
			if((isOpen())||(!hasADoor()))
			{
				mob.tell(L("There is nothing to push over there."));
				return false;
			}
			return true;
		case CMMsg.TYP_DELICATE_HANDS_ACT:
		case CMMsg.TYP_JUSTICE:
		case CMMsg.TYP_CAST_SPELL:
		case CMMsg.TYP_SPEAK:
			return true;
		case CMMsg.TYP_PULL:
			if((isOpen())||(!hasADoor()))
			{
				mob.tell(L("There is nothing to pull over there."));
				return false;
			}
			return true;
		case CMMsg.TYP_LOCK:
			if(!hasADoor())
			{
				mob.tell(L("There is nothing to lock that way!"));
				return false;
			}
		//$FALL-THROUGH$
		case CMMsg.TYP_UNLOCK:
			if(!hasADoor())
			{
				mob.tell(L("There is nothing to unlock that way!"));
				return false;
			}
			if(isOpen())
			{
				mob.tell(L("The @x1 is already @x2!",doorName(),openWord()));
				return false;
			}
			else
			if(!hasALock())
			{
				mob.tell(L("There is no lock!"));
				return false;
			}
			else
			{
				if((!isLocked())&&(msg.targetMinor()==CMMsg.TYP_UNLOCK))
				{
					mob.tell(L("The @x1 is not locked.",doorName()));
					return false;
				}
				else
				if((isLocked())&&(msg.targetMinor()==CMMsg.TYP_LOCK))
				{
					mob.tell(L("The @x1 is already locked.",doorName()));
					return false;
				}
				else
				{
					for(int i=0;i<mob.numItems();i++)
					{
						final Item item=mob.getItem(i);
						if((item!=null)
						&&(item instanceof DoorKey)
						&&((DoorKey)item).getKey().equals(keyName())
						&&((item.container()==null)
						   ||((item.container().container()==null)
							  &&((item.container().containTypes()&Container.CONTAIN_KEYS)>0)))
						&&(CMLib.flags().canBeSeenBy(item,mob)))
						{
							if(msg.tool()==null)
								msg.setTool(item);
							return true;
						}
					}
					mob.tell(L("You don't seem to have the key."));
					return false;
				}
			}
			//break;
		default:
			break;
		}
		if(msg.amITarget(this))
		{
			mob.tell(L("You can't do that."));
			return false;
		}
		return true;
	}

	@Override
	public StringBuilder viewableText(final MOB mob, final Room room)
	{
		final StringBuilder viewMsg=new StringBuilder("");
		final CMFlagLibrary flags=CMLib.flags();
		if(mob.isAttributeSet(MOB.Attrib.SYSOPMSGS))
		{
			if(room==null)
				viewMsg.append("^Z(null)^.^? ");
			else
				viewMsg.append("^H("+CMLib.map().getExtendedRoomID(room)+")^? "+room.displayText(mob)+flags.getDispositionBlurbs(room,mob)+" ");
			viewMsg.append("via ^H("+ID()+")^? "+(isOpen()?displayText():closedText()));
		}
		else
		if(((flags.canBeSeenBy(this,mob))||(isOpen()&&hasADoor()))
		&&(flags.isSeeable(this)))
		{
			if(isOpen())
			{
				if(room != null)
				{
					if(!flags.canBeSeenBy(room,mob))
						viewMsg.append("darkness");
					else
					if(displayText().length()>0)
						viewMsg.append(displayText()
								+flags.getDispositionBlurbs(this,mob)
								+flags.getDispositionBlurbs(room,mob));
					else
					if(room!=null)
						viewMsg.append(room.displayText(mob)+flags.getDispositionBlurbs(room,mob));
				}
				else
				if(displayText().length()>0)
					viewMsg.append(displayText()+flags.getDispositionBlurbs(this,mob));
			}
			else
			if((flags.canBeSeenBy(this,mob))
			&&(closedText().trim().length()>0))
				viewMsg.append(closedText()+flags.getDispositionBlurbs(this,mob));
		}
		return viewMsg;
	}

	@Override
	public void executeMsg(final Environmental myHost, final CMMsg msg)
	{
		if(numBehaviors()>0)
		{
			eachBehavior(new EachApplicable<Behavior>()
			{
				@Override
				public final void apply(final Behavior B)
				{
					B.executeMsg(me, msg);
				}
			});
		}
		if(numScripts()>0)
		{
			eachScript(new EachApplicable<ScriptingEngine>()
			{
				@Override
				public final void apply(final ScriptingEngine S)
				{
					S.executeMsg(me, msg);
				}
			});
		}
		if(numEffects()>0)
		{
			eachEffect(new EachApplicable<Ability>()
			{
				@Override
				public final void apply(final Ability A)
				{
					A.executeMsg(me,msg);
				}
			});
		}

		final MOB mob=msg.source();
		if((!msg.amITarget(this))&&(msg.tool()!=this))
			return;
		switch(msg.targetMinor())
		{
		case CMMsg.TYP_LOOK:
		case CMMsg.TYP_EXAMINE:
			CMLib.commands().handleBeingLookedAt(msg);
			break;
		case CMMsg.TYP_READ:
			CMLib.commands().handleBeingRead(msg);
			break;
		case CMMsg.TYP_CLOSE:
			if((!hasADoor())||(!isOpen()))
				return;
			isOpen=false;
			break;
		case CMMsg.TYP_OPEN:
			if((!hasADoor())||(isOpen()))
				return;
			if(defaultsClosed()||defaultsLocked())
			{
				CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_REOPEN);
				CMLib.threads().startTickDown(this,Tickable.TICKID_EXIT_REOPEN,openDelayTicks());
			}
			isLocked=false;
			isOpen=true;
			break;
		case CMMsg.TYP_LOCK:
			if((!hasADoor())||(!hasALock())||(isLocked()))
				return;
			isOpen=false;
			isLocked=true;
			break;
		case CMMsg.TYP_PULL:
		case CMMsg.TYP_PUSH:
			mob.tell(L("It doesn't appear to be doing any good."));
			break;
		case CMMsg.TYP_UNLOCK:
			if((!hasADoor())||(!hasALock())||(isOpen())||(!isLocked()))
				return;
			isLocked=false;
			break;
		default:
			break;
		}
	}

	@Override
	public int compareTo(final CMObject o)
	{
		return CMClass.classID(this).compareToIgnoreCase(CMClass.classID(o));
	}

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		if(amDestroyed())
			return false;

		if(usage<=0)
		{
			destroy();
			return false;
		}

		if(tickID==Tickable.TICKID_EXIT_REOPEN)
		{
			if(defaultsClosed())
				isOpen=false;
			if(defaultsLocked())
			{
				isOpen=false;
				isLocked=true;
			}
			return false;
		}
		else
		if(tickID==Tickable.TICKID_EXIT_BEHAVIOR)
		{
			if(numBehaviors()>0)
			{
				eachBehavior(new EachApplicable<Behavior>()
				{
					@Override
					public final void apply(final Behavior B)
					{
						B.tick(ticking, tickID);
					}
				});
			}
			if(numScripts()>0)
			{
				eachScript(new EachApplicable<ScriptingEngine>()
				{
					@Override
					public final void apply(final ScriptingEngine S)
					{
						S.tick(ticking, tickID);
					}
				});
			}
			return !amDestroyed();
		}
		else
		{
			if(numEffects()>0)
			{
				eachEffect(new EachApplicable<Ability>()
				{
					@Override
					public final void apply(final Ability A)
					{
						if(!A.tick(ticking,tickID))
							A.unInvoke();
					}
				});
			}
			return true;
		}
	}

	@Override
	public boolean isOpen()
	{
		return isOpen;
	}

	@Override
	public boolean isLocked()
	{
		return isLocked;
	}

	@Override
	public void setDoorsNLocks(final boolean newHasADoor,
								  final boolean newIsOpen,
								  final boolean newDefaultsClosed,
								  final boolean newHasALock,
								  final boolean newIsLocked,
								  final boolean newDefaultsLocked)
	{
		isOpen=newIsOpen;
		isLocked=newIsLocked;
	}

	@Override
	public String readableText()
	{
		return (isReadable() ? miscText : "");
	}

	@Override
	public boolean isReadable()
	{
		return false;
	}

	@Override
	public void setReadable(final boolean isTrue)
	{
	}

	@Override
	public void setReadableText(final String text)
	{
		miscText = temporaryDoorLink() + text;
	}

	@Override
	public void setExitParams(final String newDoorName, final String newCloseWord, final String newOpenWord, final String newClosedText)
	{
	}

	@Override
	public String keyName()
	{
		return (hasALock() ? miscText : "");
	}

	@Override
	public void setKeyName(final String newKeyName)
	{
		miscText = temporaryDoorLink() + newKeyName;
	}

	@Override
	public Room lastRoomUsedFrom(final Room fromRoom)
	{
		return CMLib.map().getRoom(lastRoomID);
	}

	@Override
	public void affectPhyStats(final Physical affected, final PhyStats affectableStats)
	{
	}// exits will never be asked this, so this method should always do NOTHING

	@Override
	public void affectCharStats(final MOB affectedMob, final CharStats affectableStats)
	{// exits will never be asked this, so this method should always do NOTHING
	}

	@Override
	public void affectCharState(final MOB affectedMob, final CharState affectableMaxState)
	{// exits will never be asked this, so this method should always do NOTHING
	}

	@Override
	public String temporaryDoorLink()
	{
		if(miscText.startsWith("{#"))
		{
			final int x=miscText.indexOf("#}");
			if(x>=0)
				return miscText.substring(2,x);
		}
		return "";
	}

	@Override
	public void setTemporaryDoorLink(final String link)
	{
		if(link.startsWith("{{#"))
		{
			final int x=link.indexOf("#}}");
			if(x>=0)
				lastRoomID=link.substring(3,x);
			return;
		}
		if(miscText.startsWith("{#"))
		{
			final int x=miscText.indexOf("#}");
			if(x>=0)
				miscText=miscText.substring(x+2);
		}
		if(link.length()>0)
			miscText="{#"+link+"#}"+miscText;
	}

	@Override
	public void addNonUninvokableEffect(final Ability to)
	{
		if(to==null)
			return;
		if(fetchEffect(to.ID())!=null)
			return;
		if(affects==null)
			affects=new SVector<Ability>(1);
		to.makeNonUninvokable();
		to.makeLongLasting();
		affects.add(to);
		to.setAffectedOne(this);
	}

	@Override
	public void addEffect(final Ability to)
	{
		if(to==null)
			return;
		if(fetchEffect(to.ID())!=null)
			return;
		if(affects==null)
			affects=new SVector<Ability>(1);
		affects.add(to);
		to.setAffectedOne(this);
	}

	@Override
	public void delEffect(final Ability to)
	{
		if(affects==null)
			return;
		if(affects.remove(to))
			to.setAffectedOne(null);
	}

	@Override
	public void eachEffect(final EachApplicable<Ability> applier)
	{
		final List<Ability> affects=this.affects;
		if(affects==null)
			return;
		try
		{
			for(int a=0;a<affects.size();a++)
			{
				final Ability A=affects.get(a);
				if(A!=null)
					applier.apply(A);
			}
		}
		catch(final ArrayIndexOutOfBoundsException e)
		{
		}
	}

	@Override
	public void delAllEffects(final boolean unInvoke)
	{
		final CList<Ability> affects=this.affects;
		if(affects==null)
			return;
		for(int a=numEffects()-1;a>=0;a--)
		{
			final Ability A=fetchEffect(a);
			if(A!=null)
			{
				if(unInvoke)
					A.unInvoke();
				A.setAffectedOne(null);
			}
		}
		affects.clear();
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<Ability> effects()
	{
		return (affects == null) ? EmptyEnumeration.INSTANCE : affects.elements();
	}

	@Override
	public int numEffects()
	{
		if(affects==null)
			return 0;
		return affects.size();
	}

	@Override
	public Ability fetchEffect(final int index)
	{
		if(affects==null)
			return null;
		try
		{
			return affects.get(index);
		}
		catch (final java.lang.ArrayIndexOutOfBoundsException x)
		{
		}
		return null;
	}

	@Override
	public Ability fetchEffect(final String ID)
	{
		if(affects==null)
			return null;
		for(final Enumeration<Ability> a=effects();a.hasMoreElements();)
		{
			final Ability A=a.nextElement();
			if((A!=null)&&(A.ID().equals(ID)))
				return A;
		}
		return null;
	}

	/** Manipulation of Behavior objects, which includes
	 * movement, speech, spellcasting, etc, etc.*/
	@Override
	public void addBehavior(final Behavior to)
	{
		if(behaviors==null)
			behaviors=new SVector<Behavior>(1);
		if(to==null)
			return;
		for(final Behavior B : behaviors)
		{
			if((B!=null)&&(B.ID().equals(to.ID())))
				return;
		}
		// first one! so start ticking...
		if(behaviors.size()==0)
			CMLib.threads().startTickDown(this,Tickable.TICKID_EXIT_BEHAVIOR,1);
		to.startBehavior(this);
		behaviors.add(to);
	}

	@Override
	public void delBehavior(final Behavior to)
	{
		if(behaviors==null)
			return;
		if(behaviors.remove(to))
			to.endBehavior(this);
		if(((behaviors==null)||(behaviors.size()==0))&&((scripts==null)||(scripts.size()==0)))
			CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
	}

	@Override
	public void delAllBehaviors()
	{
		final boolean didSomething=(behaviors!=null)&&(behaviors.size()>0);
		if(didSomething)
			behaviors.clear();
		behaviors=null;
		if(didSomething && ((scripts==null)||(scripts.size()==0)))
			CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
	}

	@Override
	public int numBehaviors()
	{
		if(behaviors==null)
			return 0;
		return behaviors.size();
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<Behavior> behaviors()
	{
		return (behaviors == null) ? EmptyEnumeration.INSTANCE : behaviors.elements();
	}

	@Override
	public Behavior fetchBehavior(final int index)
	{
		if(behaviors==null)
			return null;
		try
		{
			return behaviors.get(index);
		}
		catch(final java.lang.ArrayIndexOutOfBoundsException x)
		{
		}
		return null;
	}

	@Override
	public Behavior fetchBehavior(final String ID)
	{
		if(behaviors==null)
			return null;
		for(final Behavior B : behaviors)
		{
			if((B!=null)&&(B.ID().equalsIgnoreCase(ID)))
				return B;
		}
		return null;
	}

	@Override
	public void eachBehavior(final EachApplicable<Behavior> applier)
	{
		final List<Behavior> behaviors=this.behaviors;
		if(behaviors!=null)
		try
		{
			for(int a=0;a<behaviors.size();a++)
			{
				final Behavior B=behaviors.get(a);
				if(B!=null)
					applier.apply(B);
			}
		}
		catch (final ArrayIndexOutOfBoundsException e)
		{
		}
	}

	/** Manipulation of the scripts list */
	@Override
	public void addScript(final ScriptingEngine S)
	{
		if(scripts==null)
			scripts=new SVector<ScriptingEngine>(1);
		if(S==null)
			return;
		if(!scripts.contains(S))
		{
			ScriptingEngine S2=null;
			for(int s=0;s<scripts.size();s++)
			{
				S2=scripts.get(s);
				if((S2!=null)&&(S2.getScript().equalsIgnoreCase(S.getScript())))
					return;
			}
			if(scripts.size()==0)
				CMLib.threads().startTickDown(this,Tickable.TICKID_EXIT_BEHAVIOR,1);
			scripts.add(S);
		}
	}

	@Override
	public void delScript(final ScriptingEngine S)
	{
		if(scripts!=null)
		{
			if(scripts.remove(S))
			{
				if(scripts.size()==0)
					scripts=new SVector<ScriptingEngine>(1);
				if(((behaviors==null)||(behaviors.size()==0))&&((scripts==null)||(scripts.size()==0)))
					CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
			}
		}
	}

	@Override
	public void delAllScripts()
	{
		final boolean didSomething=(scripts!=null)&&(scripts.size()>0);
		if(didSomething)
			scripts.clear();
		scripts=null;
		if(didSomething && ((behaviors==null)||(behaviors.size()==0)))
			CMLib.threads().deleteTick(this,Tickable.TICKID_EXIT_BEHAVIOR);
	}

	@Override
	public int numScripts()
	{
		return (scripts == null) ? 0 : scripts.size();
	}

	@Override
	@SuppressWarnings("unchecked")
	public Enumeration<ScriptingEngine> scripts()
	{
		return (scripts == null) ? EmptyEnumeration.INSTANCE : scripts.elements();
	}

	@Override
	public ScriptingEngine fetchScript(final int x)
	{
		try
		{
			return scripts.get(x);
		}
		catch (final Exception e)
		{
		}
		return null;
	}

	@Override
	public void eachScript(final EachApplicable<ScriptingEngine> applier)
	{
		final List<ScriptingEngine> scripts=this.scripts;
		if(scripts!=null)
		try
		{
			for(int a=0;a<scripts.size();a++)
			{
				final ScriptingEngine S=scripts.get(a);
				if(S!=null)
					applier.apply(S);
			}
		}
		catch(final ArrayIndexOutOfBoundsException e)
		{
		}
	}

	@Override
	public int openDelayTicks()
	{
		return 45;
	}

	@Override
	public void setOpenDelayTicks(final int numTicks)
	{
	}

	@Override
	public String L(final String str, final String... xs)
	{
		return CMLib.lang().fullSessionTranslation(str, xs);
	}

	@Override
	public int getSaveStatIndex()
	{
		return getStatCodes().length;
	}

	private static final String[]	CODES	= { "CLASS", "TEXT" };

	@Override
	public String[] getStatCodes()
	{
		return CODES;
	}

	@Override
	public boolean isStat(final String code)
	{
		return CMParms.indexOf(getStatCodes(), code.toUpperCase().trim()) >= 0;
	}

	protected int getCodeNum(final String code)
	{
		for(int i=0;i<CODES.length;i++)
		{
			if(code.equalsIgnoreCase(CODES[i]))
				return i;
		}
		return -1;
	}

	@Override
	public String getStat(final String code)
	{
		switch(getCodeNum(code))
		{
		case 0:
			return ID();
		case 1:
			return text();
		}
		return "";
	}

	@Override
	public void setStat(final String code, final String val)
	{
		switch(getCodeNum(code))
		{
		case 0:
			return;
		case 1:
			setMiscText(val);
			break;
		}
	}

	@Override
	public boolean sameAs(final Environmental E)
	{
		if(!(E instanceof StdExit))
			return false;
		final String[] codes=getStatCodes();
		for(int i=0;i<codes.length;i++)
		{
			if(!E.getStat(codes[i]).equals(getStat(codes[i])))
				return false;
		}
		return true;
	}
}
