/*******************************************************************************
 Jimm - Mobile Messaging - J2ME ICQ clone
 Copyright (C) 2003-05  Jimm Project

 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.
 ********************************************************************************
 File: src/jimm/comm/ConnectAction.java
 Version: 0.5.1  Date: 2006/10/31
 Author(s): Manuel Linsmayer, Andreas Rossbacher
 *******************************************************************************/

package jimm.comm;

import java.util.*;
import java.io.*;

import jimm.ContactListContactItem;
import jimm.ContactListGroupItem;
import jimm.ContactListItem;
import jimm.ContactList;
import jimm.Jimm;
import jimm.JimmException;
import jimm.Options;
import jimm.RunnableImpl;
import jimm.comm.Icq.HTTPConnection;

public class ConnectAction extends Action
{
    // Action states
    public static final int STATE_ERROR = -1;
    public static final int STATE_INIT_DONE = 0;
	public static final int STATE_AUTHKEY_REQUESTED = 1;
    public static final int STATE_CLI_IDENT_SENT = 2;
    public static final int STATE_CLI_DISCONNECT_SENT = 3;
    public static final int STATE_CLI_COOKIE_SENT = 4;
    public static final int STATE_CLI_CHECKROSTER_SENT = 5;
    public static final int STATE_CLI_REQOFFLINEMSGS_SENT = 6;
    public static final int STATE_CLI_ACKOFFLINEMSGS_SENT = 7;

