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 java.net.*;
import java.io.*;
import java.util.*;
import java.lang.*;

public class Main implements StartupHandler
{
	/**
	  *  Set to true if you want to halve your disk space usage
	 */
	public static final boolean COMPRESS_DISK_FILES = false;
	public static final boolean CONNECT_TO_DATABASE = false;

	/**
	  *  Set to true if you want key to do automatic ident
	  *  lookups
	 */
	public static final boolean SOCKET_IDENT_LOOKUP = true;
	
	private File db;
	private Key key;
	private Registry registry;
	private boolean adminMode;
	
	public Main( boolean create, boolean admin )
	{
		Key.creation = create;
		adminMode = admin;
	}
	
	public int doMenu( Menu m ) throws IOException
	{
		int choice;
		
		do
		{
			System.err.println( m.title );
			int i;
			
			for( i = 0; i < m.options.length; i++ )
			{
				MenuOption mo = m.options[i];
				System.err.println( Integer.toString( mo.number ) + "] " + mo.title );
			}
			
			System.err.println( "\n\n(enter displays this menu again)\nPlease select a menu option: " );
			
			do
			{
				try
				{
					String s = getLine( "[1-" + i + "]: " );
					if( s.length() > 0 )
					{
						choice = Integer.parseInt( s );

						if( choice > i || choice <= 0 )
						{
							System.err.println( "Your choice must be between 1 and " + i + "." );
							choice = -1;
						}
					}
					else
						choice = 0;
				}
				catch( NumberFormatException e )
				{
					System.err.println( "Please make your choice using digits only." );
					choice = -1;
				}
			} while( choice < 0 );
		} while( choice <= 0 );
		
		return( choice );
	}
	
	public boolean findDB()
	{
		db = new File( "database" );
		
		return( db.exists() );
	}
	
	public boolean load()
	{
		if( db.exists() )
		{
			Registry.baseDirectory = Key.confirmedDirectory( "distinct" );
			Log.bootLog( "Loading '" + db.getPath() + "'..." );
			registry = (Registry) Factory.loadObject( db );
			key = Key.instance();
			key.initFieldCache();
			return true;
		}
		
		return( false );
	}
	
	private Player getValidPlayer() throws IOException
	{
		Player p;
		
		do
		{
			String name = getValidName();
			p = (Player) key.getResidents().getElement( name );
			
			if( p != null )
				return( p );
			
			System.err.println( "\nCould not find player '" + name + "'.  Please enter a different name." );
		}
		while( true );
	}

	private File getValidFile() throws IOException
	{
		File f;
		
		do
		{
			String name = getLine( "Filename: " );
			f = new File( name );
			
			if( f.exists() )
				return( f );
			
			System.err.println( "\nCould not find file '" + f.getAbsolutePath() + "'.\nPlease enter a different filename." );
		}
		while( true );
	}
	
	public void addRevoke() throws IOException
	{
		Player p = getValidPlayer();
		
		if( p.isBeyond() )
		{
			p.maybeClearBeyond();
			System.err.println( p.getName() + " no longer has the beyond priviledge." );
		}
		else
		{
			p.maybeSetBeyond();
			System.err.println( p.getName() + " now has the beyond priviledge." );
		}
		
		p.sync();
	}
	
	public void doScript() throws IOException
	{
		System.err.println(
			"\n\n" +
			"Two things are required to execute a script.  First, a player\n" +
			"name to execute the script as, and second, the filename of the\n" +
			"script itself." );
		
		Player p = getValidPlayer();
		File f = null;
		
		File file = getValidFile();
		FileIC fic = new FileIC( file );
		
		runScript( p, file, fic );
	}
	
	public void doConsoleLogin() throws IOException
	{
		System.err.println(
			"\n\n" +
			"Enter a player name to execute the script as." );
		
		Player p = getValidPlayer();
		
		System.err.println(
			"\nConsole Login for " + p.getName() + "...\n" +
			"Press EOF <CONTROL-D on unix or CONTROL-Z on dos> to close the connection.\n" );
		
		ConsoleIC cic = new ConsoleIC();
		
		try
		{
			p.connectTo( cic );
			cic.interactWith( p );
			p.run( cic );
			System.err.println( "Connection terminated." );
		}
		catch( BadKeyException e )
		{
			e.printStackTrace();
		}
		catch( NonUniqueKeyException e )
		{
			e.printStackTrace();
		}
	}
	
	public void runScript( Player p, File file, FileIC fic ) throws IOException
	{
		System.err.println( "\nWould you like to watch the output of the script scroll past?" );
		fic.setDisplay( yesNo() );
		
		System.err.println( "\nExecuting script '" + file.getAbsolutePath() + "'..." );
		
		try
		{
			p.connectTo( fic );
			fic.interactWith( p );
			p.run( fic );
			System.err.println( "Script complete." );
		}
		catch( BadKeyException e )
		{
			e.printStackTrace();
		}
		catch( NonUniqueKeyException e )
		{
			e.printStackTrace();
		}
		catch( NetworkException e )
		{
			System.out.println( e.getMessage() );
		}
		catch( Error e )
		{
			e.printStackTrace();
		}
	}
	
