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

package key;

import key.collections.*;
import key.util.MultiEnumeration;

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

public class Room extends Landscape
{
	private static final long serialVersionUID = 2178840055236700139L;
	
	public static final int MAX_DESCRIPTION_LINES = Player.MAX_DESCRIPTION_LINES;
	public static final int MAX_DESCRIPTION_BYTES = Player.MAX_DESCRIPTION_BYTES;
	
	public static final AtomicElement[] ELEMENTS =
	{
		AtomicElement.construct( Room.class, String.class, "called",
			AtomicElement.PUBLIC_FIELD,
			"the title of this room" ),
		AtomicElement.construct( Room.class, TextParagraph.class,
			"description", "description",
			AtomicElement.PUBLIC_FIELD,
			"a description of this room",
				//  these constants should be put elsewhere
			AtomicSpecial.TextParagraphLengthLimit( MAX_DESCRIPTION_BYTES,
			                                        MAX_DESCRIPTION_LINES ) ),
		AtomicElement.construct( Room.class, String.class, "portrait",
			AtomicElement.PUBLIC_FIELD,
			"a short description of this room" ),
		AtomicElement.construct( Room.class, String.class, "relation",
			AtomicElement.PUBLIC_FIELD,
			"the type of room, such as 'in', 'on' or 'at'" ),
		AtomicElement.construct( Room.class, String.class, "fullPortrait",
			AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
			"relation followed by portrait" )
	};
	
	public static final AtomicStructure STRUCTURE = new AtomicStructure( Landscape.STRUCTURE, ELEMENTS );
	
	/**
	  *  what the room is called and the player
	  *  sees as a short description
	 */
	public String called;
	
	public TextParagraph description;
	public String portrait;
	public String relation;
	
	public static final int MAX_IN_ROOM = 20;
	
	public Room()
	{
		description = new TextParagraph( "" );
		portrait = "somewhere";
		called = "";
		relation = "in";
		
		setLimit( MAX_IN_ROOM );
		
			//  generally, its okay to go into a room
		permissionList.allow( enterAction );

			//  these would be permission problems if
			//  we had user-coding: some system needs
			//  to be set up here.
		permissionList.allow( addToAction );
		permissionList.allow( removeFromAction );
		
		setConstraint( Type.THING );
		contained = new ObjectCollection();
	}
	
	public Room( Object key )
	{
		this();
		setKey( key );
	}
	
	public AtomicStructure getDeclaredStructure()
	{
		return( STRUCTURE );
	}
	
	public void clearTransient()
	{
		Room backup = (Room) new Search( "landscape/void", Key.instance() ).result;
		
		if( backup == null || backup == this )
		{
			Log.debug( this, "No voidroom in Room::clearTransient" );
			throw new UnexpectedResult( "Cannot clear rooms when there is no void room." );
		}
		
		String msg = getLoginMessage();
		
		for( Enumeration e = players(); e.hasMoreElements(); )
		{
			try
			{
				Player p = (Player) e.nextElement();
				if( p.connected() )
				{
					Room r = (Room) p.getProperty( "lastPublicRoomLocation" );
					
					if( r == null || r == this )
						r = backup;
					
					p.sendSystem( "\n\nThe room around you vanishes!\n\n" );
					p.putCode( 'o', p.getName() );
					p.moveTo( r, null, new key.effect.Enter( p, r, Grammar.substitute( msg, p.getCodes() ) ) );
				}
			}
			catch( PlayerNotConnectedException ex )
			{
			}
		}
		
		super.clearTransient();
	}
	
	public void setCalled( String nc )
	{
		permissionList.check( modifyAction );
		called = nc;
	}
	
		/**  A players aspect is its description when looked at */
	public Paragraph aspect( Player l )
	{
		MultiParagraph.Generator p = new MultiParagraph.Generator();
		
		if( called != null && called.length() > 0 )
			p.append( new TextParagraph( TextParagraph.CENTERALIGNED, called ) );
		
		p.append( description );
		p.append( BlankLineParagraph.BLANKLINE );
		p.append( who( l ) );
		p.append( exits() );
		
		return( p.getParagraph() );
	}
	
