/*
 Eteria IRC Client, an RFC 1459 compliant client program written in Java.
 Copyright (C) 2000-2001  Javier Kohen <jkohen at tough.com>
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
 
 import java.applet.AudioClip;
 import java.applet.Applet;
 import java.awt.*;
 import java.awt.event.*;
 import java.io.*;
 import java.lang.reflect.*;
 import java.net.*;
 import java.text.*;
 import java.util.*;
 
 import ar.com.jkohen.applet.*;
 import ar.com.jkohen.awt.*;
 import ar.com.jkohen.awt.event.*;
 import ar.com.jkohen.irc.*;
 import ar.com.jkohen.net.*;
 import ar.com.jkohen.util.*;
 import com.splendid.awtchat.*;
 
 /**
  * This is Eteria IRC Client's core class. It contains the main control loop, some high level communication methods and topmost-GUI code.
  *
  * @author Javier Kohen
  */
 
 public class EIRC extends Applet implements ClientProcess, Observer, ActionListener, ItemListener, MouseListener, ChatPanelListener, WindowListener, Runnable
 {
 	/**
 	 * Program's name.
 	 * Used as part of "About" info.
 	 */
 
 	public final static String PACKAGE = "Eteria IRC Client beta";
 
 	/**
 	 * Program's release version.
 	 * Used as part of "About" info.
 	 * Should be either a "YYYYMMDD" date or a "X.Y.Z" version number.
 	 */
 
 	public final static String VERSION = "20120410.12.55";
 
 	/**
 	 * Program's release extra version.
 	 * Used as part of "About" info.
 	 * Should be a String describing in-site's modifications.
 	 */
 
 	public final static String VERSION_EXTRA = "version www.coolsmile.net + avatars";
 
 	/**
 	 * Author's name.
 	 * Used as part of "About" info.
 	 */
 
 	public final static String AUTHOR = "Javier Kohen";
 
 	private static Hashtable commands;
 	private static ResourceBundle user_commands;
 	private static Resources res;
 
 	private Collator collator;
 	private Vector channels;
 	private CollatedHashtable channel_windows;
 	private CollatedHashtable privates;
 	private Vector ignore_list;
 	private Vector names;
 	
 //	private boolean missing_default_channel_in_list;

 
 	// Used to build the users list from the server replies to the WHO command.

 	private Vector who_reply;
 
 	// Store a channel's +b/e/I list

 	private Vector list;
 
 	private Properties server_support;
 	private ConfigurationProperties properties;
 
 	private static String defaultfont = "SansSerif";
 	private static Font mainfont = new Font(defaultfont, Font.PLAIN, 12);
 	private static Color mainbg = SystemColor.control ;
 	private static Color mainfg = SystemColor.controlText;
 	private static Color textbg = SystemColor.window;
 	private static Color textfg = SystemColor.textText;
 	private static Color selbg = SystemColor.textHighlight;
 	private static Color selfg = SystemColor.textHighlightText;
 
 	/* GUI. */
 	private Image main_icon;
 	private TextField nick_entry;
 	private Label label;
 	private Label away;
 	private PopupMenu menu;
 	private ControlPanel control_panel;
 	private ChatPanelContainer chat_panel;
 	private StatusWindow status;
 	private ChanListWindow chan_list;
 	private WhoListWindow who_list;
 	private OutputWindow debug_window;
 	private Frame f;
 	private boolean application = false;
 	private Hashtable color_list;
 
 	private String current_server;
 	private String status_tag;
 	private String current_nick, username, realname;
 	private String new_nick;
 	private String original_nick;
 	private ServerProcess server;
 	private IRCServices irc_services;
 	private boolean is_away;
 	private String away_str;
 	
 	/* Configuration properties. */
 	private boolean special_services;
 	private int debug_traffic;
 	private String nicksrv_pass;
 	private String irc_pass;
 	private String quit_message;
 	private boolean request_motd;
 	private boolean see_everything_from_server;
 	private boolean see_join;
 	private boolean see_invite;
 	private boolean on_dcc_notify_peer;
 	private String service_bots;
 	private boolean hideip;
 	private boolean focus_opening_privates;
 	private boolean no_privates;
 	private boolean auto_list;
 	private String user_modes;
 	private int write_color;
 	private int scroll_speed;
 	private int silent;
 	private AudioClip event_sounds[];
 	private String decoding, encoding;
 	private String cfg_name;
 
 	private String network_name = "";
 	private String default_join;
 	private int restrict_join;
 	private int ping;
 	private boolean connected;
 	private boolean logged_in;
 	private boolean send_quit;
 	private boolean server_admin;
 	private boolean ircop_override;
 	private boolean who_invisible;
 	private boolean login_time;
 
 	// Dialogs.

 	private static Configurator configurator;
 	private static ModeList mode_list;
 	private static TextAreaCopy tac;
 	private static ASLBox asl;
 	private static HelpBox help;
 
 	private MessageFormat chan_title, private_title, chanlist_title, wholist_title;
 
 	static
 	{
 		commands = new Hashtable(100);
 		commands.put("PING", new Integer(-1));
 		commands.put("NICK", new Integer(-2));
 		commands.put("JOIN", new Integer(-3));
 		commands.put("MODE", new Integer(-4));
 		commands.put("PART", new Integer(-5));
 		commands.put("QUIT", new Integer(-6));
 		commands.put("KICK", new Integer(-7));
 		commands.put("TOPIC", new Integer(-8));
 		commands.put("PRIVMSG", new Integer(-9));
 		commands.put("NOTICE", new Integer(-10));
 		commands.put("ERROR", new Integer(-11));
 		commands.put("WALLOPS", new Integer(-12));
 		commands.put("INVITE", new Integer(-13));
 		commands.put("PONG", new Integer(-14));
 		commands.put("001", new Integer(001));
 		commands.put("005", new Integer(005));
 		commands.put("232", new Integer(232));
 		commands.put("251", new Integer(251));
 		commands.put("301", new Integer(301));
 		commands.put("303", new Integer(303));
 		commands.put("302", new Integer(302));
 		commands.put("305", new Integer(305));
 		commands.put("306", new Integer(306));
 		commands.put("307", new Integer(307));
   		commands.put("310", new Integer(310));
   		commands.put("311", new Integer(311));
 		commands.put("312", new Integer(312));
   		commands.put("313", new Integer(313));
   		commands.put("314", new Integer(314));
   		commands.put("315", new Integer(315));
   		commands.put("317", new Integer(317));
 		commands.put("318", new Integer(318)); // No action needed.

 		commands.put("319", new Integer(319));
   		commands.put("320", new Integer(320));
   		commands.put("321", new Integer(321));
 		commands.put("322", new Integer(322));
   		commands.put("323", new Integer(323));
   		commands.put("324", new Integer(324));
 		commands.put("329", new Integer(329)); // No action needed.

 		commands.put("331", new Integer(331));
 		commands.put("332", new Integer(332));
   		commands.put("333", new Integer(333));
   		commands.put("335", new Integer(335));
 		commands.put("346", new Integer(346));
 		commands.put("347", new Integer(347));
 		commands.put("348", new Integer(348));
   		commands.put("349", new Integer(349));
 	  	commands.put("352", new Integer(352));
   		commands.put("353", new Integer(353));
 		commands.put("366", new Integer(366));
 		commands.put("367", new Integer(367));
 		commands.put("368", new Integer(368));
 		commands.put("371", new Integer(371));
 		commands.put("372", new Integer(372));
 		commands.put("375", new Integer(375));
 		commands.put("376", new Integer(376));
 		commands.put("378", new Integer(378));
 		commands.put("381", new Integer(381));
 		commands.put("391", new Integer(391));
 		commands.put("401", new Integer(401));
 		commands.put("403", new Integer(403));
 		commands.put("404", new Integer(404));
 		commands.put("406", new Integer(406));
 		commands.put("421", new Integer(421));
 		commands.put("422", new Integer(422));
 	  	commands.put("432", new Integer(432));
   		commands.put("433", new Integer(433));
 		commands.put("437", new Integer(437));
   		commands.put("438", new Integer(438));
 		commands.put("447", new Integer(447));
   		commands.put("461", new Integer(461));
 		commands.put("464", new Integer(464));
 		commands.put("465", new Integer(465));
   		commands.put("471", new Integer(471));
   		commands.put("473", new Integer(473));
 	  	commands.put("474", new Integer(474));
 	  	commands.put("475", new Integer(475));
 		commands.put("477", new Integer(477));
 		commands.put("478", new Integer(478));
 	  	commands.put("481", new Integer(481));
 	  	commands.put("482", new Integer(482));
 	  	commands.put("489", new Integer(489));
 	  	commands.put("491", new Integer(491));
 		commands.put("536", new Integer(536));
 	  	commands.put("600", new Integer(600));
 	  	commands.put("601", new Integer(601));
 	  	commands.put("604", new Integer(604));
 	  	commands.put("605", new Integer(605));
 	  	commands.put("671", new Integer(671));
 	}
 
 	/**
 	 * Constructor for Applet instances.
 	 * Must be present, even if empty.
 	 */
 
 	public EIRC()
 	{
 	}
 
 
 	/**
 	 * Execution starts here when the program is run out of Applet context.
 	 * This method supplies a simple substitute for the Applet viewer.
 	 *
 	 * @param args a String[] containing command-line arguments.
 	 */
 
 	public static void main(String args[])
 	{
 		EIRC eirc = new EIRC();
 		eirc.application = true;
 		eirc.setStub(new SimpleAppletStub(args));
 		eirc.init();
 		eirc.start();
 		new Thread(eirc).start();
 	}
 	
 	public void init()
 	{
 		this.collator = RFC1459.getCollator();
 
 	  	this.channels = new Vector();
 		this.channel_windows = new CollatedHashtable(collator);
 		this.privates = new CollatedHashtable(collator);
 		this.ignore_list = new Vector();
 		this.names = new Vector();
 		this.server_support = new Properties();
 		this.event_sounds = new AudioClip[res.EVENTS];
 
 		res = new Resources(this, getParameter("language"));
 		
 
 		/* Initialize with the default configuration */
 
 		Properties default_props = new Properties();
  		try
  		{
  			default_props.load(Resources.getResourceAsStream("configuration.properties"));
 		}
  		catch (IOException ex)
  		{
  			System.err.println(ex);
  		}
 
 		/* Get saved configuration */
 
 		cfg_name = getParameter("configuration");	
 		if (application)
 			properties = new LocalConfiguration(cfg_name, default_props);
 		else
 			properties = new NetworkConfiguration(this, cfg_name, default_props);
 		properties.addObserver(this);
 
 
 		/* Default quit */
 		
 		this.quit_message = res.getString("eirc.default_quit");
 		if(properties.getString("quit_message") == null || properties.getString("quit_message").equals(""))
 			properties.setString("quit_message", quit_message);
 
 		/* Read user's colors and sounds configurations */
 		
 		Vector sound_list = new Vector();
 		color_list = new Hashtable();
 		for (Enumeration e = properties.keys(); e.hasMoreElements() ;)
 		{
 			String key = (String)e.nextElement();
 			try
 			{
 				if (key.indexOf("sound.") == 0 && Integer.parseInt(key.substring(6)) > 0)
 					sound_list.addElement(properties.getProperty(key));
 				if (key.indexOf("color.") == 0)
 					color_list.put(key.substring(6), Color.decode(properties.getProperty(key)));
 			}
 			catch(NumberFormatException ex) {}
 		}
 
 		/* Load sounds in background */
 
 		if (properties.getBoolean("load_sounds"))
 		{
 			res.addObserver(this);
 			res.cacheSounds(sound_list, properties.getProperty("sound.path"));
 		}
 
 		/* Force an update to initialize some variables to their defaults. */
 		update(properties, null);
 
 
 //		EIRC.user_commands = ResourceBundle.getBundle("Commands");

 		EIRC.user_commands = new Commands();
 
 		/* Read text colors */
 		Color fixedColors[] = new Color[16]; // 16 colors + 2 (fg/bg) internal.

 		for (int i = 0; i < fixedColors.length; i++)
 		{
 			try
 			{
 				fixedColors[i] = Color.decode(properties.getString("n" + i));
 			}
 			catch (Exception ex)
 			{
 				fixedColors[i] = Color.black;
 				System.err.println(ex);
 			}
 		}
 		SmileyTextArea.setColors(fixedColors);
 		TextAttributePicker.setColors(fixedColors);
 		int order[] = new int[16];
 		try
 		{
 			for (int i = 0; i < order.length; i++)
 				order[i] = Integer.parseInt(properties.getString("c" + i));
 		}
 		catch (Exception ex)
 		{
 			for (int i = 0; i < order.length; i++)
 				order[i] = i;
 		}
 		TextAttributePicker.setOrder(order);
 
 
 		for (Enumeration e = Resources.lang_resource.propertyNames(); e.hasMoreElements() ;)
 		{
 			String key = (String)e.nextElement();
 			if (key.indexOf("tld.") == 0)
 				NickInfo.addTLD(key.substring(key.indexOf(".") + 1), res.getString(key));
 		}
 		try
 		{
 			NickInfo.setParseASLType(Integer.parseInt(properties.getString("default_asl")));
 		} catch (NumberFormatException e) {}
 
 
 		chan_title = new MessageFormat(res.getString("top_panel.chan"));
 		private_title = new MessageFormat(res.getString("top_panel.private"));
 		chanlist_title = new MessageFormat(res.getString("top_panel.chanlist"));
 		wholist_title = new MessageFormat(res.getString("top_panel.wholist"));
 		main_icon = res.getImage("icon");
 
 		irc_services = new IRCServices(this);
 
 
 		String mySys = System.getProperty("os.name").replace(' ', '_');
 		if (mySys.indexOf("Windows") == 0)
 			mySys = "Windows";
 		SimpleAppletContext.setPath(properties.getProperty("navigator." + mySys));
 
 		this.default_join = getParameter("join");
 
 
 		String t;
 		int s = 12;
 
 		if ((t = getParameter("font_name")) != null)
 			defaultfont = t;
 			
 		try
 		{
 			if ((t = getParameter("font_size")) != null)
 				s = Integer.parseInt(t);
 		}
 		catch (NumberFormatException e)	{}
 		this.mainfont = new Font(defaultfont, Font.PLAIN, s);
 
 		try
 		{
 			if ((t = getParameter("mainbg")) != null)
 				this.mainbg = Color.decode(t);
 		}
 		catch (NumberFormatException e)	{}
 		try
 		{
 			if ((t = getParameter("mainfg")) != null)
 				this.mainfg = Color.decode(t);
 		}
 		catch (NumberFormatException e)	{}
 		try
 		{
 			if ((t = getParameter("textbg")) != null)
 				this.textbg = Color.decode(t);
 		}
 		catch (NumberFormatException e)	{}
 		try
 		{
 			if ((t = getParameter("textfg")) != null)
 				this.textfg = Color.decode(t);
 		}
 		catch (NumberFormatException e)	{}
 		try
 		{
 			if ((t = getParameter("selbg")) != null)
 				this.selbg = Color.decode(t);
 		}
 		catch (NumberFormatException e)	{}
 		try
 		{
 			if ((t = getParameter("selfg")) != null)
 				this.selfg = Color.decode(t);
 		}
 		catch (NumberFormatException e)	{}
 
 
 		// Send traffic to stderr.

 		// Turn off for production setups!

 		debug_traffic = 0;
 		if ((t = getParameter("debug_traffic")) != null)
 		{
 			try
 			{
 				debug_traffic = Integer.parseInt(t);
 			} catch (NumberFormatException e) {}
 		}
 
 		write_color = 1;
 		if ((t = getParameter("write_color")) != null)
 		{
 			try
 			{
 				write_color = Integer.parseInt(t);
 				if (write_color < 0 || write_color > 15)
 					write_color = 1;
 			} catch (NumberFormatException e) {}
 			properties.setInt("write_color", write_color);
 		}
 
 		if ((t = getParameter("nicksrv_pass")) == null)
 			t = "";
 		properties.setString("nicksrv_pass", t);
 
 		if ((t = getParameter("user_modes")) != null)
 		{
 			user_modes = "";
 			for (int i = 0; i < t.length(); i++)
 			{
 				char c = t.charAt(i);
 				if (Character.isLetter(c) || c == '+' || c == '-')
 					user_modes += c;
 			}
 		}
 		
 		
 		if (debug_traffic > 0)
 		{
 			String propertyNames[] = { "file.separator", "line.separator", "path.separator", "java.class.version", "java.vendor", "java.vendor.url", "java.version", "os.name", "os.arch", "os.version" };
 			debug(getAppletInfo());
 			debug("\n");
 			for (int i = 0; i < propertyNames.length; i++)
 				debug(propertyNames[i] + " = " + System.getProperty(propertyNames[i]));
 			debug("\n");
 			Locale l = Locale.getDefault();
 			debug("country = " + l.getCountry() + " (" + l.getDisplayCountry() + ")");
 			debug("language = " + l.getLanguage() + " (" + l.getDisplayLanguage() + ")");
 			debug("encoding = " +  new OutputStreamWriter(System.out).getEncoding());
 			debug("\n");
 		}
 		
 		
 		/* Platform depend security */
 		askPermissions();
 	
 		
 		/* Create main GUI. */
 		
 		// Check some supports which may not be available on all virtual machines

 		NewMouseWheelEvent.checkMouseWheelSupport();
 		NewGraphics2D.checkRenderSupport();
 		LinkProcess.init();
 
 		t = getParameter("spawn_frame");
 
 		if ((t != null && !t.equals("0")) || application)
 		{
 			f = new Frame();
 			f.setIconImage(main_icon);
 			f.addWindowListener(this);
 
 			int width = 800;
 			int height = 600;
 			try
 			{
 				width = Integer.parseInt(getParameter("width"));
 				height = Integer.parseInt(getParameter("height"));
 			}
 			catch (Exception ex) {}
 			initGUI(f);
 	  		f.pack();
 			f.setSize(width, height);
 			Toolkit tkit = getToolkit();
    			f.setLocation((tkit.getScreenSize().width - f.getSize().width) / 2, (tkit.getScreenSize().height - f.getSize().height) / 2);
 			f.setVisible(true);
 			f.addWindowListener(this);
 			f.setName("main");
 		}
 		else
 		{
 			f = null;
 			initGUI(this);
 		}
 		
 
 		setBackground(mainbg);
 		setForeground(mainfg);
 		setSelectedBackground(selbg);
 		setFont(mainfont);
 		updateWindowTitle();
 
 
 		/* Event listeners and observers. */
 
 		nick_entry.addActionListener(this);
 		away.addMouseListener(this);
 		properties.addObserver(chan_list);
 		properties.addObserver(who_list);
 		properties.addObserver(status);
 		properties.addObserver(irc_services);
 		chan_list.update(properties, null);
 		who_list.update(properties, null);
 		status.update(properties, null);
 		irc_services.update(properties, null);
 
 
 		/* Misc. GUI init. */
 
 		if ((t = getParameter("nickname")) != null)
 		{
 			char [] str = t.toCharArray();
 
 			// Replace every '?' occurrence by a random decimal digit.

 			for (int i = 0; i < str.length; i++)
 				if ('?' == str[i])
 				   	str[i] = Character.forDigit((int) Math.floor(Math.random() * 10.0), 10);
 
 			nick_entry.setText(String.valueOf(str));
 			original_nick = String.valueOf(str);
 		}
 
 
 		if ((t = getParameter("irc_pass")) != null && t.length() > 0)
 			properties.setString("irc_pass", t);
 
 		username = "CS" + Calendar.getInstance().get(Calendar.WEEK_OF_YEAR);
 		realname = "www.coolsmile.net";
 		
 		if ((t = getParameter("username")) != null && t.length() > 0)
 			username = t;
 
 		if ((t = getParameter("realname")) != null && t.length() > 0)
 			realname = t;
 
 	}
 	
 	public void askPermissions()
 	{
 		/* Platform-dependent security checks
 		** see : http://support.microsoft.com/kb/175622
 		** or	http://support.microsoft.com/kb/193877
 		**	   http://java.sun.com/j2se/1.5.0/docs/guide/deployment/deployment-guide/upgrade-guide/article-05.html
 		*/
 	
 		try
 		{
 			if (System.getProperty("java.vendor").indexOf("Microsoft") >= 0)
 			{
 				/*
 				** com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.NETIO);
 				*/
 				Class PolicyClass = Class.forName("com.ms.security.PolicyEngine");
 				Class IdClass = Class.forName("com.ms.security.PermissionID");
 				Method assertMethod = PolicyClass.getMethod("assertPermission", new Class[] { IdClass });
 				Field permission = IdClass.getField("NETIO");
 				Object permObj = permission.get(null);
 				Object none = assertMethod.invoke(null, new Object[] { permObj });
 				
 				/*
 				** com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.UI);
 				*/
 				permission = IdClass.getField("UI");
 				permObj = permission.get(null);
 				none = assertMethod.invoke(null, new Object[] { permObj });
 			}
 		}
 		catch (Exception ex) { System.err.println("Permission error : " + ex); }
 	}
 
 	public void openASL()
 	{
 		String t = getParameter("asl");
 		
 		if((t != null && !t.equals("0")) || t == null)
 		{
 			if (asl == null)
 			{
 				asl = new ASLBox(this, res.getString("asl.connection"));
 				asl.addWindowListener(this);
 				asl.setName("asl");
 			}
 			else
 			{
 				asl.toFront();
 			}
 			asl.init(nick_entry.getText(), nicksrv_pass, realname);
 		}
 	}
 
 	public void start()
 	{
 		String t = getParameter("login");
 
 		// See if auto-login was requested.

 		if (t != null && !t.equals("0"))
 		{
 			// Can't login if no nickname was provided.

 			if ((t = getParameter("nickname")) == null || t.length() == 0)
 				status.printError(res.getString("eirc.s26"));
 			else
 				connect();
 		}
 		else
 		{
 			openASL();
 		}
 	}
 	
 	public void run()
 	{
 		while (ping > 0)
 		{
 			if (server != null && connected && server.timedOut())
 			{
 				String a[] = { network_name };
 				sendMessage("PING", a);
 			}
 			
 			try
 			{
 				Thread.sleep(ping * 1000);
 			} catch (InterruptedException ex) {}
 		}
 	}
 
 	public void stop()
 	{
 		disconnect();
 	}
 
 	public void destroy()
 	{
 		// Remove all components from container.

 		removeAll();
 	}
 
 	private void initGUI(Container cont)
 	{
 		String t;
 		
 		GridBagLayout gb = new GridBagLayout();
 		GridBagConstraints gbc = new GridBagConstraints();
 
 		gbc.insets = new Insets(4, 4, 4, 4);
 		cont.setLayout(gb);
 
 		label = new Label(res.getString("eirc.enter_nick"), Label.RIGHT);
 		gbc.anchor = GridBagConstraints.EAST;
 		gb.setConstraints(label, gbc);
 		label.setBackground(mainbg);
 		t = getParameter("gui_nick");
 		if (t == null || !t.equals("0"))
 			cont.add(label);
 
 		/* Textfield(int) and Textfield(String) look buggy on Linux */
 		this.nick_entry = new TextField();
 		gbc.weightx = 0.5;
 		gbc.fill = GridBagConstraints.HORIZONTAL;
 		gbc.anchor = GridBagConstraints.WEST;
 		gb.setConstraints(nick_entry, gbc);
 		nick_entry.setBackground(textbg);
 		if (t == null || !t.equals("0"))
 			cont.add(nick_entry);
 
 		this.menu = new PopupMenu();
 		CheckboxMenuItem cbm = new CheckboxMenuItem(res.getString("eirc.away_custom"));
 		cbm.addItemListener(this);
 		menu.add(cbm);
 		menu.addSeparator();
 		StringTokenizer tk = new StringTokenizer(res.getString("eirc.away_list"), "/");
 		while (tk.hasMoreTokens())
 		{
 			cbm = new CheckboxMenuItem(tk.nextToken());
 			cbm.addItemListener(this);
 			menu.add(cbm);
 		}
 
 		away = new Label(res.getString("eirc.away"));
 		gbc.weightx = 0.5;
 		gbc.fill = GridBagConstraints.NONE;
 		gbc.anchor = GridBagConstraints.WEST;
 		gb.setConstraints(away, gbc);
 		t = getParameter("gui_away");
 		if (t == null || !t.equals("0"))
 			cont.add(away);
 		away.add(menu);
 		away.setBackground(mainbg);
 		away.setForeground(Color.blue);
 
 		/*
 		** Check if we can add some buttons.
 		*/
 		boolean enabled[] = new boolean[ControlPanel.COMPONENTS];
 		for (int i = 0; i < ControlPanel.COMPONENTS; i++)
 			enabled[i] = true;
 		
 		t = getParameter("gui_chanlist");
 		if (t != null && t.equals("0"))
 				enabled[ControlPanel.CHANNELS] = false;
 		t = getParameter("gui_userlist");
 		if (t != null && t.equals("0"))
 			enabled[ControlPanel.WHO] = false;
 		t = getParameter("gui_options");
 		if (t != null && t.equals("0"))
 			enabled[ControlPanel.SETUP] = false;
 		t = getParameter("gui_help");
 		if (t != null && t.equals("0"))
 			enabled[ControlPanel.HELP] = false;
 		t = getParameter("gui_connect");
 		if (t != null && t.equals("0"))
 			enabled[ControlPanel.CONNECT] = false;
 		
 		this.control_panel = new ControlPanel(this, enabled);
 		gbc.anchor = GridBagConstraints.EAST;
 		gb.setConstraints(control_panel, gbc);
 		cont.add(control_panel);
 		gbc.anchor = GridBagConstraints.CENTER;
 
 		gbc.gridy = 1;
 
 		this.chat_panel = new ChatPanelContainer(this);
 		gbc.weightx = 1.0;
 		gbc.weighty = 1.0;
 		gbc.gridwidth = GridBagConstraints.REMAINDER;
 		gbc.fill = GridBagConstraints.BOTH;
 		gb.setConstraints(chat_panel, gbc);
 		cont.add(chat_panel);
 
 		/* Create and add Channel list window. */
 		String tag = "*Chans*";
 
 		this.chan_list = new ChanListWindow(this, tag);
 		chan_list.setForeground(mainfg);
 		chan_list.setBackground(mainbg);
 		chan_list.setTextBackground(textbg);
 		chan_list.setTextForeground(textfg);
 // 		chan_list.setSelectedForeground(selfg);

  		chan_list.setSelectedBackground(selbg);
 		chat_panel.addPanel(chan_list, tag);
 
 		/* Create and add Who list window. */
 		tag = "*Who*";
 
 		this.who_list = new WhoListWindow(this, tag);
 		who_list.setForeground(mainfg);
 		who_list.setBackground(mainbg);
 		who_list.setTextBackground(textbg);
 //		who_list.setTextForeground(textfg);

  		who_list.setSelectedForeground(selfg);
 		who_list.setSelectedBackground(selbg);
 		chat_panel.addPanel(who_list, tag);
 
 		/* Create and add StatusWindow. */
 		status_tag = Resources.getString("eirc.status");
 		String title = Resources.getString("status_panel");
 
 		this.status = new StatusWindow(this, status_tag);
 		status.setBackground(mainbg);
 		status.setForeground(mainfg);
 		status.setTextBackground(textbg);
 		status.setTextForeground(textfg);
 		status.setSelectedBackground(selbg);
 	   	status.requestFocus();
 
 	  	chat_panel.add(status, status_tag, true);
 	  	chat_panel.setTitle(status_tag, title);
 		chat_panel.show(status_tag);
 
 		cont.setBackground(mainbg);
 		cont.setForeground(mainfg);
 		cont.setFont(mainfont);
 	}
 
 	public String getAppletInfo()
 	{
 		return (PACKAGE + " " + VERSION + "\n" + VERSION_EXTRA + ", an RFC 1459 compliant client program written in Java.\n" + "Copyright (C) 2000-2001  Javier Kohen <jkohen at tough.com>");
 	}
 
 	public String[][] getParameterInfo()
 	{
 		return param_info;
 	}
 
 	void connect()
 	{
 		String host = null;
 		if (current_server != null)
 			host = current_server;
 		else
 			host = getParameter("server");
 		if (host == null || host.length() == 0)
 		{
 			host = getDocumentBase().getHost();
 			if (host.length() == 0)
 				host = "localhost";
 		}
 		
 		boolean ssl = false;
 		try
 		{
 			ssl = (Integer.parseInt(getParameter("ssl")) == 1 ? true : false);
 		}
 		catch (Exception e) {}
 		
 		connect(host, ssl);
 	}
 
 	void connect(String host, boolean ssl)
 	{
 		int port = 6667; // Set default port.

 		if (ssl)
 			port = 6697;
 			
 		try
 		{
 			port = Integer.parseInt(getParameter("port"));
 		} catch (Exception e) {}
 		
 		connect(host, port, ssl);
 	}
 
 	synchronized void connect(String host, int port, boolean ssl)
 	{
 		if (nick_entry.getText().length() == 0)
 		{
 			status.printError(res.getString("eirc.s1"));
 			return;
 		}
 
 		status.printInfo(res.getString("eirc.login"));
 		
 		/* See if this Applet can connect promiscuously.
 		 * No Exceptions will be thrown out of Applet context.
 		 */
 
 		InetAddress server_addr = null;
 		Socket s = null;
 		ServerThread st = null;
 
 		try
 		{
 			/* Resolve server's address. */
 			InetAddress [] addresses = InetAddress.getAllByName(host);
 			server_addr = addresses[(int) Math.floor(Math.random() * addresses.length)];
 			server_addr = InetAddress.getByName(host);
 			
 			/* Establish a connection to the server. */
 			s = createSocket(server_addr, port, ssl);
 			
 			/* Spawn a new thread to handle the connection. */
 			st = new ServerThread(this, s, decoding, encoding, ping);
 		}
 		catch (UnknownHostException e)
 		{
 			Object a[] = { host };
 			String ptn = res.getString("eirc.s2");
 			status.printError(MessageFormat.format(ptn, a));
 			return;
 		}
 		catch (IOException e)
 		{
    			Object a[] = { server_addr.getHostName(), (new Integer(port)).toString() };
    			String ptn = res.getString("eirc.s3");
    			status.printError(MessageFormat.format(ptn, a));
 			status.printInfo(e.toString());
    			return;			
 		}
 		catch (SecurityException e)
 		{
 			status.printError(res.getString("eirc.not_in_applet"));
 			return;	
 		}
 		catch (ClassNotFoundException e)
 		{
 			status.printError(res.getString("eirc.nossl"));
 			return;
 		}
 		catch (Exception e)
 		{
 			reportException(e);
 			return;
 		}
 
 		if (connected)
 			disconnect();
 
 		properties.addObserver(st);
    		this.server = st;
    		st.start();
 
 		this.connected = true;
 		this.send_quit = true;
 
 		
 		/* Login into server. */
 		login();
 		this.current_server = host;
 
 		/* Enable controls that depend on being connected. */
 		control_panel.setConnected(true);
 
 		/*
 		** Enable previously opened windows.
 		*/
 
 		/* Enable opened channels and re-join */
 		String c;
 		String p[] = { "", "" };
 		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 			ChannelWindow w = (ChannelWindow)e.nextElement();
 			if (w != null)
 			{
 				if (p[0].length() > 0)
 				{
 					p[0] += ",";
 					p[1] += ",";
 				}
 				c = w.getChannel().getTag();
 				if (c != null)
 				{
 					p[0] += c;
 					c = w.getChannel().getKey();
 					if (c.length() == 0) c = " ";
 					p[1] += c;
 				}
 					
 				w.setEnabled(true);
 			}
 		}
 		if (p[0].length() > 0)
 			sendMessage("JOIN", p);
 
 		/* Enable privates */
 	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
 		{
   			PrivateWindow pw = (PrivateWindow) e.nextElement();
   			pw.setEnabled(true);
   		}
 		
 		status.clear();
 		chan_list.clear();
 		who_list.clear();
 	}
 
 	public Socket createSocket(InetAddress addr, int port, boolean sec) throws Exception
 	{
 		Socket s = null;
 
 		if (sec)
 		{
 			Class SSLClass = Class.forName("javax.net.ssl.SSLSocket");
 			if(SSLClass == null)
 				throw new ClassNotFoundException();
 
 			/*
 			** SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
 			** SSLSocket ssl = (SSLSocket)factory.createSocket(server_addr, port);
 			*/
 
 			Class FactoryCLass = Class.forName("javax.net.ssl.SSLSocketFactory");
 			Method getdefault = FactoryCLass.getMethod("getDefault", new Class[] {});
 			Object factory = getdefault.invoke(null, new Object[] {});
 				
 			Method createsocket = FactoryCLass.getMethod("createSocket", new Class[] { InetAddress.class, int.class });
 			Object ssl = createsocket.invoke(factory, new Object[] { addr, new Integer(port) } );
 	
 			s = (Socket)ssl;
 		}
 		else
 		{
 			s = new Socket(addr, port);
 		}
 
 		return(s);
 	}
 
 	public void connect(String nick, String pass, String real)
 	{
 		nick_entry.setText(nick);
 		nicksrv_pass = pass;
 		realname = real;		
 
 		properties.setString("nickname", nick);
 		properties.setString("realname", realname);
 
 		connect();
 	}
 
 	private synchronized void login()
 	{
 		if (!connected)
 			return;
 
 		if (irc_pass != null && irc_pass.length() > 0)
 		{
 			String p[] = { irc_pass };
 			sendMessage("PASS", p);
 		}
 
 		this.new_nick = nick_entry.getText();
 		String n[] = { new_nick };
 		sendMessage("NICK", n);
 		
    		String u[] = { username, "0", "0", realname };
    		sendMessage("USER", u);
 	}
 
 	public synchronized void disconnect()
 	{
 		/* Disable controls that depend on being connected. */
 		control_panel.setConnected(false);
 
 		if (!connected)
 			return;
 
 		/* Logout from server */
 		logout();
 
 		/* Kill the connection */
 		server.disconnect();			
 		server = null;
 
 	  	this.connected = false;
 		status.printError(res.getString("eirc.disconnected"));
 		chat_panel.show(status_tag);
 
 		/* Close what should be closed on disconnection. */
 
 //		closePrivates();

 //		closeChannels();

 
 		/* Disable opened channels */
 
 		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 			ChannelWindow w = (ChannelWindow)e.nextElement();
 			w.setEnabled(false);
 			w.clearUsers();
 		}
 
 		/* Disable opened privates */
 	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
 		{
   			PrivateWindow pw = (PrivateWindow) e.nextElement();
   			pw.setEnabled(false);
   		}
 
 		/*
 		** Reset away
 		*/
 
 		away.setForeground(Color.blue);
 		for (int i = 0; i < menu.getItemCount(); i++)
 		{
 			MenuItem mi = menu.getItem(i);
 			if (mi instanceof CheckboxMenuItem)
 				((CheckboxMenuItem)mi).setState(false);
 		}
 		is_away = false;
 		
 		/*
 		** Clear modes
 		*/
 		setGlobalModes("-*");
 
 	}
 
 	private synchronized void logout()
 	{
 //		if (!logged_in)

 //			return;

 
 		if (send_quit)
 		{
 		
 			String p[] = { quit_message };
 			sendMessage("QUIT", p);
 			this.send_quit = false;
 		}
 
 		this.logged_in = false;
 	}
 
 	private synchronized void pos_login()
 	{
 		if (!connected)
 			return;
 
 		current_nick = new_nick;
 		status.requestFocus();
 		
 		chan_list.setEnabled(true);
 		who_list.setEnabled(true);
 
 		if (request_motd)
 		{
 			String p[] = { };
 			sendMessage("MOTD", p);
 		}
 
 		if (user_modes != null && user_modes.length() > 0)
 		{
 			String p[] = { this.current_nick, user_modes };
 			sendMessage("MODE", p);
 		}
 
 		if (default_join != null && default_join.length() > 0)
 		{
 			String p[] = null;
 			default_join = default_join.trim();
 			int space = default_join.indexOf(" ");
 			if (space > 0)
 			{
 				// Keyed channels

 				
 				p = new String[2];
 				p[0] = default_join.substring(0, space);
 				p[1] = default_join.substring(space + 1);
 			}
 			else
 			{
 				p = new String[1];
 				p[0] = default_join;
 			}
 			sendMessage("JOIN", p);
 			default_join = null;
 		}
 		
 		if (chan_list != null && auto_list)
 			chan_list.listChannels();
 	}
 
 	public void printMode(ChannelWindow cw, String prefix, String param, String msg_tag, int sound)
 	{
 		if (msg_tag != null && cw != null)
 		{
 			Object a[] = { prefix, param };
 			String ptn = res.getString(msg_tag);
 			cw.printInfo(MessageFormat.format(ptn, a));
 		}
 
 		/*
 		** Play an event sound only if user is focused on the channel
 		*/
 
 		if (sound > -1 && cw == getCurrentPanel())
 			playSound(sound);
 	}
 	
 	public void reportException(Exception ex)
 	{
 		report("err", ex.toString());
 	}
 	
 	public void reportIOException(IOException ex)
 	{
 		report("eirc.errnet", ex.toString());
 	}
 	
 	public void report(String key, String details)
 	{
 		/* Report verbose error on status window */
 
 		status.printError(res.getString(key));
 		status.printInfo(details);
 	}
 	
 	public void debug(String s)
 	{
 		if (debug_traffic == 1 || debug_window == null)
 			System.err.println(s);
 
 		if (debug_traffic == 2 && debug_window != null)
 			debug_window.printServerNotice(s, "Debug");
 	}
 
 	public void processMessage(Message m)
 	{
 		String command_key = m.getCommand();
 		String mask = m.getPrefix();
 		String params[] = m.getParameters();
 		Integer t_cmd = (Integer)commands.get(command_key);
 
 		String prefix = mask;
 		int endOff = mask.indexOf('!');
 		int atHost = mask.indexOf('@');
 		if (endOff != -1)
 		{
 			prefix = mask.substring(0, endOff);
 			if (atHost > endOff)
 			{
 				String user = mask.substring(endOff + 1, atHost);
 				String host = mask.substring(atHost + 1);
 				if (!NickInfo.hasInfos(prefix) && !isService(prefix))
 				{
 					NickInfo.add(prefix, "", user, host);
 
 					// Retrieve user's info.

 					String p[] = { prefix };
 					sendMessage("WHO", p);
 				}
 			}
 		}
 
 		if (debug_traffic > 0 || t_cmd == null)
 		{
 			// Strip CRLF from the end of the message.

 			String t = m.toString();
 			t = t.substring(0, t.length() - 2);
 
 			if (debug_traffic > 0)
 				debug("<< " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date()) + " " + t);
 
 			if (t_cmd == null)
 			{
 				if (see_everything_from_server)
 				{
 					try
 					{
 						// We don't care about the result, we just want to know if it's a number.

 						Integer.parseInt(command_key);
 
 						// Numeric command, take the crud out of it.

 						StringBuffer buf = new StringBuffer(params[1]);
 						for (int i = 2; i < params.length; i++)
 						{
 				  		  	buf.append(' ');
 				  		  	buf.append(params[i]);
 						}
 
 						status.printUnmangled(buf.toString());
 					}
 					catch (NumberFormatException e)
 					{
 						// Not numeric command.

 						status.printUnmangled(t);
 					}
 				}
 				return;
 			}
 		}
 
 		// This flag is checked by NOTICE and PRIVMSG handlers.

 		boolean is_prefix_ignored = false;
 		String addr = NickInfo.getInetAddr(prefix);
 		if (addr != null)
 			is_prefix_ignored = ignore_list.contains(addr);
 
 		int command = t_cmd.intValue();
 		switch (command)
 		{
 			case -1:	// PING

 			{
 				String p[] = { params[0] };
 				sendMessage("PONG", p);
 				break;
 			}
 
 			case -2:	// NICK

 			{
 				if (prefix.equals(current_nick))
 				{
 					// Self nick change

 
 					current_nick = params[0];
 					nick_entry.setText(current_nick);
 
 					Object a[] = { current_nick };
 					String ptn = res.getString("eirc.s4");
 					getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 					
 					updateWindowTitle();
 				}
 				else
 				{
 					// Other's nick change

 
 					Object a[] = { prefix, params[0] };
 					String ptn = res.getString("eirc.s5");
 					String msg_text = MessageFormat.format(ptn, a);
 					OutputWindow [] ow = getUserPanels(prefix);
 					for (int i = 0; i < ow.length; i++)
 						ow[i].printInfo(msg_text);
 				}
 
 				// Update nick from nick info entries

 		   		NickInfo.changeNick(prefix, params[0]);
 
 				// Update private window (if there's one) accordingly

 				if (privates.containsKey(prefix) && !privates.containsKey(params[0]))
 				{
 					PrivateWindow pw = (PrivateWindow) privates.get(prefix);
 					pw.setUser(params[0]);
 					chat_panel.rename(prefix, params[0]);
 					Object o[] = { params[0] };
 					chat_panel.setTitle(params[0], private_title.format(o));
 					privates.remove(prefix);
 					privates.put(params[0], pw);
 				}
 
 				// Update channel window accordingly

 				for (Enumeration e = channels.elements(); e.hasMoreElements(); )
 				{
 					Channel channel = (Channel) e.nextElement();
 					if (channel.contains(prefix))
 					{
 						channel.rename(prefix, params[0]);
 						channel.update();
 					}
 				}
 				break;
 			}
 
 			case -3:	// JOIN

 			{
 				// If it's me, just open the channel window.

 				if (current_nick.equals(prefix))
 				{
 					openChannel(params[0]);
 
 					// Retrieve users' info.

 		  			String p[] = { params[0] };
 					sendMessage("WHO", p);
 				}
 				else
 				{
 					Channel channel = getChannel(params[0]);
 					if (channel != null)
 					{
 						channel.add(prefix);
 						channel.update();
 						
 						if (see_join)
 						{
 							ChannelWindow cw = getChannelWindow(params[0]);
 							if (cw != null)
 							{
 								Object a[] = { prefix };
 								String ptn = res.getString("eirc.s6");
 								cw.printInfo(MessageFormat.format(ptn, a));
 	
 								if (cw == getCurrentPanel())
 									playSound(res.EVENT_JOIN);
 							}
 						}
 						updateChanTitle(getChannel(params[0]));
 					}
 				}
 
 				break;
 			}
 
 			case -4:	// MODE

 			{
 				String [] modes_params;
 				if (params.length > 2)
 				{
 					modes_params = new String [params.length - 2];
 					for (int i = 0; i < modes_params.length; i++)
 						modes_params[i] = params[i + 2];
 				}
 				else
 				{
 					modes_params = new String[1];
 					modes_params[0] = params[0];
 				}
 
 				// Process server wide user modes.

 				if (!Channel.isChannel(params[0]))
 				{
 					setGlobalModes(params[1]);
 
 				  	// Refresh all channels' popup menus.

 					for (Enumeration e = channels.elements(); e.hasMoreElements(); )
 
 
 
 
 					{
 						Channel channel = (Channel) e.nextElement();
 						channel.setModes();
 					}
 
 
 
 
 					break;
 				}
 
 				Channel channel = getChannel(params[0]);
 				if (channel != null)
 				{
 					channel.setModes(params[1], modes_params);
 					channel.update();
 				}
 
 				ChannelWindow cw = getChannelWindow(params[0]);
 				boolean sign = false;
 				char modes[] = params[1].toCharArray();
 
 				int j = 0;
 				for (int i = 0; i < modes.length; i++)
 				{
 					String msg_tag = null;
 					int sound = -1;
 
 					switch (modes[i])
 					{
 					case '+':
 						sign = true;
 						break;
 					case '-':
 						sign = false;
 						break;
 					case 'v':
 						msg_tag = sign ? "eirc.+v" : "eirc.-v";
 						sound = sign ? res.EVENT_OP : res.EVENT_DEOP;
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 					case 'o':
 						msg_tag = sign ? "eirc.+o" : "eirc.-o";
 						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 					case 'h':
 						msg_tag = sign ? "eirc.+h" : "eirc.-h";
 						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 					case 'b':
 						msg_tag = sign ? "eirc.+b" : "eirc.-b";
 						sound = sign ? res.EVENT_BAN : -1;
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 					case 'e':
 						msg_tag = sign ? "eirc.+e" : "eirc.-e";
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 					case 'I':
 						msg_tag = sign ? "eirc.+I" : "eirc.-I";
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 
 					// Channel modes

 					case 'm':
 						msg_tag = sign ? "eirc.+m" : "eirc.-m";
 						printMode(cw, prefix, modes_params[0], msg_tag, sound);
 						break;
 					case 's':
 						msg_tag = sign ? "eirc.+s" : "eirc.-s";
 						printMode(cw, prefix, modes_params[0], msg_tag, sound);
 						break;
 					case 'i':
 						msg_tag = sign ? "eirc.+i" : "eirc.-i";
 						printMode(cw, prefix, modes_params[0], msg_tag, sound);
 						break;
 					case 'k':
 						msg_tag = sign ? "eirc.+k" : "eirc.-k";
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 					case 'l':
 						msg_tag = sign ? "eirc.+l" : "eirc.-l";
 						printMode(cw, prefix, sign ? modes_params[j++] : "", msg_tag, sound);
 						break;
 
 					// User modes not RFC compliant. //////////////////////////

 
 					case 'a':
 						msg_tag = sign ? "eirc.+a" : "eirc.-a";
 						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 						
 					case 'q':
 						msg_tag = sign ? "eirc.+q" : "eirc.-q";
 						sound = sign ? res.EVENT_OP: res.EVENT_DEOP;
 						printMode(cw, prefix, modes_params[j++], msg_tag, sound);
 						break;
 
 					// Channel modes not RFC compliant. //////////////////////////

 
 					case 'r':
 						msg_tag = sign ? "eirc.+r" : "eirc.-r";
 						printMode(cw, prefix, modes_params[0], msg_tag, sound);
 						break;
 					case 'N':
 						msg_tag = sign ? "eirc.+N" : "eirc.-N";
 						printMode(cw, prefix, modes_params[0], msg_tag, sound);
 						break;
 
 					/////////////////////////////////////////////////////////////

 
 					}
 		   		}
 
 				break;
 			}
 
 			case -5:	// PART

 			{
 				if (current_nick.equals(prefix))
 				{
 					closeChannel(params[0]);
 				}
 				else
 				{
 					Channel channel = getChannel(params[0]);
 					channel.remove(prefix);
 					channel.update();
 
 					if (see_join)
 					{
 						ChannelWindow cw = getChannelWindow(params[0]);
 						String part_msg = "";
 						if (params.length >= 2)
 							part_msg = params[1];
 						Object a[] = { prefix, part_msg };
 						String ptn = res.getString("eirc.s8");
 						cw.printInfo(MessageFormat.format(ptn, a));
 
 						if (cw == getCurrentPanel())
 							playSound(res.EVENT_PART);
 					}
 					
 					updateChanTitle(getChannel(params[0]));
 				}
 
 				break;
 			}
 
 			case -6:	// QUIT

 			{
 				Object a[] = { prefix, params[0] };
 				String ptn = res.getString("eirc.s9");
 				String msg_text = MessageFormat.format(ptn, a);
 
 				OutputWindow [] ow = getUserPanels(prefix);
 				for (int i = 0; i < ow.length; i++)
 				{
 					ow[i].printInfo(msg_text);
 
 					if(ow[i] == (OutputWindow)getCurrentPanel())
 						playSound(res.EVENT_QUIT);
 				}
 
 				// Remove the user from the channels he was in.

 				for (Enumeration e = channels.elements(); e.hasMoreElements(); )
 				{
 					Channel channel = (Channel) e.nextElement();
 					channel.remove(prefix);
 					channel.update();
 				}
 				updateChanTitle(getChannel(params[0]));
 
 				NickInfo.remove(prefix);
 				
 		   		break;
 			}
 
 			case -7:	// KICK

 			{
 				String reason = (params[2] != null ? params[2] : "");
 
 				if (current_nick.equals(params[1]))
 				{
 		  			closeChannel(params[0]);
 
 					Object a[] = { params[0], prefix, reason };
 					String ptn = res.getString("eirc.s10");
 					status.printInfo(MessageFormat.format(ptn, a));
 
 					playSound(res.EVENT_KICK);
 				}
 				else
 				{
 					Channel channel = getChannel(params[0]);
 					channel.remove(params[1]);
 					channel.update();
 
 					ChannelWindow cw = getChannelWindow(params[0]);
 
 					Object a[] = { params[1], prefix, reason };
 					String ptn = res.getString("eirc.s11");
 					cw.printInfo(MessageFormat.format(ptn, a));
 
 					updateChanTitle(getChannel(params[0]));
 
 					if(cw == getCurrentPanel())
 						playSound(res.EVENT_KICK);
 				}
 
 				break;
 			}
 			case -8:	// TOPIC

 			{
 				Channel channel = getChannel(params[0]);
 				channel.setTopic(MircMessage.filterMircAttributes(params[1]));
 
 				ChannelWindow cw = getChannelWindow(params[0]);
 
 				Object a[] = { prefix, params[1] };
 				String ptn = res.getString("eirc.s36");
 				cw.printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case -9:	// PRIVMSG

 			{
 				// If the source of this message is in the ignore list, ignore this message. This flag is set at the top of the switch block.

 				if (is_prefix_ignored)
 					break;
 
 				boolean isChannel = Channel.isChannel(params[0]);
 				
 				if (CTCPMessage.isCTCPMessage(params[1]))
 				{
 					CTCPMessage ctcp = new CTCPMessage(params[1]);
 
 					{
 						Object [] a = { ctcp.getCommandString(), prefix };
 						String ptn = res.getString("eirc.ctcp_received");
 //		   				status.printWarning(MessageFormat.format(ptn, a));

 					}
 
 					switch (ctcp.getCommand())
 					{
 					case CTCPMessage.ACTION:
 					{
 						if (!ctcp.hasParameter())
 							break;
 
 	   					ChatWindow target = (isChannel ? (ChatWindow)getChannelWindow(params[0]) : (ChatWindow)openPrivate(prefix, no_privates));
 						if (target != null)
 		   					target.printAction(ctcp.getParameter(), prefix);
 						break;
 					}
 					case CTCPMessage.PING:
 					{
 						String param = ctcp.hasParameter() ? ctcp.getParameter() : "";
 						CTCPMessage reply = new CTCPMessage("PING", param);
 						String p[] = { prefix, reply.toString() };
 //						sendMessage("NOTICE", p);

 						break;
 					}
 					case CTCPMessage.DCC:
 					{
 						Object a[] = { prefix };
 						String ptn = res.getString("eirc.dcc_not_supported");
 						OutputWindow target = getCurrentPanel();
 						target.printInfo(MessageFormat.format(ptn, a));
 
 			  			if (on_dcc_notify_peer)
 						{
 							String p[] = { prefix, res.getString("eirc.dcc_notify.remote")	};
 							sendMessage("NOTICE", p);
 
 							ptn = res.getString("eirc.dcc_notify.local");
 							target.printInfo(MessageFormat.format(ptn, a));
 						}
 						break;
 					}
 					case CTCPMessage.VERSION:
 					{
 						CTCPMessage reply = new CTCPMessage("VERSION", PACKAGE.concat("-").concat(VERSION).concat(VERSION_EXTRA).concat(" ").concat(AUTHOR));
 
 						String p[] = { prefix, reply.toString() };
 //						sendMessage("NOTICE", p);

 						break;
 					}
 					}
 				}
 				else
 				{
 			   		if (isChannel)
 					{
 						ChannelWindow target = (ChannelWindow) getChannelWindow(params[0]);
 						if (target != null)
 						{
 							User user = target.getChannel().get(prefix);
 							target.printPrivmsg(params[1], prefix, user);
 						}
 					}
 					else
 					{
 						PrivateWindow target = openPrivate(prefix, no_privates);
 						if (target != null)
 						{
 							target.printPrivmsg(params[1], prefix);
 							playSound(res.EVENT_PRVMSG);
 						}
 					}
 				}
 				break;
 			}
 
 			case -10:	// NOTICE

 			{
 				// If the source of this message is in the ignore list, ignore this message. This flag is set at the top of the switch block.

 				if (is_prefix_ignored)
 					break;
 
 //				if (prefix.length() > 0)

 //				{

 //					endOff = prefix.indexOf('!');

 //					if (endOff != -1)

 //						prefix = prefix.substring(0, endOff);

 //				}

 
 				// NickServ checking user's password

 
 				if (irc_services.isPassOk(prefix, params[1]))
 					login_time = false;
 
 				if (irc_services.isIdPrompt(prefix, params[1]) && !login_time)
 				{
 					if (current_nick.equalsIgnoreCase(original_nick) && !(nicksrv_pass != null || nicksrv_pass.equals("")))
 						identify(nicksrv_pass);
 					else
 						openIDWin();
 				}
 
 				if (irc_services.isPassBad(prefix, params[1]))
 					badIDWin();
 
 				if (Channel.isChannel(params[0]))
 				{
 
 					getChannelWindow(params[0]).printNotice(params[1], prefix);
 				}
 				else if (prefix.length() > 0 && prefix.toLowerCase().endsWith(network_name.toLowerCase()))
 				{
 					// Output notices from servers to status window.

 
 					status.printServerNotice(params[1], res.getString("eirc.server"));
 				}
 				else if (special_services && prefix.length() > 0 && isService(prefix) && !Channel.isChannel(prefix))
 				{
 					// Output notices from service bots to a dedicated (per bot) window.

 
 					PrivateWindow target = openPrivate(prefix, false);
 					if (target != null)
 						target.printNotice(params[1], prefix);
 				}
 				else if (params[0].equals(current_nick))
 				{
 					if (!CTCPMessage.isCTCPMessage(params[1]))
 					{
 						// Personal notice.

 
 						OutputWindow ow = getCurrentPanel();
 						if (ow != null && ow != status)
 							ow.printNotice(params[1], prefix);
 						else
 							status.printNotice(params[1], prefix);
 					}
 					else
 					{
 						CTCPMessage ctcp = new CTCPMessage(params[1]);
 
 						switch (ctcp.getCommand())
 						{
 						case CTCPMessage.PING:
 						{
 							double diff = Double.NEGATIVE_INFINITY;
 
 							if (ctcp.hasParameter())
 							{
 								try
 								{
 									long launch = Long.parseLong(ctcp.getParameter());
 									long arrive = (new Date()).getTime();
 									diff = (arrive - launch) / 1000.0;
 								}
 								catch (NumberFormatException e) {}
 							}
 
 	   						Object a[] = { prefix, new Double(diff) };
 	   						MessageFormat mf = new MessageFormat(res.getString("eirc.s12"));
 	   						double limits[] = { ChoiceFormat.previousDouble(0.0), 0.0, 1.0, ChoiceFormat.nextDouble(2.0) };
 	   						String times[] = { res.getString("eirc.s12.0"), res.getString("eirc.s12.1"), res.getString("eirc.s12.2"), res.getString("eirc.s12.3")};
 	   						mf.setFormat(1, new ChoiceFormat(limits, times));
 	   						getCurrentPanel().printInfo(mf.format(a));
 							break;
 						}
 						default:
 						{
 							Object a[] = { ctcp.getCommandString(), ctcp.getParameter() };
 							String ptn = res.getString("eirc.ctcp_reply");
 							getCurrentPanel().printNotice(MessageFormat.format(ptn, a), prefix);
 							break;
 						}
 						}
 					}
 				}
 				else
 				{
 					// These can only be originated on a server.

 					status.printNotice(params[0] + " " + params[1], prefix);
 				}
 				break;
 			}
 
 			case -11:	// ERROR

 			{
 				status.printError(params[0]);
 				
 				if (params[0].startsWith("Closing Link"))
 				{
 					send_quit = false;
 					disconnect();
 				}				
 				break;
 			}
 
 			case -12:	// WALLOPS

 			{
 				status.printServerNotice(params[0], prefix);
 				break;
 			}
 
 			case -13:	// INVITE

 			{
 				if (see_invite)
 				{
 					Object a[] = { params[1], prefix };
 					String ptn = res.getString("eirc.invite");
 					status.printInfo(MessageFormat.format(ptn, a));
 				}
 				break;
 			}
 
 			case -14:	// PONG

 			{
 				if (params.length >= 1)
 				{
 					Object a[] = { params[1] };
 					String ptn = res.getString("eirc.pong");
    			 		getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				}
 				break;
 			}
 
 			case 001:	// RPL_WELCOME

 			{
 				if (m.isFromServer())
 				{
 					if (prefix.indexOf(".") == -1)
 					{
 						network_name = prefix;
 					}
 					else
 					{
 						StringTokenizer st = new StringTokenizer(prefix, ".");
 						String token = "";
 						while (st.countTokens() > 2)
 							token = st.nextToken();
 						network_name = st.nextToken() + "." + st.nextToken();
 					}
 				}
 
 				if (nicksrv_pass != null && nicksrv_pass.length() > 0)
 
 				{
 					identify(nicksrv_pass);
 					login_time = true;
 				}
 
 				break;
 			}
 
 			case 005: // RPL_ISUPPORT

 			{
 				if (m.isFromServer() && params.length >= 3)
 				{
 					/* The first and last parameters are ignored */
 					
 					for (int i = 1; i < params.length - 1; i++)
 					{
 						int n = params[i].indexOf('=');
 						if (n > 1 && n < params[i].length() + 1)
 						{
 							String name = params[i].substring(0, n);
 							String val = params[i].substring(n + 1);
 							server_support.put(name, val);
 						}
 						else
 						{
 							/* No value but it means server actually support that */
 							server_support.put(params[i], "1");
 						}
 					}
 				}
 
 				break;
 			}
 			
 			case 251:	// RPL_LUSERCLIENT

 			{
 				MessageFormat mf = new MessageFormat("There are {0} users and {1} invisible on {2} servers");
 				try
 	   			{
 					Object o[] = mf.parse(params[1]);
 					int visibles = 0, invisibles = 0, total = 0;
 					try
 					{
 						visibles = Integer.parseInt((String)o[0]);
 
 
 						invisibles = Integer.parseInt((String)o[1]);
 						total = visibles + invisibles;
 					}
 					catch (NumberFormatException e) {}
 
 					Object s[] = { new Integer(total), new Integer(invisibles) };
 					String tag = "*Who*";
 					chat_panel.setTitle(tag, wholist_title.format(s));
 				}
 				catch (ParseException ex) {}
 				break;
 			}
 
 
 
 			case 301:	// RPL_AWAY

 			case 391:	// RPL_TIME

 			{
 				Object a[] = { params[1], params[2] };
 				String ptn = res.getString("eirc." + command);
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 302:	// RPL_USERHOST

 			{
 				String list  = params[1];
 				if (list.length() > 0)
 				{
 					StringTokenizer st = new StringTokenizer(list, " ");
 					while (st.hasMoreTokens())
 					{
 						String s = st.nextToken();
 						int i = s.indexOf("=");
 						String nick = s.substring(0, i);
 						if (nick.endsWith("*"))
 						{
 							nick = nick.substring(0, nick.length() - 1);
 							Object a[] = { nick };
 							String ptn = res.getString("eirc.s16");
 							getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 						}
 						if (s.charAt(i + 1) == '-')
 						{
 							Object a[] = { nick };
 							String ptn = res.getString("eirc.302.away");
 							getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 						}
 						String hostmask = s.substring(i + 2);
 						Object a[] = { nick, hostmask };
 						String ptn = res.getString("eirc.302.host");
 						getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 					}
 				}
 				break;
 			}
 
 			case 303:	// RPL_ISON

 			{
 				String list  = params[1];
 				if (list.length() == 0)
 				{
 					getCurrentPanel().printInfo(res.getString("eirc.isoff"));
 				}
 				else
 				{
 					StringTokenizer st = new StringTokenizer(list, " ");
 					while (st.hasMoreTokens())
 					{
 						Object a[] = { st.nextToken() };
 						String ptn = res.getString("eirc.ison");
 						getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 					}
 				}
 				break;
 			}
 
 			case 305:	// RPL_UNAWAY

 			case 306:	// RPL_NOWAWAY

 			{
 				is_away = (command == 306);
 				
 				away.setForeground(is_away ? Color.red : Color.blue);
 				away.repaint();	// For Macintosh MRJ

 			
 				break;
 			}
 
 			case 310:	// RPL_WHOISOPSTATUS

 			{
 				Object a[] = { params[1], params[2] };
 				String ptn = res.getString("eirc.310");
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 311:	// RPL_WHOISUSER

 			{
 				if (hideip)
 					params[3] = "?";
 
 	   			Object a[] = { params[1], params[2], params[3] };
 	   			String ptn = res.getString("eirc.s13");
 	   			getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 
 				// Real name is used to pass extra information about the user.

 				break;
 			}
 
 			case 312:	// RPL_WHOISSERVER

 			{
 				Object a[] = { params[1], params[2], params[3] };
 				String ptn = res.getString("eirc.s15");
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 313:	// RPL_WHOISOPERATOR

 			{
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc.s16");
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 314:	// RPL_WHOWASUSER

 			{
 				if (hideip)
 					params[3] = "?";
 
 	   			Object a[] = { params[1], params[2], params[3], params[5] };
 	   			String ptn = res.getString("eirc.314");
 	   			getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 
 				break;
 			}
 
 			case 315:	// RPL_ENDOFWHO

 			{
 				ChannelWindow cw = getChannelWindow(params[1]);
 
 	   			updateChanTitle(getChannel(params[1]));
 
 				if (params[1].indexOf("*") >= 0)
 	   			 	replyWhoEnd();
 				else
 					who_reply = null;
 				
 				for (Enumeration e = channels.elements(); e.hasMoreElements(); )
 				{
 					Channel channel = (Channel) e.nextElement();
 					channel.update();
 		   		}
 
 				break;
 			}
 
 			case 317:	// RPL_WHOISIDLE

 			{
 				int hrs = 0, mins = 0, secs = 0;
 				try
 				{
 					hrs = Integer.parseInt(params[2]) / 3600;
 					mins = (Integer.parseInt(params[2]) % 3600) / 60;
 					secs = (Integer.parseInt(params[2]) % 3600) % 60;
 				} catch (NumberFormatException e) {}
 				Object a[] = { params[1], new Integer(hrs), new Integer(mins), new Integer(secs) };
 				getCurrentPanel().printInfo(MessageFormat.format(res.getString("eirc.317"), a));
 
 				if (params.length >= 4)
 				{
 					Object b[] = { params[1], new Date(Long.parseLong(params[3]) * 1000) };
 					getCurrentPanel().printInfo(MessageFormat.format(res.getString("eirc.317.signon"), b));
 				}
 				break;
 			}
 
 			case 319:	// RPL_WHOISCHANNELS

 			{
 				Object a[] = { params[1], params[2] };
 				String ptn = res.getString("eirc.s18");
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 321:	// RPL_LISTSTART

 			{
 				replyListStart();
 				break;
 			}
 
 			case 322:	// RPL_LIST

 			{
 				// Some servers (ie. Bahamut) report a channel named '*' under some circumstances. Ignore those channels.

 				if (params[1].equals("*"))
 					break;
 
 				int users = 0;
 				try
 				{
 					users = Integer.parseInt(params[2]);
 				}
 				catch (NumberFormatException ex) {}
 
 				replyListAdd(params[1], users, params[3]);
 				break;
 			}
 			case 323:	// RPL_LISTEND

 			{
 				replyListEnd();
 				break;
 			}
 
 			case 324:	// RPL_CHANNELMODEIS

 			{
 				String [] modes_params = new String [params.length - 3];
 				for (int i = 0; i < modes_params.length; i++)
 					modes_params[i] = params[i + 3];
 
 				Channel channel = getChannel(params[1]);
 				if (channel != null)
 					channel.setModes(params[2], modes_params);
 
 				break;
 			}
 
 			case 331:	// RPL_NOTOPIC

 			{
 				Channel channel = getChannel(params[1]);
 				if (channel != null)
 					channel.setTopic("");
 
 				OutputWindow cw = getChannelWindow(params[1]);
 				if (cw == null)
 					cw = getCurrentPanel();
 
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc.s37");
 				cw.printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 332:	// RPL_TOPIC

 			{
 				Channel channel = getChannel(params[1]);
 				if (channel != null)
 					channel.setTopic(MircMessage.filterMircAttributes(params[2]));
 
 				OutputWindow cw = getChannelWindow(params[1]);
 				if (cw == null)
 					cw = getCurrentPanel();
 
 				Object a[] = { params[1], params[2] };
 				String ptn = res.getString("eirc.s38");
 				cw.printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 333:	// RPL_TOPICINFO

 			{
 				// We're sent the time in seconds, but Date expects millis.

 				Date topic_date = new Date(Long.parseLong(params[3]) * 1000);
 
 				OutputWindow cw = getChannelWindow(params[1]);
 				if (cw == null)
 					cw = getCurrentPanel();
 
 				Object a[] = { params[2], topic_date, topic_date };
 				String ptn = res.getString("eirc.s35");
 				cw.printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 			
 			case 336:	// RPL_INVITELIST

 			{
 				listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
 				break;
 			}
 
 			case 337:	// RPL_ENDOFINVITELIST

 			{
 				listEnd(params[1], 'I');
 				break;
 			}
 
 			case 348:	// RPL_EXCEPTLIST

 			{
 				listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
 				break;
 			}
 
 			case 349:	// RPL_ENDOFEXCEPTLIST

 			{
 				listEnd(params[1], 'e');
 				break;
 			}
 
 			case 352:	// RPL_WHOREPLY

 			{
 				if (params.length >= 6)
 				{
 					NickInfo.add(params[5], params[params.length - 1], params[2], params[3]);
 					replyWhoAdd(params[5]);
 					
 					for (Enumeration e = channels.elements(); e.hasMoreElements(); )
 					{
 						Channel channel = (Channel) e.nextElement();
 						channel.update(params[5]);
 			   		}
 				}
 
 				break;
 			}
 
 			case 353:	// RPL_NAMREPLY

 			{
 				Channel channel = getChannel(params[2]);
 
 				StringTokenizer st = new StringTokenizer(params[3], " ");
 				int tokens = st.countTokens();
 				for (int i = 0; i < tokens; i++)
 				{
 					if (channel != null)
 						channel.add(st.nextToken());
 					else
 						names.addElement(st.nextToken());
 				}
 
 				break;
 			}
 
 			case 366:	// RPL_ENDOFNAMES

 			{
 				if (getChannel(params[1]) == null)
 				{
 					Object a[] = { params[1] };
 	   				String s = res.getString("eirc.366");
 	   				getCurrentPanel().printInfo(MessageFormat.format(s, a));
 					
 					s = "";
 					for (Enumeration e = names.elements() ; e.hasMoreElements() ;)
 						s += (String)e.nextElement() + " ";
 					getCurrentPanel().printInfo(s);
 				}
 				names.removeAllElements();
 				
 				break;
 			}
 			
 			case 367:	// RPL_BANLIST

 			{
 				listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
 				break;
 			}
 
 			case 368:	// RPL_ENDOFBANLIST

 			{
 				listEnd(params[1], 'b');
 				break;
 			}
 
 			case 6:		// RPL_ADMINME

 			case 7:		// RPL_ADMINLOC1

 			case 8:		// RPL_ADMINLOC2

 			case 9:		// RPL_ADMINEMAIL

 			case 371:	// RPL_INFO

 			case 372:	// RPL_MOTD

 			{
 				status.printInfo(params[1]);
 				break;
 			}
 
 			case 381:	// RPL_YOUREOPER

 			{
 				getCurrentPanel().printInfo(res.getString("eirc." + command));
 				break;
 			}
 
 			case 376:   // RPL_ENDOFMOTD

 			case 422:	// ERR_NOMOTD

 			{
 				// If this point was reached, login has been successful.

 				if (!logged_in)
 				{
 					this.logged_in = true;
 
 					// See what the server thinks our nick is from the message it sent. Servers trim nicks to fit an n-char limit.

 					if (!new_nick.equals(params[0]))
 					{
 						this.new_nick = params[0];
 						nick_entry.setText(new_nick);
 
 						Object a[] = { new_nick };
 						String ptn = res.getString("eirc.s4");
 						getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 					}
 
 					pos_login();
 					updateWindowTitle();
 				}
 				
 				setServerSupport();
 				
 				break;
 			}
 
 			case 404:	// ERR_CANNOTSENDTOCHAN

 			{
 				Object a[] = { params[2] };
 				String ptn = res.getString("eirc." + command);
 				getCurrentPanel().printError(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 432:	// ERR_ERRONEUSNICKNAME

 			case 433:	// ERR_NICKNAMEINUSE

 			{
 				getCurrentPanel().printError(res.getString("eirc." + command));
 				break;
 			}
 
 			case 401:   // ERR_NOSUCHNICK

 			case 403:   // ERR_NOSUCHCHANNEL

 			case 406:	// ERR_WASNOSUCHNICK

 			case 421:	// ERR_UNKNOWNCOMMAND

 			case 461:   // ERR_NEEDMOREPARAMS

 			case 464:   // ERR_PASSWDMISMATCH

 			case 491:	// ERR_NOOPERHOST

 			{
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc." + command);
 				getCurrentPanel().printError(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 437:	// ERR_UNAVAILRESOURCE

 			case 465:   // ERR_YOUREBANNEDCREEP

 			case 471:   // ERR_CHANNELISFULL

 			case 473:   // ERR_INVITEONLYCHAN

 			case 474:   // ERR_BANNEDFROMCHAN

 			case 478:   // ERR_BANLISTFULL

 			case 482:	// ERR_CHANOPRIVSNEEDED

 			{
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc." + command);
 				status.printError(MessageFormat.format(ptn, a));
 				status.requestFocus();
 
 				break;
 			}
 
 			case 475:	// ERR_BADCHANNELKEY

 			{
 				openKeyWin(params[1]);
 				break;
 			}
 
 /* ----------------- RFC not compliant special messages ---------------- */
 
 			case 232:	// RPL_RULES (Unreal)

 			case 536:	// RPL_RULES (IrcDreams)

 			{
 				status.printInfo(params[1]);
 				break;
 			}
 
 			case 307:	// RPL_WHOISREGISTERED (Unreal, Bahamut)

 			{
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc.s27");
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 320:	// RPL_WHOISSPECIAL (Unreal)

 			case 378:	// RPL_WHOISHOST (Unreal)

 			{
 				Object a[] = { params[1], params[2] };
 				String ptn = res.getString("eirc." + command);
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 335:	// RPL_WHOISBOT (Unreal)

 			case 671:	// RPL_WHOISSECURE

 			{
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc." + command);
 				getCurrentPanel().printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 			
 			case 346:	// RPL_INVEXLIST (Unreal)

 			{
 				listAdd(params[2], params[3], new Date(Long.parseLong(params[4]) * 1000));
 				break;
 			}
 
 			case 347:	// RPL_ENDOFINVEXLIST (Unreal)

 			{
 				listEnd(params[1], 'I');
 				break;
 			}
 
 			case 438:	// ERR_NICKCHANGETOOFAST (Unreal)

 			case 447:	// ERR_NONICKCHANGE (Unreal)

 			{
 				Object a[] = { params[params.length - 1] };
 				String ptn = res.getString("eirc.nickchange");
 				getCurrentPanel().printError(MessageFormat.format(ptn, a));
 				break;
 			}
 			
 			case 498:	// Reserved nick Voila.fr

 			{
 				getCurrentPanel().printError(res.getString("eirc.433"));
 				break;
 			}
 			
 			case 477:   // ERR_NEEDREGGEDNICK (Unreal, Bahamut)

 			case 489:	// ERR_SECUREONLYCHAN (Unreal)

 			{
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc." + command);
 				status.printError(MessageFormat.format(ptn, a));
 				status.requestFocus();
 				break;
 			}
 			
 			case 600:	// RPL_LOGON (Unreal, Bahamut)

 			case 601:	// RPL_LOGOFF (Unreal, Bahamut)

 			{
 				Date log = new Date(Long.parseLong(params[4]) * 1000);
 
 				Object a[] = { params[1], log };
 				String ptn = res.getString("eirc." + command);
 				status.printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 
 			case 604:	// RPL_NOWON (Unreal, Bahamut)

 			case 605:	// RPL_NOWOFF (Unreal, Bahamut)

 			{
 				Object a[] = { params[1] };
 				String ptn = res.getString("eirc." + command);
 				status.printInfo(MessageFormat.format(ptn, a));
 				break;
 			}
 /* ----------------- End of RFC not compliant messages ---------------- */
 
 
 		}
 	}
 	
 	private void setServerSupport()
 	{
 		// IRC RPL_ISUPPORT still draft but widely supported

 		
 		try
 		{
 			String prefixes = (String)server_support.get("PREFIX");
 			if (prefixes != null && prefixes.length() > 2)
 				Modes.parseSupportedPrefixes(prefixes);
 		} catch (ParseException ex)	{ System.err.println(ex); }
 		
 		try
 		{
 			String modes = (String)server_support.get("MODES");
 			if (modes != null && modes.length() > 0)
 				ModeList.setMaximumNumber(Integer.parseInt(modes));
 		} catch (NumberFormatException ex)	{ System.err.println(ex); }
 	}
 	
 	private void replyListStart()
 	{
 		chan_list.clear();
 	}
 
 	private void replyListAdd(String tag, int users, String topic)
 	{
 		// Some buggy IRCd's don't send RPL_LISTSTART.

 //		if (!chan_list.initialized())

 //			replyListStart();

 
 		chan_list.add(new ChannelItem(tag, users, topic));
 		
 		if (chan_list.number() % 100 == 0);
 			updateChanListTitle();
 	}
 
 	private void replyListEnd()
 	{
 		// Some buggy IRCd's don't send RPL_LISTSTART.

 //		if (!chan_list.initialized())

 //			replyListStart();

 			
 		if (chan_list.number() == 0)
 			status.printInfo(res.getString("eirc.s19"));
 
 		chan_list.stop();
 		updateChanListTitle();
 	}
 
 	private void replyWhoAdd(String tag)
 	{
 		if (who_reply == null)
 			who_reply = new Vector();
 
 		who_reply.addElement(tag);
 	}
 
 	private void replyWhoEnd()
 	{
 		if (who_reply == null)
 			who_reply = new Vector();
 			
 		who_list.loadUsers(who_reply);
 		this.who_reply = null;
 /*
 		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 			ChannelWindow cw = (ChannelWindow)e.nextElement();
 			if (cw != null)
    				cw.refreshUsers();
 		}
 */
 	}
 
 	private void listStart()
 	{
 		this.list = new Vector(1);
 	}
 
 	private void listAdd(String mask, String op, Date date)
 	{
 		if (list == null)
 			listStart();
 
 		list.addElement(new ListItem(mask, op, date));
 	}
 
 	private void listEnd(String channel, char param)
 	{
 		if (list == null)
 			listStart();
 		String supported_modes = "beI";
 		if (supported_modes.indexOf(param) >= 0)
 			openList(channel, param);
 		this.list = null;
 	}
 
 	public void sendCommand(String text, OutputWindow target)
 	{
 		if (text.trim().length() <= 0)
 	  		throw new IllegalArgumentException("empty command");
 
 		String [] result = null;
 		try
 		{
 			result = Commands.parseCommand(user_commands, text);
 		}
 		catch (MissingResourceException ex)
 		{
 			// Let the server decide whether the command is correct.

 			String [] a = new String [0];
 			sendMessage(text, a);
 
 			return;
 		}
 		catch (IllegalArgumentException ex)
 		{
 			// Peek command. Text won't be neither empty nor have only white space (checked above).

 			String name = new StringTokenizer(text).nextToken();
 
 			// If excution gets here, command "name" does exist, otherwise the previous catch statement would have came into action.

 			Command c = Commands.getCommand(user_commands, name);
 			String action = c.getTag();
 			int required = c.getRequiredParameters();
 
 			// Show help.

 
 			String help = res.getString(action.concat(".short").toLowerCase());
 			if (help != null)
 			{
 				Object [] a = { help };
 				String ptn = res.getString("eirc.s22");
 				target.printError(MessageFormat.format(ptn, a));
 			}
 			else
 			{
 				// No help for this command. Warn the user.

 				MessageFormat mf = new MessageFormat(res.getString("eirc.bad_invocation"));
 				Object [] a = { action, new Integer(required) };
 				double [] limits = { 1.0, ChoiceFormat.nextDouble(1.0) };
 				String [] fragments =
 				{
 					res.getString("eirc.bad_invocation.0"),
 					res.getString("eirc.bad_invocation.1"),
 				};
 				mf.setFormat(1, new ChoiceFormat(limits, fragments));
 				target.printError(mf.format(a));
 			}
 
 			return;
 		}
 
 		/* Invoke the command. */
 
 		String name = result[0];
 		Vector parameters = new Vector(result.length - 1);
 		for (int i = 1; i < result.length; i++)
 			parameters.addElement(result[i]);
 
 		invokeCommand(name, parameters, target);
 	}
 
 	void invokeCommand(String name, Vector parameters, OutputWindow target)
 	{
 		// Command disabled in config ?

 		if (isDisabledCmd(name))
 		{
 			Object [] a = { name };
 			String ptn = res.getString("eirc.forbid");
 			target.printError(MessageFormat.format(ptn, a));
 			return;
 		}
 		
 		// Only add commands here, DON'T add aliases.

 		if (name.equalsIgnoreCase("HELP"))
 			cmd_help(parameters, target);
 		else if (name.equalsIgnoreCase("CHARSET"))
 			cmd_charset(parameters, target);
 		else if (name.equalsIgnoreCase("CLEAR"))
 			cmd_clear(parameters, target);
 		else if (name.equalsIgnoreCase("QUOTE"))
 			cmd_quote(parameters, target);
 		else if (name.equalsIgnoreCase("JOIN"))
 			cmd_join(parameters, target);
 		else if (name.equalsIgnoreCase("PART"))
 			cmd_part(parameters, target);
 		else if (name.equalsIgnoreCase("PRIVMSG"))
 			cmd_msg(parameters, target);
 		else if (name.equalsIgnoreCase("NOTICE"))
 			cmd_notice(parameters, target);
 		else if (name.equalsIgnoreCase("PINGTIME"))
 			cmd_pingtime(parameters, target);
 		else if (name.equalsIgnoreCase("QUIT"))
 			cmd_quit(parameters, target);
 		else if (name.equalsIgnoreCase("NICK"))
 			cmd_nick(parameters, target);
 		else if (name.equalsIgnoreCase("ME"))
 			cmd_me(parameters, target);
 		else if (name.equalsIgnoreCase("QUERY"))
 			cmd_query(parameters, target);
 		else if (name.equalsIgnoreCase("CTCP"))
 			cmd_ctcp(parameters, target);
 		else if (name.equalsIgnoreCase("EIRC"))
 			cmd_eirc(parameters, target);
 		else if (name.equalsIgnoreCase("KBAN"))
 			cmd_kban(parameters, target);
 		else if (name.equalsIgnoreCase("IGNORE"))
 			cmd_ignore(parameters, target);
 		else if (name.equalsIgnoreCase("UNIGNORE"))
 			cmd_unignore(parameters, target);
 		else if (name.equalsIgnoreCase("SAVE"))
 			cmd_save(parameters, target);
 		else if (name.equalsIgnoreCase("SERVER"))
 			cmd_server(parameters, target, false);
 		else if (name.equalsIgnoreCase("SERVERSSL"))
 			cmd_server(parameters, target, true);
 		else if (name.equalsIgnoreCase("GHOST"))
 			cmd_ghost(parameters, target);
 		else if (name.equalsIgnoreCase("DEBUG"))
 			cmd_debug(parameters, target);
 		else
 		{
 			// Let the server decide whether the command is correct.

 			String [] a = new String [parameters.size()];
 			parameters.copyInto(a);
 			sendMessage(name, a);
 		}
 	}
 	
 	boolean isDisabledCmd(String cmd)
 	{
 		String list = getParameter("disabled_cmds");
 		if (list != null && cmd != null && list.length() > 0 && cmd.length() > 0)
 		{
 			StringTokenizer tk = new StringTokenizer(list, ", ");
 			while (tk.hasMoreTokens())
 			{
 				if (tk.nextToken().equalsIgnoreCase(cmd))
 					return(true);
 			}
 		}
 		
 		return(false);		
 	}
 
 	void cmd_help(Vector params, OutputWindow target)
 	{
 		// If no command has been specified.

   		if (params.size() == 0)
 		{
   			// List internal commands.

 			for (Enumeration e= user_commands.getKeys();e.hasMoreElements();)
 				target.printInfo((String) e.nextElement());
 			return;
   		}
 
 		String action = (String)params.elementAt(0);
 		try
 		{
 			// Resolve aliases.

 			action = Commands.getCommand(user_commands, action).getTag();
 		}
 		catch (MissingResourceException e)
 		{
 		}
 
    		String help_on = action.toLowerCase();
 		// See if there's help locally provided for this command.

 		String h1 = res.getString(help_on.concat(".short"));
 		String h2 = res.getString(help_on.concat(".long"));
 		if (h1 != null && h2 != null)
 		{
 			target.printInfo(h1);
 			target.printInfo(h2);
 		}
 		else
 		{
 			// No help for this command.

 			Object a[] = { action };
 			String ptn = res.getString("eirc.no_help");
 			target.printError(MessageFormat.format(ptn, a));
 		}
 	}
 
 	void cmd_quote(Vector params, OutputWindow target)
 	{
 		String a[] = new String [0];
 		sendMessage((String) params.elementAt(0), a);
 	}
 
 	void cmd_nick(Vector params, OutputWindow target)
 	{
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 		a[0] = RFC1459.filterString(a[0]);
 
 		sendMessage("NICK", a);
 	}
 
 	void cmd_me(Vector params, OutputWindow target)
 	{
 		if (!(target instanceof ChatWindow))
 		{
 			target.printError(res.getString("eirc.s23"));
 			return;
 		}
 
 		String to = ((ChatWindow) target).getPanelTag();
 		params.insertElementAt(to, 0);
 
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 
 		((ChatWindow) target).printMyAction(a[1]);
 
 		a[1] = "\001ACTION " + a[1] + "\001";
 		sendMessage("PRIVMSG", a);
 	}
 
 	void cmd_query(Vector params, OutputWindow target)
 	{
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 
 		PrivateWindow pw = openPrivate(a[0]);
 		chat_panel.show(a[0]);
 		pw.requestFocus();
 
 		if (params.size() == 2)
 		{
 			sendMessage("PRIVMSG", a);
 			pw.printMyPrivmsg(a[1]);
 		}
 	}
 	void cmd_msg(Vector params, OutputWindow target)
 	{
 		cmd_query(params, target);
 	}
 	
 	void cmd_notice(Vector params, OutputWindow target)
 	{
 		if (!(target instanceof ChatWindow))
 		{
 			target.printError(res.getString("eirc.s23"));
 			return;
 		}
 
 		if (params.size() == 2)
 		{
 			String a[] = new String [2];
 			a[0] = (String)params.elementAt(0);
 			a[1] = (String)params.elementAt(1);
 			sendMessage("NOTICE", a);
 		   	((ChatWindow)target).printMyNotice(a[1]);
 		}
 	}
 	
 	void cmd_join(Vector params, OutputWindow target)
 	{
 		if (!canJoin())
 			return;
 				
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 
 		if (!Channel.isChannel(a[0]))
 		{
 			a[0] = '#' + a[0];
 		}
 		sendMessage("JOIN", a);
 	}
 
 	void cmd_part(Vector params, OutputWindow target)
 	{
 		if (0 == params.size())
 		{
 			if (!(target instanceof ChannelWindow))
 			{
 				target.printError(res.getString("eirc.s24"));
 				return;
 			}
 			params.insertElementAt(((ChannelWindow) target).getPanelTag(), 0);
 		}
 
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 
 		if (!Channel.isChannel(a[0]))
 		{
 			a[0] = '#' + a[0];
 		}
 
 		// The Listener for the event takes care of sending the message.

 	  	sendMessage("PART", a);
 	}
 
 	void cmd_pingtime(Vector params, OutputWindow target)
 	{
 		params.addElement("\001PING " + (new Date()).getTime() + "\001");
 
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 
 		sendMessage("PRIVMSG", a);
 	}
 
 	void cmd_quit(Vector params, OutputWindow target)
 	{
 		String a[] = new String [1];
 
 		a[0] = 0 == params.size() ? quit_message : (String) params.elementAt(0);
 
 		this.send_quit = false;
 		sendMessage("QUIT", a);
 	}
 
 	void cmd_ctcp(Vector params, OutputWindow target)
 	{
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 
 		a[1] = "\001" + a[1] + "\001";
 		sendMessage("PRIVMSG", a);
 	}
 
 	void cmd_eirc(Vector params, OutputWindow target)
 	{
 		Object a[] = { PACKAGE, VERSION.concat(" ").concat(VERSION_EXTRA), res.getString("author"), res.getString("update") };
 		String ptn = res.getString("info");
 		target.printInfo(MessageFormat.format(ptn, a));
 	}
 
 	void cmd_charset(Vector params, OutputWindow target)
 	{
 		String charset = encoding;
 		if (params.size() > 0)
 		{
 			charset = (String)params.elementAt(0);
 			try
 			{
 				CharsConverter conv = new CharsConverter(charset, charset);
 				if (server != null)
 					server.setCharsConverter(conv);
 
 				encoding = charset;
 				decoding = charset;
 				properties.setString("decoding", charset);
 				properties.setString("encoding", charset);
 			}
 			catch(UnsupportedEncodingException e)
 			{
 				target.printError(e.toString());
 				return;
 			}
 		}
 		
 		target.printInfo("Charset : " + charset);
 	}
 	
 	void cmd_clear(Vector params, OutputWindow target)
 	{
 		target.clear();
 	}
 
 	void cmd_kban(Vector params, OutputWindow target)
 	{
 		int i = 0;
 		String channel = "";
 		if (target instanceof ChannelWindow)
 			channel = ((ChannelWindow)target).getPanelTag();
 		if (params.size() > 2)
 			channel = (String)params.elementAt(i++);
 		String nick = (String)params.elementAt(i++);
 		String why = "";
 		if (i == params.size() - 1)
 			why = (String)params.elementAt(i);
 
 		if (getChannel(channel) == null)
 		{
 			target.printError(res.getString("eirc.s24"));
 			return;
 		}
 
 		String mask = nick;
 		String addr = NickInfo.getInetAddr(nick);
 		String user = NickInfo.getUser(nick);
 		if (addr == null)
 		{
 			if (user != null)
 				mask = "*!" + user + "@*";
 		}
 		else
 		{
 			mask = "*!*@" + addr;
 		}
 
 	  	sendCommand("MODE " + channel + " +b " + mask, target);
   		sendCommand("KICK " + channel + " " + nick + " " + why, target);
 	}
 
 	void cmd_ignore(Vector params, OutputWindow target)
 	{
 		if (params.size() == 0)
 		{
 			if (ignore_list.size() == 0)
 			{
 				target.printWarning(res.getString("eirc.no_ignored_users"));
 				return;
 			}
 
 			StringBuffer line = new StringBuffer();
 			for (Enumeration e = NickInfo.getList().elements(); e.hasMoreElements();)
 			{
 				String nick = (String) e.nextElement();
 				if (ignore_list.contains(NickInfo.getInetAddr(nick)))
 					line.append(' ').append(nick);
 			}
 
 			Object a[] = { line.toString() };
 			String ptn = res.getString("eirc.ignored_users");
 			target.printInfo(MessageFormat.format(ptn, a));
 			return;
 		}
 
 		String nick = (String) params.elementAt(0);
 		String addr = NickInfo.getInetAddr(nick);
 
 		if (addr != null && !ignore_list.contains(addr))
 		{
 			ignore_list.addElement(addr);
 
 			Object a[] = { nick };
 			String ptn = res.getString("eirc.ignore");
 			target.printInfo(MessageFormat.format(ptn, a));
 
 			String p[] = { nick, res.getString("eirc.ignore_notice") };
 			sendMessage("NOTICE", p);
 		}
 	}
 
 	void cmd_unignore(Vector params, OutputWindow target)
 	{
 		String nick = (String) params.elementAt(0);
 		String addr = NickInfo.getInetAddr(nick);
 
 		if (addr != null && ignore_list.removeElement(addr))
 		{
 			Object a[] = { nick };
 			String ptn = res.getString("eirc.unignore");
 			target.printInfo(MessageFormat.format(ptn, a));
 
 			String p[] = { nick, res.getString("eirc.unignore_notice") };
 			sendMessage("NOTICE", p);
 		}
 	}
 
 	void cmd_save(Vector params, OutputWindow target)
 	{
 		String name = null;
 		if (params.size() > 0)
 		{
 			saveTo((String)params.elementAt(0));
 			return;
 		}
 		
 		if (isConfDefined())
 		{
 			save();
 			return;
 		}
 		
 		target.printError(res.getString("eirc.errsav"));
 	}
 	
 	void cmd_server(Vector params, OutputWindow target, boolean ssl)
 
 	{
 		String server_name = (String)params.elementAt(0);
 		if (params.size() > 2)
 			this.irc_pass = (String)params.elementAt(2);
 
 		// If user passed a port number.

 		if (params.size() > 1)
 		{
 			try
 			{
 				connect(server_name, Integer.parseInt((String)params.elementAt(1)), ssl);
 				return;
 			}
 			catch (NumberFormatException e) {}
 		}
 		
 		connect(server_name, ssl);
 	}
 
 	void cmd_ghost(Vector params, OutputWindow target)
 	{
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 		irc_services.killGhost(a[0], a[1]);
 
 	}
 	
 	void cmd_debug(Vector params, OutputWindow target)
 	{
 		String a[] = new String [params.size()];
 		params.copyInto(a);
 		if (a[0].equalsIgnoreCase("OFF"))
 			debug_traffic = 0;
 		if (a[0].equalsIgnoreCase("CONSOLE"))
 			debug_traffic = 1;
 		if (a[0].equalsIgnoreCase("ON"))
 		{
 			debug_window = getCurrentPanel();
 			debug_traffic = 2;
 		}
 	}
 
 	public void sendMessage(String command, String parameters[])
 	{
 		if (connected)
 		{
 			Message m = new MircMessage(command, parameters);
 			if (debug_traffic > 0)
 			{
 				String t = m.toString();
 				// Strip CRLF.

 				t = t.substring(0, t.length() - 2);
 				debug(">> " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM).format(new Date()) + " " + t);
 			}
 
 			try
 			{
 				server.enqueueMessage(m);
 			}
 			catch (IOException e)
 			{
 				System.err.println(e);
 			}
 		}
 		else
 		{
 			// FIXME: it can't distinguish user and program issued messages.

 	  		status.printError(res.getString("eirc.disconnected"));
 		}
 	}
 
 	public void joinChannel(String name)
 	{
 		if (getChannel(name) == null)
 		{
 			if (canJoin())
 			{
 				String p[] = { name };
 				sendMessage("JOIN", p);
 			}
 		}
 		else
 		{
 			showPanel(name);
 		}
 	}
 	
 	public boolean canJoin()
 	{
 		// Permission ?

 		if (restrict_join == 0)
 			return(true);
 			
 		return(false);
 	}
 	
 	public OutputWindow getCurrentPanel()
 	{
 		ChatPanel cp = chat_panel.getVisible();
 		return (cp != null && cp instanceof OutputWindow ? (OutputWindow)cp : status);
 	}
 
 	public OutputWindow getUserPanel(String user)
 	{
 		OutputWindow ow = getPrivate(user);
 
 		return (ow != null ? ow : status);
 	}
 
 	public OutputWindow [] getUserPanels(String user)
 	{
 		Vector v = new Vector();
 
 		for (Enumeration e = channel_windows.elements(); e.hasMoreElements();)
 		{
 			ChannelWindow cw = (ChannelWindow) e.nextElement();
 			Channel c = cw.getChannel();
 
 			if (c.contains(user))
 				v.addElement(cw);
 		}
 
 		OutputWindow pw = getPrivate(user);
 		if (pw != null)
 
 			v.addElement(pw);
 
 		OutputWindow [] a = new OutputWindow [v.size()];
 		v.copyInto(a);
 
 		return(a);
 	}
 
 	public String[] getChans()
 	{
 		Vector v = new Vector();
 
 		for (Enumeration e = channel_windows.elements(); e.hasMoreElements();)
 		{
 			ChannelWindow cw = (ChannelWindow) e.nextElement();
 			Channel ch = cw.getChannel();
 			v.addElement(ch.getTag());
 		}
 
 		String[] a = new String[v.size()];
 		v.copyInto(a);
 
 		return(a);
 	}
 
 	public String getNick()
 	{
 		return(current_nick);
 	}
 
 	public Channel getChannel(String tag)
 	{
 		ChannelWindow cw = getChannelWindow(tag);
 		return (cw != null ? cw.getChannel() : null);
 	}
 
 	public ChannelWindow getChannelWindow(String tag)
 	{
 		return (ChannelWindow)channel_windows.get(tag);
 	}
 
 	public Channel openChannel(String tag)
 	{
 		Channel channel = getChannel(tag);
 		if (channel != null)
 		{
 			chat_panel.show(tag);
 			return(channel);
 		}
 
 		channel = new Channel(tag);
 	  	channels.addElement(channel);
 
 		String [] p = { tag };
 		sendMessage("MODE", p);
 
 		/* Create GUI object.
 		 */
 
 		ChannelWindow cw = new ChannelWindow(this, channel);
 		properties.addObserver(cw);
 		cw.update(properties, null);
 
 		cw.setFont(getFont());
 		cw.setForeground(mainfg);
 		cw.setBackground(mainbg);
 		cw.setTextForeground(textfg);
 		cw.setTextBackground(textbg);
 		cw.setSelectedForeground(selfg);
 		cw.setSelectedBackground(selbg);
 
 		channel_windows.put(tag, cw);
 
 		chat_panel.add(cw, tag);
 
 	  	cw.validate();
 
 		chat_panel.show(tag);
 
   		cw.requestFocus();
 		cw.addChatPanelListener(this);
 
 		return channel;
 	}
 
 	public void close(String name)
 	{
 		if (Channel.isChannel(name))
 		{
 			String p[] = { name };
 			sendMessage("PART", p);
 			closeChannel(name);
 		}
 		else
 		{
 			closePrivate(name);
 		}
 	}
 	
 	public void closeAllChannels()
 	{
 		/* Close all open channel panels */
 		
 		String chans[] = getChans();
 		String param[] = new String[1];
 
 		if (connected && chans.length > 0)
 		{
 			int i = chans.length - 1;
 			param[0] = chans[i--];
 			for (; i >= 0; i--)
 				param[0] += "," + chans[i];
 
 			sendMessage("PART", param);
 		}
 
 		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 			ChannelWindow w = (ChannelWindow)e.nextElement();
 			w.dispose();
 		}
 		channel_windows.clear();
 	  	channels.removeAllElements();
 	}
 	
 	public void closeChannel(String s)
 	{
 		for (Enumeration e = channels.elements(); e.hasMoreElements(); )
 		{
 			Channel channel = (Channel) e.nextElement();
 			if (channel.getTag().equals(s))
 				channels.removeElement(channel);
 		}
 
 		ChannelWindow cw = getChannelWindow(s);
 		if (cw != null)
 			cw.dispose();
 	}
 
 	public void closePrivate(String name)
 	{
 		/* Close a private */
 
 		for (Enumeration e = privates.elements(); e.hasMoreElements();)
 		{
 			PrivateWindow pw = (PrivateWindow) e.nextElement();
 			if (pw.getUser().equals(name))
 			{
 				privates.remove(name);
 				pw.dispose();
 			}
 		}			
 	}
 	
 	public void closeUnreadPrivates()
 	{
 		for (Enumeration e = privates.elements(); e.hasMoreElements();)
 		{
 			PrivateWindow pw = (PrivateWindow) e.nextElement();
 			if (!pw.isRead())
 			{
 				privates.remove(pw.getPanelTag());
 				pw.dispose();
 			}
 		}
 	}
 	
 	public void closeAllPrivates()
 	{
 		/* Close privates panels */
 		
 		for (Enumeration e = privates.elements(); e.hasMoreElements(); )
 		{
   			PrivateWindow pw = (PrivateWindow) e.nextElement();
   			pw.dispose();
   		}
 		privates.clear();
 	}
 
 	public void updateChanTitle(Channel ch)
 	{
 		if (ch != null)
 		{
 			String tag = ch.getTag();
 			Object o[] = { tag, new Integer(ch.number()) };
 			chat_panel.setTitle(tag, chan_title.format(o));
 		}
 	}
 
 	public void updateChanListTitle()
 	{
 		String tag = "*Chans*";
 		Object o[] = { new Integer(chan_list.number()) };
 		chat_panel.setTitle(tag, chanlist_title.format(o));
 	}
 
 	public PrivateWindow getPrivate(String target)
 	{
 		return (PrivateWindow) privates.get(target);
 	}
 
 	public PrivateWindow openPrivate(String target, boolean no_more)
 	{
 		PrivateWindow pw = getPrivate(target);
 		if (pw != null)
 			return pw;
 
 		if (no_more)
 			return(null);
 		else
 			return(openPrivate(target));
 	}
 
 	public PrivateWindow openPrivate(String target)
 	{
 		PrivateWindow pw = getPrivate(target);
 		if (pw != null)
 			return pw;
 
 		pw = new PrivateWindow(this, target, target);
 		properties.addObserver(pw);
 		pw.update(properties, null);
 
 		pw.setFont(getFont());
 		pw.setForeground(mainfg);
 		pw.setBackground(mainbg);
 		pw.setTextForeground(textfg);
 		pw.setTextBackground(textbg);
 		pw.setSelectedBackground(selbg);
 
 		privates.put(target, pw);
 		chat_panel.add(pw, target);
 
 		Object o[] = { target };
 		chat_panel.setTitle(target, private_title.format(o));
 
 		pw.validate();
 		if (focus_opening_privates)
 		{
 			chat_panel.show(target);
 			pw.requestFocus();
 		}
 
 		pw.addChatPanelListener(this);
 
 		return pw;
 	}
 
 	public void showPanel(String name)
 	{
 		chat_panel.show(name);
 	}
 
 	public void openChannelList()
 	{
 		chat_panel.showPanel("*Chans*");
 	}
 
 	public void openWhoList()
 	{
 //		String p[] = { };

 //		sendMessage("LUSERS", p);

 		chat_panel.showPanel("*Who*");
 	}
 
 	public void openConfigurator()
 	{
 		if (configurator == null)
 		{
 			configurator = new Configurator(this, this.properties);
 			configurator.addWindowListener(this);
 			configurator.setName("configurator");
 		}
 		else
 		{
 			configurator.toFront();
 		}
 	}
 
 	public void openList(String tag, char mode)
 	{
 		if (list != null)
 		{
 			Channel chan = getChannel(tag);
 			boolean is_op = false;
 			if (chan != null)
 			{
 				User me = chan.get(getNick());
 				is_op = me.isOwner() || me.isAdmin() || me.isOp() || me.isHalfOp() || canOverride();
 			}
 
 			MessageFormat mf = new MessageFormat(res.getString(mode + "_list.title"));
 			Object o[] = { tag };
 			String title = mf.format(o);
 			mf = new MessageFormat(Resources.getString("eirc." + mode + "_list"));
 			String button = res.getString(mode + "_list.delete");
 
 			if (mode_list == null)
 			{
 				mode_list = new ModeList(this);
 				mode_list.addWindowListener(this);
 				mode_list.setName("mode_list");
 			}
 			else
 			{
 				mode_list.toFront();
 			}
 			mode_list.initList(tag, is_op, list, title, mf, button, mode);
 		}
 	}
 
 	public String openAwayWin()
 	{
 		Box b = new Box(this, res.getString("away.title"), res.getString("away.prompt"), res.getString("away.label"), away_str, false);
 		if (b.getResult() == 1)
 		{
 			away_str = b.getString();
 			return(away_str != null ? away_str : "");
 		}
 		return("");
 	}
 
 	public Box openWin(String cmd, String nick)
 	{
 		String key = cmd.toLowerCase(Locale.US);
 		MessageFormat mf = new MessageFormat(res.getString(key + ".prompt"));
 		Object o[] = { nick };
 		String prompt = mf.format(o);
 		return(new Box(this, res.getString(key + ".title"), prompt, res.getString(key + ".label"), null, false));
 	}
 
 	public void openKill(String cmd, String nick, OutputWindow target)
 	{
 		Box b = openWin(cmd, nick);
 		String why = b.getString();
 		if (b.getResult() == 1 && why.length() > 0)
 			sendCommand(cmd + " " + nick + " " + why, target);
 	}
 
 	public void openKick(String cmd, String channel, String nick, OutputWindow target)
 	{
 		Box b = openWin(cmd, nick);
 		String why = b.getString();
 		if (b.getResult() == 1 && why.length() > 0)
 			sendCommand(cmd + " " + channel + " " + nick + " " + why, target);
 	}
 
 	public void openKeyWin(String chan)
 	{
 		MessageFormat mf = new MessageFormat(res.getString("join.title"));
 		Object o[] = { chan };
 		String title = mf.format(o);
 		Box b = new Box(this, title, res.getString("join.prompt"), res.getString("join.label"), null, true);
 		if (b.getResult() == 1)
 		{
 			String p[] = { chan, b.getString() };
 			sendMessage("JOIN", p);
 		}
 	}
 
 	public void openGhostWin(String cmd, String nick, OutputWindow target)
 	{
 		Box b = new Box(this, res.getString("ghost.title"), res.getString("ghost.prompt"), res.getString("ghost.label"), null, true);
 		String pw = b.getString();
 		if (b.getResult() == 1 && pw.length() > 0)
 			sendCommand(cmd + " " + nick + " " + pw, target);
 	}
 
 	public void openIDWin()
 	{
 		Box b = new Box(this, res.getString("services.id.title"), res.getString("services.prompt"), res.getString("services.label"), null, true);
 		String pw = b.getString();
 		if (b.getResult() == 1 && pw.length() > 0)
 		{
 			identify(pw);
 		}
 		else
 		{
 			String p[] = { original_nick };
 			sendMessage("NICK", p);
 		}
 	}
 
 	public void badIDWin()
 	{
 		new Box(this, res.getString("services.id.title"), res.getString("services.ns.loginbad"), null);
 		String p[] = { original_nick };
 		sendMessage("NICK", p);
 	}
 	
 	public boolean isNickCorrect(String nick)
 	{
 		int i = 0;
 		boolean compliant = true;
 		char c = '?';
 		while (i < nick.length() && compliant)
 		{
 			c = nick.charAt(i);
 			compliant = RFC1459.isDeclaredChar(c);
 			i++;
 		}
 		if (!compliant)
 		{
 			Object o[] = { new Character(c) };
 			MessageFormat mf = new MessageFormat(res.getString("nick.message"));
 			new Box(this, res.getString("nick.title"), mf.format(o), null, null, false);
 			return(false);
 		}
 		return(true);
 	}
 	
 	public void openHelp()
 	{
 		if (help == null)
 		{
 			help = new HelpBox(this);
 			help.addWindowListener(this);
 			help.setName("help");
 		}
 		else
 		{
 			help.toFront();
 		}
 	}
 
 	public void updateWindowTitle()
 	{
 		String p = getParameter("spawn_frame");
 		if ((p == null || p.equals("0")) && !application)
 			return;
 			
 		String title = "";
 		if (current_nick != null)
 			title = current_nick;
 
 		if (current_server != null && current_server.length() > 0)
 			title += " - " + current_server;
 			
 		String te;
 		if ((te = res.getString("title")) != null)
 		{
 			// Force a space char at the beginning, browsers trim PARAMs.

 			if (te.charAt(0) != ' ')
 				te = ' ' + te;
 			title = title.concat(te);
 		}
 
 		f.setTitle(title);
 	}
 
 	public void update(Observable o, Object argument)
 	{
 		if (o instanceof ConfigurationProperties)
 		{
 			ConfigurationProperties props = (ConfigurationProperties)o;
 			
 			String arg = null;
 			if (argument != null)
 				arg = (String)argument;
 
 			if (arg == null || arg.equals("special_services"))
 				this.special_services = props.getBoolean("special_services");
 
 			if (arg == null || arg.equals("nicksrv_pass"))
 				this.nicksrv_pass = props.getString("nicksrv_pass");
 
 			if (arg == null || arg.equals("irc_pass"))
 				this.irc_pass = props.getString("irc_pass");
 
 			if (arg == null || arg.equals("request_motd"))
 				this.request_motd = props.getBoolean("request_motd");
 
 			if (arg == null || arg.equals("see_everything_from_server"))
 				this.see_everything_from_server	= props.getBoolean("see_everything_from_server");
 
 			if (arg == null || arg.equals("see_join"))
 				this.see_join = props.getBoolean("see_join");
 
 			if (arg == null || arg.equals("see_invite"))
 				this.see_invite = props.getBoolean("see_invite");
 
 			if (arg == null || arg.equals("on_dcc_notify_peer"))
 				this.on_dcc_notify_peer = props.getBoolean("on_dcc_notify_peer");
 
 			if (arg == null || arg.equals("service_bots"))
 				this.service_bots = props.getString("service_bots");
 
 			if (arg == null || arg.equals("hideip"))
 				this.hideip = props.getBoolean("hideip");
 
  			if (arg == null || arg.equals("focus_opening_privates"))
 				this.focus_opening_privates = props.getBoolean("focus_opening_privates");
 
 			if (arg == null || arg.equals("no_privates"))
    		 		this.no_privates = props.getBoolean("no_privates");
 
 			if (arg == null || arg.equals("write_color"))
 				this.write_color = props.getInt("write_color");
 
 			if (arg == null || arg.equals("scroll_speed"))
    				this.scroll_speed = props.getInt("scroll_speed");
 
 			if (arg == null || arg.equals("silent"))
 				this.silent = props.getInt("silent");
 
 			if (arg == null || arg.equals("auto_list"))
 				this.auto_list = props.getBoolean("auto_list");
 				
 			if (arg == null || arg.equals("decoding"))
 				this.decoding = props.getString("decoding");
 				
 			if (arg == null || arg.equals("encoding"))
 				this.encoding = props.getString("encoding");
 				
 			if (arg == null || arg.equals("quit_message"))
 				this.quit_message = props.getString("quit_message");
 
 			if (arg == null || arg.startsWith("event_"))
 			{
 				for (int i = 1; i &lt;= res.EVENTS; i++)
 					this.event_sounds[i - 1] = (AudioClip)res.SOUNDS.get(props.getString("event_" + i));
 			}
 			
 			if (arg == null || arg.equals("restrict_join"))
 				this.restrict_join = props.getInt("restrict_join");
 				
 			if (arg == null || arg.equals("ping"))
 				this.ping = props.getInt("ping");
 		}
 	}
 
 	public void requestFocus()
 	{
 		if (connected && status != null)
 	  		status.requestFocus();
 		if (!connected && nick_entry != null)
 			nick_entry.requestFocus();
 	}
 
 	public boolean isService(String tag)
 	{
 		return (status_tag.equals(tag) || irc_services.isService(tag));
 	}
 
 	public void setGlobalModes(String modes)
 	{
 		char [] mode_ary = modes.toCharArray();
 		boolean sign = false;
 
 		int j = 0;
 		for (int i = 0; i < mode_ary.length; i++)
 		{
 			switch (mode_ary[i])
 			{
 			case '+':
 				sign = true;
 				break;
 			case '-':
 				sign = false;
 				break;
 			case 'o':
 				server_admin = sign;
 				break;
 			case 'i':
 				who_invisible = sign;
 				who_list.invisible.setState(sign);
 				break;
 			case 'N':
 				ircop_override = sign;
 				break;
 			case '*':
 				server_admin = sign;
 				who_invisible = sign;
 				who_list.invisible.setState(sign);
 				ircop_override = sign;
 			}
 		}
 	}
 
 	public boolean isIRCop()
 	{
 		return(server_admin);
 	}
 
 	public boolean canOverride()
 	{
 		// If true, ircops can override a channel's modes without beeing opped. (Unreal global mode)

 
 		return(ircop_override);
 	}
 
 	public boolean isInvisible()
 	{
 		return(who_invisible);
 	}
 
 	public void identify(String passwd)
 	{
 		irc_services.identifyNick(current_nick, username, passwd);
 	}
 
 	public void register(String passwd, String email)
 	{
 		irc_services.registerNick(passwd, email);
 	}
 
 	public int scrollSpeed()
 	{
 		return(scroll_speed);
 	}
 	
 	public boolean isConfDefined()
 	{
 		return(cfg_name != null && cfg_name.length() > 0);
 	}
 	
 	public void save()
 	{
 		saveTo(cfg_name);
 	}
 	
 	public void saveTo(String dest)
 	{
 		try
 		{
 			properties.write(dest);
 		}
 		catch(IOException e)
 		{
 			report("eirc.errsav", e.toString());
 		}
 	}
 
 	public String[] getUserColors()
 	{
 		String c[] = new String[color_list.size()];
 		int i = 0;
 		for (Enumeration e = color_list.keys(); e.hasMoreElements(); )
 			c[i++] = (String)e.nextElement();
 
 		return(c);
 	}
 
 	public Color getUserColor(String s)
 	{
 		return((Color)color_list.get(s));
 	}
 
 	public void paint(Graphics g)
 	{
 		getFrame().setBackground(getBackground());
 		super.paint(g);
 	}
 
 	public Frame getFrame()
 	{
 		if (f != null)
 			return(f);
 
    		Container c = this;
 		while(c != null && !(c instanceof Frame))
 			c = c.getParent();
 			
 		if (c == null)
 			return(new Frame());
 		else
 			return((Frame)c);
 	}
 
 	public void setFont(Font ft)
 	{
 		Frame ff = getFrame();
 		mainfont = ft;
 		ff.setFont(ft);
 		ff.validate();
 
 	  	status.setFont(ft);
 	  	status.validate();
 	  	chan_list.setFont(ft);
 	  	chan_list.validate();
 	  	who_list.setFont(ft);
 	  	who_list.validate();
 
 	  	// Propagate changes to other windows.

 	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 	  		ChannelWindow cw = (ChannelWindow) e.nextElement();
   			cw.setFont(ft);
   			cw.validate();
   		}
 	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
 		{
   			PrivateWindow pw = (PrivateWindow) e.nextElement();
   			pw.setFont(ft);
   			pw.validate();
   		}
 
 		// Popup menu font

 		for (int i = 0; i < menu.getItemCount(); i++)
 			menu.getItem(i).setFont(ft);
 
 		label.setFont(ft);
   		nick_entry.setFont(ft);
 		away.setFont(ft);
 		control_panel.setFont(ft);
 		control_panel.repaint();
 		chat_panel.setFont(ft);
 		chat_panel.validate();
 	}
 
 	public Font getFont()
 	{
 		return(mainfont);
 	}
 
 	public void setBackground(Color c)
 	{
 
 		mainbg = c;
 		getFrame().setBackground(c);
 		super.setBackground(c);
 
 	  	status.setBackground(c);
 	 	chan_list.setBackground(c);
 	  	who_list.setBackground(c);
 
 	  	/* Propagate changes to other windows */
 
 	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 	  		ChannelWindow cw = (ChannelWindow) e.nextElement();
   			cw.setBackground(c);
   		}
 
 	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
 		{
   			PrivateWindow pw = (PrivateWindow) e.nextElement();
   			pw.setBackground(c);
   		}
 
 		label.setBackground(c);
 		away.setBackground(c);
 		control_panel.setBackground(c);
 		chat_panel.setBackground(c);
 		
 		repaint();
 	}
 
 	public void setForeground(Color c)
 	{
 		mainfg = c;
 		getFrame().setForeground(c);
 		control_panel.setForeground(c);
 		chat_panel.setForeground(c);
 	}
 
 	public void setTextBackground(Color c)
 	{
 		textbg = c;
 		nick_entry.setBackground(c);
 	  	status.setTextBackground(c);
 	  	chan_list.setTextBackground(c);
 	  	who_list.setTextBackground(c);
 
 	  	// Propagate changes to other windows.

 	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 	  		ChannelWindow cw = (ChannelWindow) e.nextElement();
   			cw.setTextBackground(c);
   		}
 	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
 		{
   			PrivateWindow pw = (PrivateWindow) e.nextElement();
   			pw.setTextBackground(c);
   		}
 	}
 
 	public void setTextForeground(Color c)
 	{
 		textfg = c;
 		nick_entry.setForeground(c);
 	}
 	
 	public void setSelectedBackground(Color c)
 	{
 	  	// Propagate changes to other windows.

 	  	for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 	  		ChannelWindow cw = (ChannelWindow) e.nextElement();
   			cw.setSelectedBackground(c);
   		}
 	  	for (Enumeration e = privates.elements(); e.hasMoreElements(); )
 		{
   			PrivateWindow pw = (PrivateWindow) e.nextElement();
   			pw.setSelectedBackground(c);
   		}
 	}
 
 	public Color getBackground()
 	{
 		return(mainbg);
 	}
 
 	public Color getTextBackground()
 	{
 		return(textbg);
 	}
 
 	public Color getTextForeground()
 	{
 		return(textfg);
 	}
 
 	public void cutPaste(String s)
 	{
 		if (tac == null)
 		{
 			tac = new TextAreaCopy(this, s);
 			tac.setName("tac");
 			tac.addWindowListener(this);
 		}
 		else
 		{
 			tac.toFront();
 		}
 		tac.setText(s);
 	}
 
 	public void visitURL(URL url)
 	{
 		visitURL(url, "_blank");
 	}
 
 	public void visitURL(URL url, String target)
 	{
 		getAppletContext().showDocument(url, target);
 	}
 	
 	public void actionPerformed(ActionEvent ev)
 	{
    		this.new_nick = nick_entry.getText();
    		if (new_nick.length() == 0)
    			return;
 	
 		if (!isNickCorrect(new_nick))
 			return;		
 
    		if (!connected)
    		{
    			connect();
    		}
    		else
    		{
    			String p[] = { new_nick };
    			sendMessage("NICK", p);
    		}
 	}
 
 	public void mouseClicked(MouseEvent ev)
 	{
 	}
 
 	public void mouseEntered(MouseEvent ev)
 	{
 		getFrame().setCursor(new Cursor(Cursor.HAND_CURSOR));
 	}
 
 	public void mouseExited(MouseEvent ev)
 	{
 		getFrame().setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
 	}
 
 	public void mousePressed(MouseEvent ev)
 	{
 		menu.show(away, ev.getPoint().x, ev.getPoint().y);
 	}
 
 	public void mouseReleased(MouseEvent ev)
 	{
 	}
 
 	public void itemStateChanged(ItemEvent ev)
 	{
 		String p[] = { "" };
 
 		for (int i = 0; i < menu.getItemCount(); i++)
 		{
 			MenuItem item = menu.getItem(i);
 			if (item instanceof CheckboxMenuItem)
 			{
 				if(menu.getItem(i) == ev.getSource() && ((CheckboxMenuItem)item).getState())
 				{
 					if (i == 0)
 					{
 						p[0] = openAwayWin();
 						if (p[0].equals(""))
 							((CheckboxMenuItem)item).setState(false);
 					}
 					else
 					{
 						p[0] = item.getLabel();
 					}
 				}
 				else
 				{
 					((CheckboxMenuItem)item).setState(false);
 				}
 			}
 		}
 
 		sendMessage("AWAY", p);
 	}
 
 	public void playSound(int i)
 	{
 		if (silent == res.SND_OFF || (silent == res.SND_OFFAWAY && is_away))
 			return;
 
 		if (event_sounds[i] != null)
 			event_sounds[i].play();
 	}
 
 	public void chatPanelClosing(ChatPanelEvent ev)
 	{
 		ChatPanel source = ev.getChatPanel();
 		String name = source.getPanelTag();
 
 		chat_panel.remove(name);
 		if (source instanceof PrivateWindow)
 		{
 			properties.deleteObserver((PrivateWindow) source);
 			privates.remove(name);
   		}
 		else if (source instanceof ChannelWindow)
 		{
 			properties.deleteObserver((ChannelWindow) source);
 			channel_windows.remove(name);
 		}
 	}
 
 	public void windowOpened(WindowEvent e)
 	{
 	}
 
 	public void windowClosing(WindowEvent e)
 	{
 		String name = e.getWindow().getName();
 
 		if (name.equals("main"))
 
 		{
 			if (f != null)
 				f.dispose();
 		  	stop();
 		  	destroy();
   			System.exit(0);
 		}
 		if (name.equals("tac") && tac != null)
 		{
 			tac.dispose();
 			tac = null;
 		}
 		if (name.equals("configurator") && configurator != null)
 		{
 			configurator.dispose();
 			configurator = null;
 		}
 		if (name.equals("asl") && asl != null)
 		{
 			asl.dispose();
 			asl = null;
 		}
 		if (name.equals("mode_list") && mode_list != null)
 		{
 			mode_list.dispose();
 			mode_list = null;
 		}
 		if (name.equals("help") && help != null)
 		{
 			help.dispose();
 			help = null;
 		}
 	}
 
 	public void windowClosed(WindowEvent e)
 	{
 	}
 
 	public void windowIconified(WindowEvent e)
 	{
 	}
 
 	public void windowDeiconified(WindowEvent e)
 	{
 	}
 
 	public void windowActivated(WindowEvent e)
 	{
 		ImageButton.setWindowFocus(true);
 		SmileyTextAreaArea.setWindowFocus(true);
 		NickList.setWindowFocus(true);
 	}
 
 	public void windowDeactivated(WindowEvent e)
 	{
 		if (!(e.getSource() instanceof NewDialog))
 		{
 			ImageButton.setWindowFocus(false);
 			SmileyTextAreaArea.setWindowFocus(false);
 			NickList.setWindowFocus(false);
 		}
 	}
 	
 	public String getParameter(String param)
 	{
 		String s = super.getParameter(param);
 		if (s != null && s.length() > 0)
 			return(s);
 		
 		if (properties != null)
 			return(properties.getString(param));
 			
 		return(null);
 	}
 	
 	private final static String [][] param_info =
 	{
 		{ "server", "string", "IRC server's address" },
 		{ "port", "0-65535", "IRC server's port" },
 		{ "ssl", "0/1", "Plain or SSL connection" },
 		{ "irc_pass", "string", "clients' password to server" },
 		{ "mainbg", "color", "general background color" },
 		{ "mainfg", "color", "general foreground color" },
 		{ "textbg", "color", "text background color" },
 		{ "textfg", "color", "text foreground color" },
 		{ "selbg", "color", "selected background color" },
 		{ "selfg", "color", "selected foreground color" },
 		{ "join", "string", "channel(s) to auto-join" },
 		{ "username", "string", "username part of hostmask" },
 		{ "realname", "string", "user's real name" },
 		{ "nickname", "RFC 1459 string", "user's nick name" },
 		{ "nicksrv_pass", "string", "nickserv user's password" },
 		{ "login", "0/1", "whether to try auto-login" },
 		{ "disable_cmds", "string", "list of commands to disable" },
 		{ "user_modes", "string", "set or unset user modes after connection" },
 		{ "spawn_frame", "0/1", "spawn separate frame" },
 		{ "gui_nick", "0/1", "remove or add nick change field" },
 		{ "gui_away", "0/1", "remove or add away menu" },
 		{ "gui_chanlist", "0/1", "remove or add channels list" },
 		{ "gui_userlist", "0/1", "remove or add users list" },
 		{ "gui_options", "0/1", "remove or add options" },
 		{ "gui_help", "0/1", "remove or add help" },
 		{ "gui_connect", "0/1", "remove or add connect button" },
 		{ "width", "int", "frame width" },
 		{ "height", "int", "frame height" },
 		{ "debug_traffic", "0-2", "disable, enable debugging to console or to applet" },
 		{ "write_color", "0-15", "messages color" },
 		{ "font_name", "string", "font name" },
 		{ "font_size", "integer", "font size" },
 		{ "language", "string", "preferred language, or leave empty for auto-detect" },
 		{ "userid", "string", "some data to identify a web user server-side" },
 		{ "configuration", "string", "path/name of configuration file" }
 	};
 	
 	public class COMClassObject
 	{
 		/* Documented bug at :
 		// http://support.microsoft.com/default.aspx?scid=kb;en-us;243771
 		//
 		// Empty class prevent MS's JVM
 		// to load a non-existant "<applet>$COMClassObject.class".
 		// Could save www errors log...
 		*/
 	}
 }