/* Author: Jeff Dalton <J.Dalton@ed.ac.uk> &
 *         Stephen Potter <stephenp@aiai.ed.ac.uk>
 * Last Updated: Thu Sep 02 13:03:48 2004
 * Copyright: (c) 2001-4, AIAI, University of Edinburgh
 */

package ix.jabber;

import org.jdom.Element;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

import org.w3c.dom.*;

import ix.util.xml.XML;
import ix.util.Strings;
import ix.util.ThreadCondition;
import ix.icore.IXAgent;
import ix.util.*;
import ix.iface.util.IFUtil;
import ix.ichat.ChatMessage;
import ix.iface.util.ToolController;
import ix.ispace.ISpaceTool;

import org.jabber.jabberbeans.*;
import org.jabber.jabberbeans.Extension.*;
import org.jabber.jabberbeans.serverside.*;
import org.jabber.jabberbeans.util.*;

import java.io.*;
import java.net.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;


/*
 * A class that encapsulates the knowledge needed for
 * sending and receiving messages using the Jabber protocol.
 */

public class JabberCommunicationStrategy 
       implements IPC.CommunicationStrategy {

    protected static JabberCommunicationStrategy theStrategy;

    public ConnectionBean connectionBean;

    // These are defaults:
    public String jabberServer = "jabber.org";
    public String username = Util.getUserName();
    public String resource = "I-X";
    public String password = "";
    //public String password = "jabber";
    public String presence = "Online";
    public String port = "5222";
    public boolean sync = true; // is communication synchronous or async?
    public boolean autosubscribe = true; 
    public int priority = 1;    // actually -128=<priority<=127, default=0 
    static public RosterBean rbean;
    static public PresenceBean pbean;


    DefaultMutableTreeNode presenceTree = new DefaultMutableTreeNode(new JabberDisplay("Online Contacts","","root"));
    DefaultTreeModel treeModel = new DefaultTreeModel(presenceTree);


    public IPC.MessageListener ipcListener = null;
    ThreadCondition singleUser = new ThreadCondition("single-user");
    ThreadCondition userResponse = new ThreadCondition("user-response");
    ThreadCondition validInput = new ThreadCondition("valid-input");
    ThreadCondition newAccount = new ThreadCondition("new-account");
    static public LoginState loginState;
    public boolean loggedOn = false;
    ThreadCondition receivedServerResponse = new ThreadCondition("received-server-response");

    public JabberCommunicationStrategy() {
	super();
        Debug.expect(theStrategy == null, "Attempt to make 2 Jabbers");
	theStrategy = this;
    }

    public static JabberCommunicationStrategy getTheStrategy() {
        if (theStrategy == null)
            throw new NullPointerException("null Jabber strategy");
        else
            return theStrategy;
    }


    /**
     * LoginState - the single object of this class during each login is
     * intended to reflect the current state of the logging-in process.
     * failure is set to true if the current process has failed (and should
     * be reset to false with the initiation of a further attempt) with 
     * errorCode storing the Jabber error code - if applicable - and 
     * errorText some text describing the reason for the failure.
     */ 
    public class LoginState {
	public boolean failure; // if failed then true
	public String errorCode;
	public String errorText;

	public LoginState(){
	    failure = false;
	    errorCode = "";
	    errorText = "";
	}
   
	public LoginState(boolean st){
	    failure = st;
	}

	public LoginState(boolean st, String code, String text){
	    failure = st;
	    errorCode = code;
	    errorText = text;
	}

	public void setState(boolean st){
	    failure = st;
	    errorCode = "";
	    errorText = "";
	}

	public void setState(boolean st, String code, String text){
	    failure = st;
	    errorCode = code;
	    // ensure that there is some failure reason (even if it's not a good one):
	    if(failure && (text==null || text.equalsIgnoreCase("")))
		errorText = "Connection error";
	    else
		errorText = text;
	}

	public boolean failed(){
	    return failure;
	}
	
	public String getReason(){
	    return errorText;
	}
    }


    /* sendCheck performs a check to see whether the message should be sent
     * at the current time; if not, it throws an exception, otherwise it
     * exits normally.
     */
    public void sendCheck(Object destination) throws IPC.IPCException {
	 
	/* The logic here is as follows:
	 * recipient may - or may not - include a resource name.
	 *
	 * if resource name specified:
	 * - if message passing is sync., is resource on-line? if so,
	 * send message, if not fail.
	 * - if message passing is async, send message.
	 *
	 * if no resource name: 
	 * - if recipient is the user of this panel, add "I-X" resource,
	 * and send message.
	 * - else if the recipient has an "I-X" resource on-line, then 
	 * assume this is this the target: add resource & send.
	 * - else if sync. message passing and the recipient has *some*
	 * resource on-line, send message (with null resource - let the
	 * jabber server sort it out).
	 */

	String toName = (String)destination;
	boolean subscribed = true;
	boolean online = true;
	boolean sendself = false;
	boolean sendfail = false; // have the conditions for failure been met?
	JID recipient = (new JID(jabberServer)).fromString(toName);

	// perform a rudimentary validity check:
	if(recipient.getUsername()==null || (recipient.getUsername()).equalsIgnoreCase("")){
	    throw new IPC.IPCException(recipient.toString()+" is an invalid Jabber username.");
	}

	/* First provide a rudimentary synchronisation check: this should probably
	 * be altered to something more sophisticated.*/
	if(!loggedOn){
	    Debug.noteln("Trying to send before logging on to Jabber server");
	    throw new IPC.IPCException("Trying to send before logging on to Jabber server");
	}

	Debug.noteln("Request to send message to: "+toName);
	//if(recipient.getResource()==null || (recipient.getResource()).equalsIgnoreCase("")) hasresource = false;

	if(username.equalsIgnoreCase(recipient.getUsername()) && jabberServer.equalsIgnoreCase(recipient.getServer())){
	    sendself=true;
	    Debug.noteln("sending message to self.");
	}

	if(sendself) subscribed = true;
	else subscribed = isSubscribed(recipient);

	if(sync && !subscribed) sendfail = true;

	if(!subscribed)
	    Debug.noteln("not subscribed to "+recipient.toString());

	if(recipient.getResource()!=null){ // ie a resource *is* specified
	    if(sync){
		if(sendself && resource.equalsIgnoreCase(recipient.getResource())){ // ie sending to this panel!
		    online = true;
		}
		else
		    online = isOnline(recipient);

		if(!online) sendfail = true;
	    }
	}
	else{ // if no resource is specified...

	    Debug.noteln("No resource specified.");

	    if(sendself){
		/* is the recipient the user of this panel?
		 * if so, assume this panel is the target...
		 */
		toName = toName+"/"+resource; // ...and add resource
	    }
	    else if(isIXOnline(recipient)){
		/* if recipient is not this panel, and no resource is 
		 * specified and the ix panel of the recipient user *is* 
		 * on-line, then assume that that panel is the intended 
		 * recipient...
		 */
		toName = toName+"/"+resource; // ...and add resource
		Debug.noteln(toName+" is on-line: assuming this is intended recipient.");
	    }
	    else if(sync && !isOnline(recipient)){
		/* else, if recipient is not this panel, and no resource
		 * is specified, synchronous messaging is specified and 
		 * the recipient user has *no* on-line resources:
		 */
		online = false;
		sendfail = true;
		Debug.noteln(toName+ " has no on-line resources: message not sent.");
	    }
	}

	if(sendfail){
	    Debug.noteln("Failure to send to: "+toName);
	    if(!subscribed) {
		if(!autosubscribe)
		    throw new IPC.IPCException("You have not subscribed to the presence of "+toName+" : message not sent");
		else{
		    subscribeToContact(toName);
		    throw new IPC.IPCException("You have not subscribed to the presence of "+toName+" : message not sent.\n(Generated and sent subscription request to "+toName+")");
		}
	    }
	    else if(!online) throw new IPC.IPCException("Recipient "+toName+" is not on-line : message not sent");
	    else throw new IPC.IPCException("Error sending to "+toName+" : message not sent");
	}


	//return sendfail;
    }


    /*
     * sendObject sends an IX message to a named jabber agent after converting
     * it to I-X XML.
     */
    public void sendObject(Object destination, Object contents) throws IPC.IPCException {
	String toName = (String)destination;
	try{
	    // this will throw exceptions if it is not possible to send msg:
	    sendCheck(destination);

	    /* construct message */
	    MessageBuilder mb=new MessageBuilder();
	    Message msg;

	    // assume that toName is of the form username@jabberserver/resource...
	    Debug.noteln("\nSending to:"+toName);
	    mb.setToAddress((new JID(jabberServer)).fromString(toName));

	    /* It is necessary to check whether this object constitutes an I-X 
	     * "chat-message" - if it does, it must first be converted into a
	     * jabber "chat" message (since the recipient may be a non-IX Jabber
	     * agent). All messages of other types are meaningful only to other
	     * IX agents, and so no conversion is necessary (or possible).
	     */

	    if(contents instanceof ix.ichat.ChatMessage){
		/* if we have an I-X "chat-message", we now want to translate
		   it into the equivalent Jabber message of type "chat":
		*/
		mb.setType("chat");
		mb.setBody(((ChatMessage) contents).getText());
	    }
	    else
		mb.setBody(XML.objectToXMLString(contents));

	    try{
		msg=(Message)mb.build();
	    }
	    catch (InstantiationException e){
		/* build failed ?*/
		throw new IPC.IPCException("Error creating Jabber message packet", e);
	    }
	    /* send message: */
	    connectionBean.send(msg);
	}
	catch (IPC.IPCException e){
	    // pass on any exceptions...
	    throw e;
	}
    }


    /*
     * sendPlainObject sends contents (which may or may not be an I-X message) as a default 
     * jabber message to a named jabber agent.
     */
    public void sendPlainObject(Object destination, String contents) throws IPC.IPCException {
	String toName = (String)destination;
	try{
	    // this will throw exceptions if it is not possible to send msg:
	    sendCheck(destination);

	    /* construct message */
	    MessageBuilder mb=new MessageBuilder();
	    Message msg;

	    // assume that toName is of the form username@jabberserver/resource...
	    Debug.noteln("\nSending to:"+toName);
	    mb.setToAddress((new JID(jabberServer)).fromString(toName));
	    mb.setBody(contents);
	    try{
		msg=(Message)mb.build();
	    }
	    catch (InstantiationException e){
		/* build failed ?*/
		throw new IPC.IPCException("Error creating Jabber message packet", e);
	    }
	    /* send message: */
	    connectionBean.send(msg);
	}
	catch (IPC.IPCException e){
	    // pass on any exceptions...
	    throw e;
	}
    }






    /**
     * JabberLoginException: a simple extension of Exception.
     */
    public class JabberLoginException extends Exception {
	public JabberLoginException() {}
	public JabberLoginException(String msg){
	    super(msg);
	}
    }

    /**
     * logon: attempts to log-on to the current jabberServer (with which
     * a connection has already been established). If the user has indicated
     * a new account is to be set up, this is done first.
     */
    public void logon() throws JabberLoginException {

	boolean accountCreated = false;
	receivedServerResponse.setFalse();

	if(newAccount.isTrue()){
	    Debug.noteln("Request for creation of new account");
	    InfoQueryBuilder iqb=new InfoQueryBuilder();
	    InfoQuery iq;
	    IQRegisterBuilder iqRegb = new IQRegisterBuilder();

	    //we are setting info
	    iqb.setType("set");
	    iqRegb.set("username",username);
	    iqRegb.set("password",password);
	    iqRegb.set("resource",resource);

	    //build the Register data
	    try{
		iqb.addExtension(iqRegb.build());
	    }
	    catch (InstantiationException e){
		//building failed ?
		Debug.noteln("Fatal Error on Register object build:");
		Debug.noteln(e.toString());
		return;
	    }

	    //build the iq packet
	    try{
		//build the full InfoQuery packet
		iq=(InfoQuery)iqb.build();
	    }
	    catch (InstantiationException e){
		//building failed ?
		Debug.noteln("Fatal Error on IQ object build:");
		Debug.noteln(e.toString());
		return ;
	    }

	    //send the InfoQuery packet to the server to request new account:
	    connectionBean.send(iq);
	    Debug.noteln("Sent request for new account");
	    receivedServerResponse.waitUntilTrue(5000);
	    if(!receivedServerResponse.isTrue()){
		loginState.setState(true,"","timed out");
		accountCreated = false;
		newAccount.setFalse();
		throw (new JabberLoginException(loginState.getReason()));
	    }
	    else if(loginState.failed()){
		accountCreated = false;
		newAccount.setFalse();
		throw (new JabberLoginException(loginState.getReason()));
	    }
	    else{
		Debug.noteln("Account created");
		accountCreated = true;
	    }
	    // and reset conditional:
	    receivedServerResponse.setFalse();	    
	}

	/* if a new account is not requested, or else the account has just
	 * been successfully created, now want to log on to the server; this
	 * follows essentially the same process as above, except an Auth IQ
	 * object is built, rather than a Register one.
	 */
	if(accountCreated || !newAccount.isTrue()){
	
	    InfoQueryBuilder iqb=new InfoQueryBuilder();
	    InfoQuery iq;
	    //and the auth data builder
	    IQAuthBuilder iqAuthb=new IQAuthBuilder();	
	    //we are setting info
	    iqb.setType("set");
	    //user,password & resource are from the parameter list (or defaults).
	    iqAuthb.setUsername(username);
	    iqAuthb.setPassword(password);
	    iqAuthb.setResource(resource);

	    //build the Auth data
	    try{
		iqb.addExtension(iqAuthb.build());
	    }
	    catch (InstantiationException e){
		//building failed ?
		Debug.noteln("Fatal Error on Auth object build:");
		Debug.noteln(e.toString());
		return;
	    }

	    //build the iq packet
	    try{
		//build the full InfoQuery packet
		iq=(InfoQuery)iqb.build();
	    }
	    catch (InstantiationException e){
		//building failed ?
		Debug.noteln("Fatal Error on IQ object build:");
		Debug.noteln(e.toString());
		return ;
	    }

	    //send the InfoQuery packet to the server to log in
	    connectionBean.send(iq);
	    Debug.noteln("Sent login request");
	    receivedServerResponse.waitUntilTrue(5000);
	    if(!receivedServerResponse.isTrue()){
		loginState.setState(true,"","timed out");
		throw new JabberLoginException(loginState.getReason());
	    }
	    else if(loginState.failed()){
		throw new JabberLoginException(loginState.getReason());
	    }
	}
    }


    /**
     * loginWindow invokes a pop-up window prompting the user for log-in details.
     * Once supplied, these are used to (attempt to) connect to a jabberServer, unless
     * the user has clicked "Cancel", in which case I-X will not be connected to a 
     * jabber server. This routine may be called multiple times if there are problems
     * connecting/logging in with the supplied details.  
     */
    public void loginWindow(String message) {

	Frame frame = new Frame();
	URL iconurl = PresenceWindow.class.getClassLoader().getResource("jabbericons/select.gif");
	frame.setIconImage((new ImageIcon(iconurl)).getImage());
	UserPassword db = new UserPassword(frame,"Jabber login",username,password,jabberServer,resource,port,message,priority,autosubscribe);
	db.show();
	userResponse.setFalse();
	singleUser.setFalse();
	validInput.setFalse();
	newAccount.setFalse();
				
	db.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
		    String response = e.getActionCommand();
		    if(!response.equalsIgnoreCase("cancel")){
			/* expects window code to return whitespace-delimited string:
			 * username password jabbberServer port resource ?newaccount
			 */
			try{
			    StringTokenizer st = new StringTokenizer(response," ",true);
			    String tusername=st.nextToken(" ");
			    if(tusername.equalsIgnoreCase("") || tusername.equalsIgnoreCase(" ")){
				loginState.setState(true,"","You must supply a username");
				throw new JabberLoginException("You must supply a username");
			    }
			    else username = tusername;
			    st.nextToken(" ");
			    String tpassword=st.nextToken(" ");
			    if(tpassword.equalsIgnoreCase(" ")){
				loginState.setState(true,"","You must supply a password");
				throw new JabberLoginException("You must supply a password");
			    }
			    else password = tpassword;
			    st.nextToken(" ");
			    String tjabberServer=st.nextToken(" ");
			    if(tjabberServer.equalsIgnoreCase(" ")){
				loginState.setState(true,"","You must supply a hostname");
				throw new JabberLoginException("You must supply a hostname");
			    }
			    else jabberServer = tjabberServer;
			    st.nextToken(" ");
			    String tport=st.nextToken(" ");
			    if(!tport.equalsIgnoreCase(" ")){
				port = tport;
				st.nextToken(" ");
			    }
			    String tresource=st.nextToken(" ");
			    if(!tresource.equalsIgnoreCase(" ")){
				resource = tresource;
				st.nextToken(" ");
			    }
			    if((new Boolean(st.nextToken())).booleanValue()) newAccount.setTrue();
			    st.nextToken(" ");
			    if((new Boolean(st.nextToken())).booleanValue()) autosubscribe=true;
			    st.nextToken(" ");
			    priority=(new Integer(st.nextToken())).intValue();
			    Debug.noteln("Priority set to:"+priority);
			    if(priority<0) priority=0;
			    validInput.setTrue();
			}
			catch (Exception ex){
			    Debug.noteln("Problem parsing input");
			    validInput.setFalse();
			}
		    }
		    else {
			/* user has clicked "cancel": */
			singleUser.setTrue();
			validInput.setTrue();
		    }
		    userResponse.setTrue();
		}});

	/* windowListener: assumes that it will be invoked *after* the actionListener.*/ 
	db.addWindowListener(new WindowListener() {
		public void windowOpened(WindowEvent e){};
		public void windowClosed(WindowEvent e){};
		public void windowClosing(WindowEvent e){
		    //Debug.noteln("closing "+e);
		    if(!userResponse.isTrue()){
			singleUser.setTrue();
			userResponse.setTrue();
		    }
		};
		public void windowIconified(WindowEvent e){};
		public void windowDeiconified(WindowEvent e){};
		public void windowActivated(WindowEvent e){};
		public void windowDeactivated(WindowEvent e){};
		public void actionPerformed(WindowEvent e){}
	    });

	/* wait until some user response: */
	userResponse.waitUntilTrue();

	if(singleUser.isTrue()){
	    Debug.noteln("login cancelled.");
	    /* pop up message window here: */
	    JOptionPane.showMessageDialog(null, "Login cancelled - you will not be connected to a Jabber server.", "Login Cancelled",  JOptionPane.INFORMATION_MESSAGE); 
	}
    }


    /**
     * connectToHost takes the current user/server details, and tries to connect
     * to the stipulated jabber server, and, if successful, then calls logon to 
     * log the user onto that server. 
     */

    public boolean connectToHost(){
	InetAddress host;
	IXConnectionListener cl = new IXConnectionListener();
	// want to use IXLoginPacketListener for establishing connection/login:
	IXLoginPacketListener lpl = new IXLoginPacketListener();

	connectionBean = new ConnectionBean();
	loginState.setState(false);

	try{
	    connectionBean.addPacketListener(lpl);
	    connectionBean.addConnectionListener(cl);
	    host = InetAddress.getByName(jabberServer);

	    Debug.noteln("located host "+jabberServer);
	    connectionBean.connect(host,((new Integer(0)).parseInt(port,10)));
	    Debug.noteln("connected to "+jabberServer);	    
	    logon();
	    connectionBean.delPacketListener(lpl);
	    connectionBean.addPacketListener(new IXPacketListener());

	    /* test that a connection has been established...*/

	    //Debug.noteln("STATE="+(connectionBean.getConnectionState()).toString());
	    if((connectionBean.getConnectionState()).toString() != "CONNECTED")
		throw new JabberLoginException("Connection refused");

	    /* finally want to set the panel symbol name: this should be equal to:
	       username@jabberServer */
	    Util.swingAndWait(new Runnable() {
		    public void run() {
			IXAgent.getAgent().setAgentSymbolName(username+"@"+jabberServer+"/"+resource);
		    }
		}
	    );
	    loggedOn = true;

	    // new stuff:
	    IQBean iqbean = new IQBean(connectionBean);
	    rbean.setIQBean(iqbean);
	    rbean.addRosterListener(new IXRosterListener());
	    rbean.refreshRoster();

	    pbean.setConnBean(connectionBean);
	    pbean.addPresenceListener(new IXPresenceListener());

	    // want to send message of form: <presence><priority>1</priority></presence>
	    // next to set the priority of this particular presence
	    PresenceBuilder pb = new PresenceBuilder();
	    Presence prioritypr;
	    pb.setPriority(priority);
	    try{
		prioritypr = (Presence)pb.build();
	    }
	    catch(InstantiationException e){
		/* build failed ?*/
		//Debug.noteln("error creating presence packet:");
		//Debug.noteln(e.toString());
		throw new IPC.IPCException("Error creating Jabber priority presence packet", e);
	    }
	    connectionBean.send(prioritypr);

	    ispaceSetup();

	}
	catch (java.net.UnknownHostException e){
	    loginState.setState(true,"","Unknown host");
	}
	catch (java.net.ConnectException e){
	    loginState.setState(true,"","Connection refused");
	}
	catch (Throwable e){
	    loginState.setState(true,"","Connection error");
	    Debug.noteException(e);
	}
	// finally:
	if(loginState.failure){
	    Debug.noteln("Login failed: tidying up.");
	    /* tidy-up current connectionBean state:*/
	    connectionBean.delPacketListener(lpl);
	    connectionBean.delConnectionListener(cl);
	    connectionBean.disconnect();
	    return false;
	}
	else return true;	
    }

    protected void ispaceSetup() {
        // Best to do this in the AWT event thread ...
	Util.swingAndWait(new Runnable() {
		public void run() {
  		    do_ispaceSetup();
  		}
  	    });
    }

    protected void do_ispaceSetup() {
        final IXAgent agent = IXAgent.getAgent();
        // Create tool-controller
        final ToolController tc = new ToolController("Presence Monitor") {
     		    public Object createTool() {
   			return new PresenceWindow(presenceTree,agent.getAgentDisplayName()+" Presence Monitor");
  		    } 
        };

	// Add an I-Space Tool menu
	ISpaceTool ispace = null;
	try{
	    ispace = (ISpaceTool)agent.ensureTool("I-Space");
	}
	catch (java.lang.UnsupportedOperationException e) {
	    // No way to get an I-Space tool, so forget about the menu.
	    return;
	}

	JMenu jabberMenu = new JMenu("Jabber");
	ispace.addMenu(jabberMenu);

        jabberMenu.add
            (IFUtil.makeMenuItem
             ("Presence Monitor",
              new ActionListener() {
                 public void actionPerformed(ActionEvent event) {
                     tc.ensureToolVisible();
                 }
             }));
    }


    /**
     * setUpServer: this code initiates the jabber communication strategy, setting
     * up a connection for receiving and sending messages.
     */
    public void setupServer(Object destination, 
			    IPC.MessageListener listener) {

	connectionBean = new ConnectionBean();
	ipcListener = listener;
	//jabberworld = new JabberWorld();
	loginState = new LoginState();
	//roster = new CurrentRoster();
	rbean = new RosterBean();
	pbean = new PresenceBean();

	/* A conflict arises if the user specifies both a symbol-name and either (or
	 * both) a jabber-username or (and) a jabber-server. Currently, this is resolved
	 * as follows:
	 * - if *both* jabber-username and jabber-server are specified, but no symbol-name,
	 *   then these are used as expected (and the symbol-name of the current panel must
	 *   be explicitly set).
	 * - if *neither* jabber-username nor jabber-server are given, but symbol-name is,
	 *   then the symbol-name is used to construct a username and server.
	 * - if *either* (*both*) jabber-username or (and) jabber-server are specified along
	 *   with a symbol-name, then the user is informed of the clash, and the symbol-name
	 *   is ignored, with the other parameters being used with defaults where necessary.
	 * - if none of jabber-username, jabber-server and symbol-name are specified, then
	 *   defaults are used.
	 */

	if(Parameters.haveParameter("jabber-username") && Parameters.haveParameter("jabber-server")){
	    username = Parameters.getParameter("jabber-username");
	    jabberServer = Parameters.getParameter("jabber-server");
	    if(Parameters.haveParameter("symbol-name")){
		/* clash! */
		String tsymbolname = Parameters.getParameter("symbol-name");
		JOptionPane.showMessageDialog(null, "You have supplied both a symbol-name ("+ tsymbolname +"),\nand a jabber-username ("+ username +") and jabber-server ("+ jabberServer +").\nThese last two will be used to log on.", "Parameter clash",  JOptionPane.WARNING_MESSAGE);
	    }
	}
	else if(Parameters.haveParameter("jabber-username")){
	    username = Parameters.getParameter("jabber-username");
	    // & leaving jabberServer as its default.
	    if(Parameters.haveParameter("symbol-name")){
		/* clash! */
		String tsymbolname = Parameters.getParameter("symbol-name");
		JOptionPane.showMessageDialog(null, "You have supplied both a symbol-name ("+ tsymbolname +")\nand a jabber-username ("+ username +").\nThe latter will be used to log on.", "Parameter clash",  JOptionPane.WARNING_MESSAGE);
	    }
	}
	else if(Parameters.haveParameter("jabber-server")){
	    jabberServer = Parameters.getParameter("jabber-server");
	    // & leaving username as its default.
	    if(Parameters.haveParameter("symbol-name")){
		/* clash! */
		String tsymbolname = Parameters.getParameter("symbol-name");
		JOptionPane.showMessageDialog(null, "You have supplied both a symbol-name ("+ tsymbolname +")\nand a jabber-server ("+ jabberServer +").\nThe latter will be used to log on.", "Parameter clash",  JOptionPane.WARNING_MESSAGE);
	    }
	}
	else if(Parameters.haveParameter("symbol-name")){
	    /* try to construct username and jabberServer from symbol-name */
	    String tsymbolname = Parameters.getParameter("symbol-name"); 
	    String[] brokensymbol = Strings.breakAtFirst("@", tsymbolname);
	    /* if the first element of the array is not empty then use that as the
	     * username; otherwise, stick with the default.
	     */
	    if(!brokensymbol[0].equalsIgnoreCase("")) username = brokensymbol[0];
	    /* if the second element of the array is not empty then use that as the
	     * jabberServer and (possibly) resource; otherwise, stick with the defaults.
	     */
	    if(!brokensymbol[1].equalsIgnoreCase("")){
		String[] brokenserver = Strings.breakAtFirst("/", brokensymbol[1]);
		if(!brokenserver[0].equalsIgnoreCase("")) jabberServer = brokenserver[0];
		if(!brokenserver[1].equalsIgnoreCase("")) resource = brokenserver[1];
	    }
	}

	if(Parameters.haveParameter("jabber-password")){
	    password = Parameters.getParameter("jabber-password");
	}

	/* Note: this next overrides any resource given as part of the symbol-name,
	 * as set above. No warning is given, though.
	 */
	if(Parameters.haveParameter("jabber-resource")){
	    resource = Parameters.getParameter("jabber-resource");
	    // don't allow a null resource name:
	    if(resource.equalsIgnoreCase("")) resource = "I-X";
	}

	if(Parameters.haveParameter("jabber-port")){
	    port = Parameters.getParameter("jabber-port");
	    // don't allow a null port number:
	    if(resource.equalsIgnoreCase("")) port = "5222";
	}

	if(Parameters.haveParameter("jabber-allow-queuing")){
	    if((Parameters.getParameter("jabber-allow-queuing")).equalsIgnoreCase("true")) sync = false;
	    else sync = true;
	}

	if(sync==true) Debug.noteln("Synchronous messaging selected.");
	else Debug.noteln("Asynchronous messaging selected.");

	if(Parameters.haveParameter("jabber-autosubscribe")){
	    if((Parameters.getParameter("jabber-autosubscribe")).equalsIgnoreCase("false")) 
		autosubscribe = false;
	    else 
		autosubscribe = true;
	}

	if(Parameters.haveParameter("jabber-priority")){
	    String tempp = Parameters.getParameter("jabber-priority");
	    try{
		priority = (new Integer(tempp)).intValue();
	    }
	    catch (Exception e){
		Debug.noteln("jabber-priority parameter set to non-integer value");
	    }
	    if(priority<0) priority=0;
	}
	    


	/* use login window to confirm/collect user/server details: */
	loginWindow("Enter/confirm your Jabber login details:");
	
	/* if singleUser mode is *not* selected (the user has not selected "Cancel")
	 * and the connection/login has failed, then bring up login window again. 
	 */ 
	boolean connected = false; 
	if(validInput.isTrue() && !singleUser.isTrue()) connected = connectToHost();

	while(!singleUser.isTrue() && !connected){
	    JOptionPane.showMessageDialog(null, loginState.getReason()+ " - please try again.", "Login Error",  JOptionPane.ERROR_MESSAGE);
	    loginWindow(loginState.getReason()+" - please try again:");
	    if(validInput.isTrue() && !singleUser.isTrue()) connected = connectToHost();
	}

	/* Once logged on - and if not in singleUser mode - want to construct and
	 *  send presence information to the server: 
	 */
	if(!singleUser.isTrue()){	    
	    PresenceBuilder pb=new PresenceBuilder();	    
	    if(Parameters.haveParameter("jabber-presence")){
		presence = Parameters.getParameter("jabber-presence");
		// in case of empty/null parameter declaration:
		if(presence.equalsIgnoreCase("")) presence = "Online";
	    }

	    String show = "";

	    /* An available presence is qualified through the use of two elements:
	     * <status/> and <show/>.
	     * For our purposes, we assume that the IX user specifies at most a
	     * status (stored in the variable presence), and if none is stated it
	     * is assumed to be "Online" (as above).
	     * By convention, the <show/> part of a presence message contains one
	     * of five values: "away", "chat" (ie., available for chat), "dnd" (do 
	     * not disturb), "xa" (extended away) and "normal". The last of these
	     * is assumed if now <show/> information is included in a presence 
	     * message.
	     * Here, we further assume that the <show/> value can be determined 
	     * from the supplied/inferred <status/> information, as follows:
	     */  
	    if(presence.equalsIgnoreCase("Away") || 
	       presence.equalsIgnoreCase("Lunch") ||
	       presence.equalsIgnoreCase("Bank"))
		show = "away";
	    else if(presence.equalsIgnoreCase("Busy") || 
		    presence.equalsIgnoreCase("Working") ||
		    presence.equalsIgnoreCase("Mad"))
		show = "dnd";
	    else if(presence.equalsIgnoreCase("Extended Away") || 
		    presence.equalsIgnoreCase("Gone Home") ||
		    presence.equalsIgnoreCase("Gone to Work") ||
		    presence.equalsIgnoreCase("Sleeping"))
		show = "xa";
	    else if(presence.equalsIgnoreCase("Wants to Chat") ||
		    presence.equalsIgnoreCase("Free for chat"))				    
		show = "chat";
	    
	    pb.setStatus(presence);
	    pb.setStateShow(show);

	    try{
		//build and send presence
		//Debug.noteln(pb.toString());
		connectionBean.send(pb.build());
	    }
	    catch (InstantiationException e){
		    //building failed ?
		    Debug.noteln
			("Fatal Error on Presence object build:");
		    Debug.noteln(e.toString());
		    return ;
	    }

	    /* Finally, want to add a KeepAlive thread: this periodically sends
	     * some whitespace to the server to prevent (NAT-System?) timeouts.
	     * Second parameter is periodicity in milliseconds.
	     */ 
	    KeepAlive keepalive = new KeepAlive(connectionBean, 600000);
	}
    }
    

    /**
     * IXConnectionListener: doesn't do a great deal - used here only for
     * debugging purposes.
     */
    static class IXConnectionListener implements ConnectionListener
    {
	public void connected(ConnectionEvent ce){
	    //    Debug.noteln("CLDebug: Connected!");
	}
	public void disconnected(ConnectionEvent ce){
	    //    Debug.noteln("CLDebug: Disconnected");
	}
	public void connecting(ConnectionEvent ce){
	    //    Debug.noteln("CLDebug: Connecting...");
	}
	public void connectFailed(ConnectionEvent ce){
	    //    Debug.noteln("CLDebug: Connect Failed");
	}
	public void connectionChanged(ConnectionEvent ce){
	    //    Debug.noteln("CLDebug: Connection Changed");
	}
    }


    /**
     * IXLoginPacketListener is a handler of Jabber messages that listens expressly
     * for relevant messages during the login stage, and if an error is reported
     * by the server, makes a record of this.
     * Once connection/login is successfully completed, it is assumed that any
     * instances of this class will be removed from the connectionBean and 
     * destroyed: the responses encoded here only make sense during the login
     * phase, and might be inappropriate at any later time.
     * 
     * -*-Note: this handler ignores messages of type Message or Presence: messages
     * of these sorts are not expected during the lifetime of this handler.-*-
     */
    class IXLoginPacketListener implements PacketListener {

	public void receivedPacket(PacketEvent pe){
	    Packet p = pe.getPacket();
	    Debug.noteln("Login received packet:" + p.toString());
	    if(p instanceof InfoQuery){
		try{
		    InfoQuery iq = (InfoQuery)p;
		    /* valid types of IQ msg = "get", "set", "result", "error" */
		    String iqtype = iq.getType();
		    if(iqtype==null) iqtype="null";
		    Debug.noteln("IQ message of type: " + iqtype);

		    /* if an error message is received, assume that something has
		     * gone wrong during login, and alter the state of the loginState
		     * object accordingly:
		     */
		    if(iqtype.equalsIgnoreCase("error")){ 
			loginState.setState(true,iq.getErrorCode(),iq.getErrorText());
			Debug.noteln("Error message received.");
			Debug.noteln(iq.getErrorText());
			receivedServerResponse.setTrue();
		    }
		    /* likewise, if a result message is received, this is taken as
		     * an indication of no failure:
		     */
		    if(iqtype.equalsIgnoreCase("result")){
			receivedServerResponse.setTrue();
		    }
		    /* don't expect to receive messages of type "get" or "set" at
		     * this stage: accordingly, these are ignored.
		     */
		}
		catch (Exception e){
		    Debug.noteException(e);
		}
	    }
	}

	/* two debugging methods: */
	public void sentPacket(PacketEvent pe){
	    Debug.noteln("Login Sent Packet: " + pe.getPacket().toString());
	}
	public void sendFailed(PacketEvent pe){
	    Debug.noteln("Login Send Failed Packet: "+pe.getPacket().toString());
	}
    }



    /**
     * IXPacketListener is the handler for Jabber messages once connection/login is
     * established: it replaces IXLoginListener.
     * The contents/type of a message determine how it is handled and relayed to
     * the generic ipcListener.
     *
     * -*-Note: at present, this handler simply ignores all IQ messages from the
     * server. This may not always be appropriate behaviour.-*-
     */
    class IXPacketListener implements PacketListener {

	public void receivedPacket(PacketEvent pe){
	    Packet p = pe.getPacket();
	    //Debug.noteln("Received packet: " + p.toString());

	    /* if the packet consists of a jabber Message, then want to inspect
	     * the body of this message. If this is valid IX XML, and of type
	     * "issue", "activity", "constraint" or "report", then the body is
	     * converted into an XML object and passed back to the ipcListener.
	     * Else (if the message is of type "chat-message" or is not an IX
	     * message at all), then the body is used to construct an IX 
	     * "chat-message", which is then passed back.
	     */
	    if(p instanceof Message){
		try{
		    Message m = (Message)p;
		    // normal, chat, or headline
		    String mtype = m.getType();
		    if(mtype==null) mtype="null";
		    String from = m.getFromAddress().toString();
		    String mbody = m.getBody();
		    Debug.noteln("Message of type: " + mtype);
		    Debug.noteln("\treceived from: " + from);
		    Debug.noteln("\tbody: " + mbody);
		    Object contents;

		    /* want to inspect mbody, to see whether it is valid IX-XML */
		    try{
			Document doc = XML.parseXML(mbody);
			if(XML.looksLikeAnIXDocument(doc) && !mtype.equalsIgnoreCase("chat")){
			    Debug.noteln("Recognised I-X XML message.");
			    contents = XML.objectFromDocument(doc);
			}
			else
			    contents = new ChatMessage(mbody,from);
		    }
		    catch(Exception e){
			Debug.noteln("Incoming message is not a structured I-X XML message: converting to chat-message.");
			contents = new ChatMessage(mbody,from);
		    }

		    if(mtype.equalsIgnoreCase("error")) 
			/* if there is an error, do nothing (for now). */
			Debug.noteln("Error message received.");
		    else
			/* else notify the ipcListener: */
			ipcListener.messageReceived(new IPC.BasicInputMessage(contents));
		}
		catch (Exception e){
		    Debug.noteException(e);
		}
	    }
	}

	/* debugging handlers: */
	public void sentPacket(PacketEvent pe){
	    Debug.noteln("Sent Packet : " + pe.getPacket().toString());
	}
	public void sendFailed(PacketEvent pe){
	    Debug.noteln("Send Failed Packet : " + pe.getPacket().toString());
	}
    }

    static class IXRosterListener implements RosterListener
    {
	public void changedRoster(Roster r){
	    //Debug.noteln("Roster changed");
	    //pprintRoster();
	}
	public void replacedRoster(Roster r){
	    //Debug.noteln("Roster replaced");
	    //pprintRoster();
	}
	
    }

    private void passConstraint(JID contact, String value){
	Object contents = XML.objectFromXML("<?xml version=\"1.0\" encoding=\"UTF-8\"?><constraint type=\"world-state\" relation=\"effect\" sender-id=\""+contact.toString()+"\"><parameters><list><pattern-assignment><pattern><list><symbol>presence</symbol><symbol>"+getPrettyName(contact)+" ("+contact.getResource()+")</symbol></list></pattern><value><symbol>"+value+"</symbol></value></pattern-assignment></list></parameters></constraint>");
	ipcListener.messageReceived(new IPC.BasicInputMessage(contents));
    }

    class IXPresenceListener implements PresenceListener
    {
	public void changedPresence(java.util.Hashtable t, Presence p, PresenceUserNode n, java.lang.String s){
	    //Debug.noteln("Changed!!");
	    //Debug.noteln("hash="+ t.toString() + " p="+ p.toString() + " n="+ n.toString() + " s="+s);
	    JID from = p.getFromAddress();
	    //Debug.noteln("From="+from.toString());
	    String show = p.getStateShow();
	    String status = p.getStatus();
	    if(show==null || show.equalsIgnoreCase("")) {
		if(status!=null){
		    if(status.equalsIgnoreCase("Physically elsewhere"))
			show = "online but elsewhere";
		    else if(status.equalsIgnoreCase("Low attention, busy"))
			show = "low attention, busy";
		    else
			show = "online";
		}
		else show = "online";
	    }
	
	    if(show.equalsIgnoreCase("xa")) show = "extended away";
	    if(show.equalsIgnoreCase("dnd")) show = "do not disturb";

	    /* this string represents the xml of an appropriate IX constraint
	     * message encoding this presence information. This should be 
	     * replaced at some point by a less cumbersome method of constructing
	     * such a message.
	     */

	    if(s.equalsIgnoreCase("unavailable")) show = "unavailable";
	    if(!isSubscribed(from)) show = "unsubscribed";

	    passConstraint(from,show);


	    //	    if(s.equalsIgnoreCase("unavailable")) show = "unavailable";

	    // test here is due to the curious behaviour of the server: users continue
	    // to receive a contact's presence notifications from the server after 
	    // unsubscribing to that contact, until *both* clients have logged off from 
	    // their respective servers.
	    if(isSubscribed(from)) addPTreeNode(from,show);

	};

	public void subscribe(Presence pr){
	    JID contact = pr.getFromAddress();

	    Debug.noteln("Received presence subscription request from: "+contact.toString()); 
	    if(autosubscribe){

		// intialise this contact in the roster:
		if(!isSubscribed(contact)) setRosterContact(contact);
		else Debug.noteln("Already subscribed: no need to initialise roster contact");

		// inform contact of acceptance:
		sendSubscriptionMessage(contact,"subscribed");

		// want to check: are we already subscribed to this contact?
		if(!isSubscribed(contact)){
		    // send reciprocal request:
		    sendSubscriptionMessage(contact,"subscribe");
		}
		else Debug.noteln("Already subscribed: no need to send reciprocal request");
		/*
		  SENT: <iq id="36" type="set"><query xmlns="jabber:iq:roster"><item jid="spotter@jabber.aiai.ed.ac.uk" name="sp"><group>new</group></item></query></iq>
		  ---
		  SENT: <presence to="spotter@jabber.aiai.ed.ac.uk" id="37" type="subscribe"></presence>
		*/

		// <iq type='set'><query xmlns='jabber:iq:roster'><item jid='spotter@jabber.org' name='spotter' subscription='to'><group>Local</group></item></query></iq><iq type='set'><query xmlns='jabber:iq:roster'><item jid='spotter@jabber.org' name='spotter' subscription='to'><group>Local</group></item></query></iq>


	    }
	    else Debug.noteln("Request ignored");
	};
     
	public void unsubscribe(Presence pr){
	    JID contact = pr.getFromAddress();
	    Debug.noteln("Unsubscribe msg: "+pr.toString());
	    // Respond to unsubscribe request with confirmation:
	    // sendSubscriptionMessage(contact,"unsubscribed");
	    try{
		// do a roster delete for this contact...
		RosterItemBuilder rib = new RosterItemBuilder();
		rib.setJID(contact);
		RosterItem ri = new RosterItem(rib);
		Debug.noteln("Delete roster item: "+contact.toString());
		rbean.delRosterItem(ri);
	    }
	    catch (InstantiationException e) {
		//Debug.noteln("Problem building RosterItem?\n"+e.toString());
		throw new IPC.IPCException("Error deleting Jabber roster item", e);
	    }

	    // Can't do the following here, since the unsubscribe msg sender has
	    // no resource information attached: 
	    // update constraint set:
	    // passConstraint(contact,"unsubscribed");
	    
	    // finally update PresenceWindow JTree:
    	    addPTreeNode(contact,"unsubscribed");
	};

	public void subscribed(Presence pr){
	    //Debug.noteln("Subscribed!!");
	    String from = pr.getFromAddress().toString();
	    Debug.noteln("\nSubscription to presence of "+from+" accepted.\n");
	};

	public void unsubscribed(Presence pr){
	    Debug.noteln("Unsubscribed msg: "+pr.toString());
	    JID contact = pr.getFromAddress();
	    Debug.noteln("Contact "+ contact.toString() + " unsubscribed.");
	    // Confirm message receipt:
	    //	    sendSubscriptionMessage(contact,"unsubscribe");
	    // Reciprocal unsubscription:
	    //sendSubscriptionMessage(contact,"unsubscribed");
//  	    try{
//  		// do a roster delete for this contact...
//  		RosterItemBuilder rib = new RosterItemBuilder();
//  		rib.setJID(contact);
//  		RosterItem ri = new RosterItem(rib);
//  		Debug.noteln("Delete roster item: "+contact.toString());
//  		rbean.delRosterItem(ri);
//  	    }
//  	    catch (InstantiationException e) {
//  		//Debug.noteln("Problem building RosterItem?\n"+e.toString());
//  		throw new IPC.IPCException("Error deleting Jabber roster item", e);
//  	    }

//  	    // finally update PresenceWindow JTree:
//      	    addPTreeNode(contact,"unsubscribed");
	};

	public void error(Presence pr){
	    //Debug.noteln("error!!");
	    JID from = pr.getFromAddress();
	    String show = "disconnected";
	    Debug.noteln("\nPresence error noted - assuming:");
	    passConstraint(from,show);
	};
   }

    /*****************************************/
    /* some miscellaneous Roster operations: */
    /*****************************************/

    private void subscribeToContact(String contactString){
	// generate and send subscription to a new contact.
	// (if the contact wishes to subscribe to our presence,
	// this will be handled by the presence listener.
	Debug.noteln("New contact: "+ contactString);
	JID temp = (new JID(jabberServer)).fromString(contactString);
	// contactString may include resource name; remove this for subscription:
	JID contact = new JID(temp.getUsername(),temp.getServer(),null);
	Debug.noteln(contact.toString());
	setRosterContact(contact);
	sendSubscriptionMessage(contact,"subscribe");
    }

    private void sendSubscriptionMessage(JID contact, String type){
	// type should be "subscribe", "unsubscribe", etc.
	PresenceBuilder pb = new PresenceBuilder();
	Presence subpr;
	pb.setType(type);
	pb.setToAddress(contact);
	pb.setFromAddress(new JID(username,jabberServer,null));
	try{
	    subpr = (Presence)pb.build();
	}
	catch(InstantiationException e){
	    /* build failed ?*/
	    //Debug.noteln("error creating presence packet:");
	    //Debug.noteln(e.toString());
	    throw new IPC.IPCException("Error creating Jabber presence packet", e);
	}
	connectionBean.send(subpr);
	Debug.noteln("\nSent subscription message ("+type+") to "+ contact.toString());
    };

    public static void pprintRoster(){
	
	Enumeration re = rbean.entries(); 
	while(re.hasMoreElements()){		
	    RosterItem ri = (RosterItem) re.nextElement();
	    String name = ri.getFriendlyName();
	    if(name==null) name=(ri.getJID()).toString();
	    Debug.noteln(name +" "+ri.getSubscriptionType());
	}
    }

    public static String getPrettyName(JID target){
	boolean found = false;
	String pname = "";
	Enumeration re = rbean.entries(); 
	try{
	    while(re.hasMoreElements() && !found){		
		RosterItem ri = (RosterItem) re.nextElement();
		// int bit mask of which parts to compare (name, server, resource):
		if(target.equals((ri.getJID()),JID.USERNAME+JID.SERVER)){
		    pname = ri.getFriendlyName();
		}
	    }
	}
	catch (Throwable e){
	    Debug.noteln(e.toString());
	}
       
	//Debug.noteln("pname="+pname+"<<");
	if(pname==null || pname.equalsIgnoreCase(""))
	    pname = target.getUsername();
	return pname;
    }	

    private void setRosterContact(JID contact){
	/* Should look something like this:
	  SENT: <iq id="36" type="set"><query xmlns="jabber:iq:roster"><item jid="spotter@jabber.aiai.ed.ac.uk" name="sp"><group>new</group></item></query></iq>
	*/

	try{
	    // do a roster set for this contact...
	    RosterItemBuilder rib = new RosterItemBuilder();
	    rib.setJID(contact);
	    rib.setFriendlyName(contact.getUsername());
	    // have to add a group, otherwise BuddySpace (appears to) fail to recognise contact
	    rib.addGroup("New Contacts");
	    RosterItem ri = new RosterItem(rib);
	    Debug.noteln("New roster item: "+contact.toString());
	    rbean.addRosterItem(ri);
	}
	catch (InstantiationException e) {
	    //Debug.noteln("Problem building RosterItem?\n"+e.toString());
	    throw new IPC.IPCException("Error creating Jabber roster item", e);
	}
	try{
	    rbean.refreshRoster();
	}
	catch (InstantiationException e) {
	    throw new IPC.IPCException("Error refreshing Jabber roster", e);
	}
    };


    public static boolean isSubscribed(JID target) throws IPC.IPCException {
	boolean sub = false;
	Enumeration re = rbean.entries(); 
	while(re.hasMoreElements() && !sub){		
	    RosterItem ri = (RosterItem) re.nextElement();

	    try{
		// int bit mask of which parts to compare (name, server, resource):
		if(target.equals((ri.getJID()),JID.USERNAME+JID.SERVER)){
		    String subtype = ri.getSubscriptionType();
		    // Subscription Type: the type of subscription between the user specified and the actual client. 
		    // Can be 'none', 'to' (we receive their presence), 'from' (they receive our presence) or 'both'. 
		    // A fifth, non-visible option is 'remove', which is sent to delete a message and returned to 
		    // indicate an item has been removed and should be deleted from the local cache.
		    if(subtype.equals("both")||(subtype.equals("to"))){
			sub=true;
			Debug.noteln("(Both/to) subscribed to "+target.toString());
		    }
		}
	    }
	    catch (Exception e){
		throw new IPC.IPCException(target.toString()+" is an invalid Jabber username.");
	    }
	}
	return sub;
    }

    public static boolean isOnline(JID target){
	boolean online = false;
	Enumeration pe = (pbean.getJIDTree()).elements();
	// each element of the hashtable JIDTree is of type PresenceUserNode:
	while(pe.hasMoreElements() && !online){	
	    //if(target.equals((ri.getJID()),JID.USERNAME+JID.SERVER))
	    PresenceUserNode pi = (PresenceUserNode) pe.nextElement();
	    Hashtable resources = pi.getResources();
	    Debug.noteln("Presence item: "+pi.toString()+" "+ resources.toString());
	}

	Hashtable currentp = pbean.getJIDTree();
	JID mask = new JID(target.getUsername(),target.getServer(),null);

	if(currentp.containsKey(mask)){
	    Debug.noteln(target.toString()+ " is pb online");

	    // if a resource has been specified, need to check that as well:
	    if(target.getResource()!=null){
		PresenceUserNode match = (PresenceUserNode) currentp.get(mask);
		//Hashtable presources = match.getResources();
		if((match.getResources()).containsKey(target)){
		    Debug.noteln("...and resource " + target.getResource() + " is available");
		    online = true;
		}
	    }
	    else
		online = true;
	}
	return online;
    }


    public boolean isIXOnline(JID target){
	// Assume that no resource name is specified
	boolean online = false;
	String ixresname = resource;
	Hashtable currentp = pbean.getJIDTree();
	JID mask = new JID(target.getUsername(),target.getServer(),null);

	if(currentp.containsKey(mask)){
	    //Debug.noteln(target.toString()+ " is pb online");
	    PresenceUserNode match = (PresenceUserNode) currentp.get(mask);
	    JID ixmask = new JID(target.getUsername(),target.getServer(),ixresname);	
	    if((match.getResources()).containsKey(ixmask)){
		Debug.noteln("...and resource " + ixresname + " is available");
		online = true;
	    }
	}
	return online;
    }

    public String getStatus(String tar){
	// Assume that no resource name is specified
	//boolean online = false;
	JID target = (new JID(jabberServer)).fromString(tar);
	String status = "online";
	Hashtable currentp = pbean.getJIDTree();
	JID mask = new JID(target.getUsername(),target.getServer(),null);

	if(currentp.containsKey(mask)){
	    //Debug.noteln(target.toString()+ " is pb online");
	    PresenceUserNode match = (PresenceUserNode) currentp.get(mask);
	    
	    Hashtable resources = match.getResources();
	    //JID ixmask = new JID(target.getUsername(),target.getServer(),ixresname);	
	    if(resources.containsKey(target)){
		ResourceNode rn = (ResourceNode) resources.get(target);
		//Debug.noteln("State:"+rn.getState()+" status:"+rn.getStatus());
		status = rn.getState();
		//Debug.noteln("getting state:"+ match.toString() + "----" + (resources.get(target)).getClass());
		//online = true;
	    }
	}

	if(status==null || status.equalsIgnoreCase("null")) status = "online";
	else if(status.equalsIgnoreCase("dnd")) status = "do not disturb";
	else if(status.equalsIgnoreCase("xa")) status = "extended away";
	
	return status;
    }

    public void setStatus(String pres){
	presence = pres;
	PresenceBuilder pb=new PresenceBuilder();
	String show = "";
	if(presence.equalsIgnoreCase("Away") || 
	   presence.equalsIgnoreCase("Lunch") ||
	   presence.equalsIgnoreCase("Bank"))
	    show = "away";
	else if(presence.equalsIgnoreCase("Busy") || 
		presence.equalsIgnoreCase("Working") ||
		presence.equalsIgnoreCase("Mad") ||
		presence.equalsIgnoreCase("Do not disturb"))
	    show = "dnd";
	else if(presence.equalsIgnoreCase("Extended Away") || 
		presence.equalsIgnoreCase("Gone Home") ||
		presence.equalsIgnoreCase("Gone to Work") ||
		presence.equalsIgnoreCase("Sleeping"))
	    show = "xa";
	else if(presence.equalsIgnoreCase("Wants to Chat") ||
		presence.equalsIgnoreCase("Free for chat"))				    
	    show = "chat";
	    
	if(presence.equalsIgnoreCase("Online but elsewhere"))
	    pb.setStatus("Physically elsewhere");
	else
	    pb.setStatus(presence);

	pb.setStateShow(show);

	try{
	    //build and send presence
	    //Debug.noteln(pb.toString());
	    connectionBean.send(pb.build());
	}
	catch (InstantiationException e){
	    //building failed ?
	    Debug.noteln
		("Fatal Error on Presence object build:");
	    Debug.noteln(e.toString());
	    return ;
	}
    }

    private boolean betterStatus(String st1, String st2){
	// returns true if the first status is 'better' than the second:
	int nstates = 9;
	// states array contains implicit ordering from best to worst:
	String[] states = {"online","chat","low attention, busy","online but elsewhere","away","extended away","do not disturb","unavailable","unsubscribed"};
	int sv1=0;
	int sv2=0;
	for(int i=0;i<nstates;i++){
	    if(st1.equalsIgnoreCase(states[i])) sv1=i;
	}
	for(int i=0;i<nstates;i++){
	    if(st2.equalsIgnoreCase(states[i])) sv2=i;
	}

	Debug.noteln(st1+" Vs. "+st2+":");
	if(sv1<sv2) return true;
	else return false;
    }

    public void addPTreeNode(JID contact, String status){

	String user = contact.getUsername() + "@" + contact.getServer();
	String resource = contact.getResource();
	boolean remove = false;
	if(status.equalsIgnoreCase("unavailable") || status.equalsIgnoreCase("unsubscribed")) remove = true;

	DefaultMutableTreeNode newn = null;
	if(presenceTree.getNextNode()!=null){
	    Enumeration contacts = presenceTree.breadthFirstEnumeration();
	    boolean found = false;
	    int depth=0;
	    int nchildren=0;
	    TreeNode[] path;
	    DefaultMutableTreeNode node = null;
	    JabberDisplay jd = new JabberDisplay();

	    // is this contact already in the tree?
	    while(contacts.hasMoreElements() && !found && depth<=1){
		node =  (DefaultMutableTreeNode) (contacts.nextElement());
		depth = node.getLevel();
		jd = (JabberDisplay) (node.getUserObject());
		if(user.equalsIgnoreCase(jd.jids)){
		    found = true;
		    path = node.getPath();
		    nchildren = node.getChildCount();
		}
	    }
	    DefaultMutableTreeNode rnode = null;
	    DefaultMutableTreeNode tnode = null;
	    if(found){
		String bestStatus = status;
		//Debug.noteln("Set bestStatus to: "+bestStatus);

		// is this contact's resource already in tree?
		Enumeration resources = node.breadthFirstEnumeration();
		boolean rfound = false;
		boolean unsubscribed = false;
		if(status.equalsIgnoreCase("unsubscribed")){
		    unsubscribed=true;
		    rfound=true;
		}
		while(!unsubscribed && resources.hasMoreElements()){
		    tnode = (DefaultMutableTreeNode) (resources.nextElement());
		    JabberDisplay rjd = (JabberDisplay) (tnode.getUserObject());
		    //Debug.noteln("JD.JIDS="+rjd.jids);
		    String thisResource = ((new JID(jabberServer)).fromString(rjd.jids)).getResource();
		    if(resource.equalsIgnoreCase(thisResource)){
			rfound = true;
			rnode = tnode;
		    }
		    else{
			if(thisResource!=null && betterStatus(rjd.getStatus(),bestStatus)){
			    bestStatus = rjd.getStatus();
			    //Debug.noteln("Set bestStatus to: "+bestStatus);
			}
		    }
		}

		// update this contact's 'root' node:
		node.setUserObject(new JabberDisplay(jd.getName(),jd.getJID(),bestStatus));
		treeModel.reload(node);
		if(rfound){
		    // if the resource has a node in the tree already:
		    if(!remove){
			// alter the status of the resource node accordingly:
			//rnode.setUserObject(new JabberDisplay(resource, contact.toString(), getStatus(contact.toString())));
			rnode.setUserObject(new JabberDisplay(resource, contact.toString(),status));
			newn = rnode;
		    }
		    else{
			// if this resource has become unavailable:
			if(nchildren==1 || status.equalsIgnoreCase("unsubscribed")) 
			    // if this resource node is only child need to remove parent as well;
			    // alternatively, if the contact is now unsubscribed, need to remove
			    // it and all its children:
			    treeModel.removeNodeFromParent(node); 
			else
			    // otherwise remove just this resource node:
			    treeModel.removeNodeFromParent(rnode);
		    }
		}
		else{
		    // if this resource is not already in tree, generate a new node & insert it:
		    newn = extractPresenceTreeResource(user, resource, status);
		    treeModel.insertNodeInto(newn,node,0);
		}
	    }
	    else{
		// This user is not already in the tree; generate a new node and add it:
		newn = extractPresenceTree(user, status);
		treeModel.insertNodeInto(newn,presenceTree,0);
	    }
	}
	else{
	    // This is first non-root node in the tree; generate a new node and add it:
	    newn = extractPresenceTree(user, status);
	    treeModel.insertNodeInto(newn,presenceTree, 0);
	}
	if(!remove) treeModel.reload(newn);
    }



    public DefaultMutableTreeNode extractPresenceTree(){

	DefaultMutableTreeNode 
	    thisnode = new DefaultMutableTreeNode(new JabberDisplay("Online Contacts", "x", "online"));

	Hashtable currentp = pbean.getJIDTree();
	Enumeration pelements = currentp.elements();

	while(pelements.hasMoreElements()){	
	    PresenceUserNode pi = (PresenceUserNode) (pelements.nextElement());
	    JabberDisplay jd = new JabberDisplay(getPrettyName((new JID(jabberServer)).fromString(pi.getName())),pi.getName(), getStatus(pi.getName()));
	    DefaultMutableTreeNode newnode = new DefaultMutableTreeNode(jd);
	    Hashtable resources = pi.getResources();
	    Enumeration reskeys = resources.keys();
	    while(reskeys.hasMoreElements()){
		JID ri = (JID) reskeys.nextElement();
		//Debug.noteln(ri.toString()+ " *** "+ (ri.getClass()).toString());

		JabberDisplay rjd = new JabberDisplay(ri.getResource(),pi.getName()+"/"+ri.getResource(), getStatus(pi.getName()+"/"+ri.getResource()));
		newnode.add(new DefaultMutableTreeNode(rjd));
	    }
	    Debug.noteln("Presence item: "+pi.toString()+" "+ resources.toString());
	    thisnode.add(newnode);
	}
	return thisnode;
    }

    public DefaultMutableTreeNode extractPresenceTree(String target, String show){

	DefaultMutableTreeNode newnode = null; 

	Hashtable currentp = pbean.getJIDTree();
	Enumeration pelements = currentp.elements();

	while(pelements.hasMoreElements()){	
	    PresenceUserNode pi = (PresenceUserNode) (pelements.nextElement());
	    if(target.equalsIgnoreCase(pi.getName())){

		//	JabberDisplay jd = new JabberDisplay(getPrettyName((new JID(jabberServer)).fromString(pi.getName())),pi.getName(), getStatus(pi.getName()));
		JabberDisplay jd = new JabberDisplay(getPrettyName((new JID(jabberServer)).fromString(pi.getName())),pi.getName(), show);
		newnode = new DefaultMutableTreeNode(jd);
		Hashtable resources = pi.getResources();
		Enumeration reskeys = resources.keys();
		while(reskeys.hasMoreElements()){
		    JID ri = (JID) reskeys.nextElement();
		    //Debug.noteln(ri.toString()+ " *** "+ (ri.getClass()).toString());
		    //JabberDisplay rjd = new JabberDisplay(ri.getResource(),pi.getName()+"/"+ri.getResource(), getStatus(pi.getName()+"/"+ri.getResource()));
		    JabberDisplay rjd = new JabberDisplay(ri.getResource(),pi.getName()+"/"+ri.getResource(), show);
		    newnode.add(new DefaultMutableTreeNode(rjd));
		}
		//Debug.noteln("Presence item: "+pi.toString()+" "+ resources.toString());
	    }
	}
	return newnode;
    }


    public DefaultMutableTreeNode extractPresenceTreeResource(String target, String resource, String show){

	DefaultMutableTreeNode newnode = null; 

	Hashtable currentp = pbean.getJIDTree();
	Enumeration pelements = currentp.elements();

	while(pelements.hasMoreElements()){	
	    PresenceUserNode pi = (PresenceUserNode) (pelements.nextElement());
	    if(target.equalsIgnoreCase(pi.getName())){
		Hashtable resources = pi.getResources();
		Enumeration reskeys = resources.keys();
		while(reskeys.hasMoreElements()){
		    JID ri = (JID) reskeys.nextElement();
		    //Debug.noteln(ri.toString()+ " *** "+ (ri.getClass()).toString());
		    if((ri.getResource()).equalsIgnoreCase(resource)){
			//JabberDisplay rjd = new JabberDisplay(ri.getResource(),pi.getName()+"/"+ri.getResource(), getStatus(pi.getName()+"/"+ri.getResource()));
			JabberDisplay rjd = new JabberDisplay(ri.getResource(),pi.getName()+"/"+ri.getResource(), show);
			newnode = new DefaultMutableTreeNode(rjd);
		    }
		}
		//Debug.noteln("Presence item: "+pi.toString()+" "+ resources.toString());
	    }
	}
	return newnode;
    }




    private class JabberDisplay {
	public String pname;
	public String jids;
	public JID jid;
	public String status;

	public JabberDisplay(){
	    pname = "";
	    jids = "";
	    status = "";
	}

	public JabberDisplay(String pn, String js, String st){
	    pname = pn;
	    jids = js;
	    status = st;
	}

	public String toString(){
	    return pname;
	}

	public String getName(){
	    return pname;
	}

	public String getJID(){
	    return jids;
	}
	   
	public String getStatus(){
	    return status;
	}

	public void setStatus(String st){
	    status = st;
	}

    }



    /*********************************************/
    /* presence window stuff*/
    /*********************************************/ 



    //    public class PresenceWindow extends JFrame implements ActionListener {

    public class PresenceWindow extends ix.iface.util.ToolFrame implements ActionListener {
	//Optionally play with line styles.  Possible values are
	//"Angled", "Horizontal", and "None" (the default).
	private boolean playWithLineStyle = true;
	private String lineStyle = "Angled"; 
	//DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root Node");
	//DefaultTreeModel treeModel = new DefaultTreeModel(rootNode);
	JMenu presmenu = new JMenu("My Presence");
	
       
    public PresenceWindow(DefaultMutableTreeNode prestree, String title) {

       	JMenuBar menubar = new JMenuBar();
	//menubar.setAlignmentX(Component.RIGHT_ALIGNMENT);
	setJMenuBar(menubar);
	JMenu filemenu = new JMenu("File");
	JMenuItem filemenuItem = new JMenuItem("Close");
	filemenuItem.addActionListener(new ActionListener(){
		public void actionPerformed(ActionEvent e){
		    setVisible(false);
		}});
	filemenu.add(filemenuItem);

	//JMenu presmenu = new JMenu("My Presence");
	menubar.add(filemenu);
	menubar.add(presmenu);
	//menubar.add(presicon);
	//presmenu.setIcon(getPIcon("switch",true));
	setPIcon(presence);

	JMenuItem onlinem = new JMenuItem("Online", getPIcon("online",false));
	JMenuItem chatm = new JMenuItem("Free for chat", getPIcon("chat",false));
	JMenuItem labm = new JMenuItem("Low attention, busy", getPIcon("lab",false));
	JMenuItem obem = new JMenuItem("Online but elsewhere", getPIcon("obe",false));
	JMenuItem awaym = new JMenuItem("Away", getPIcon("away",false));
	JMenuItem xam = new JMenuItem("Extended away", getPIcon("xa",false));
	JMenuItem dndm = new JMenuItem("Do not disturb", getPIcon("dnd",false));

	presmenu.add(onlinem);
	presmenu.add(chatm);
	presmenu.add(labm);
	presmenu.add(obem);
	presmenu.add(awaym);
	presmenu.add(xam);
	presmenu.add(dndm);
	onlinem.addActionListener(this);
	chatm.addActionListener(this);
	labm.addActionListener(this);
	obem.addActionListener(this);
	awaym.addActionListener(this);
	xam.addActionListener(this);
	dndm.addActionListener(this);

	setPWIcon(presence);
	setTitle(title);
	
	init(prestree);
	pack();
	setVisible(true);
	DefaultMutableTreeNode ptree = null;

    }

	public ImageIcon getPIcon(String name, boolean large){
	    // there are two types of icon, large and not large: filenames of large icons have an "l" placed
	    // immediately before the extension: 
	    String iconname;
	    // this is the location of the icons within the jabbericons.jar file (just in
	    // case of unfortunate name clash with some other icon elsewhere):
	    String imagedir = "jabbericons/";
	    if(large) iconname = imagedir + name + "l.gif";
	    else iconname = imagedir + name + ".gif";
	    URL url = PresenceWindow.class.getClassLoader().getResource(iconname);
	    //Debug.noteln("creating imageicon from: "+ url.toString());
	    return (new ImageIcon(url));
	}

	public void setPIcon(String s){
	    if(s.equalsIgnoreCase("Online")) presmenu.setIcon(getPIcon("online",true));
	    else if(s.equalsIgnoreCase("Free for chat")) presmenu.setIcon(getPIcon("chat",true));
	    else if(s.equalsIgnoreCase("Low attention, busy")) presmenu.setIcon(getPIcon("lab",true));
	    else if(s.equalsIgnoreCase("Online but elsewhere")) presmenu.setIcon(getPIcon("obe",true));
	    else if(s.equalsIgnoreCase("Away")) presmenu.setIcon(getPIcon("away",true));
	    else if(s.equalsIgnoreCase("Extended away")) presmenu.setIcon(getPIcon("xa",true));
	    else if(s.equalsIgnoreCase("Do not disturb")) presmenu.setIcon(getPIcon("dnd",true));
	    else presmenu.setIcon(getPIcon("online",true));
	}

	public void setPWIcon(String s){
	    if(s.equalsIgnoreCase("Online")) setIconImage((getPIcon("online",true)).getImage());
	    else if(s.equalsIgnoreCase("Free for chat")) setIconImage((getPIcon("chat",true)).getImage());
	    else if(s.equalsIgnoreCase("Low attention, busy")) setIconImage((getPIcon("lab",true)).getImage());
	    else if(s.equalsIgnoreCase("Online but elsewhere")) setIconImage((getPIcon("obe",true)).getImage());
	    else if(s.equalsIgnoreCase("Away")) setIconImage((getPIcon("away",true)).getImage());
	    else if(s.equalsIgnoreCase("Extended away")) setIconImage((getPIcon("xa",true)).getImage());
	    else if(s.equalsIgnoreCase("Do not disturb")) setIconImage((getPIcon("dnd",true)).getImage());
	    else setIconImage((getPIcon("online",true)).getImage());
	}
	public void actionPerformed(ActionEvent e) {
	    JMenuItem source = (JMenuItem)(e.getSource());
	    String s =  source.getText();
	    setStatus(s);
	    setPWIcon(s);
	    setPIcon(s);
	}

	private void init(DefaultMutableTreeNode prestree) {

	try{


	    final JTree krtree = new JTree(treeModel);
	    krtree.setEditable(false);
	    krtree.getSelectionModel().setSelectionMode
		(TreeSelectionModel.SINGLE_TREE_SELECTION);
	    krtree.setShowsRootHandles(true);
	    treeModel.addTreeModelListener(new PTMListener());

	    ToolTipManager.sharedInstance().registerComponent(krtree);
	    krtree.setCellRenderer(new PTRenderer());

	    krtree.getSelectionModel().setSelectionMode
                (TreeSelectionModel.SINGLE_TREE_SELECTION);

	    //Listen for when the selection changes.
	    krtree.addTreeSelectionListener(new TreeSelectionListener() {
		    public void valueChanged(TreeSelectionEvent e) {

			// Don't do anything here at the moment - but might do!:
			//			DefaultMutableTreeNode node = (DefaultMutableTreeNode)
			//    krtree.getLastSelectedPathComponent();
        
			//if (node == null) return;

			//Object nodeInfo = node.getUserObject();
			//if (node.isLeaf()) {
			    //Debug.noteln(node.toString());
			//} else {
			    //Debug.noteln(node.toString());
			//}
		    }
		});


	    if (playWithLineStyle) {
		krtree.putClientProperty("JTree.lineStyle", lineStyle);
	    }


	    //Create the scroll pane and add the tree to it. 
	    JScrollPane krtreeView = new JScrollPane(krtree);

	    ActionListener listener = new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			PresenceWindow.this.dispose();
			/* if (listeners != null) 
			   listeners.actionPerformed(
			   new ActionEvent(PresenceWindow.this,e.getID(),e.getActionCommand()));*/
		    }};
      
	    getContentPane().add(krtreeView, BorderLayout.CENTER);

	    this.pack();
	}
	catch (Exception e){
	    System.out.println(e);
	}
    }

    protected ActionListener listeners = null;

    public void addActionListener(ActionListener l) {
	listeners = AWTEventMulticaster.add(listeners,l);
    }

    public void removeActionListener(ActionListener l) {
	listeners = AWTEventMulticaster.remove(listeners,l);
    }




    class PTMListener implements TreeModelListener {
	public void treeNodesChanged(TreeModelEvent e) {
	    DefaultMutableTreeNode node;
	    node = (DefaultMutableTreeNode)
		(e.getTreePath().getLastPathComponent());

        /*
         * If the event lists children, then the changed
         * node is the child of the node we've already
         * gotten.  Otherwise, the changed node and the
         * specified node are the same.
         */
	    try {
		int index = e.getChildIndices()[0];
		node = (DefaultMutableTreeNode)
		    (node.getChildAt(index));
	    } catch (NullPointerException exc) {}

	    System.out.println("The user has finished editing the node.");
	    System.out.println("New value: " + node.getUserObject());
	}
	public void treeNodesInserted(TreeModelEvent e) {
	    //Debug.noteln("inserted!"+ e.toString());
	    //treeModel.reload();
	}
	public void treeNodesRemoved(TreeModelEvent e) {
	}
	public void treeStructureChanged(TreeModelEvent e) {
	}
    }


    private class PTRenderer extends DefaultTreeCellRenderer {
	//ImageIcon onlineIcon;
	public PTRenderer() {
	}

	public Component getTreeCellRendererComponent(
						      JTree tree,
						      Object value,
						      boolean sel,
						      boolean expanded,
						      boolean leaf,
						      int row,
						      boolean hasFocus) {

	    super.getTreeCellRendererComponent(
					       tree, value, sel,
					       expanded, leaf, row,
					       hasFocus);
	    
	    JabberDisplay jd = (JabberDisplay) (((DefaultMutableTreeNode)value).getUserObject());

	    if((jd.status).equalsIgnoreCase("away")) setIcon(getPIcon("away",false));
	    else if((jd.status).equalsIgnoreCase("extended away")) setIcon(getPIcon("xa",false));
	    else if((jd.status).equalsIgnoreCase("chat")) setIcon(getPIcon("chat",false));
	    else if((jd.status).equalsIgnoreCase("do not disturb")) setIcon(getPIcon("dnd",false));
	    else if((jd.status).equalsIgnoreCase("online but elsewhere")) setIcon(getPIcon("obe",false));
	    else if((jd.status).equalsIgnoreCase("low attention, busy")) setIcon(getPIcon("lab",false));
	    else setIcon(getPIcon("online",false));

	    if((jd.status).equalsIgnoreCase("root"))
		setToolTipText(null); // turn off for root node
	    else
		setToolTipText(jd.jids + " : " + jd.status);

	    return this;
	}

    }
    }



    
}