		//  returns, for eg: "the courtyard", or
		//  "a place belonging to subtle", or
		//  "a small hill".
	public String portrait()
	{
		return( portrait );
	}
	
	public String relation()
	{
		return( relation );
	}
	
		//  returns, for eg: "in the courtyard", where
		//  'in' is the prop, and 'the courtyard' is the portrait.
		//  META: used to be called 'fullPortrait'
	public String getFullPortrait()
	{
		return( relation + " " + portrait );
	}

	public Paragraph getDescription()
	{
		return( description );
	}

	public String getLoginMessage()
	{
		return( (String) Key.instance().getConfig().getProperty( "loginMessage" ) );
	}

	public String getLogoutMessage()
	{
		return( (String) Key.instance().getConfig().getProperty( "logoutMessage" ) );
	}
	
	public String getCalled()
	{
		return( called );
	}

	public String countPlayers( int np, boolean here )
	{
		if( np == 0 )
			return( "There is no-one in " + getId() );
		else if( np == 1 && here )
			return( "There is nobody here but you" );
		else
		{
			StringBuffer w = new StringBuffer();
			w.append( "There " );

			if( here )
				np--;

			w.append( Grammar.isAreCount( np ) );
			if( here )
				w.append( " other " );
			else
				w.append( " " );
			w.append( Grammar.personPeople( np ) );
			w.append( " here" );

			return( w.toString() );
		}
	}
	
	public Paragraph who( Player l )
	{
		boolean here=isCurrentHere();
		
		int np = numberPlayers();
		
		String cString = countPlayers( np, here );
		
		if( np == 0 || (np == 1 && here) )
			return( new TextParagraph( cString + "." ) );
		else
		{
				// see how tolerate the user is to the titles they see
			if( np < l.titleTolerance )	// remember the orginator is included in the count
			{
				StringBuffer w = new StringBuffer( cString );
				w.append( ":^@\n" );
			
				for( Enumeration e = players(); e.hasMoreElements(); )
				{
					Player p = (Player) e.nextElement();
					if( l != p )
						w.append( p.getTitledName() + "^$\n" );
				}
				return( new TextParagraph( w.toString() ) );
			}
			else
			{
				int i = 0;
					// allplayers is the total count of people in the scape
				String[] allplayers = new String[ np ];
				
					// generate a string array of all the players
				for( Enumeration e = players(); e.hasMoreElements(); )
				{
					Player p = (Player) e.nextElement();
					if( l != p ) // don't include the observer in the list
						allplayers[i++] = p.getName();
				}
						//  META very inefficient: replace with bounded comma seperate
						//  in grammar.
					// other players is all players - 1
				String[] otherplayers = new String[ i ];
				
					// copy from all players to other players (don't include the observer
				for( int j = 0; j < i; j++ )
						otherplayers[j] = allplayers[j];
				
				return( new TextParagraph( cString + ": " + Grammar.commaSeperate( otherplayers ) + ".\n" ) );
				
			}

		}
	}

	public String withPlayers( int np, boolean here, Player o, Player targetPlayer )
	{
		if( np == 0 )
			return( "That's strange..." );
		else if( np == 1 && !here )
			return( targetPlayer.getProperty( "Prefix") + " " + targetPlayer.getProperty( "Name" ) + " is alone" );
		else if( np == 1 && here )
			return( "It's just you here." );
		else if( np == 2 && here )
			return( "Well, its just you and " + targetPlayer.prefix + " " + targetPlayer.getName() );
		else
		{
			StringBuffer w = new StringBuffer();
			w.append( "There " );
			
			// take one off so the target player is not counted in the total...
			np--;
			
			w.append( Grammar.isAreCount( np ) );
			if( here )
				w.append( " other " );
			else
				w.append( " " );
			
			w.append( Grammar.personPeople( np ) );
			w.append( " with " );
			w.append( targetPlayer.prefix + " " + targetPlayer.getName() );
			
			return( w.toString() );
		}
	}
	
