key0-96/
key0-96/doc/key/
key0-96/doc/key/credits/
key0-96/doc/key/developers/
key0-96/doc/key/developers/resources/
key0-96/setup/caves/
key0-96/setup/help/
key0-96/setup/ruins/
key0-96/src/
key0-96/src/commands/
key0-96/src/events/
key0-96/src/hack/
key0-96/src/sql/
key0-96/src/swing/
key0-96/src/talker/forest/
key0-96/src/talker/objects/
key0-96/src/terminals/
/*
**               j###t  ########## ####   ####
**              j###t   ########## ####   ####
**             j###T               "###L J###"
**          ######P'    ##########  #########
**          ######k,    ##########   T######T
**          ####~###L   ####
**          #### q###L  ##########   .#####
**          ####  \###L ##########   #####"
**
**  $Id$
**
**  Class History
**
**  Date        Name         Description
**  ---------|------------|-----------------------------------------------
**  19Aug98     subtle       start of recorded history
**
*/

package key;

import key.util.Trie;

import java.util.StringTokenizer;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.*;

/**
  *  The base class for a command also includes some nice utility
  *  routines that command writers tend to need
 */
public abstract class Command extends Atom implements Commandable
{
	private static final long serialVersionUID = 7107251377754958246L;
	public static final AtomicElement[] ELEMENTS =
	{
		AtomicElement.construct( Command.class, String.class, "usage",
			AtomicElement.PUBLIC_FIELD,
			"the usage specifier for this command" ),
		AtomicElement.construct( Command.class, Boolean.TYPE, "disabled",
			AtomicElement.PUBLIC_FIELD,
			"true if this command can't be used (usually set when something goes wrong)" )
	};
	
	public static final AtomicStructure STRUCTURE = new AtomicStructure( Atom.STRUCTURE, ELEMENTS );
	
	protected String usage = "~unspecified~";
	protected boolean disabled = false;
	
	public Command()
	{
	}
	
	public AtomicStructure getDeclaredStructure()
	{
		return( STRUCTURE );
	}
	
	public boolean isDisabled()
	{
		return( disabled );
	}
	
	public void disable()
	{
		disabled = true;
	}
	
	public void usage( InteractiveConnection ic )
	{
		ic.send( "Format: " + getName() + " " + usage );
	}

	public void usage( InteractiveConnection ic, CategoryCommand caller )
	{
		ic.send( "Format: " + getQualifiedName( caller ) + " " + usage );
	}
	
	public final String getQualifiedName( CategoryCommand caller )
	{
		if( caller instanceof Atom )
			return( ((Atom)caller).getName() + " " + getName() );
		else
			return( getName() );
	}

	public String getWhichId()
	{
		return( getId() );
	}
	
	/**
	  *  If this method is not overridden, it calls
	  *  the others.  Only override *one*, depending on
	  *  what arguments you want to recieve
	  *  <p>
	  *  Presumably you'll want to do one or the other,
	  *  overload the method you'd prefer
	  * @param p the player who is executing the command
	  * @param args a tokenizer (whitespace) for the command arguments
	  * @param fullLine the complete line that was typed
	  * @param caller the parent CategoryCommand that contains this command
	  * @param ic the interactiveConnection to send the results to
	 */
	public abstract void run( Player p, StringTokenizer args, String fullLine, CategoryCommand caller, InteractiveConnection ic, Flags flags ) throws IOException;

	/**
	  *  An atom is returned in case they are like 'player,player,player',
	  *  so it will return a scape.
	 */
	public static final Splashable getPlayerInside( Player p, InteractiveConnection ic, String name, Scape inside )
	{
		Object t = inside.getPlayer( p, name );
		if( t == null )
		{
			ic.sendError( "Cannot find player '" + name + "' online" );
			return null;
		}
		else if( t instanceof Splashable )
		{
			return( (Splashable) t );
		}
		else if( t instanceof Trie )
		{
				//  its more efficient to only work with online
				//  players - ie, prefer only holds when they're
				//  around - because its a smaller list to scan
			for( Enumeration e = ((Trie)t).elements(); e.hasMoreElements(); )
			{
				Player o = (Player) e.nextElement();
				
				if( p.getPrefer().containsPlayer( o ) )
					return( o );
			}
			
			int i = ((Trie)t).length();

			if( i > 10 )
				ic.sendError( i + " multiple matches (not listed)" );
			else
				ic.sendError( "Multiple matches: " + ((Trie)t).contents() );
			
			return null;
		}
		else
		{
			ic.sendError( "'" + name + "' is not a player" );
			return null;
		}
	}
	