	public String getValidName() throws IOException
	{
		int len;
		boolean con;
		String name;
		
		System.err.println();
		
		do
		{
			con = false;
			name = getLine( "Administrator name: " );
			len = name.length();
			
			if( len < Player.MIN_NAME )
			{
				System.err.println( "Key requires that names are at least " + Player.MIN_NAME + " characters long." );
				con = true;
			}
			else if( len > Player.MAX_NAME )
			{
				System.err.println( "Key requires that names are less than " + Player.MAX_NAME + " characters long." );
				con = true;
			}
			else if( !Grammar.isStringCompletelyAlphabetical( name ) )
			{
				System.err.println( "Names must contain only alphabetical characters." );
				con = true;
			}
			else if( name.equalsIgnoreCase( "quit" ) )
			{
				System.err.println( "You wouldn't be able to log in with this name." );
				con = true;
			}
		} while( con );
		
		return( name );
	}
	
	public void create( Class cl ) throws IOException
	{
		Key.creation = true;
		adminMode = true;
		
		Registry.baseDirectory = Key.confirmedDirectory( "distinct" );
		
		registry = new Registry();
		key = (Key) Factory.makeAtom( cl );
		key.initFieldCache();
		
			//  set up the initial admin player
		System.err.println();
		System.err.println(
			"It is necessary to create a single administration account in order to\n" +
			"allow you to set up the server.  You will now be prompted for the name\n" +
			"and password of this account." );
		
		String name;
		String password;
		Player admin = null;
		
		do
		{
			name = getValidName();
			password = getLine( "Password (will echo): " );
			
			System.err.println();
			System.err.println( "Administrator name: " + name + ", password: " + password + ".  Okay?" );
		} while( !yesNo() );
		
		System.err.println();
		
		try
		{
			admin = (Player) Factory.makeAtom( Player.class, name );
			admin.setPassword( password );
			admin.maybeSetBeyond();
			admin.canSave = true;
			key.getResidents().add( admin );
			admin.sync();
			
			if( registry.getStorageTypeIndex( admin.index, admin.timestamp ) != Registry.STORAGE_LOADED )
			{
				System.err.println( "An error has occurred creating this character: it has not been sync'd." );
				System.exit( 1 );
			}
		}
		catch( BadKeyException e )
		{
			System.err.println( "The name you have selected is not appropriate: " + e.toString() );
			System.exit( 1 );
		}
		catch( NonUniqueKeyException e )
		{
			System.err.println( "The name you have selected is in use for something else: " + e.toString() );
			System.exit( 1 );
		}
		
		key.sync();
		System.err.println( "Created new Administrator '" + name + "'." );
		System.err.println();

		File setupDirectory = new File( "setup" );
		File basicSetup = new File( setupDirectory, "basic.ks" );
		File standardSetup = new File( setupDirectory, "standard.ks" );
		
		if( basicSetup.exists() && standardSetup.exists() )
		{
			int mo = doMenu( seedMenu );
			switch( mo )
			{
				case 1:
				case 2:
				case 3:
					File use;
					
					if( mo == 1 )
						use = basicSetup;
					else if( mo == 2 )
						use = standardSetup;
					else
					{
						System.err.println();
						use = getValidFile();
					}
					
					FileIC fic = new FileIC( use );
					runScript( admin, use, fic );
					break;
				case 4:
					return;
			}
		}
		else
		{
			if( basicSetup.exists() || standardSetup.exists() )
			{
				System.err.println(
				"Unfortunately, this distribution of key seems to be missing one of these\n" +
				"ready-made setup scripts.  They normally reside in the 'setup' directory\n" +
				"of the distribution, and are called 'standard.ks' and 'basic.ks'." );
			}
			else
			{
				System.err.println(
				"Unfortunately, Key is either not running in its distribution directory, or\n" +
				"these scripts have been inadvertently erased." );
			}
			
			System.err.println(
				"\n" +
				"This means that it is impossible at this time to automatically seed the\n" +
				"database: you will need to set it up by hand." ); 
		}
		
			//  this code doesn't get executed if they choose to go
			//  straight to the administration menu from option 4, above
		System.err.println( 
			"\n" +
			"We will now proceed to the administration menu.\n" +
			"Press any key to continue." );
		
		getLine( "" );
	}

	public void boot()
	{
		//System.setSecurityManager( Security.instance );
		key.start();
	}
	