	private boolean isCurrentHere()
	{
			//  start out by determining if observer is
			//  a player, and then if the player is *in*
			//  the room
		Player l = Player.getCurrent();
		if( l != null && l.location == this )
			return( true );
		
		return( false );
	}
	
	public String with( Player targetPlayer )
	{
		Player l=null;
		boolean here=isCurrentHere();

		int np = numberPlayers();
		String cString = withPlayers( np, here, l, targetPlayer );

		if( np == 0 || (np == 1 && !here) || (np == 1 && here) || (np == 2 && here) )
			return( cString + "." );
		else
		{
			StringBuffer w = new StringBuffer( cString );
			w.append( ":\n" );

			for( Enumeration e = players(); e.hasMoreElements(); )
			{
				Player p = (Player) e.nextElement();
				if( targetPlayer != p )
				{
					if( p.hidden )
						w.append( "Someone [hiding]" + "\n" );
					else
						w.append( p.prefix + " " + p.getName() + "\n" );
				}
			}
			
			return( w.toString() );
		}
	}
	
		/**  eventually make this a property that can change */
	public Realm getRealm()
	{
		return( Key.instance().getDefaultRealm() );
	}
	
	public Paragraph exits()
	{
		String[] exits = new String[ this.count() ];
		String[] other = new String[ this.count() ];
		int i = 0;
		int k = 0;
		
			// the first iteration is basically to count the number of exits
			// and create an array of exits only.
		for( Enumeration e = elements(); e.hasMoreElements(); )
		{
			Object o = e.nextElement();
			if( o instanceof Exit )
				exits[i++] = ((Exit)o).getName();
			else
				other[k++] = ((Atom)o).getName();
		}
			// i is the number of objects that are actually exits
		if( i >= 1 || k >= 1 )
		{
			MultiParagraph.Generator mp = new MultiParagraph.Generator();
			
			if( i >= 1 )
			{
				/*
				String[] actual = new String[ i ];
				
				for( int j = 0; j < i; j++ )
					actual[j] = exits[j];
				*/
				
				mp.append( new TextParagraph( "Exits: " + Grammar.enumerate( exits, i  ) + "." ) );
			}
			
			if( k >= 1 )
			{
				/*
				String[] actual = new String[ k ];
				
				for( int j = 0; j < k; j++ )
					actual[j] = other[j];
				*/
				
				mp.append( new TextParagraph( "Objects: " + Grammar.enumerate( other, k ) + "." ) );
			}

			return( mp.getParagraph() );
		}
		else
			return( null );
	}
	
	public void ejectPlayer( Player p, Effect enter, Effect leave )
	{
		permissionList.check( ejectFromAction );
		p.ejected( this, enter, leave );
	}
	
	/**
	  *  Only stops a direct trans - not using an exit
	 */
	public boolean canMoveInto( Player p )
	{
		return( permissionList.check( p.getThis(), enterAction ) );
	}
	
	//---  actions  ---//
	
	protected static StringKeyCollection staticActions;
	public static Action ejectFromAction;
	public static Action enterAction;
	public static Action setHomeAction;

	static
	{
		staticActions = new StringKeyCollection();
		ejectFromAction = newAction( Room.class, staticActions, "ejectFrom", false, false );
		enterAction = newAction( Room.class, staticActions, "enter", false, false );
		setHomeAction = newAction( Room.class, staticActions, "setHome", false, false );
	}
	
	public Enumeration getActions()
	{
		return( new MultiEnumeration( staticActions.elements(), super.getActions() ) );
	}
	
	public boolean containsAction( Action a )
	{
		return( staticActions.contains( a ) || super.containsAction( a ) );
	}
	
	/**
	  *  Returns the action corresponding to the
	  *  supplied name.  This routine will need to
	  *  be overriden by sub-classes using actions.
	 */
	public Action getAction( String name )
	{
		Action a = (Action) staticActions.get( name );

		if( a == null )
			return( super.getAction( name ) );
		else
			return( a );
	}
}