/*
 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.IOException;
 import java.lang.reflect.*;
 import java.net.*;
 import java.text.ChoiceFormat;
 import java.text.Collator;
 import java.text.MessageFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.*;
 
 import ar.com.jkohen.applet.*;
 import ar.com.jkohen.awt.ChatPanel;
 import ar.com.jkohen.awt.ImageCanvas;
 import ar.com.jkohen.awt.NickInfo;
 import ar.com.jkohen.awt.event.ChatPanelEvent;
 import ar.com.jkohen.awt.event.ChatPanelListener;
 import ar.com.jkohen.awt.MircSmileyTextArea;
 import ar.com.jkohen.awt.TextAttributePicker;
 import ar.com.jkohen.irc.*;
 import ar.com.jkohen.net.*;
 import ar.com.jkohen.util.*;
 
 /**
  * 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
 {
 	/**
 	 * Program's name.
 	 * Used as part of "About" info.
 	 */
 
 	public final static String PACKAGE = "Eteria IRC Client";
 
 	/**
 	 * 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 = "Beta 20081014 19:43";
 
 	/**
 	 * 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;
 
 	// These are used to build the channel list from the server replies to the LIST command.
 	private Vector list_reply;
 //	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 ConfigurationProperties properties;
 
 	private static Font mainfont = new Font("Helvetica", Font.PLAIN, 11);
 	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 date_pattern;
 	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 net_encoding;
 
 	private String network_name = "";
 	private String default_join;
 	private int restrict_join;
 	private boolean connected;
 	private boolean logged_in;
 	private boolean quit_sent;
 	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(50, 1);
 		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("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("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("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));
 	}
 
 	/**
 	 * Constructor for Applet instances.
 	 * Must be present, even if empty.
 	 */
 
 	public EIRC()
 	{
 	}
 
 	public EIRC(boolean b)
 	{
 		this.application = b;
 	}
 
 	/**
 	 * 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 <code>String[]</code> containing command-line arguments.
 	 */
 
 	public static void main(String args[])
 	{
 		EIRC eirc = new EIRC(true);
 		eirc.setStub(new SimpleAppletStub(args));
 		eirc.init();
 		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.event_sounds = new AudioClip[res.EVENTS];
 
 		res = new Resources(this, getParameter("language"));
 
 		/* Get main configuration
 		*/
 		Properties default_props = new Properties();
 		try
 		{
 			default_props.load(getClass().getResourceAsStream("configuration.properties"));
 			this.properties = new ConfigurationProperties(default_props);
 		} catch (IOException ex)
 		{
 			System.err.println(ex);
 			this.properties = new ConfigurationProperties();
 		}
 		properties.addObserver(this);
 
 
 		/* Read user's colors and sounds configurations
 		*/
 		Vector sound_list = new Vector();
 		color_list = new Hashtable();
 		for (Enumeration e = default_props.keys(); e.hasMoreElements() ;)
 		{
 			String key = (String)e.nextElement();
 			try
 			{
 				if (key.indexOf("sound.") == 0 && Integer.parseInt(key.substring(6)) > 0)
 					sound_list.addElement(default_props.getProperty(key));
 				if (key.indexOf("color.") == 0)
 					color_list.put(key.substring(6), Color.decode(default_props.getProperty(key)));
 			}
 			catch(NumberFormatException ex) {}
 		}
 
 		/* Load sounds in background
 		*/
 		if (properties.getBoolean("load_sounds"))
 		{
 			res.addObserver(this);
 			res.cacheSounds(sound_list, default_props.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);
 			}
 		}
 		MircSmileyTextArea.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);
 
 		SimpleAppletContext.setPath(default_props.getProperty("navigator"));
 
 		this.default_join = getParameter("join");
 
 
 		String t, n = "Helvetica";
 		int s = 12;
 
 		if ((t = getParameter("font_name")) != null)
 			n = t;
 			
 		try
 		{
 			if ((t = getParameter("font_size")) != null)
 				s = Integer.parseInt(t);
 		}
 		catch (NumberFormatException e)	{}
 		this.mainfont = new Font(n, 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;
 			}
 		}
 		
 		/* Create main GUI. */
 
 //		try
 //		{
 //			if (Class.forName("com.ms.security.PolicyEngine") != null)
 //				com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.UI);
 //		}
 //		catch (Exception ex) {}
 
 		t = getParameter("spawn_frame");
 
 		if ((t != null && !t.equals("0")) || application)
 		{
 			f = new Frame(makeWindowTitle(""));
 			f.setIconImage(main_icon);
 			f.addWindowListener(this);
 
 			int width = 600;
 			int height = 400;
 			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.show();
 			f.addWindowListener(this);
 			f.setName("main");
 		}
 		else
 		{
 			f = null;
 			initGUI(this);
 		}
 
 		setBackground(mainbg);
 		setForeground(mainfg);
 		setSelectedBackground(selbg);
 		setFont(mainfont);
 
 
 		/* 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 = "cool";
 		realname = "smile";
 		
 		if ((t = getParameter("username")) != null && t.length() > 0)
 			username = t;
 
 		if ((t = getParameter("realname")) != null && t.length() > 0)
 			realname = t;
 
 		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 java.io.OutputStreamWriter(System.out).getEncoding());
 			debug("\n");
 		}
 	}
 
 	public void openASL()
 	{
 		String t = getParameter("asl");
 		
 		if((t != null && !t.equals("0")) || t == null)
 		{
 			if (asl == null)
 			{
 				asl = new ASLBox(this, getFrame(), 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 stop()
 	{
 		disconnect();
 		closePrivates();
 	}
 
 	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);
 
 		this.nick_entry = new TextField(9);
 		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.anchor = GridBagConstraints.EAST;
 		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";
 		}
 		connect(host);
 	}
 
 	void connect(String host)
 	{
 		int port = 6667; // Set default port.
 		try
 		{
 			port = Integer.parseInt(getParameter("port"));
 		} catch (Exception e) {}
 
 		connect(host, port);
 	}
 
 	synchronized void connect(String host, int port)
 	{
 		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
 		{
 			if (Class.forName("netscape.security.PrivilegeManager") != null)
 				netscape.security.PrivilegeManager.enablePrivilege("UniversalConnect");
 
 			if (System.getProperty("java.vendor").indexOf("Microsoft") >= 0)
 			{
 				if (Class.forName("com.ms.security.PolicyEngine") != null)
 					com.ms.security.PolicyEngine.assertPermission(com.ms.security.PermissionID.NETIO);
 			}
 
 			/* Resolve server's address. */
 			InetAddress [] addresses = InetAddress.getAllByName(host);
 			server_addr = addresses[(int) Math.floor(Math.random() * addresses.length)];
 			
 			/* Establish a connection to the server. */
 			s = createSocket(server_addr, port);
 			
 			/* Spawn a new thread to handle the connection. */
 			st = new ServerThread(this, s, net_encoding);
 		}
 		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) };
    			String ptn = res.getString("eirc.s3");
    			status.printError(MessageFormat.format(ptn, a));
    			return;			
 		}
 		catch (SecurityException e)
 		{
 			status.printError(res.getString("eirc.not_in_applet"));
 			return;	
 		}
 		catch (Exception e)
 		{
 			status.printError(res.getString("eirc.not_in_applet"));
 			return;
 		}
 
 		if (connected)
 			disconnect();
 
 		this.quit_sent = false;
 
 		properties.addObserver(st);
    		this.server = st;
    		st.start();
 
 		this.connected = 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) throws java.io.IOException
 	{
 		Socket s = null;
 /*
 		try
 		{
 			port = Integer.parseInt(getParameter("ssl"));
 		}
 		catch (Exception e) {}
 		
 		if (port > 0)
 		{
 			try
 			{
 				Class SSLClass = Class.forName("javax.net.ssl.SSLSocket");
 				if(SSLClass != null)
 				{
 					/*
 					** 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", null);
 					Object factory = getdefault.invoke(null, null);
 
 				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;
 			}
 		}
 		catch (ClassNotFoundException e)
 		{
 			System.out.println("Your Java system is too old and does not support SSL. You should update at www.java.com");
 		}
 		catch (Exception e)
 		{
 			System.out.println("SSL connection failed.");
 		}
 */		
 		s = new Socket(addr, port);
 		return(s);
 	}
 
 	public void connect(String nick, String pass, String realname)
 	{
 		this.nick_entry.setText(nick);
 		this.nicksrv_pass = pass;
 		this.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.
 		*/
 
 		Socket s = server.getSocket();
 
 		server.disconnect();
 		try
 		{
 			s.close();
 		}
 		catch (IOException e)
 		{
 //			System.err.println(e);
 		}
 
 	  	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 windows.
 		*/
 
 		/* Disable opened channels */
 		for (Enumeration e = channel_windows.elements(); e.hasMoreElements(); )
 		{
 			ChannelWindow w = (ChannelWindow)e.nextElement();
 			w.setEnabled(false);
 			w.clearUsers();
 		}
 
 		/* Disable 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 (!quit_sent)
 		{
 			String p[] = { quit_message };
 			sendMessage("QUIT", p);
 		}
 
 		this.logged_in = false;
 	}
 
 	private synchronized void pos_login()
 	{
 		if (!connected)
 			return;
 
 		this.current_nick = new_nick;
 		status.requestFocus();
 
 		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 (chan_list != null && auto_list)
 			chan_list.listChannels();
 
 		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;
 		}
 	}
 
 	public void printMode(ChannelWindow cw, String prefix, String param, String msg_tag, int sound)
 	{
 		if (msg_tag != 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 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().toLowerCase();
 		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)
 			{
 				SimpleDateFormat date_format = new SimpleDateFormat(date_pattern);
 				debug("< " + date_format.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));
 				}
 				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]);
 				}
 				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);
 						if (see_join)
 						{
 							ChannelWindow cw = getChannelWindow(params[0]);
 							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.refresh();
 					}
 					break;
 				}
 
 				Channel channel = getChannel(params[0]);
 				channel.setModes(params[1], modes_params);
 
 				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;
 
 					/////////////////////////////////////////////////////////////
 
 
 					// These modes aren't handled, but are known to carry a parameter which must be skipped.
 //					case 'I':
 //						j++;
 //						break;
 					}
 		   		}
 				break;
 			}
 
 			case -5:	// PART
 			{
 				if (current_nick.equals(prefix))
 				{
 					closeChannel(params[0]);
 				}
 				else
 				{
 					Channel channel = getChannel(params[0]);
 					channel.remove(prefix);
 
 					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 she was in.
 				for (Enumeration e = channels.elements(); e.hasMoreElements(); )
 				{
 					Channel channel = (Channel) e.nextElement();
 					channel.remove(prefix);
 				}
 				updateChanTitle(getChannel(params[0]));
 
 				// Remove nick info entry.
 				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(