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

/**
 */
public abstract class SocketIC
extends InteractiveConnection
{
	public static final AtomicElement[] ELEMENTS =
	{
			//  String getName();
		AtomicElement.construct( SocketIC.class, Integer.TYPE, "localPort",
			AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
			"the local port number of this connection" ),
		AtomicElement.construct( SocketIC.class, Integer.TYPE, "remotePort",
			AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
			"the remote port number of this connection" ),
		AtomicElement.construct( SocketIC.class, String.class, "address",
			AtomicElement.PUBLIC_ACCESSORS | AtomicElement.READ_ONLY,
			"the address that this connection is to" ),
		AtomicElement.construct( SocketIC.class, Site.class, "site",
			AtomicElement.PUBLIC_FIELD,
			"the site that this connection is to" )
	};
	
	public static final AtomicStructure STRUCTURE = new AtomicStructure( InteractiveConnection.STRUCTURE, ELEMENTS );
	public static final int MAXIMUM_CONNECTIONS_OPEN = 200;
	private static int connectionCount = 0;
	
	Socket socket;
	Writer output;
	PushbackInputStream inputStream;
	public transient Site site;
	
	public int getLocalPort() { return( socket.getLocalPort() ); }
	public int getRemotePort() { return( socket.getPort() ); }
	public String getAddress() { return( socket.getInetAddress().toString() ); }
	
	String identString = "";
	
	/**
	  *  The constructor must take a socket as its sole
	  *  argument
	  *
	  * @param s The socket that the connection is on
	 */
	public SocketIC( Socket s ) throws IOException
	{
		socket = s;
		connectionCount++;
		
		try
		{
			output = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
			inputStream = new PushbackInputStream( socket.getInputStream() );
		}
		catch( IOException e )
		{
			close();
			throw e;
		}
		
		if( connectionCount > MAXIMUM_CONNECTIONS_OPEN )
		{
			close();
			throw new LimitExceededException( "too many open connections" );
		}
		
		InetAddress inetAddress = s.getInetAddress();
		//Log.debug( this, "Connected to: " + inetAddress.getHostName() +":"+ socket.getPort() );
		
		Internet inet = Key.getInternet();
		byte[] baddress = inetAddress.getAddress();
		int[] address = new int[ baddress.length ];
		for( int i = 0; i < baddress.length; i++ )
		{
			if( baddress[i] < 0 )
				address[i] = 256 + baddress[i];
			else
				address[i] = baddress[i];
		}
		
		site = inet.search( address );
		if( site == null )
		{
			//Log.debug( this, "search for " + inetAddress.toString() + " failed." );
			site = new Site( address );
			inet.insert( site );
		}
		//else
			//Log.debug( this, "search (inet) suceeded: " + site.toString() );
		
		String dns = inetAddress.getHostName();
		if( dns != null && !Character.isDigit( dns.charAt( 0 ) ) )
			site.registerSite( dns );
		
		site.connectionStats().startConnection();
		
		if( key.Main.SOCKET_IDENT_LOOKUP )
		{
			new key.util.Ident( socket, new key.util.Ident.Callback()
			{
				public void identResolvedError( String error )
				{
					identString = "<" + error + "> ";
				}
				
		        public void identProtocolError( String error )
				{
					identString = "<" + error + "> ";
				}
				
		        public void identResolvedUserId( String machine, String user )
				{
					identString = "<" + machine + "> " + user + "@";
				}
			} ).start();
		}
	}
	
	public String getName()
	{
		return( super.getName() + " " + getFullSiteName() );
	}
	
	public AtomicStructure getDeclaredStructure()
	{
		return( STRUCTURE );
	}
	
	public final String getSiteName()
	{
		return( socket.getInetAddress().getHostName() );
	}

	public final boolean newbiesAllowed()
	{
		return( site.newbiesAllowed() );
	}
	
	public void flush()
	{
		try
		{
			output.flush();
		}
		catch( IOException e )
		{
			throw new NetworkException( e );
		}
	}
	
	public final String getFullSiteName()
	{
		InetAddress ia = socket.getInetAddress();
		return( identString + ia.getHostName() + " [" + ia.getHostAddress() + "]" );
	}
	
	public final String getSiteIP()
	{
		InetAddress ia = socket.getInetAddress();
		return( ia.getHostAddress() );
	}
	
	public final Site getSite()
	{
		return( site );
	}
	
	public final void discard() throws IOException
	{
		inputStream.skip( inputStream.available() );
	}

	public final void printStackTrace( Throwable t )
	{
		PrintWriter pw = new PrintWriter( output );
		t.printStackTrace( pw );
		pw.flush();
	}
	
	public void finalize() throws Throwable
	{
		super.finalize();
		
		if( socket != null )
		{
			try
			{
				socket.close();
			}
			catch( IOException e )
			{
			}
			finally
			{
				socket = null;
				connectionCount--;
			}
		}
	}
	
	public synchronized void close()
	{
		super.close();
		
		if( site != null )
		{
			site.connectionStats().endConnection();
			site = null;
		}
		
		if( socket != null )
		{
			try
			{
				socket.close();
			}
			catch( Exception t )
			{
			}
			finally
			{
				if( socket != null )
				{
					socket = null;
					connectionCount--;
				}
				
				Registry.instance.delete( this );
			}
		}
	}
	
	/**
	  * return true if the socket is still open
	 */
	public boolean isConnected()
	{
		if( socket != null )
			return true;
		else
			return false;
	}
	
	/**
	  *  This function will be called every 'now and
	  *  again', as a 'check' function when no input
	  *  is required (we're not waiting), but some
	  *  is allowed.  This is useful for the telnet
	  *  protocol in particular, which can recieve
	  *  IAC's before the login prompt.  This function
	  *  should be harmless, no matter when its called
	 */
	public abstract void check() throws IOException;
}