	/**
	  *  The basic concept is this:  The routine returns a player, or null.
	  *  If it returns null, it has already output an error message to the
	  *  player involved, so you don't have to.  (just return)
	  *
	  *  An atom is returned in case they are like 'player,player,player',
	  *  so it will return a scape
	 */
	public static final Splashable getOnlinePlayer( Player p, InteractiveConnection ic, String name )
	{
		if( name.equalsIgnoreCase( "me" ) )
			return( p );
		else
			return( getPlayerInside( p, ic, name, Key.instance() ) );
	}
	
	public static final Symbol getReferenceSymbolInside( InteractiveConnection ic, String s, Atom inside )
	{
		try
		{
			Object o = new Search( s, inside, false, true ).result;
			if( o == null )
				ic.sendError( "Could not find '" + s + "'" );
			else
			{
				if( o instanceof Atom || o instanceof Reference )
					return( (Symbol) o );
				else if( o instanceof Trie )
				{
					int i = ((Trie)o).length();
					if( i > 10 )
						ic.sendError( i + " multiple matches (not listed)" );
					else
						ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
				}
				else
					ic.sendError( "'" + s + "' is non-atomic" );
			}
		}
		catch( NoSuchPropertyException e )
		{
			ic.sendError( e.getMessage() );
		}

		return( null );
	}
	
	public static final Atom getSymbolInside( InteractiveConnection ic, String s, Atom inside )
	{
		try
		{
			Object o = new Search( s, inside ).result;
			if( o == null )
				ic.sendError( "Could not find '" + s + "'" );
			else
			{
				if( o instanceof Atom )
					return( (Atom) o );
				else if( o instanceof Trie )
				{
					int i = ((Trie)o).length();
					if( i > 10 )
						ic.sendError( i + " multiple matches (not listed)" );
					else
						ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
				}
				else
					ic.sendError( "'" + s + "' is non-atomic" );
			}
		}
		catch( NoSuchPropertyException e )
		{
			ic.sendError( e.getMessage() );
		}

		return( null );
	}
		
	/**
	  *  A slightly higher level routine - will match the
	  *  symbol and give error messages as appropriate.  It
	  *  is not necessary to check for anything other than
	  *  the return being null, in which case an error
	  *  has already been sent to the provided connection.
	 */
	public static final Atom getSymbol( InteractiveConnection ic, String s )
	{
		return( getSymbolInside( ic, s, Key.instance() ) );
	}

	public static final Reference getReferenceElementInside( InteractiveConnection ic, String s, Container inside )
	{
		Object o = inside.getReferenceElement( s );
		if( o != null )
		{
			if( o instanceof Atom )
				return( ((Atom)o).getThis() );
			else if( o instanceof Reference )
				return( (Reference) o );
			else if( o instanceof Trie )
			{
				int i = ((Trie)o).length();
				if( i > 10 )
					ic.sendError( i + " multiple matches (not listed)" );
				else
					ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
			}
			else
				ic.sendError( "'" + s + "' is non-atomic or doesn't match required type" );
		}

		return( null );
	}
	
	public static final Atom getElementInside( InteractiveConnection ic, String s, Type type, Container inside )
	{
		Object o = inside.getElement( s );
		if( o != null )
		{
			if( o instanceof TransitAtom )
				o = ((TransitAtom)o).getRealAtom();
			
			if( o instanceof Atom && Type.typeOf( o ).isA( type ) )
				return( (Atom) o );
			else if( o instanceof Trie )
			{
				int i = ((Trie)o).length();
				if( i > 10 )
					ic.sendError( i + " multiple matches (not listed)" );
				else
					ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
			}
			else
				ic.sendError( "'" + s + "' is non-atomic or doesn't match required type" );
		}
		else
			ic.sendError( "Couldn't find '" + s + "'" );
		
		return( null );
	}
	
	public static final Thing getObjectFromInventory( Player p, InteractiveConnection ic, String name )
	{
		return( getObjectIn( p, p.getInventory(), ic, name, "You don't seem to be holding that object." ) );
	}
	