	public void acceptControl( Main m )
	{
		try
		{
			if( findDB() )
			{
				if( load() )
				{
					if( !adminMode )
					{
						boot();
						return;
					}
				}
			}
			else
			{
				System.err.println( "Database file '" + db.getPath() + "' does not exist." );
				System.err.println();
				System.err.println( "Would you like to store Key's files in '" + System.getProperty( "user.dir", "" ) + "'?" );
				
				if( !yesNo() )
				{
					System.err.println();
					System.err.println( "Please run this program from the directory you wish to store the files in." );
					
					String home = System.getProperty( "user.home", null );
					String sep = System.getProperty( "file.separator", null );
					if( home != null && sep != null )
						System.err.println( "For instance, you may wish to run this program from '" + home + sep + "key'." );
					return;
				}
				else
					System.err.println();
				
					//  at the moment, this is hard coded.  we might
					//  change it in the future.
				create( Forest.class );
			}
			
			while( adminMode )
			{
				switch( doMenu( mainMenu ) )
				{
					case 1:
						adminMode = false;
						System.err.println( "\n" );
						break;
					case 2:
						addRevoke();
						break;
					case 3:
						doScript();
						break;
					case 4:
						doConsoleLogin();
						break;
					case 5:
						System.err.println( "\n" );
						key.shutdown();
						System.exit( 0 );
						break;
				}
			}
			
			boot();
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}
	}
	
	public boolean yesNo() throws IOException
	{
		String yn = getLine( "(y/N): " );

		if( yn.length() > 0 )
		{
			char c = yn.charAt( 0 );
			
			if( c == 'y' || c == 'Y' )
				return( true );
		}

		return false;
	}
	
	BufferedReader br = new BufferedReader( new InputStreamReader( System.in ) );
	
	public String getLine( String prompt ) throws IOException
	{
		System.err.print( prompt );
		System.err.flush();
		return( br.readLine() );
	}
	
	public static void main( String args[] )
	{
		boolean admin = false;
		boolean gui = false;
		boolean creation = false;
		
		for( int i = 0; i < args.length; i++ )
		{
			if( args[i].equalsIgnoreCase( "admin" ) )
				admin = true;
			else if( args[i].equalsIgnoreCase( "swing" ) )
				gui = true;
			else if( args[i].equalsIgnoreCase( "creation" ) )
				creation = true;
			else
			{
				System.err.println( "Unknown argument '" + args[i] + "'" );
				System.err.println(
					"Available arguments:\n" +
					"\n" +
					"\tadmin   \tStarts the program in an interactive mode\n" +
					"\tswing   \tAllows program monitoring with a Swing GUI\n" +
					"\tcreation\tAllows the program to create directories as needed\n" );
				
				return;
			}
		}
		
		System.err.println( Key.copyright );
		System.err.println();
		
		try
		{
			StartupHandler sh = null;
			
			if( gui )
			{
				try
				{
					Class c = Class.forName( "key.swing.Startup" );
					sh = (StartupHandler) c.newInstance();
				}
				catch( Exception e )
				{
					System.err.println( "Could not initialise swing interface:" );
					e.printStackTrace( System.err );
				}
			}
			
			Main m = new Main( creation, admin );
			
			if( sh == null )
				sh = m;
			
			sh.acceptControl( m );
		}
		catch( ExceptionInInitializerError e )
		{
			System.out.println( e.toString() + ":" );
			Throwable t = e.getException();
			t.printStackTrace();
		}
		catch( Exception e )
		{
			System.out.println( e.toString() );
			e.printStackTrace( System.out );
		}
	}
	
	static MenuOption[] mainMenuOptions = 
	{
		new MenuOption( 1, "Start Key" ),
		new MenuOption( 2, "Add or Revoke Administrator priviledges" ),
		new MenuOption( 3, "Execute a KeyScript" ),
		new MenuOption( 4, "Log in on the console" ),
		new MenuOption( 5, "Exit" )
	};
	
	static Menu mainMenu = new Menu( "\n\nKey Administration Menu\n", mainMenuOptions );
	
	static MenuOption[] seedMenuOptions = 
	{
		new MenuOption( 1, "Basic (commands and ranks only)" ),
		new MenuOption( 2, "Standard (basic + sample room descriptions & help files)" ),
		new MenuOption( 3, "A custom script" ),
		new MenuOption( 4, "Exit to the administration menu" )
	};
	
	static Menu seedMenu = new Menu(
		"A brand new Key database is generally fairly sparse and unusable.  It is\n" +
		"strongly recommended that you seed the initial database using one of the\n" +
		"provided setup scripts.\n", seedMenuOptions );
}

class MenuOption
{
	String title;
	int number;

	MenuOption( int n, String t )
	{
		number = n;
		title = t;
	}
}

class Menu
{
	MenuOption[] options;
	String title;
	
	Menu( String t, MenuOption[] o )
	{
		title = t;
		options = o;
	}
}