    // CLI_SETUSERINFO packet data
    public static final byte[] CLI_SETUSERINFO_DATA =
	{
		(byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x60,   // 5 capabilities follow

		(byte) 0x09, (byte) 0x46, (byte) 0x13, (byte) 0x49, // CAP_AIM_SERVERRELAY
		(byte) 0x4C, (byte) 0x7F, (byte) 0x11, (byte) 0xD1, 
		(byte) 0x82, (byte) 0x22, (byte) 0x44, (byte) 0x45, 
		(byte) 0x53, (byte) 0x54, (byte) 0x00, (byte) 0x00,
		
		(byte) 0x09, (byte) 0x46, (byte) 0x13, (byte) 0x44,   // CAP_AIM_ISICQ
		(byte) 0x4C, (byte) 0x7F, (byte) 0x11, (byte) 0xD1, 
		(byte) 0x82, (byte) 0x22, (byte) 0x44, (byte) 0x45, 
		(byte) 0x53, (byte) 0x54, (byte) 0x00, (byte) 0x00,		
		
		(byte) 0x09, (byte) 0x46, (byte) 0x00, (byte) 0x00,   //CAP_UNKNOWN
		(byte) 0x4C, (byte) 0x7F, (byte) 0x11, (byte) 0xD1, 
		(byte) 0x82, (byte) 0x22, (byte) 0x44, (byte) 0x45, 
		(byte) 0x53, (byte) 0x54, (byte) 0x00, (byte) 0x00,
		
		(byte) 0x09, (byte) 0x46, (byte) 0x13, (byte) 0x4E,   //CAP_UTF8
		(byte) 0x4C, (byte) 0x7F, (byte) 0x11, (byte) 0xD1, 
		(byte) 0x82, (byte) 0x22, (byte) 0x44, (byte) 0x45, 
		(byte) 0x53, (byte) 0x54, (byte) 0x00, (byte) 0x00,
		
		/* Jimm */ 'J', 'i', 'm', 'm', 						//Jimm version
		(byte) 0x20, (byte) 0x00, (byte) 0x00, (byte) 0x00,	//Place for string & raw version
		(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, //Place for string & raw version
		(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, //Last byte - target
		
		(byte) 0x56, (byte) 0x3f, (byte) 0xc8, (byte) 0x09,      // CAP_MTN
		(byte) 0x0b, (byte) 0x6f, (byte) 0x41, (byte) 0xbd,
		(byte) 0x9f, (byte) 0x79, (byte) 0x42, (byte) 0x26,
		(byte) 0x09, (byte) 0xdf, (byte) 0xa2, (byte) 0xf3
	};

    // CLI_SETICBM packet data
    public static final byte[] CLI_SETICBM_DATA =
    { 0, 0, 0, 0, 0, 0x0B, 0x1F,  0x40, 3, (byte)0xE7, 3, (byte)0xE7, 0, 0, 0, 0};

    // CLI_SETSTATUS packet data
    public static final byte[] CLI_SETSTATUS_DATA =
    { (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x04, 
	  (byte) 0x11, (byte) 0x00, (byte) 0x00, (byte) 0x00, // Online status
      (byte) 0x00, (byte) 0x0C, (byte) 0x00, (byte) 0x25, // TLV(C)
      (byte) 0xC0, (byte) 0xA8, (byte) 0x00, (byte) 0x01, // 192.168.0.1, cannot get own IP address
      (byte) 0x00, (byte) 0x00, (byte) 0xAB, (byte) 0xCD, // Port 43981
      (byte) 0x00, // Firewall
      (byte) 0x00, (byte) 0x08, // Support protocol version 8
      (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 
      (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x50, 
      (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x03, // 3                                                                                                                                                                 // follow
      (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE, // Timestamp 1
      (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, // Timestamp 2
      (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFE, // Timestamp 3
      (byte) 0x00, (byte) 0x00};

    // CLI_READY packet data
    public static final byte[] CLI_READY_DATA =
    {
    		(byte)0x00, (byte)0x01, (byte)0x00, (byte)0x04, 
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4, 
			(byte)0x00, (byte)0x13, (byte)0x00, (byte)0x04,
			(byte)0x01,(byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x02, (byte)0x00, (byte)0x01,
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x03, (byte)0x00, (byte)0x01,
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x15, (byte)0x00, (byte)0x01, 
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x04, (byte)0x00, (byte)0x01,
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x06, (byte)0x00, (byte)0x01,
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x09, (byte)0x00, (byte)0x01,
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x0a, (byte)0x00, (byte)0x01,
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4,
			(byte)0x00, (byte)0x0b, (byte)0x00, (byte)0x01,
			(byte)0x01, (byte)0x10, (byte)0x08, (byte)0xe4
    };
//    {(byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x03,
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04,	(byte) 0x7B,
//	 (byte) 0x00, (byte) 0x13, (byte) 0x00, (byte) 0x02,
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x01, 
//	 (byte) 0x01, (byte) 0x01, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x01, 
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x15, (byte) 0x00, (byte) 0x01,
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x01, 
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x01, 
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x01, 
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x0A, (byte) 0x00, (byte) 0x01, 
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B,
//     (byte) 0x00, (byte) 0x0B, (byte) 0x00, (byte) 0x01, 
//	 (byte) 0x01, (byte) 0x10, (byte) 0x04, (byte) 0x7B};

    // Timeout
    // #sijapp cond.if modules_PROXY is "true" #
    public static final int TIME_OUT = 20 * 1000;
    // #sijapp cond.end #
    public int TIMEOUT = 20 * 1000; // milliseconds

    /** *********************************************************************** */

    // UIN
    private String uin;

    // Password
    private String password;

    // Server host
    private String srvHost;

    // Server port
    private String srvPort;

    // Action state
    private int state;

    // Last activity
    private Date lastActivity = new Date();
    private boolean active;

    // Temporary variables
	private String server;
    private byte[] cookie;
    private boolean srvReplyRosterRcvd;

    // Constructor
    public ConnectAction(String uin, String password, String srvHost, String srvPort)
    {
    	super(true, false);
        this.uin = new String(uin);
        this.password = new String(password);
        this.srvHost = new String(srvHost);
        this.srvPort = new String(srvPort);
    }

    // Returns the UID
    public String getUin()
    {
        return (new String(this.uin));
    }

    // Returns the password
    public String getPassword()
    {
        return (new String(this.password));
    }

    // Returns the server host
    public String getSrvHost()
    {
        return (new String(this.srvHost));
    }

    // Returns the server port
    public String getSrvPort()
    {
        return (new String(this.srvPort));
    }

    // Init action
    protected void init() throws JimmException
    {
        // #sijapp cond.if modules_PROXY is "true" #
        int retry = 1;
        try
        {
            retry = Integer.parseInt(Options.getString(Options.OPTION_AUTORETRY_COUNT));
            retry = (retry > 0) ? retry : 1;
        } catch (NumberFormatException e)
        {
            retry = 1;
        }

        this.TIMEOUT = ConnectAction.TIME_OUT * retry;
        // #sijapp cond.end#
 
        // Check parameters
        if ((this.uin.length() == 0) || (this.password.length() == 0))
        {
            this.state = ConnectAction.STATE_ERROR;
            throw (new JimmException(117, 0));
        }

        // Open connection
        // #sijapp cond.if modules_PROXY is "true" #

        for (int i = 0; i < retry; i++)
        {
            // #sijapp cond.end#

            try
            {
            	Icq.c.connect(this.srvHost + ":" + this.srvPort);
                // #sijapp cond.if modules_PROXY is "true" #
                break;
                // #sijapp cond.end #
            } catch (JimmException e)
            {
                // #sijapp cond.if modules_PROXY is "true" #
                if (i >= (retry - 1) || ((this.lastActivity.getTime() + this.TIMEOUT) < System.currentTimeMillis()))
                {
                    // #sijapp cond.end #

                    this.state = ConnectAction.STATE_ERROR;
                    throw (e);
                }
                // #sijapp cond.if modules_PROXY is "true" #
                else
                    if ((this.lastActivity.getTime() + this.TIMEOUT) > System.currentTimeMillis())
                    {
                    	Icq.c.close();
                        try
                        {
                            // Wait the given time
                            Thread.sleep(2000);
                        } catch (InterruptedException er)
                        {
                            // Do nothing
                        }
                    }
            }
        }
        // #sijapp cond.end #

        // Set STATE_INIT
        this.state = ConnectAction.STATE_INIT_DONE;

        // Update activity timestamp
        this.lastActivity = new Date();

    }

    // Forwards received packet, returns true if packet has been consumed
    protected boolean forward(Packet packet) throws JimmException
    {
        // #sijapp cond.if modules_PROXY is "true" #

        int retry = 1;
        try
        {
            retry = Integer.parseInt(Options.getString(Options.OPTION_AUTORETRY_COUNT));
            retry = (retry > 0) ? retry : 1;
        } catch (NumberFormatException e)
        {
            retry = 1;
        }
        // #sijapp cond.end #

        // Set activity flag
        this.active = true;

        // Catch JimmExceptions
        try
        {

            // Flag indicates whether packet has been consumed or not
            boolean consumed = false;

            // Watch out for STATE_INIT_DONE
            if (this.state == ConnectAction.STATE_INIT_DONE)
            {
                // Watch out for SRV_CLI_HELLO packet
                if (packet instanceof ConnectPacket)
                {
                    ConnectPacket connectPacket = (ConnectPacket) packet;
                    if (connectPacket.getType() == ConnectPacket.SRV_CLI_HELLO)
                    {
						if (Options.getBoolean(Options.OPTION_MD5_LOGIN)) {
							Icq.c.sendPacket(new ConnectPacket());
							byte[] buf = new byte[4 + this.uin.length()];
							Util.putWord(buf, 0, 0x0001);
							Util.putWord(buf, 2, this.uin.length());
							byte[] uinRaw = Util.stringToByteArray(this.uin);
							System.arraycopy(uinRaw, 0, buf, 4, uinRaw.length);
							Icq.c.sendPacket(new SnacPacket(0x0017, 0x0006, 0, new byte[0], buf));
						} else {
							// Send a CLI_IDENT packet as reply
							ConnectPacket reply = new ConnectPacket(this.uin, this.password);
							Icq.c.sendPacket(reply);
						}

                        // Move to next state
                        this.state = !Options.getBoolean(Options.OPTION_MD5_LOGIN) ? ConnectAction.STATE_CLI_IDENT_SENT :
							STATE_AUTHKEY_REQUESTED;

                        // Packet has been consumed
                        consumed = true;

                    }
                }

            }
			else if (state == STATE_AUTHKEY_REQUESTED) {
				if (packet instanceof SnacPacket) {
					SnacPacket snacPacket = (SnacPacket)packet;
					if ((snacPacket.getFamily() == 0x0017) && (snacPacket.getCommand() == 0x0007)) {
						byte[] rbuf = snacPacket.getData();
						int len = Util.getWord(rbuf, 0);
						byte[] authkey = new byte[len];
						System.arraycopy(rbuf, 2, authkey, 0, len);
						rbuf = null;
						byte[] buf = new byte[2 + 2 + this.uin.length() + 2 + 2 + 16];
						int marker = 0;
						Util.putWord(buf, marker, 0x0001);
						marker += 2;
						Util.putWord(buf, marker, this.uin.length());
						marker += 2;
						byte[] uinRaw = Util.stringToByteArray(this.uin);
						System.arraycopy(uinRaw, 0, buf, marker, uinRaw.length);
						marker += uinRaw.length;
						Util.putWord(buf, marker, 0x0025);
						marker += 2;
						Util.putWord(buf, marker, 0x0010);
						marker += 2;
						byte[] md5buf = new byte[authkey.length + this.password.length() + Util.AIM_MD5_STRING.length];
						int md5marker = 0;
						System.arraycopy(authkey, 0, md5buf, md5marker, authkey.length);
						md5marker += authkey.length;
						byte[] passwordRaw = Util.stringToByteArray(this.password);
						System.arraycopy(passwordRaw, 0, md5buf, md5marker, passwordRaw.length);
						md5marker += passwordRaw.length;
						System.arraycopy(Util.AIM_MD5_STRING, 0, md5buf, md5marker, Util.AIM_MD5_STRING.length);
						byte[] hash = Util.calculateMD5(md5buf);
						System.arraycopy(hash, 0, buf, marker, 16);
						Icq.c.sendPacket(new SnacPacket(0x0017, 0x0002, 0, new byte[0], buf));
						state = STATE_CLI_IDENT_SENT;
					} else {
						throw new JimmException(100,0);
					}
				}
				consumed = true;
			}
            // Watch out for STATE_CLI_IDENT_SENT
            else if (this.state == ConnectAction.STATE_CLI_IDENT_SENT)
			{
				int errcode = -1;
				if (Options.getBoolean(Options.OPTION_MD5_LOGIN)) {
					if (packet instanceof SnacPacket) {
						SnacPacket snacPacket = (SnacPacket)packet;
						if ((snacPacket.getFamily() == 0x0017) && (snacPacket.getCommand() == 0x0003)) {
							byte[] buf = snacPacket.getData();
							int marker = 0;
							while (marker < buf.length) {
								byte[] tlvData = Util.getTlv(buf, marker);
								int tlvType = Util.getWord(buf, marker);
								marker += 4 + tlvData.length;
								switch (tlvType) {
									case 0x0008:
										errcode = Util.getWord(tlvData, 0);
										break;
									case 0x0005:
										this.server = Util.byteArrayToString(tlvData);
										break;
									case 0x0006:
										this.cookie = tlvData;
										break;
								}
							}
						}
					} else if (packet instanceof DisconnectPacket) {
						consumed = true;
					}
				} else {
					// watch out for channel 4 packet
					if (packet instanceof DisconnectPacket) {
						DisconnectPacket disconnectPacket = (DisconnectPacket) packet;
						// Watch out for SRV_COOKIE packet
						if (disconnectPacket.getType() == DisconnectPacket.TYPE_SRV_COOKIE) {
							// Save cookie
							this.cookie = disconnectPacket.getCookie();
							this.server = disconnectPacket.getServer();
						}
						// Watch out for SRV_GOODBYE packet
						else if (disconnectPacket.getType() == DisconnectPacket.TYPE_SRV_GOODBYE)
							errcode = disconnectPacket.getError();
						consumed = true;
					}
				}

				if (errcode != -1) {
					int toThrow = 100;
					switch (errcode) {
						// Multiple logins
						case 0x0001:
							toThrow = 110;
							break;
						// Bad password
						case 0x0004: case 0x0005:
							toThrow = 111;
							break;
						// Non-existant UIN
						case 0x0007: case 0x0008:
							toThrow = 112;
							break;
						// Too many clients from same IP
						case 0x0015: case 0x0016:
							toThrow = 113;
							break;
						// Rate exceeded
						case 0x0018: case 0x001d:
							toThrow = 114;
							break;
					}
					throw new JimmException(toThrow, errcode);
				}

				if (consumed & (this.server != null) & (this.cookie != null)) {
					// Close connection (only if not HTTP Connection)
					if (!(Icq.c instanceof HTTPConnection))
						Icq.c.close();
					// #sijapp cond.if target is "DEFAULT" | target is "MIDP2"#
					if (Options.getBoolean(Options.OPTION_SHADOW_CON)) try
					{
						// Wait the given time before starting the
						// new connection
						Thread.sleep(2000);
					} catch (InterruptedException e) {}
					// #sijapp cond.end#
					// Open connection
					// #sijapp cond.if modules_PROXY is "true" #
					for (int i = 0; i < retry; i++)
					{
						try
						{
							// #sijapp cond.end #
							Icq.c.connect(server);
							// #sijapp cond.if modules_PROXY is "true" #
							break;

						} catch (JimmException e)
						{
							if (i >= (retry - 1) || ((this.lastActivity.getTime() + this.TIMEOUT) < System.currentTimeMillis()))
							{
								this.active = false;
								this.state = ConnectAction.STATE_ERROR;
								throw (e);
							}
							else if ((this.lastActivity.getTime() + this.TIMEOUT) > System.currentTimeMillis())
							{
								Icq.c.close();
								try
								{
									// Wait the given time
									Thread.sleep(2000);
								} catch (InterruptedException er)
								{
									// Do nothing
								}
							}
						}
					}
					// #sijapp cond.end #
					// Move to next state
					this.state = ConnectAction.STATE_CLI_DISCONNECT_SENT;
				}

			}
			// Watch out for STATE_CLI_DISCONNECT_SENT
			else if (this.state == ConnectAction.STATE_CLI_DISCONNECT_SENT)
			{

				// Watch out for SRV_HELLO packet
				if (packet instanceof ConnectPacket)
				{
					ConnectPacket connectPacket = (ConnectPacket) packet;
					if (connectPacket.getType() == ConnectPacket.SRV_CLI_HELLO)
					{

						// Send a CLI_COOKIE packet as reply
						ConnectPacket reply = new ConnectPacket(this.cookie);
						Icq.c.sendPacket(reply);

						// Send a CLI_SETUSERINFO packet
						// Set version information to this packet in our capability
						byte[] tmp = ConnectAction.CLI_SETUSERINFO_DATA;
						byte[] ver = Util.stringToByteArray("0.5.1");
						if (ver.length <=10)
							System.arraycopy(ver,0,tmp,tmp.length-11-16,ver.length);
						
						// If typing notify is on, we send full caps..with typing
						byte[] tmp_packet;
						
						//#sijapp cond.if target isnot "DEFAULT"#
						if (Options.getInt(Options.OPTION_TYPING_MODE) > 0)
						{
						    tmp_packet = tmp;
						}
						// If typing notify option is disabled,
						// We must remove typing capability
						else
						{
						//#sijapp cond.end#
							
						    tmp_packet = new byte[tmp.length-16];
						    System.arraycopy(tmp,0,tmp_packet,0,tmp.length-16);
						    
						    // Length correction
						    tmp_packet[3] = (byte)0x40;
                        //#sijapp cond.if target isnot "DEFAULT"#
						}
						//#sijapp cond.end#
					    Icq.c.sendPacket(new SnacPacket(0x0002, 0x0004, 0, new byte[0], tmp_packet));

						// Send a CLI_SETICBM packet
						SnacPacket reply1;
						
						//#sijapp cond.if target isnot "DEFAULT"#
						if (Options.getInt(Options.OPTION_TYPING_MODE) > 0)
						{
							reply1 = new SnacPacket(SnacPacket.CLI_SETICBM_FAMILY, SnacPacket.CLI_SETICBM_COMMAND, 0x00000000, new byte[0], ConnectAction.CLI_SETICBM_DATA);
						}
						else
						{
							//#sijapp cond.end#
							tmp_packet = ConnectAction.CLI_SETICBM_DATA;
							tmp_packet[5] = 0x03;
							reply1 = new SnacPacket(SnacPacket.CLI_SETICBM_FAMILY, SnacPacket.CLI_SETICBM_COMMAND, 0x00000000, new byte[0], tmp_packet);
							//#sijapp cond.if target isnot "DEFAULT"#
						}
						//#sijapp cond.end#
						Icq.c.sendPacket(reply1);

						// Send a CLI_SETSTATUS packet
						Util.putDWord(ConnectAction.CLI_SETSTATUS_DATA, 4, (0x10<<24)|Util.translateStatusSend((int)Options.getLong(Options.OPTION_ONLINE_STATUS)));
						SnacPacket reply2 = new SnacPacket(SnacPacket.CLI_SETSTATUS_FAMILY, SnacPacket.CLI_SETSTATUS_COMMAND, 0x00000000, new byte[0], ConnectAction.CLI_SETSTATUS_DATA);
						Icq.c.sendPacket(reply2);

						// Move to next state
						this.state = ConnectAction.STATE_CLI_COOKIE_SENT;

						// Packet has been consumed
						consumed = true;

					}
				}

			}
			// Watch out for STATE_CLI_COOKIE_SENT
			else if (this.state == ConnectAction.STATE_CLI_COOKIE_SENT)
			{

				// Send a CLI_REQROSTER or
				// CLI_CHECKROSTER packet
				long versionId1 = ContactList.getSsiListLastChangeTime();
				int versionId2 = ContactList.getSsiNumberOfItems();
				if (((versionId1 == -1) && (versionId2 == -1)) || (ContactList.getSize() == 0))
				{
					SnacPacket reply2 = new SnacPacket(SnacPacket.CLI_REQROSTER_FAMILY, SnacPacket.CLI_REQROSTER_COMMAND, 0x00000000, new byte[0], new byte[0]);
					Icq.c.sendPacket(reply2);
				}
				else
				{
					byte[] data = new byte[6];
					Util.putDWord(data, 0, versionId1);
					Util.putWord(data, 4, versionId2);
					SnacPacket reply2 = new SnacPacket(SnacPacket.CLI_CHECKROSTER_FAMILY, SnacPacket.CLI_CHECKROSTER_COMMAND, 0x00000000, new byte[0], data);
					Icq.c.sendPacket(reply2);
				}

				// Move to next state
				this.state = ConnectAction.STATE_CLI_CHECKROSTER_SENT;

			}
			// Watch out for STATE_CLI_CHECKROSTER_SENT
			else if (this.state == ConnectAction.STATE_CLI_CHECKROSTER_SENT)
			{

				// Watch out for SNAC packet
				if (packet instanceof SnacPacket)
				{
					SnacPacket snacPacket = (SnacPacket) packet;

					// Watch out for
					// SRV_REPLYROSTEROK
					if ((snacPacket.getFamily() == SnacPacket.SRV_REPLYROSTEROK_FAMILY) && (snacPacket.getCommand() == SnacPacket.SRV_REPLYROSTEROK_COMMAND))
					{
						this.srvReplyRosterRcvd = true;

						// Packet has been consumed
						consumed = true;

					}
					// watch out for SRV_REPLYROSTER
					// packet
					else if ((snacPacket.getFamily() == SnacPacket.SRV_REPLYROSTER_FAMILY) && (snacPacket.getCommand() == SnacPacket.SRV_REPLYROSTER_COMMAND))
					{
						if (snacPacket.getFlags() != 1) this.srvReplyRosterRcvd = true;

						// System.out.println("Flag:
						// "+snacPacket.getFlags());

						// Initialize vector for
						// items
						Vector items = new Vector();

						// Get data
						byte[] buf = snacPacket.getData();
						int marker = 0;

						// Check length
						if (buf.length < 3) { throw (new JimmException(115, 0)); }

						// Skip
						// SRV_REPLYROSTER.UNKNOWN
						marker += 1;

						// Iterate through all
						// items
						int count = Util.getWord(buf, marker);
						marker += 2;
				
						
						// System.out.println("elemente in serverlist: "+count);
						for (int i = 0; i < count; i++)
						{

							// Check length
							if (buf.length < marker + 2) { throw (new JimmException(115, 1)); }

							// Get name length
							int nameLen = Util.getWord(buf, marker);
							marker += 2;

							// Check length
							if (buf.length < marker + nameLen + 2 + 2 + 2 + 2) { throw (new JimmException(115, 2)); }

							// Get name
							String name = Util.byteArrayToString(buf, marker, nameLen, Util.isDataUTF8(buf, marker, nameLen));
							marker += nameLen;

							// Get group, id and type
							int group = Util.getWord(buf, marker);
							int id = Util.getWord(buf, marker + 2);
							int type = Util.getWord(buf, marker + 4);
							marker += 6;

							// Get length of the following TLVs
							int len = Util.getWord(buf, marker);
							marker += 2;

							// Check length
							if (buf.length < marker + len) { throw (new JimmException(115, 3)); }

							// Normal contact
							if (type == 0x0000)
							{
								// ByteArrayOutputStream serverData = new ByteArrayOutputStream();
								
								// Get nick
								String nick = new String(name);
								
								boolean noAuth = false;
								while (len > 0)
								{
									byte[] tlvData = Util.getTlv(buf, marker);
									if (tlvData == null) { throw (new JimmException(115, 4)); }
									int tlvType = Util.getWord(buf, marker);
									if (tlvType == 0x0131)
									{
										nick = Util.byteArrayToString(tlvData, true);
									}
									else if (tlvType == 0x0066)
									{
										noAuth = true;
									}
									
									//else if (tlvType == 0x006D) /* Server-side additional data */
									//{
									//	Util.writeWord(serverData, tlvType, true);
									//	Util.writeWord(serverData, tlvData.length, true);
									//	Util.writeByteArray(serverData, tlvData);
									//	
									//	Util.showBytes(serverData.toByteArray());
									//}
									
									len -= 4;
									len -= tlvData.length;
									marker += 4 + tlvData.length;
								}
								if (len != 0) { throw (new JimmException(115, 5)); }

								// Add this contact item to the vector
								try
								{
									ContactListContactItem item = new ContactListContactItem(id, group, name, nick, noAuth, true);
									// if (serverData.size() != 0) item.ssData = serverData.toByteArray();
									items.addElement(item);
								}
								catch (Exception e)
								{
									// Contact with wrong uin was received  
								}

							}
							// Group of contacts
							else if (type == 0x0001)
							{
								//System.out.println("g "+i+": "+name);
								// Skip TLVs
								marker += len;

								// Add this group item to the vector
								if (group != 0x0000)
								{
									items.addElement(new ContactListGroupItem(group, name));
								}

							}
							// My visibility settings
							else if (type == 0x0004)
							{
								while (len > 0)
								{
									byte[] tlvData = Util.getTlv(buf, marker);
									if (tlvData == null) { throw (new JimmException(115, 110)); }
									int tlvType = Util.getWord(buf, marker);

									if (tlvType == 0x00CA)
									{
										Options.setInt(Options.OPTION_VISIBILITY_ID, (int)id);
									}

									len -= 4;
									len -= tlvData.length;
									marker += 4 + tlvData.length;
								}
								if (len != 0) { throw (new JimmException(115, 111)); }
							}
							// All other item types
							else
							{
								//System.out.println("x "+i+": ");
								// Skip TLVs
								marker += len;

							}

						}

						// Check length
						if (buf.length != marker + 4) { throw (new JimmException(115, 6)); }

						// Get timestamp
						int timestamp = (int)Util.getDWord(buf, marker);

						// Update contact list
						ContactListItem[] itemsAsArray = new ContactListItem[items.size()];
						items.copyInto(itemsAsArray);
						ContactList.update(snacPacket.getFlags(), timestamp, count, itemsAsArray);

						// Packet has been consumed
						consumed = true;

					}

					// Check if all required packets have been received
					if (this.srvReplyRosterRcvd)
					{

						// Send a CLI_ROSTERACK packet
						SnacPacket reply1 = new SnacPacket(SnacPacket.CLI_ROSTERACK_FAMILY, SnacPacket.CLI_ROSTERACK_COMMAND, 0x00000000, new byte[0], new byte[0]);
						Icq.c.sendPacket(reply1);

						int onlineStatusOpt = (int)Options.getLong(Options.OPTION_ONLINE_STATUS);
						int onlineStatus = Util.translateStatusSend(onlineStatusOpt);
						int visibilityItemId = Options.getInt(Options.OPTION_VISIBILITY_ID);
						byte[] buf = new byte[15];
						byte bCode = 0;
						if(visibilityItemId != 0)
						{
							// Build packet for privacy setting changing
							int marker = 0;

							if(onlineStatus == Util.SET_STATUS_INVISIBLE)
								bCode = (onlineStatusOpt == ContactList.STATUS_INVIS_ALL)?(byte)2:(byte)3;
							else
								bCode = (byte)4;

							Util.putWord(buf, marker,    0); marker += 2; // name (null)
							Util.putWord(buf, marker,    0); marker += 2; // GroupID
							Util.putWord(buf, marker,  visibilityItemId); marker += 2; // EntryID
							Util.putWord(buf, marker,    4); marker += 2; // EntryType
							Util.putWord(buf, marker,    5); marker += 2; // Length in bytes of following TLV
							Util.putWord(buf, marker, 0xCA); marker += 2; // TLV Type
							Util.putWord(buf, marker,    1); marker += 2; // TLV Length
							Util.putByte(buf, marker,bCode);              // TLV Value

							// Change privacy setting according to new status
							if(onlineStatus == Util.SET_STATUS_INVISIBLE)
							{
								SnacPacket reply2pre = new SnacPacket(SnacPacket.CLI_ROSTERUPDATE_FAMILY,
														   SnacPacket.CLI_ROSTERUPDATE_COMMAND,
														   SnacPacket.CLI_ROSTERUPDATE_COMMAND,
														   new byte[0],
														   buf);
								Icq.c.sendPacket(reply2pre);
							}
						}

						// Send to server sequence of unuthoruzed contacts to see their statuses 
						String[] noauth = ContactList.getUnauthAndTempContacts();
						if (noauth.length > 0) Icq.addLocalContacts(noauth);

						// Change privacy setting according to new status
						if(visibilityItemId != 0 && onlineStatus != Util.SET_STATUS_INVISIBLE)
						{
							SnacPacket reply2post = new SnacPacket(SnacPacket.CLI_ROSTERUPDATE_FAMILY,
														SnacPacket.CLI_ROSTERUPDATE_COMMAND,
														SnacPacket.CLI_ROSTERUPDATE_COMMAND,
														new byte[0],
														buf);
							Icq.c.sendPacket(reply2post);
						}

						// Send a CLI_READY packet
						SnacPacket reply2 = new SnacPacket(SnacPacket.CLI_READY_FAMILY, SnacPacket.CLI_READY_COMMAND, 0x00000000, new byte[0], ConnectAction.CLI_READY_DATA);
						Icq.c.sendPacket(reply2);

						// Send a CLI_TOICQSRV/CLI_REQOFFLINEMSGS packet
						ToIcqSrvPacket reply3 = new ToIcqSrvPacket(0x00000000, this.uin, ToIcqSrvPacket.CLI_REQOFFLINEMSGS_SUBCMD, new byte[0], new byte[0]);
						Icq.c.sendPacket(reply3);

						// Move to next state
						this.state = ConnectAction.STATE_CLI_REQOFFLINEMSGS_SENT;

					}

				}

			}
			// Watch out for STATE_CLI_REQOFFLINEMSGS_SENT
			else if (this.state == ConnectAction.STATE_CLI_REQOFFLINEMSGS_SENT)
			{

				if (packet instanceof FromIcqSrvPacket)
				{
					FromIcqSrvPacket fromIcqSrvPacket = (FromIcqSrvPacket) packet;

					// Watch out for SRV_OFFLINEMSG
					if (fromIcqSrvPacket.getSubcommand() == FromIcqSrvPacket.SRV_OFFLINEMSG_SUBCMD)
					{

						// Get raw data
						byte[] buf = fromIcqSrvPacket.getData();

						// Check length
						if (buf.length < 14) return false;
							

						// Extract UIN
						long uinRaw = Util.getDWord(buf, 0, false);

						String uin = String.valueOf(uinRaw);
						
						// Extract date of dispatch
						long date = Util.createLongTime
									  (
											Util.getWord(buf, 4, false),
											Util.getByte(buf, 6),
											Util.getByte(buf, 7),
											Util.getByte(buf, 8),
											Util.getByte(buf, 9),
											0
									   );
						
						//System.out.println("Offline mess:");
						//System.out.println("hour="+(int)Util.getByte(buf, 8));
						//System.out.println("min="+(int)Util.getByte(buf, 9));
						//System.out.println();

						// Get type
						int type = Util.getWord(buf, 10, false);

						// Get text length
						int textLen = Util.getWord(buf, 12, false);

						// Check length
						if (buf.length != 14 + textLen) { throw (new JimmException(116, 1)); }

						// Get text
						String text = Util.removeCr(Util.byteArrayToString(buf, 14, textLen, Util.isDataUTF8(buf, 14, textLen)));

						// Normal message
						if (type == 0x0001)
						{
							 // Forward message to contact list
							System.out.println("bef");
							PlainMessage message = new PlainMessage(uin, this.uin, Util.gmtTimeToLocalTime(date), text, true);
							System.out.println("aft");
							RunnableImpl.addMessageSerially(message);
						}
						// URL message
						else if (type == 0x0004)
						{

							// Search for delimiter
							int delim = text.indexOf(0xFE);

							// Split message, if delimiter could be found
							String urlText;
							String url;
							if (delim != -1)
							{
								urlText = text.substring(0, delim);
								url = text.substring(delim + 1);
							}
							else
							{
								urlText = text;
								url = "";
							}

							// Forward message message to contact list
							UrlMessage message = new UrlMessage(uin, this.uin, Util.gmtTimeToLocalTime(date), url, urlText);
							RunnableImpl.addMessageSerially(message);
						}

						// Packet has been consumed
						consumed = true;

					}
					// Watch out for SRV_DONEOFFLINEMSGS
					else if (fromIcqSrvPacket.getSubcommand() == FromIcqSrvPacket.SRV_DONEOFFLINEMSGS_SUBCMD)
					{

						// Send a CLI_TOICQSRV/CLI_ACKOFFLINEMSGS packet
						ToIcqSrvPacket reply = new ToIcqSrvPacket(0x00000000, this.uin, ToIcqSrvPacket.CLI_ACKOFFLINEMSGS_SUBCMD, new byte[0], new byte[0]);
						Icq.c.sendPacket(reply);

						// Move to next state
						this.state = ConnectAction.STATE_CLI_ACKOFFLINEMSGS_SENT;

						// Move to STATE_CONNECTED
						Icq.setConnected();

						// Packet has been consumed
						consumed = true;

					}

				}

			}

            // Update activity timestamp and reset activity flag
            this.lastActivity = new Date();
            this.active = false;

            // Return consumption flag
            return (consumed);

        }
        // Catch JimmExceptions
        catch (JimmException e)
        {

            // Update activity timestamp and reset activity flag
            this.lastActivity = new Date();
            this.active = false;

            // Set error state if exception is critical
            if (e.isCritical()) this.state = ConnectAction.STATE_ERROR;

            // Forward exception
            throw (e);

        }

    }

    // Returns true if the action is completed
    public boolean isCompleted()
    {
        return (this.state == ConnectAction.STATE_CLI_ACKOFFLINEMSGS_SENT);
    }

    // Returns true if an error has occured
    public boolean isError()
    {
    	if ((this.state != ConnectAction.STATE_ERROR) && !this.active && (this.lastActivity.getTime() + this.TIMEOUT < System.currentTimeMillis()))
        {
            JimmException.handleException(new JimmException(118, 0));
            this.state = ConnectAction.STATE_ERROR;
        }
        return (this.state == ConnectAction.STATE_ERROR);
    }

    // Returns a number between 0 and 100 (inclusive) which indicates the current progress
    public int getProgress()
    {
        switch (this.state)
        {
        case ConnectAction.STATE_INIT_DONE:
            return 12;
		case STATE_AUTHKEY_REQUESTED:
			return 25;
        case ConnectAction.STATE_CLI_IDENT_SENT:
            return 37;
        case ConnectAction.STATE_CLI_DISCONNECT_SENT:
            return 50;
        case ConnectAction.STATE_CLI_COOKIE_SENT:
            return 62;
        case ConnectAction.STATE_CLI_CHECKROSTER_SENT:
            return 75;
        case ConnectAction.STATE_CLI_REQOFFLINEMSGS_SENT:
            return 87;
        case ConnectAction.STATE_CLI_ACKOFFLINEMSGS_SENT:
            return 100;
        default:
            return (0);
        }
    }
    
    public void onEvent(int eventType)
    {
    	switch (eventType)
    	{
    	case ON_COMPLETE:
    		ContactList.activate();
    		break;
    	}
    }

}