	public static final Thing getObjectFromLocation( Player p, InteractiveConnection ic, String name )
	{
		return( getObjectIn( p, p.getLocation(), ic, name, "That object isn't in this room." ) );
	}
	
	public static final Thing getObjectIn( Player p, Container c, InteractiveConnection ic, String name, String notFound )
	{
		Object temp = c.getElement( name );
		
		if( temp == null )
		{
			if( notFound != null )
				ic.sendFeedback( notFound );
		}
		else
		{
			if( temp instanceof TransitAtom )
				temp = ((TransitAtom)temp).getRealAtom();
			
			if( !(temp instanceof Thing) )
			{
				if( temp instanceof Trie )
					ic.sendError( "Multiple matches: " + ((Trie)temp).contents() );
				else if( notFound != null )
					ic.sendError( "This object does not seem to be one of the primitive types of matter." );
			}
			else
				return( (Thing) temp );
		}
		
		return( null );
	}
	
	public static final Atom getElementInside( InteractiveConnection ic, String s, Container inside )
	{
		Object o = inside.getElement( s );
		if( o != null )
		{
			if( o instanceof TransitAtom )
				o = ((TransitAtom)o).getRealAtom();
			
			if( o instanceof Atom )
				return( (Atom) o );
			else if( o instanceof Trie )
			{
				int i = ((Trie)o).length();
				if( i > 10 )
					ic.sendError( i + " multiple matches (not listed)" );
				else
					ic.sendError( "Multiple matches: " + ((Trie)o).contents() );
			}
			else
				ic.sendError( "'" + s + "' is non-atomic" );
		}
		else
			ic.sendError( "Cannot find '" + s + "'" );
		
		return( null );
	}
	
	public static final Atom getSymbolInside( InteractiveConnection ic, String s, Type type, Atom i )
	{
		Atom a = getSymbolInside( ic, s, i );
		if( a != null )
		{
			if( Type.typeOf( a ).isA( type ) )
				return( a );
			else
				ic.sendError( a.getName() + " is not a " + type.getName() );
		}

		return( null );
	}

	public static final Atom getSymbol( InteractiveConnection ic, String s, Type type )
	{
		return( getSymbolInside( ic, s, type, Key.instance() ) );
	}
	
	/**
	  *  This routine will return a Player or null.
	 */
	public static final Player getPlayer( InteractiveConnection ic, String name )
	{
		Object t = Key.instance().getResidents().getElement( name );
		if( t == null )
		{
			ic.sendError( "No player of the name '" + name + "'" );
			return null;
		}
		else if( t instanceof Player )
		{
			return( (Player) t );
		}
		else if( t instanceof Trie )
		{
			int i = ((Trie)t).length();
			if( i > 10 )
				ic.sendError( i + " multiple matches (not listed)" );
			else
				ic.sendError( "Multiple matches: " + ((Trie)t).contents() );
			return null;
		}
		
		ic.sendError( "'" + name + "' is not a player" );
		return null;
	}

	public final String nextArgument( StringTokenizer st, InteractiveConnection ic )
		throws NotEnoughParametersException
	{
		if( st.hasMoreTokens() )
			return( st.nextToken() );
		
		usage( ic );
		throw new NotEnoughParametersException();
	}
	
	public String getUsage()
	{
		return( usage );
	}
	
	/**
	  *  This routine is notified just before a command is executed.
	  *  If it returns this (the default), then run() will be called.
	  *  If it returns null, another matching command will be looked
	  *  for (this might provide a neat way of disabling commands).
	  *  If it returns another command, that command has getMatch
	  *  called on it, and the process repeats.  This allows for
	  *  CategoryCommands to be implemented.
	 */
	public Commandable getMatch( final Player p, key.util.StringTokenizer st )
	{
		return( this );
	}

	public Match getFinalMatch( final Player p, key.util.StringTokenizer st )
	{
		return( new Command.Match()
		{
			{
				match = Command.this;
			}
			
			public String getErrorString()
			{
				return( "" );
			}
		} );
	}
	
	public boolean recloneArgs()
	{
		return( false );
	}
	
	public static abstract class Match
	{
		public Commandable match = null;
		public Commandable lastchance = null;
		public abstract String getErrorString();
	}
}