package it.gotoandplay.smartfoxclient;

import it.gotoandplay.smartfoxclient.data.Buddy;
import it.gotoandplay.smartfoxclient.data.Room;
import it.gotoandplay.smartfoxclient.data.RoomVariableRequest;
import it.gotoandplay.smartfoxclient.data.SFSObject;
import it.gotoandplay.smartfoxclient.data.User;
import it.gotoandplay.smartfoxclient.data.SFSVariable;
import it.gotoandplay.smartfoxclient.handlers.ExtHandler;
import it.gotoandplay.smartfoxclient.handlers.IMessageHandler;
import it.gotoandplay.smartfoxclient.handlers.SysHandler;
import it.gotoandplay.smartfoxclient.http.HttpConnection;
import it.gotoandplay.smartfoxclient.http.HttpEvent;
import it.gotoandplay.smartfoxclient.util.Entities;
import it.gotoandplay.smartfoxclient.util.SFSObjectSerializer;
import it.gotoandplay.utils.net.xmlsocket.IXMLSocketEventHandler;
import it.gotoandplay.utils.net.xmlsocket.XMLSocket;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.n3.nanoxml.IXMLElement;
import net.n3.nanoxml.IXMLParser;
import net.n3.nanoxml.IXMLReader;
import net.n3.nanoxml.StdXMLReader;
import net.n3.nanoxml.XMLException;
import net.n3.nanoxml.XMLParserFactory;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * <p>
 * SmartFoxClient is the main class in the SmartFoxServer API.
 * This class is responsible for connecting to the server and handling all related events.
 * </p>
 * 
 * <p>
 * <b>NOTE</b>: in the provided examples, {@code smartFox} always indicates a SmartFoxClient instance.
 * </p>
 * 
 * @see	SFSEvent#onAdminMessage
 * @see	SFSEvent#onDebugMessage
 * @see	SFSEvent#onExtensionResponse
 * @see	SFSEvent#onRoomDeleted
 * @see	SFSEvent#onUserEnterRoom
 * @see	SFSEvent#onUserLeaveRoom
 * @see	SFSEvent#onUserCountChange
 * 
 * @version 1.5.8
 * 
 * @author The gotoAndPlay() Team<br>
 *         <a href="http://www.smartfoxserver.com">http://www.smartfoxserver.com</a><br>
 *         <a href="http://www.gotoandplay.it">http://www.gotoandplay.it</a><br>
 */
public class SmartFoxClient extends SFSEventDispatcher implements IXMLSocketEventHandler, ISFSEventListener
{
    // -------------------------------------------------------
    // Constants
    // -------------------------------------------------------

    private static final char MSG_XML = '<';
    private static final char MSG_JSON = '{';
    private static char MSG_STR = '%';
    private static String blueBoxSeparator = "\n";
    
    private static final String MESSAGE_HEADER_SYSTEM = "sys";
    private static final String MESSAGE_HEADER_EXTENSION = "xt";

    /**
     * Moderator message type: "to user".
     * <p>
     * The Moderator message is sent to a single user.
     * </p>
     * 
     * @see #sendModeratorMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public static final String MODMSG_TO_USER = "u";

    /**
     * Moderator message type: "to room".
     * <p>
     * The Moderator message is sent to all the users in a room.
     * </p>
     * 
     * @see #sendModeratorMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public static final String MODMSG_TO_ROOM = "r";

    /**
     * Moderator message type: "to zone".
     * <p>
     * The Moderator message is sent to all the users in a zone.
     * </p>
     * 
     * @see #sendModeratorMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public static final String MODMSG_TO_ZONE = "z";

    /**
     * Server-side extension request/response protocol: XML.
     * 
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public static final String XTMSG_TYPE_XML = "xml";

    /**
     * Server-side extension request/response protocol: String (aka "raw protocol").
     * 
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public static final String XTMSG_TYPE_STR = "str";

    /**
     * Server-side extension request/response protocol: JSON.
     * 
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public static final String XTMSG_TYPE_JSON = "json";

    /**
     * Connection mode: "disconnected".
     * <p>
     * The client is currently disconnected from SmartFoxServer.
     * </p>
     * 
     * @see #getConnectionMode
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public static final String CONNECTION_MODE_DISCONNECTED = "disconnected";

    /**
     * Connection mode: "socket".
     * <p>
     * The client is currently connected to SmartFoxServer via socket.
     * </p>
     * 
     * @see #getConnectionMode
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public static final String CONNECTION_MODE_SOCKET = "socket";

    /**
     * Connection mode: "http".
     * <p>
     * The client is currently connected to SmartFoxServer via http.
     * </p>
     * 
     * @see #getConnectionMode
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public static final String CONNECTION_MODE_HTTP = "http";

    // -------------------------------------------------------
    // Properties
    // -------------------------------------------------------

    private Map<Integer, Room> roomList;
    private volatile boolean connected;
    private long benchStartTime;

    private SysHandler sysHandler;
    private ExtHandler extHandler;

    private int majVersion;
    private int minVersion;
    private int subVersion;

    private Map<String, IMessageHandler> messageHandlers;
    private XMLSocket socketConnection;

    private boolean debug;
    private Logger logger;


    /**
     * The SmartFoxServer IP address.
     * 
     * @see #connect
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public volatile String ipAddress;

    /**
     * The SmartFoxServer connection port.
     * The default port is <b>9339</b>.
     * 
     * @see #connect
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public volatile int port = 9339;

    /**
     * The default login zone.
     * 
     * @see #loadConfig
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public volatile String defaultZone;

    //--- BlueBox settings (start) ---------------------------------------------------------------------

    private volatile boolean isHttpMode = false;                // connection mode
    private HttpConnection httpConnection;             // the http connection

    /**
     * The BlueBox IP address.
     * 
     * @see #smartConnect
     * @see #loadConfig
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public volatile String blueBoxIpAddress;

    /**
     * The BlueBox connection port.
     * 
     * @see #smartConnect
     * @see #loadConfig
     * 
     * @since	SmartFoxServer Pro v1.6.0
     */
    public volatile int blueBoxPort;

    /**
     * A boolean flag indicating if the BlueBox http connection should be used
     * in case a socket connection is not available.
     * <p>
     * The default value is {@code true}.
     * </p>
     * 
     * @see #loadConfig
     * 
     * @since	SmartFoxServer Pro v1.6.0
     */
    public volatile boolean smartConnect = true;

    //--- BlueBox settings (end) ---------------------------------------------------------------------

    /**
     * A {@code List} containing {@link Buddy} objects representing each buddy of the user's buddy list.
     * <p>
     * Specific buddy can be retrieved by means of the {@link #getBuddyById} and
     * {@link #getBuddyByName} methods.
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: This property and all the buddy-related method are available only
     * if the buddy list feature is enabled for the current zone.
     * Check the SmartFoxServer server-side configuration.
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the properties of each buddy in the buddy list.
     * <pre>
     * for(Buddy buddy : smartFox.buddyList)
     * {
     * 
     *     // Trace buddy properties
     *     System.out.println("Buddy id: " + buddy.getId());
     *     System.out.println("Buddy name: " + buddy.getName());
     *     System.out.println("Is buddy online? " + (buddy.isOnline() ? "Yes" : "No"));
     *     System.out.println("Is buddy blocked? " + (buddy.isBlocked() ? "Yes" : "No"));
     * 
     *     // Trace all Buddy Variables
     *     for(String key : buddy.getVariables().keySet())
     *     {
     *         System.out.println("\t" + key + " --> " + buddy.getVariables().get(key));
     *     }
     * }
     * </pre>
     * </p>
     * 
     * @see Buddy
     * @see #myBuddyVars
     * @see #loadBuddyList
     * @see #getBuddyById
     * @see #getBuddyByName
     * @see #removeBuddy
     * @see #setBuddyBlockStatus
     * @see #setBuddyVariables
     * @see SFSEvent#onBuddyList
     * @see SFSEvent#onBuddyListUpdate
     * 
     * @since	SmartFoxServer Basic (except block status) / Pro v1.x.x
     */
    public volatile List<Buddy> buddyList;

    /**
     * The current user's Buddy Variables.
     * <p>
     * This is a {@code Map} containing the current user's properties when
     * he/she is present in the buddy lists of other users.
     * See the {@link #setBuddyVariables} method for more details.
     * </p>
     * 
     * <p>
     * The following example shows how to read the current user's own Buddy Variables.
     * <pre>
     * for(String key : smartFox.myBuddyVars.keySet())
     * {
     *     System.out.println("Variable" + key + " --> " + smartFox.myBuddyVars.get(key));
     * }
     * </pre>
     * </p>
     * 
     * @see #setBuddyVariables
     * @see #getBuddyById
     * @see #getBuddyByName
     * @see SFSEvent#onBuddyList
     * @see SFSEvent#onBuddyListUpdate
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public volatile Map<String, String> myBuddyVars;
    
    /**
     * The current user's SmartFoxServer id.
     * <p>
     * The id is assigned to a user on the server-side as soon as the client
     * connects to SmartFoxServer successfully.
     * </p>
     * 
     * <p>
     * <b>NOTE:</b> client-side, the <b>myUserId</b> property is available only
     * after a successful login is performed using the default login procedure.
     * If a custom login process is implemented, this property must be manually
     * set after the successful login!
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the user's own SmartFoxServer id.
     * <pre>
     * System.out.println("My user ID is: " + smartFox.myUserId);
     * </pre>
     * </p>
     * 
     * @see #myUserName
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public volatile int myUserId;

    /**
     * The current user's SmartFoxServer username.
     * 
     * <p>
     * <b>NOTE</b>: client-side, the <b>myUserName</b> property is available only
     * after a successful login is performed using the default login procedure.
     * If a custom login process is implemented, this property must be manually set
     * after the successful login!
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the user's own SmartFoxServer username.
     * <pre>
     * System.out.println("I logged in as: " + smartFox.myUserName);
     * </pre>
     * </p>
     * 
     * @see #myUserId
     * @see #login
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public volatile String myUserName;

    /**
     * The current user's id as a player in a game room.
     * <p>
     * The <b>playerId</b> is available only after the user successfully joined a game room.
     * This id is 1-based (player 1, player 2, etc.), but if the user is a spectator or
     * the room is not a game room, its value is -1.
     * </p>
     * <p>
     * When a user joins a game room, a player id (or "slot") is assigned to him/her,
     * based on the slots available in the room at the moment in which the user entered it;
     * for example:
     * <ul>
     * 	<li>
     *      in a game room for 2 players, the first user who joins it becomes player one (playerId = 1) and
     *      the second user becomes player two (player = 2);
     *  </li>
     * 	<li>
     *      in a game room for 4 players where only player three is missing,
     *      the next user who will join the room will be player three (playerId = 3);
     *  </li>
     * </ul>
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: if multi-room join is allowed, this property contains only
     * the last player id assigned to the user, and so it's useless.
     * In this case the {@link Room#getMyPlayerIndex} method should be used to
     * retrieve the player id for each joined room.
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the user's own player id.
     * <pre>
     * System.out.println("I'm player " + smartFox.playerId);
     * </pre>
     * </p>
     * 
     * @see Room#getMyPlayerIndex
     * @see Room#isGame
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public volatile int playerId;

    /**
     * A boolean flag indicating if the user is recognized as Moderator.
     * 
     * <p>
     * The following example shows how to check if the current user is
     * a Moderator in the current SmartFoxServer zone.
     * <pre>
     * if(smartfox.amIModerator)
     *     System.out.println("I'm a Moderator in this zone");
     * else
     *     System.out.println("I'm a standard user");
     * </pre>
     * </p>
     * 
     * @see #sendModeratorMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public volatile boolean amIModerator;

    /**
     * The property stores the id of the last room joined by the current user.
     * <p>
     * In most multiuser applications users can join one room at a time:
     * in this case this property represents the id of the current room.
     * If multi-room join is allowed, the application should track
     * the various id(s) and this property should be ignored.
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the current room object
     * (as an alternative to the {@link #getActiveRoom} method).
     * <pre>
     * Room room = smartFox.getRoom(smartFox.activeRoomId);
     * System.out.println("Current room is: " + room.getName());
     * </pre>
     * </p>
     * 
     * @see #getActiveRoom
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public volatile int activeRoomId = -1;

    /*
     * A boolean flag indicating if the process of joining a new room is in progress.
     */
    public volatile boolean changingRoom;

    /**
     * The TCP port used by the embedded webserver.
     * <p>
     * The default port is <b>8080</b>; if the webserver is listening on a different port number,
     * this property should be set to that value.
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the webserver's current http port.
     * <pre>
     * System.out.println("HTTP port is: " + smartfox.httpPort);
     * </pre>
     * </p>
     * 
     * @since SmartFoxServer Pro v1.5.0
     */
    public volatile int httpPort = 8080;

    /**
     * Returns the character used as separator for the String (raw) protocol.
     * 
     * @return  The character used as separator for the String (raw) protocol.
     * 
     * @see #XTMSG_TYPE_STR
     * @see #setRawProtocolSeparator
     * @see #sendXtMessage
     * 
     * @since	SmartFoxServer Pro v1.5.5
     */
    public char getRawProtocolSeparator()
    {
        return MSG_STR;
    }

    /**
     * Sete the character used as separator for the String (raw) protocol.
     * <p>
     * The default value is <b>%</b> (percentage character).
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: this separator must match the one set in the SmartFoxServer
     * server-side configuration file through the {@code <RawProtocolSeparator>} parameter.
     * </p>
     * 
     * <p>
     * The following example shows how to set the raw protocol separator.
     * <pre>
     * smartFox.setRawProtocolSeparator('|');
     * </pre>
     * </p>
     * 
     * @param value the character used as separator for the String (raw) protocol.
     * 
     * @see #XTMSG_TYPE_STR
     * @see #sendXtMessage
     * 
     * @since SmartFoxServer Pro v1.5.5
     */
    public void setRawProtocolSeparator(char value)
    {
        if (value != '<' && value != '{')
        {
            MSG_STR = value;
        }
    }

    /**
     * Returns {@code true} if the current user is connected to the server,
     * {@code false} if the current user is not connected to the server.
     * 
     * <p>
     * The following example shows how to check the connection status.
     * <pre>
     * System.out.println("My connection status: " + (smartFox.isConnected() ? "connected" : "not connected"));
     * </pre>
     * </p>

     * @return {@code true} if the current user is connected to the server,
     *         {@code false} if the current user is not connected to the server.
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public boolean isConnected()
    {
        return this.connected;
    }

    /**
     * Sets the {@code connected} flag.
     * 
     * @param connected {@code true} if the current user is connected to the server,
     *                  {@code false} if the current user is not connected to the server. 
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void setConnected(boolean connected)
    {
        this.connected = connected;
    }

    /**
     * Returns the minimum interval between two polling requests when connecting to
     * SmartFoxServer via BlueBox module.
     * 
     * @return The minimum interval between two polling requests when connecting to
     *         SmartFoxServer via BlueBox module.
     * 
     * @see #smartConnect
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public int getHttpPollSpeed()
    {
        return this.httpConnection.getHttpPollSpeed();
    }

    /**
     * Sets the minimum interval between two polling requests when connecting to
     * SmartFoxServer via BlueBox module.
     * <p>
     * The default value is 750 milliseconds.
     * Accepted values are between 0 and 10000 milliseconds (10 seconds).
     * </p>
     * 
     * <p>
     * The following example shows how to set the polling speed.
     * <pre>
     * smartFox.setHttpPollSpeed(200);
     * </pre>
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: <i>Which is the optimal value for polling speed?</i><br>
     * A value between 750-1000 ms is very good for chats, turn-based games and
     * similar kind of applications. It adds minimum lag to the client responsiveness and
     * it keeps the server CPU usage low. Lower values (200-500 ms) can be used where
     * a faster responsiveness is necessary. For super fast real-time games values
     * between 50 ms and 100 ms can be tried. With settings &lt; 200 ms the CPU usage
     * will grow significantly as the http connection and packet wrapping/unwrapping
     * is more expensive than using a persistent connection. Using values below 50 ms is not recommended.
     * </p>
     * 
     * @param pollSpeed The minimum interval between two polling requests
     * 
     * @see #smartConnect
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public void setHttpPollSpeed(int pollSpeed)
    {
        this.httpConnection.setHttpPollSpeed(pollSpeed);
    }

    // -------------------------------------------------------
    // Constructor
    // -------------------------------------------------------

    /**
     * The SmartFoxClient contructor.
     * 
     * @param debug turn on the debug messages.
     */
    public SmartFoxClient(boolean debug)
    {
        this.debug = debug;
        logger = Logger.getLogger(SmartFoxClient.class.getName());
        logger.setLevel(Level.INFO);
        // Initialize properties 
        this.majVersion = 1;
        this.minVersion = 5;
        this.subVersion = 8;

        this.activeRoomId = -1;

        this.messageHandlers = new HashMap<String, IMessageHandler>();
        setupMessageHandlers();

        // Initialize socket object
        socketConnection = new XMLSocket();
        socketConnection.setEventHandler(this);

        // Initialize HttpConnection
        httpConnection = new HttpConnection();
        httpConnection.addEventListener(HttpEvent.onHttpConnect, this);
        httpConnection.addEventListener(HttpEvent.onHttpClose, this);
        httpConnection.addEventListener(HttpEvent.onHttpData, this);
        httpConnection.addEventListener(HttpEvent.onHttpError, this);
    }

    // -------------------------------------------------------
    // Public methods
    // -------------------------------------------------------

    /**
     * Return {@code true} if the debug messages are enabled,
     * otherwise returns {@code false}.
     * 
     * @return {@code true} if the debug messages are enabled, otherwise {@code false}.
     */
    public boolean isDebug()
    {
        return this.debug;
    }
    
    /**
     * Toggle the client-side debugging informations.
     * <p>
     * When turned on, the developer is able to inspect all server messages that are sent and received by the
     * client in the log output.
     * This allows a better debugging of the interaction with the server during application developement.
     * </p>
     * 
     * <p>
     * The following example shows how to turn on SmartFoxServer API debugging.
     * <pre>
     * SmartFoxClient smartFox = new SmartFoxClient(false);
     * if(runningLocally)
     * {
     *     smartFox.setDebug(true);
     *     ip = "127.0.0.1";
     *     port = 9339;
     * }
     * else
     * {
     *     ip = "100.101.102.103";
     *     port = 9333;
     * }
     * smartFox.connect(ip, port);
     * </pre>
     * </p>
     * 
     * @see SFSEvent#onDebugMessage
     */
    public void setDebug(boolean debug)
    {
        this.debug = debug;
    }
    
    /**
     * Returns the {@code Logger} used to log the SmatFoxCLient debug messages.
     * 
     * @return the {@code Logger} used to log the SmatFoxCLient debug messages.
     * 
     * @see #setDebug(boolean)
     */
    public Logger getLogger()
    {
        return logger;
    }

    /**
     * Load a client configuration file.
     * <p>
     * The SmartFoxClient instance can be configured through an external
     * xml configuration file loaded at run-time. On loading completion the {@link #connect} method
     * is automatically called by the API.
     * In case of loading error, the {@link SFSEvent#onConfigLoadFailure} event is fired.
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: the SmartFoxClient configuration file (client-side)
     * should not be confused with the SmartFoxServer configuration file (server-side).
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: The external xml configuration file has the following structure:
     * ip and zone parameters are mandatory, all other parameters are optional.
     * <pre>
     * &lt;SmartFoxClient&gt;
     *     &lt;ip&gt;127.0.0.1&lt;/ip&gt;
     *     &lt;port&gt;9339&lt;/port&gt;
     *     &lt;zone&gt;simpleChat&lt;/zone&gt;
     *     &lt;debug&gt;true&lt;/debug&gt;
     *     &lt;blueBoxIpAddress&gt;127.0.0.1&lt;/blueBoxIpAddress&gt;
     *     &lt;blueBoxPort&gt;9339&lt;/blueBoxPort&gt;
     *     &lt;smartConnect&gt;true&lt;/smartConnect&gt;
     *     &lt;httpPort&gt;8080&lt;/httpPort&gt;
     *     &lt;httpPollSpeed&gt;750&lt;/httpPollSpeed&gt;
     *     &lt;rawProtocolSeparator&gt;%&lt;/rawProtocolSeparator&gt;
     * &lt;/SmartFoxClient&gt;
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to load an external configuration file.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onConfigLoadFailure, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("Failed loading config file: " +
     *             evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.loadConfig("testEnvironmentConfig.xml");
     * </pre>
     * </p>
     * 
     * @param configFileName external xml configuration file name.
     * 
     * @see #ipAddress
     * @see #port
     * @see #defaultZone
     * @see #debug
     * @see #blueBoxIpAddress
     * @see #blueBoxPort
     * @see #smartConnect
     * @see SFSEvent#onConfigLoadFailure
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public void loadConfig(String configFileName)
    {
        loadConfig(configFileName, true);
    }

    /**
     * Load a client configuration file.
     * <p>
     * The SmartFoxClient instance can be configured through an external xml configuration file
     * loaded at run-time. If the <i>autoConnect</i> parameter is set to {@code true},
     * on loading completion the {@link #connect} method is automatically called by the API,
     * otherwise the {@link SFSEvent#onConfigLoadSuccess} event is dispatched.
     * In case of loading error, the {@link SFSEvent#onConfigLoadFailure} event is fired.
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: the SmartFoxClient configuration file (client-side) should not be confused with
     * the SmartFoxServer configuration file (server-side).
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: The external xml configuration file has the following structure:
     * ip and zone parameters are mandatory, all other parameters are optional.
     * <pre>
     * &lt;SmartFoxClient&gt;
     *     &lt;ip&gt;127.0.0.1&lt;/ip&gt;
     *     &lt;port&gt;9339&lt;/port&gt;
     *     &lt;zone&gt;simpleChat&lt;/zone&gt;
     *     &lt;debug&gt;true&lt;/debug&gt;
     *     &lt;blueBoxIpAddress&gt;127.0.0.1&lt;/blueBoxIpAddress&gt;
     *     &lt;blueBoxPort&gt;9339&lt;/blueBoxPort&gt;
     *     &lt;smartConnect&gt;true&lt;/smartConnect&gt;
     *     &lt;httpPort&gt;8080&lt;/httpPort&gt;
     *     &lt;httpPollSpeed&gt;750&lt;/httpPollSpeed&gt;
     *     &lt;rawProtocolSeparator&gt;%&lt;/rawProtocolSeparator&gt;
     * &lt;/SmartFoxClient&gt;
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to load an external configuration file.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onConfigLoadSuccess, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           smartFox.connect(smartFox.ipAddress, smartFox.port);
     *       }
     *   });
     * smartFox.addEventListener(SFSEvent.onConfigLoadFailure, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("Failed loading config file: " +
     *             evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.loadConfig("testEnvironmentConfig.xml", false);
     * </pre>
     * </p>
     * 
     * @param configFileName external xml configuration file name.
     * @param autoConnect a boolean flag indicating if the connection to SmartFoxServer must be attempted upon configuration loading completion.
     * 
     * @see #ipAddress
     * @see #port
     * @see #defaultZone
     * @see #debug
     * @see #blueBoxIpAddress
     * @see #blueBoxPort
     * @see #smartConnect
     * @see #httpPort
     * @see SFSEvent#onConfigLoadSuccess
     * @see SFSEvent#onConfigLoadFailure
     * 
     * @since	SmartFoxServer Pro v1.6.0
     */
    public void loadConfig(String configFileName, boolean autoConnect)
    {
        try
        {
            IXMLParser configParser = XMLParserFactory.createDefaultXMLParser();
            IXMLReader configReader = StdXMLReader.fileReader(configFileName);
            configParser.setReader(configReader);
            parseConfig((IXMLElement) configParser.parse(), autoConnect);
        }
        catch(XMLException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
        catch(FileNotFoundException ex)
        {
            onConfigLoadFailure("Error reading config XML file: ", ex);
        }
        catch(IOException ex)
        {
            onConfigLoadFailure("Error reading config XML file: ", ex);
        }
        catch(ClassNotFoundException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
        catch(InstantiationException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
        catch(IllegalAccessException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
    }
    
    /**
     * Load a client configuration file.
     * <p>
     * The SmartFoxClient instance can be configured through an external
     * xml configuration file loaded at run-time. On loading completion the {@link #connect} method
     * is automatically called by the API.
     * In case of loading error, the {@link SFSEvent#onConfigLoadFailure} event is fired.
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: the SmartFoxClient configuration file (client-side)
     * should not be confused with the SmartFoxServer configuration file (server-side).
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: The external xml configuration file has the following structure:
     * ip and zone parameters are mandatory, all other parameters are optional.
     * <pre>
     * &lt;SmartFoxClient&gt;
     *     &lt;ip&gt;127.0.0.1&lt;/ip&gt;
     *     &lt;port&gt;9339&lt;/port&gt;
     *     &lt;zone&gt;simpleChat&lt;/zone&gt;
     *     &lt;debug&gt;true&lt;/debug&gt;
     *     &lt;blueBoxIpAddress&gt;127.0.0.1&lt;/blueBoxIpAddress&gt;
     *     &lt;blueBoxPort&gt;9339&lt;/blueBoxPort&gt;
     *     &lt;smartConnect&gt;true&lt;/smartConnect&gt;
     *     &lt;httpPort&gt;8080&lt;/httpPort&gt;
     *     &lt;httpPollSpeed&gt;750&lt;/httpPollSpeed&gt;
     *     &lt;rawProtocolSeparator&gt;%&lt;/rawProtocolSeparator&gt;
     * &lt;/SmartFoxClient&gt;
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to load an external configuration file.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onConfigLoadFailure, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("Failed loading config file: " +
     *             evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.loadConfig(this.getClass().getClassLoader().getResource("config.xml"));
     * </pre>
     * </p>
     * 
     * @param configFileUrl external xml configuration file URL.
     * 
     * @see #ipAddress
     * @see #port
     * @see #defaultZone
     * @see #debug
     * @see #blueBoxIpAddress
     * @see #blueBoxPort
     * @see #smartConnect
     * @see #httpPort
     * @see SFSEvent#onConfigLoadFailure
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public void loadConfig(URL configFileUrl)
    {
        loadConfig(configFileUrl, true);
    }

    /**
     * Load a client configuration file.
     * <p>
     * The SmartFoxClient instance can be configured through an external xml configuration file
     * loaded at run-time. If the <i>autoConnect</i> parameter is set to {@code true},
     * on loading completion the {@link #connect} method is automatically called by the API,
     * otherwise the {@link SFSEvent#onConfigLoadSuccess} event is dispatched.
     * In case of loading error, the {@link SFSEvent#onConfigLoadFailure} event is fired.
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: the SmartFoxClient configuration file (client-side) should not be confused with
     * the SmartFoxServer configuration file (server-side).
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: The external xml configuration file has the following structure:
     * ip and zone parameters are mandatory, all other parameters are optional.
     * <pre>
     * &lt;SmartFoxClient&gt;
     *     &lt;ip&gt;127.0.0.1&lt;/ip&gt;
     *     &lt;port&gt;9339&lt;/port&gt;
     *     &lt;zone&gt;simpleChat&lt;/zone&gt;
     *     &lt;debug&gt;true&lt;/debug&gt;
     *     &lt;blueBoxIpAddress&gt;127.0.0.1&lt;/blueBoxIpAddress&gt;
     *     &lt;blueBoxPort&gt;9339&lt;/blueBoxPort&gt;
     *     &lt;smartConnect&gt;true&lt;/smartConnect&gt;
     *     &lt;httpPort&gt;8080&lt;/httpPort&gt;
     *     &lt;httpPollSpeed&gt;750&lt;/httpPollSpeed&gt;
     *     &lt;rawProtocolSeparator&gt;%&lt;/rawProtocolSeparator&gt;
     * &lt;/SmartFoxClient&gt;
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to load an external configuration file.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onConfigLoadSuccess, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           smartFox.connect(smartFox.ipAddress, smartFox.port);
     *       }
     *   });
     * smartFox.addEventListener(SFSEvent.onConfigLoadFailure, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("Failed loading config file: " +
     *             evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.loadConfig(this.getClass().getClassLoader().getResource("config.xml"), false);
     * </pre>
     * </p>
     * 
     * @param configFileUrl external xml configuration file URL.
     * @param autoConnect a boolean flag indicating if the connection to SmartFoxServer must be attempted upon configuration loading completion.
     * 
     * @see #ipAddress
     * @see #port
     * @see #defaultZone
     * @see #debug
     * @see #blueBoxIpAddress
     * @see #blueBoxPort
     * @see #smartConnect
     * @see #httpPort
     * @see SFSEvent#onConfigLoadSuccess
     * @see SFSEvent#onConfigLoadFailure
     * 
     * @since	SmartFoxServer Pro v1.6.0
     */
    public void loadConfig(URL configFileUrl, boolean autoConnect)
    {
        try
        {
            URLConnection configConnection = configFileUrl.openConnection();
            BufferedReader in = new BufferedReader(
                    new InputStreamReader(configConnection.getInputStream()));
            StringBuilder configStr = new StringBuilder();
            char[] cbuf = new char[1];
            while(in.read(cbuf) != -1)
            {
                configStr.append(cbuf);
            }
            if(configStr.length() > 0)
            {
                IXMLParser configParser = XMLParserFactory.createDefaultXMLParser();
                IXMLReader configReader = StdXMLReader.stringReader(configStr.toString());
                configParser.setReader(configReader);
                parseConfig((IXMLElement)configParser.parse(), autoConnect);
            }
            else
            {
                onConfigLoadFailure("Config file seems to be empty.");
            }
        }
        catch(XMLException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
        catch(FileNotFoundException ex)
        {
            onConfigLoadFailure("Error reading config XML file: ", ex);
        }
        catch(IOException ex)
        {
            onConfigLoadFailure("Error reading config XML file: ", ex);
        }
        catch(ClassNotFoundException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
        catch(InstantiationException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
        catch(IllegalAccessException ex)
        {
            onConfigLoadFailure("Error parsing config XML file: ", ex);
        }
    }
    
    /**
     * Get the current connection mode.
     * 
     * <p>
     * The following example shows how to check the current connection mode.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onConnection, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("Connection mode: " + smartFox.getConnectionMode());
     *       }
     *   });
     * smartFox.connect("127.0.0.1", 9339)
     * </pre>
     * </p>
     * 
     * @return The current connection mode, expressed by one of the following constants:
     *         {@link #CONNECTION_MODE_DISCONNECTED} (disconnected),
     *         {@link #CONNECTION_MODE_SOCKET} (socket mode),
     *         {@link #CONNECTION_MODE_HTTP} (http mode).
     * 
     * @see #CONNECTION_MODE_DISCONNECTED
     * @see #CONNECTION_MODE_SOCKET
     * @see #CONNECTION_MODE_HTTP
     * @see #connect
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public String getConnectionMode()
    {
        String mode = CONNECTION_MODE_DISCONNECTED;

        if(this.isConnected())
        {
            if(this.isHttpMode)
            {
                mode = CONNECTION_MODE_HTTP;
            }
            else
            {
                mode = CONNECTION_MODE_SOCKET;
            }
        }

        return mode;
    }

    /**
     * Establish a connection to SmartFoxServer using the default port 9339.
     * <p>
     * The client usually gets connected to SmartFoxServer through a socket connection.
     * In SmartFoxServer Pro, if a socket connection is not available and
     * the {@link #smartConnect} property is set to {@code true},
     * an http connection to the BlueBox module is attempted.
     * </p>
     * <p>
     * When a successful connection is established, the {@link #getConnectionMode} can
     * be used to check the current connection mode.
     * </p>
     * 
     * <p>
     * The following example shows how to connect to SmartFoxServer.
     * <pre>
     * smartFox.connect("127.0.0.1");
     * </pre>
     * </p>
     * 
     * @param ipAdr the SmartFoxServer ip address.
     * 
     * @see #disconnect
     * @see #getConnectionMode
     * @see #smartConnect
     * @see SFSEvent#onConnection
     * 
     * @since SmartFoxServer Basic (except BlueBox connection) / Pro v1.x.x
     */
    public void connect(String ipAdr)
    {
        connect(ipAdr, 9339);
    }

    /**
     * Establish a connection to SmartFoxServer.
     * <p>
     * The client usually gets connected to SmartFoxServer through a socket connection.
     * In SmartFoxServer Pro, if a socket connection is not available and
     * the {@link #smartConnect} property is set to {@code true},
     * an http connection to the BlueBox module is attempted.
     * </p>
     * <p>
     * When a successful connection is established, the {@link #getConnectionMode} can
     * be used to check the current connection mode.
     * </p>
     * 
     * <p>
     * The following example shows how to connect to SmartFoxServer.
     * <pre>
     * smartFox.connect("127.0.0.1");
     * </pre>
     * </p>
     * 
     * @param ipAdr the SmartFoxServer ip address.
     * @param port the SmartFoxServer TCP port.
     * 
     * @see #disconnect
     * @see #getConnectionMode
     * @see #smartConnect
     * @see SFSEvent#onConnection
     * 
     * @since SmartFoxServer Basic (except BlueBox connection) / Pro v1.x.x
     */
    public void connect(String ipAdr, int port)
    {
        if(!connected)
        {
            initialize();
            this.ipAddress = ipAdr;
            this.port = port;

            socketConnection.connect(ipAdr, port);
        }
        else
        {
            debugMessage("*** ALREADY CONNECTED ***", Level.WARNING);
        }
    }

    /**
     * Close the current connection to SmartFoxServer.
     * 
     * <p>
     * The following example shows how to disconnect from SmartFoxServer.
     * <pre>
     * smartFox.disconnect();
     * </pre>
     * </p>
     * 
     * @see #connect
     * @see SFSEvent#onConnectionLost
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void disconnect()
    {
        connected = false;

        if(!isHttpMode)
        {
            socketConnection.close();
        }
        else
        {
            httpConnection.close();
        }
    }

    /**
     * Add a user to the buddy list.
     * <p>
     * Since SmartFoxServer Pro 1.6.0, the buddy list feature can be configured to
     * use a <i>basic</i> or <i>advanced</i> security mode
     * (see the SmartFoxServer server-side configuration file).
     * Check the following usage notes for details on the behavior of
     * the <b>addBuddy</b> method in the two cases.
     * </p>
     * 
     * <p>
     * <b>NOTE:</b><br>
     * Before you can add or remove any buddy from the list you must load the buddy-list from the server.
     * Always make sure to call {@link #loadBuddyList} before interacting with the buddy-list.<br>
     * <i>Basic security mode</i><br>
     * When a buddy is added, if the buddy list is already full,
     * the {@link SFSEvent#onBuddyListError} event is fired;
     * otherwise the buddy list is updated and the {@link SFSEvent#onBuddyList} event is fired.
     * <hr>
     * <i>Advanced security mode</i><br>
     * If the {@code <addBuddyPermission>} parameter is set to {@code true}
     * in the buddy list configuration section of a zone, before the user is
     * actually added to the buddy list he/she must grant his/her permission.
     * The permission request is sent if the user is online only; the user
     * receives the {@link SFSEvent#onBuddyPermissionRequest} event.
     * When the permission is granted, the buddy list is updated and
     * the {@link SFSEvent#onBuddyList} event is fired.
     * If the permission is not granted (or the buddy didn't receive the permission request),
     * the <b>addBuddy</b> method can be called again after a certain amount of time only.
     * This time is set in the server configuration {@code <permissionTimeOut>} parameter.
     * Also, if the {@code <mutualAddBuddy>} parameter is set to {@code true},
     * when user A adds user B to the buddy list, he/she is automatically added to user B's buddy list.
     * Lastly, if the buddy list is full, the {@link SFSEvent#onBuddyListError} event is fired.
     * </p>
     * 
     * <p>
     * The following example shows how to add a user to the buddy list.
     * <pre>
     * smartFox.addBuddy("jack");
     * </pre>
     * </p>
     * 
     * @param buddyName the name of the user to be added to the buddy list.
     * 
     * @see #buddyList
     * @see #removeBuddy
     * @see #setBuddyBlockStatus
     * @see SFSEvent#onBuddyList
     * @see SFSEvent#onBuddyListError
     * @see SFSEvent#onBuddyPermissionRequest
     * 
     * @since SmartFoxServer Basic (except <i>advanced mode</i>) / Pro v1.x.x
     */
    public void addBuddy(String buddyName)
    {
        if((!buddyName.equals(myUserName)) && (!checkBuddyDuplicates(buddyName)))
        {
            String xmlMsg = "<n>" + buddyName + "</n>";
            send(MESSAGE_HEADER_SYSTEM, "addB", -1, xmlMsg);
        }
    }

    /**
     * Automatically join the the default room (if existing) for the current zone.
     * <p>
     * A default room can be specified in the SmartFoxServer server-side configuration by
     * adding the {@code autoJoin = "true"} attribute to one of the {@code <Room>} tags in a zone.
     * When a room is marked as <i>autoJoin</i> it becomes the default room where
     * all clients are joined when this method is called.
     * </p>
     * 
     * <p>
     * The following example shows how to join the default room in the current zone.
     * <pre>
     * smartFox.autoJoin();
     * </pre>
     * </p>
     *  
     * @see #joinRoom
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void autoJoin()
    {
        if(!checkRoomList())
        {
            return;
        }

        this.send(MESSAGE_HEADER_SYSTEM, "autoJoin", activeRoomId, "");
    }

    /**
     * Remove all users from the buddy list.
     * 
     * <p>
     * The following example shows how to clear the buddy list.
     * <pre>
     * smartFox.clearBuddyList();
     * </pre>
     * </p>
     * 
     * @deprecated In order to avoid conflits with the buddy list <i>advanced security mode</i> implemented since SmartFoxServer Pro 1.6.0, buddies should be removed one by one, by iterating through the buddy list.
     * 
     * @see #buddyList
     * @see SFSEvent#onBuddyList
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    @Deprecated
    public void clearBuddyList()
    {
        buddyList = new CopyOnWriteArrayList<Buddy>();
        send(MESSAGE_HEADER_SYSTEM, "clearB", -1, "");

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("list", buddyList);

        SFSEvent evt = new SFSEvent(SFSEvent.onBuddyList, params);
        dispatchEvent(evt);
    }

    /**
     * Dynamically create a new room in the current zone.
     * 
     * <p>
     * <b>NOTE</b>: if the newly created room is a game room, the user is
     * joined automatically upon successful room creation.
     * </p>
     * 
     * <p>
     * The following example shows how to create a new room.
     * <pre>
     * Map&lt;String, Object&gt; roomProperties = new HashMap&lt;String, Object&gt;();
     * roomProperties.put("isGame", true);
     * Map&lt;String, RoomVariableRequest&gt; variables = new HashMap&lt;String, RoomVariableRequest&gt;();
     * variables.put("ogres", new RoomVariableRequest("5", SFSVariable.TYPE_NUMBER, true));
     * variables.put("skeletons", new RoomVariableRequest("4", SFSVariable.TYPE_NUMBER));
     * roomProperties.put("vars", variables);
     * smartFox.createRoom("The Cave", 15, roomProperties);
     * </pre>
     * </p>
     * 
     * @param name the room name.
     * @param maxUsers the maximum number of users that can join the room.
     * @param roomProperties a {@code Map} with the following key/value pairs:
     *        <ul>
     *        <li>
     *        password: ({@code String}) a password to make the room private (optional, default: none). 
     *        </li>
     *        <li>
     *        maxSpectators: ({@code Integer}) in game rooms only, the maximum number of
     *                       spectators that can join the room (optional, default value: 0).
     *        </li>
     *        <li>
     *        isGame: ({@code Boolean}) if {@code true}, the room is a game room
     *                (optional, default value: {@code false}).
     *        </li>
     *        <li>
     *        exitCurrentRoom: ({@code Boolean}) if {@code true} and in case of game room,
     *                         the new room is joined after creation
     *                         (optional, default value: {@code true}).
     *        </li>
     *        <li>
     *        joinAsSpectator: ({@code Boolean}) if {@code true} and in case of game room,
     *                         allows to join the new room as spectator
     *                         (optional, default value: {@code false}).
     *        </li>
     *        <li>
     *        uCount: ({@code Boolean}) if {@code true}, the new room will receive the
     *                {@link SFSEvent#onUserCountChange} notifications
     *                (optional, default <u>recommended</u> value: {@code false}).
     *        </li>
     *        <li>
     *        vars: ({@code Map<String, RoomVariableRequest>}) a {@code Map} of Room Variables,
     *              as described in the {@link #setRoomVariables} method documentation
     *              (optional, default: none).
     *        </li>
     *        <li>
     *          A Room-level extension can be attached to any room during creation
     *          using the following key/value pairs (optional)
     *          <ul>
     *          <li>
     *          extensionName: ({@code String}) the name used to reference the extension
     *          (see the SmartFoxServer server-side configuration).
     *          </li>
     *          <li>
     *          extensionScript: ({@code String}) the file name of the extension script
     *                           (for Actionscript and Python);
     *                           if Java is used, the fully qualified name of
     *                           the extension must be provided.
     *                           The file name is relative to the root of the extension folder
     *                           ("sfsExtensions/" for Actionscript and Python, "javaExtensions/" for Java).
     *          </li>
     *          </ul>
     *        </li>
     *        </ul>
     *
     * @see SFSEvent#onRoomAdded
     * @see SFSEvent#onCreateRoomError
     * @see SFSEvent#onUserCountChange
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void createRoom(String name, int maxUsers, Map<String, Object> roomProperties)
    {
        createRoom(name, maxUsers, roomProperties, activeRoomId);
    }

    /**
     * Dynamically create a new room in the current zone.
     * 
     * <p>
     * <b>NOTE</b>: if the newly created room is a game room, the user is
     * joined automatically upon successful room creation.
     * </p>
     * 
     * <p>
     * The following example shows how to create a new room.
     * <pre>
     * Map&lt;String, Object&gt; roomProperties = new HashMap&lt;String, Object&gt;();
     * roomProperties.put("isGame", true);
     * Map&lt;String, RoomVariableRequest&gt; variables = new HashMap&lt;String, RoomVariableRequest&gt;();
     * variables.put("ogres", new RoomVariableRequest("5", SFSVariable.TYPE_NUMBER, true));
     * variables.put("skeletons", new RoomVariableRequest("4", SFSVariable.TYPE_NUMBER));
     * roomProperties.put("vars", variables);
     * smartFox.createRoom("The Cave", 15, roomProperties, 25);
     * </pre>
     * </p>
     * 
     * @param name the room name.
     * @param maxUsers the maximum number of users that can join the room.
     * @param roomProperties a {@code Map} with the following key/value pairs:
     *        <ul>
     *        <li>
     *        password: ({@code String}) a password to make the room private (optional, default: none). 
     *        </li>
     *        <li>
     *        maxSpectators: ({@code Integer}) in game rooms only, the maximum number of
     *                       spectators that can join the room (optional, default value: 0).
     *        </li>
     *        <li>
     *        isGame: ({@code Boolean}) if {@code true}, the room is a game room
     *                (optional, default value: {@code false}).
     *        </li>
     *        <li>
     *        exitCurrentRoom: ({@code Boolean}) if {@code true} and in case of game room,
     *                         the new room is joined after creation
     *                         (optional, default value: {@code true}).
     *        </li>
     *        <li>
     *        joinAsSpectator: ({@code Boolean}) if {@code true} and in case of game room,
     *                         allows to join the new room as spectator
     *                         (optional, default value: {@code false}).
     *        </li>
     *        <li>
     *        uCount: ({@code Boolean}) if {@code true}, the new room will receive the
     *                {@link SFSEvent#onUserCountChange} notifications
     *                (optional, default <u>recommended</u> value: {@code false}).
     *        </li>
     *        <li>
     *        vars: ({@code Map<String, RoomVariableRequest>}) a {@code Map} of Room Variables,
     *              as described in the {@link #setRoomVariables} method documentation
     *              (optional, default: none).
     *        </li>
     *        <li>
     *          A Room-level extension can be attached to any room during creation
     *          using the following key/value pairs (optional)
     *          <ul>
     *          <li>
     *          extensionName: ({@code String}) the name used to reference the extension
     *          (see the SmartFoxServer server-side configuration).
     *          </li>
     *          <li>
     *          extensionScript: ({@code String}) the file name of the extension script
     *                           (for Actionscript and Python);
     *                           if Java is used, the fully qualified name of
     *                           the extension must be provided.
     *                           The file name is relative to the root of the extension folder
     *                           ("sfsExtensions/" for Actionscript and Python, "javaExtensions/" for Java).
     *          </li>
     *          </ul>
     *        </li>
     *        </ul>
     * 
     * @param roomId the id of the room from where the request is originated, in case the application allows multi-room join. 
     * 
     * @see SFSEvent#onRoomAdded
     * @see SFSEvent#onCreateRoomError
     * @see SFSEvent#onUserCountChange
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void createRoom(String name, int maxUsers, Map<String, Object> roomProperties, int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }

        try
        {
            String isGame = "0";
            String exitCurrentRoom = "1";
            String joinAsSpectator = "0";
            Boolean isGameBool = (Boolean)roomProperties.get("isGame");
            if(isGameBool != null)
            {
                if(isGameBool)
                {
                    isGame = "1";
                    Boolean exitCurrentRoomBool = (Boolean)roomProperties.get("exitCurrentRoom");
                    if(exitCurrentRoomBool != null)
                    {
                        exitCurrentRoom = (exitCurrentRoomBool) ? "1" : "0";
                    }
                    Boolean joinAsSpectatorBool = (Boolean)roomProperties.get("joinAsSpectator");
                    if(joinAsSpectatorBool != null)
                    {
                        joinAsSpectator = (joinAsSpectatorBool) ? "1" : "0";
                    }
                    
                }
            }
            
            String maxSpectators = "0";
            Integer maxSpectatorsInt = (Integer)roomProperties.get("maxSpectators");
            if(maxSpectatorsInt != null)
            {
                maxSpectators = maxSpectatorsInt.toString();
            }
            
            String password = (String)roomProperties.get("password");
            
            String uCount = "0";
            Boolean uCountBool = (Boolean)roomProperties.get("uCount");
            if(uCountBool != null)
            {
                uCount = (uCountBool) ? "1" : "0" ;
            }
            
            String extensionName = (String)roomProperties.get("extensionName");
            String extensionScript = (String)roomProperties.get("extensionScript");

            String xmlMsg = "<room tmp='1' gam='" + isGame + "' spec='" + maxSpectators + "' exit='" + exitCurrentRoom + "' jas='" + joinAsSpectator + "'>";

            xmlMsg += "<name><![CDATA[" + (name == null ? "" : name) + "]]></name>";
            xmlMsg += "<pwd><![CDATA[" + (password == null ? "" : password) + "]]></pwd>";
            xmlMsg += "<max>" + maxUsers + "</max>";
            xmlMsg += "<uCnt>" + uCount + "</uCnt>";

            
            // Set extension for room
            if(extensionName != null && extensionScript != null)
            {
                xmlMsg += "<xt n='" + extensionName;
                xmlMsg += "' s='" + extensionScript + "' />";
            }

            Map<String, RoomVariableRequest> vars = (Map<String, RoomVariableRequest>)roomProperties.get("vars");
            
            // Set Room Variables on creation
            if(vars == null)
            {
                xmlMsg += "<vars></vars>";
            }
            else
            {
                xmlMsg += "<vars>";

                for(String varName : vars.keySet())
                {
                    xmlMsg += getXmlRoomVariable(varName, vars.get(varName));
                }

                xmlMsg += "</vars>";
            }

            xmlMsg += "</room>";

            send(MESSAGE_HEADER_SYSTEM, "createRoom", roomId, xmlMsg);
        }
        catch(ClassCastException ex)
        {
            debugMessage("Room creation parameter is not with the required type: " + ex.getMessage(),
                    ex, Level.SEVERE);            
        }
    }

    /**
     * Get the list of rooms in the current zone.
     * <p>
     * Unlike the {@link #getRoomList} method, this method returns the list of {@link Room}
     * objects already stored on the client, so no request is sent to the server.
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the room list.
     * <pre>
     * Map&ltInteger, Room&gt rooms = smartFox.getAllRooms();
     * for(Integer id : rooms.keySet())
     * {
     *     System.out.println("Room: " + rooms.get(id).getName());
     * }
     * </pre>
     * </p>
     * 
     * @return The list of rooms available in the current zone.
     * 
     * @see #getRoomList
     * @see Room
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public Map<Integer, Room> getAllRooms()
    {
        return roomList;
    }

    /**
     * Get a buddy from the buddy list, using the buddy's username as key.
     * 
     * <p>
     * The following example shows how to retrieve a buddy from the buddy list.
     * <pre>
     * Buddy buddy = smartFox.getBuddyByName("jack");
     * System.out.println("Buddy id: " + buddy.getId());
     * System.out.println("Buddy name: " + buddy.getName());
     * System.out.println("Is buddy online? " + (buddy.isOnline() ? "Yes" : "No"));
     * System.out.println("Is buddy blocked? " + (buddy.isBlocked() ? "Yes" : "No"));
     * System.out.println("Buddy Variables:"));
     * // Trace all Buddy Variables
     * for(String key : buddy.getVariables().keySet())
     * {
     *     System.out.println("\t" + key + " --> " + buddy.getVariables().get(key));
     * }
     * </pre>
     * </p>
     * 
     * @param buddyName the username of the buddy.
     * 
     * @return The buddy object.
     * 
     * @see #buddyList
     * @see #getBuddyById
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public Buddy getBuddyByName(String buddyName)
    {
        for(Buddy buddy : buddyList)
        {
            if(buddy.getName().equals(buddyName))
            {
                return buddy;
            }
        }
        return null;
    }

    /**
     * Get a buddy from the buddy list, using the user id as key.
     * 
     * <p>
     * The following example shows how to retrieve a buddy from the buddy list.
     * <pre>
     * Buddy buddy = smartFox.getBuddyById(25);
     * System.out.println("Buddy id: " + buddy.getId());
     * System.out.println("Buddy name: " + buddy.getName());
     * System.out.println("Is buddy online? " + (buddy.isOnline() ? "Yes" : "No"));
     * System.out.println("Is buddy blocked? " + (buddy.isBlocked() ? "Yes" : "No"));
     * System.out.println("Buddy Variables:"));
     * // Trace all Buddy Variables
     * for(String key : buddy.getVariables().keySet())
     * {
     *     System.out.println("\t" + key + " --> " + buddy.getVariables().get(key));
     * }
     * </pre>
     * </p>
     * 
     * @param id the user id of the buddy.
     * 
     * @return The buddy object.
     * 
     * @see #buddyList
     * @see #getBuddyByName
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public Buddy getBuddyById(int id)
    {
        for(Buddy buddy : buddyList)
        {
            if(buddy.getId() == id)
            {
                return buddy;
            }
        }
        return null;
    }

    /**
     * Request the room id(s) of the room(s) where a buddy is currently located into.
     * 
     * <p>
     * The following example shows how to join the same room of a buddy.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onBuddyRoom, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           smartFox.joinRoom(((int[])evt.getParams().get("idList"))[0]);
     *       }
     *   });
     * Buddy buddy = smartFox.getBuddyByName("jack");
     * smartFox.getBuddyRoom(buddy);
     * </pre>
     * </p>
     * 
     * @param buddy a buddy object taken from the {@link #buddyList}.
     * 
     * @see #buddyList
     * @see SFSEvent#onBuddyRoom
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void getBuddyRoom(Buddy buddy)
    {
        // If buddy is active...
        if(buddy.getId() != -1)
        {
            send(MESSAGE_HEADER_SYSTEM, "roomB", -1, "<b id='" + buddy.getId() + "' />");
        }
    }

    /**
     * Get a {@link Room} object, using its id as key.
     * 
     * <p>
     * The following example shows how to retrieve a room from its id.
     * <pre>
     * Room roomObj = smartFox.getRoom(15);
     * System.out.println("Room name: " + roomObj.getName() + ", max users: " + roomObj.getMaxUsers());
     * </pre>
     * </p>
     * 
     * @param roomId the id of the room.
     * 
     * @return The {@link Room} object.
     * 
     * @see #getRoomByName
     * @see #getAllRooms
     * @see #getRoomList
     * @see Room
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public Room getRoom(int roomId)
    {
        if(!checkRoomList())
        {
            return null;
        }

        return roomList.get(roomId);
    }

    /**
     * Get a {@link Room} object, using its name as key.
     * 
     * <p>
     * The following example shows how to retrieve a room from its name.
     * <pre>
     * Room roomObj = smartFox.getRoom("The Entrance");
     * System.out.println("Room name: " + roomObj.getName() + ", max users: " + roomObj.getMaxUsers());
     * </pre>
     * </p>
     * 
     * @param roomName the name of the room.
     * 
     * @return The {@link Room} object.
     * 
     * @see #getRoom
     * @see #getAllRooms
     * @see #getRoomList
     * @see Room
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public Room getRoomByName(String roomName)
    {
        if(!checkRoomList())
        {
            return null;
        }

        Room room = null;
        
        for(int key : roomList.keySet())
        {
            Room r = roomList.get(key);
            if(r.getName().equals(roomName))
            {
                room = r;
                break;
            }
        }

        return room;
    }

    /**
     * Retrieve the updated list of rooms in the current zone.
     * <p>
     * Unlike the {@link #getAllRooms} method, this method sends a request to the server,
     * which then sends back the complete list of rooms with all their properties and
     * server-side variables (Room Variables).
     * </p>
     * 
     * <p>
     * If the default login mechanism provided by SmartFoxServer is used,
     * then the updated list of rooms is received right after a successful login,
     * without the need to call this method.
     * Instead, if a custom login handler is implemented, the room list must be
     * manually requested to the server using this method.
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the room list from the server.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onRoomListUpdate, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           // Dump the names of the available rooms in the current zone
     * 		for(Integer rId : evt.getParams().get("roomList").keySet())
     * 		    System.out.println(evt.getParams().get("roomList").get(r).getName());
     *       }
     *   });
     * smartFox.getRoomList();
     * </pre>
     * </p>
     * 
     * @see #getRoom
     * @see #getRoomByName
     * @see #getAllRooms
     * @see SFSEvent#onRoomListUpdate
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void getRoomList()
    {
        send(MESSAGE_HEADER_SYSTEM, "getRmList", activeRoomId, "");
    }

    /**
     * Get the currently active {@link Room} object.
     *      
     * <p>
     * SmartFoxServer allows users to join two or more rooms at the same time (multi-room join).
     * If this feature is used, then this method is useless and the application should track the
     * various room id(s) manually, for example by keeping them in an array.
     * </p>
     * 
     * <p>
     * The following example shows how to retrieve the current room object.
     * <pre>
     * Room room = smartFox.getActiveRoom();
     * System.out.println("Current room is: " + room.getName());
     * </pre>
     * </p>
     * 
     * @return the {@link Room} object of the currently active room;
     *         if the user joined more than one room, the last joined room is returned.
     * 
     * @see #activeRoomId
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public Room getActiveRoom()
    {
        if(!checkRoomList())
        {
            return null;
        }
        
        return roomList.get(activeRoomId);
    }

    /**
     * Retrieve a random string key from the server.
     * <p>
     * This key is also referred in the SmartFoxServer documentation as the "secret key".
     * It's a unique key, valid for the current session only.
     * It can be used to create a secure login system.
     * </p>
     * 
     * <p>
     * The following example shows how to handle the request a random key to the server.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onRandomKey, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("Random key received from server: " +
     *           evt.getParams().getString("key"));
     *       }
     *   });
     * smartFox.getRandomKey();
     * </pre>
     * </p>
     * 
     * @see SFSEvent#onRandomKey
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public void getRandomKey()
    {
        send(MESSAGE_HEADER_SYSTEM, "rndK", -1, "");
    }

    /**
     * Get the default upload path of the embedded webserver.
     * 
     * <p>
     * The following example shows how to get the default upload path.
     * <pre>
     * String path = smartFox.getUploadPath();
     * </pre>
     * </p>
     * 
     * @return The http address of the default folder in which files are uploaded.
     * 
     * @since SmartFoxServer Pro v1.5.0
     */
    public String getUploadPath()
    {
        return "http://" + this.ipAddress + ":" + this.httpPort + "/default/uploads/";
    }

    /**
     * Get the SmartFoxServer Flash API version.
     * 
     * <p>
     * The following example shows how to display the SmartFoxServer API version.
     * <pre>
     * System.out.println("Current API version: " + smartFox.getVersion());
     * </pre>
     * </p>
     * 
     * @return The current version of the SmartFoxServer client API.
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public String getVersion()
    {
        return this.majVersion + "." + this.minVersion + "." + this.subVersion;
    }

    /**
     * Join a room.
     * 
     * @param newRoom the id of the room to join.
     * 
     * <p>
     * In the following example the user requests to join a room with id = 10.
     * <pre>
     * smartFox.joinRoom(10);
     * </pre>
     * </p>
     * 
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void joinRoom(int newRoom)
    {
        joinRoom(newRoom, null, false, false, -1);
    }

    /**
     * Join a room.
     * 
     * <p>
     * In the following example the user requests to join a room with id = 12 and password = "mypassword";
     * SmartFoxServer will disconnect him from the previous room.
     * <pre>
     * smartFox.joinRoom(12, "mypassword", false);
     * </pre>
     * </p>
     * 
     * <p>
     * In the following example the user requests to join the room with id = 15 and
     * passes {@code true} to the <i>dontLeave</i> flag;
     * this will join the user in the new room while keeping him in the old room as well.
     * <pre>
     * smartFox.joinRoom(15, "", true);
     * </pre>
     * </p>
     * 
     * @param newRoom the id of the room to join.
     * @param pword the room's password, if it's a private room or empty {@code String} if it's not.
     * @param dontLeave a boolean flag indicating if the current room must be left after
     *        successfully joining the new room.
     * 
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void joinRoom(int newRoom, String pword, boolean dontLeave)
    {
        joinRoom(newRoom, pword, false, dontLeave, -1);
    }

    /**
     * Join a room.
     * 
     * <p>
     * In the following example the user requests to join the game room with id = 15
     * as spectator and passes {@code true} to the <i>dontLeave</i> flag - 
     * this will join the user in the new room while keeping him in the old room as well.
     * <pre>
     * smartFox.joinRoom(15, "", true, true);
     * </pre>
     * </p>
     * 
     * @param newRoom the id of the room to join.
     * @param pword the room's password, if it's a private room or empty {@code String} if it's not.
     * @param isSpectator a boolean flag indicating wheter you join as a spectator or not.
     * @param dontLeave a boolean flag indicating if the current room must be left after
     *        successfully joining the new room.
     * 
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void joinRoom(int newRoom, String pword, boolean isSpectator, boolean dontLeave)
    {
        joinRoom(newRoom, pword, isSpectator, dontLeave, -1);
    }

    /**
     * Join a room.
     * 
     * <p>
     * In the following example the user requests to join the room with id = 15
     * and leaves the romm with id = 25 
     * <pre>
     * smartFox.joinRoom(15, "", false, false, 25);
     * </pre>
     * </p>
     * 
     * @param newRoom the id of the room to join.
     * @param pword the room's password, if it's a private room or empty {@code String} if it's not.
     * @param isSpectator a boolean flag indicating wheter you join as a spectator or not.
     * @param dontLeave a boolean flag indicating if the current room must be left after
     *        successfully joining the new room.
     * @param oldRoom the id of the room to leave.
     * 
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void joinRoom(int newRoom, String pword, boolean isSpectator, boolean dontLeave, int oldRoom)
    {
        if(!checkRoomList())
        {
            return;
        }

        int isSpec = isSpectator ? 1 : 0;

        if(!this.changingRoom)
        {
            String leaveCurrRoom = dontLeave ? "0": "1";

            // Set the room to leave
            int roomToLeave = oldRoom > -1 ? oldRoom : activeRoomId;

            // CHECK: activeRoomId == -1 no room has already been entered
            if(activeRoomId == -1)
            {
                leaveCurrRoom = "0";
                roomToLeave = -1;
            }

            String message = "<room id='" + newRoom + "' pwd='" + pword + "' spec='" + isSpec + "' leave='" + leaveCurrRoom + "' old='" + roomToLeave + "' />";

            send(MESSAGE_HEADER_SYSTEM, "joinRoom", activeRoomId, message);
            changingRoom = true;
        }
    }

    /**
     * Join a room.
     * 
     * <p>
     * In the following example the user requests to join a room with name = "The Hall" and password = "mypassword";
     * SmartFoxServer will disconnect him from the previous room.
     * <pre>
     * smartFox.joinRoom("The Hall", "mypassword", false);
     * </pre>
     * </p>
     * 
     * <p>
     * In the following example the user requests to join the room with name = "The Hall" and
     * passes {@code true} to the <i>dontLeave</i> flag;
     * this will join the user in the new room while keeping him in the old room as well.
     * <pre>
     * smartFox.joinRoom("The Hall", "", true);
     * </pre>
     * </p>
     * 
     * @param newRoom the name of the room to join.
     * @param pword the room's password, if it's a private room or empty {@code String} if it's not.
     * @param dontLeave a boolean flag indicating if the current room must be left after
     *        successfully joining the new room.
     * 
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void joinRoom(String newRoom, String pword, boolean dontLeave)
    {
        joinRoom(newRoom, pword, false, dontLeave, -1);
    }
    
    /**
     * Join a room.
     * 
     * <p>
     * In the following example the user requests to join the game room with name = "The Hall"
     * as spectator and passes {@code true} to the <i>dontLeave</i> flag - 
     * this will join the user in the new room while keeping him in the old room as well.
     * <pre>
     * smartFox.joinRoom("The Hall", "", true, true);
     * </pre>
     * </p>
     * 
     * @param newRoom the name of the room to join.
     * @param pword the room's password, if it's a private room or empty {@code String} if it's not.
     * @param isSpectator a boolean flag indicating wheter you join as a spectator or not.
     * @param dontLeave a boolean flag indicating if the current room must be left after
     *        successfully joining the new room.
     * 
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void joinRoom(String newRoom, String pword, boolean isSpectator, boolean dontLeave)
    {
        joinRoom(newRoom, pword, isSpectator, dontLeave, -1);
    }
    
    /**
     * Join a room.
     * 
     * <p>
     * In the following example the user requests to join the room with name= "The Hall"
     * and leaves the romm with id = 25 
     * <pre>
     * smartFox.joinRoom("The Hall", "", false, false, 25);
     * </pre>
     * </p>
     * 
     * @param newRoom the name of the room to join.
     * @param pword the room's password, if it's a private room or empty {@code String} if it's not.
     * @param isSpectator a boolean flag indicating wheter you join as a spectator or not.
     * @param dontLeave a boolean flag indicating if the current room must be left after
     *        successfully joining the new room.
     * @param oldRoom the id of the room to leave.
     * 
     * @see SFSEvent#onJoinRoom
     * @see SFSEvent#onJoinRoomError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void joinRoom(String newRoom, String pword, boolean isSpectator, boolean dontLeave, int oldRoom)
    {
        if(!checkRoomList())
        {
            return;
        }

        int newRoomId = -1;
        int isSpec = isSpectator ? 1 : 0;

        if(!this.changingRoom)
        {
            // Search the room
            for(Integer roomId : roomList.keySet())
            {
                Room r = roomList.get(roomId);
                if(r.getName().equals(newRoom))
                {
                    newRoomId = r.getId();
                    break;
                }
            }

            if(newRoomId != -1)
            {
                String leaveCurrRoom = dontLeave ? "0": "1";

                // Set the room to leave
                int roomToLeave = oldRoom > -1 ? oldRoom : activeRoomId;

                // CHECK: activeRoomId == -1 no room has already been entered
                if(activeRoomId == -1)
                {
                    leaveCurrRoom = "0";
                    roomToLeave = -1;
                }

                String message = "<room id='" + newRoomId + "' pwd='" + pword + "' spec='" + isSpec + "' leave='" + leaveCurrRoom + "' old='" + roomToLeave + "' />";

                send(MESSAGE_HEADER_SYSTEM, "joinRoom", activeRoomId, message);
                changingRoom = true;
            }
            else
            {
                debugMessage("SmartFoxError: requested room to join does not exist!", Level.SEVERE);
            }
        }
    }

    /** 
     * Disconnect the user from the given room.
     * <p>
     * This method should be used only when users are allowed to be present in more than
     * one room at the same time (multi-room join feature).
     * </p>
     * 
     * <p>
     * The following example shows how to make a user leave a room.
     * <pre>
     * smartFox.leaveRoom(15);
     * </pre>
     * </p>
     * 
     * @param roomId the id of the room to leave.
     * 
     * @see #joinRoom
     * @see SFSEvent#onRoomLeft
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void leaveRoom(int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }
        
        String xmlMsg = "<rm id='" + roomId + "' />";

        send(MESSAGE_HEADER_SYSTEM, "leaveRoom", roomId, xmlMsg);
    }

    /**
     * Load the buddy list for the current user.
     * 
     * <p>
     * The following example shows how to load the current user's buddy list.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onBuddyList, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           for(Buddy buddy : smartFox.buddyList)
     *           {
     * 
     *               // Trace buddy properties
     *               System.out.println("Buddy id: " + buddy.getId());
     *               System.out.println("Buddy name: " + buddy.getName());
     *               System.out.println("Is buddy online? " + (buddy.isOnline() ? "Yes" : "No"));
     *               System.out.println("Is buddy blocked? " + (buddy.isBlocked() ? "Yes" : "No"));
     * 
     *               // Trace all Buddy Variables
     *               for(String key : buddy.getVariables().keySet())
     *               {
     *                   System.out.println("\t" + key + " --> " + buddy.getVariables().get(key));
     *               }
     *           }
     *       }
     *   });
     * smartFox.loadBuddyList();
     * </pre>
     * </p>
     * 
     * @see #buddyList
     * @see SFSEvent#onBuddyList
     * @see SFSEvent#onBuddyListError
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void loadBuddyList()
    {
        send(MESSAGE_HEADER_SYSTEM, "loadB", -1, "");
    }

    /**
     * Perform the default login procedure.
     * <p>
     * The standard SmartFoxServer login procedure accepts guest users.
     * If a user logs in with an empty username, the server automatically creates a name for
     * the client using the format <i>guest_n</i>, where <i>n</i> is a progressive number.
     * Also, the provided username and password are checked against the
     * moderators list (see the SmartFoxServer server-side configuration) and
     * if a user matches it, he is set as a Moderator.
     * </p>
     * 
     * <p>
     * <b>NOTE 1</b>: duplicate names in the same zone are not allowed.
     * </p>
     * 
     * <p>
     * <b>NOTE 2</b>: for SmartFoxServer Basic, where a server-side custom login procedure
     * can't be implemented due to the lack of <i>extensions</i> support, a custom client-side procedure
     * can be used, for example to check usernames against a database using a php/asp page.
     * In this case, this should be done BEFORE calling the <b>login</b> method.
     * This way, once the client is validated, the stadard login procedure can be used.
     * </p>
     * 
     * <p>
     * The following example shows how to login into a zone.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onLogin, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           if(evt.getParams().getBool("success"))
     *           {
     *               System.out.println("Successfully logged in as " + evt.getParams().getString("name"));
     *           }
     *           else
     *           {
     *               System.out.println("Zone login error; the following error occurred: " + evt.getParams().getString("error"));
     *           }
     *       }
     *   });
     * smartFox.login("simpleChat", "jack", "");
     * </pre>
     * </p>
     * 
     * @param zone the name of the zone to log into.
     * @param name the user name.
     * @param pass the user password. Empty {@code String} if no password.
     * 
     * @see #logout
     * @see SFSEvent#onLogin
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void login(String zone, String name, String pass)
    {
        String message = "<login z='" + zone + "'><nick><![CDATA[" + name + "]]></nick><pword><![CDATA[" + pass + "]]></pword></login>";

        send(MESSAGE_HEADER_SYSTEM, "login", 0, message);
    }

    /**
     * Log the user out of the current zone.
     * <p>
     * After a successful logout the user is still connected to the server,
     * but he/she has to login again into a zone, in order to be able to interact with the server.
     * </p>
     * 
     * <p>
     * The following example shows how to logout from a zone.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onLogout, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("Logged out successfully");
     *       }
     *   });
     * smartFox.logout();
     * </pre>
     * </p>
     * 
     * @see #login
     * @see SFSEvent#onLogout
     * 
     * @since SmartFoxServer Pro v1.5.5
     */
    public void logout()
    {
        send(MESSAGE_HEADER_SYSTEM, "logout", -1, "");
    }

    /**
     * Remove a buddy from the buddy list.
     * <p>
     * Since SmartFoxServer Pro 1.6.0, the buddy list feature can be configured to use a <i>basic</i> or
     * <i>advanced</i> security mode (see the SmartFoxServer server-side configuration file).
     * Check the following usage notes for details on the behavior of the <b>removeBuddy</b> method in
     * the two cases.
     * </p>
     * 
     * <p>
     * <b>NOTE:</b><br>
     * Before you can add or remove any buddy from the list you must load the buddy-list from the server.
     * Always make sure to call {@link #loadBuddyList} before interacting with the buddy-list.<br>
     * <i>Basic security mode</i><br>
     * When a buddy is removed, the buddy list is updated and the
     * {@link SFSEvent#onBuddyList} event is fired.
     * <hr>
     * <i>Advanced security mode</i><br>
     * In addition to the basic behavior, if the {@code <mutualRemoveBuddy>} server-side
     * configuration parameter is set to {@code true}, when user A removes user B from the buddy list,
     * he/she is automatically removed from user B's buddy list.
     * </p>
     * 
     * <p>
     * The following example shows how to remove a user from the buddy list.
     * <pre>
     * String buddyName = "jack";
     * smartFox.removeBuddy(buddyName);
     * </pre>
     * </p>
     * 
     * @param buddyName the name of the user to be removed from the buddy list.
     * 
     * @see #buddyList
     * @see #addBuddy
     * @see SFSEvent#onBuddyList
     * 
     * @since SmartFoxServer Basic (except <i>advanced mode</i>) / Pro v1.x.x
     */
    public void removeBuddy(String buddyName)
    {
        boolean found = false;

        for(Iterator<Buddy> it = buddyList.iterator(); it.hasNext(); )
        {
            Buddy buddy = it.next();
            
            if(buddy.getName().equals(buddyName))
            {
                buddyList.remove(buddy);
                found = true;
                break;
            }
        }

        if(found)
        {
            String xmlMsg = "<n>" + buddyName + "</n>";

            send(MESSAGE_HEADER_SYSTEM, "remB", -1, xmlMsg);

            // Fire event!
            SFSObject params = new SFSObject();
            params.put("list", buddyList);

            SFSEvent evt = new SFSEvent(SFSEvent.onBuddyList, params);
            dispatchEvent(evt);
        }
    }

    /**
     * Send a roundtrip request to the server to test the connection' speed.
     * <p>
     * The roundtrip request sends a small packet to the server which immediately responds with
     * another small packet, and causing the {@link SFSEvent#onRoundTripResponse} event to be fired.
     * The time taken by the packet to travel forth and back is called "roundtrip time" and can be
     * used to calculate the average network lag of the client.
     * A good way to measure the network lag is to send continuos requests (every 3 or 5 seconds) and
     * then calculate the average roundtrip time on a fixed number of responses
     * (i.e. the last 10 measurements).
     * </p>
     * 
     * <p>
     * The following example shows how to check the average network lag time.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onRoundTripResponse, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           long lag = (Long)evt.getParams().get("elapsed");
     *           System.out.println("Lag: " + lag + "milliseconds.");
     *       }
     *   });
     * smartFox.roundTripBench();
     * </pre>
     * </p>
     * 
     * @see SFSEvent#onRoundTripResponse
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void roundTripBench()
    {
        this.benchStartTime = System.currentTimeMillis();
        send(MESSAGE_HEADER_SYSTEM, "roundTrip", activeRoomId, "");
    }

    /**
     * Grant current user permission to be added to a buddy list.
     * <p>
     * If the SmartFoxServer Pro 1.6.0 <i>advanced</i> security mode is used
     * (see the SmartFoxServer server-side configuration), when a user wants to add a buddy to
     * his/her buddy list, a permission request is sent to the buddy.
     * Once the {@link SFSEvent#onBuddyPermissionRequest} event is received,
     * this method must be used by the buddy to grant or refuse permission.
     * When the permission is granted, the requester's buddy list is updated.
     * </p>
     * 
     * <p>
     * The following example shows how to grant permission to be added to a
     * buddy list once request is received.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onBuddyPermissionRequest, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           smartFox.sendBuddyPermissionResponse(true, evt.getParams().getString("sender"));
     *       }
     *   });
     * </pre>
     * </p>
     * 
     * @param allowBuddy {@code true} to grant permission,
     *                   {@code false} to refuse to be added to the requester's buddy list.
     * @param targetBuddy the username of the requester.
     * 
     * @see #addBuddy
     * @see SFSEvent#onBuddyPermissionRequest
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public void sendBuddyPermissionResponse(boolean allowBuddy, String targetBuddy)
    {
        String xmlMsg = "<n res='" + (allowBuddy ? "g" : "r") + "'>" + targetBuddy + "</n>";
        send(MESSAGE_HEADER_SYSTEM, "bPrm", -1, xmlMsg);
    }

    /**
     * Send a public message.
     * <p>
     * The message is broadcasted to all users in the current room, including the sender.
     * </p>
     * 
     * <p>
     * The following example shows how to send and receive a public message.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onPublicMessage, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("User " +
     *             ((User)evt.getParams().getObj("sender")).getName() +
     *             " said: " + evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.sendPublicMessage("Hello world!");
     * </pre>
     * </p>
     * 
     * @param message the text of the public message.
     * 
     * @see #sendPrivateMessage
     * @see SFSEvent#onPublicMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendPublicMessage(String message)
    {
        sendPublicMessage(message, activeRoomId);
    }

    /**
     * Send a public message.
     * <p>
     * The message is broadcasted to all users in the current room, including the sender.
     * </p>
     * 
     * <p>
     * The following example shows how to send and receive a public message.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onPublicMessage, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("User " +
     *             ((User)evt.getParams().getObj("sender")).getName() +
     *             " said: " + evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.sendPublicMessage("Hello world!", 25);
     * </pre>
     * </p>
     * 
     * @param message the text of the public message.
     * @param roomId the id of the target room, in case of multi-room join.
     * 
     * @see #sendPrivateMessage
     * @see SFSEvent#onPublicMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendPublicMessage(String message, int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }

        String xmlMsg = "<txt><![CDATA[" + Entities.encodeEntities(message) + "]]></txt>";
        send(MESSAGE_HEADER_SYSTEM, "pubMsg", roomId, xmlMsg);
    }

    /**
     * Send a private message to a user.
     * <p>
     * The message is broadcasted to the recipient and the sender.
     * </p>
     * 
     * <p>
     * The following example shows how to send and receive a private message.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onPrivateMessage, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("User " +
     *             ((User)evt.getParams().getObj("sender")).getName() +
     *             " sent the following private message: " + evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.sendPrivateMessage("Hello world!");
     * </pre>
     * </p>
     * 
     * @param message the text of the private message.
     * @param recipientId the id of the recipient user.
     * 
     * @see #sendPublicMessage
     * @see SFSEvent#onPrivateMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendPrivateMessage(String message, int recipientId)
    {
        sendPrivateMessage(message, recipientId, activeRoomId);
    }

    /**
     * Send a private message to a user.
     * <p>
     * The message is broadcasted to the recipient and the sender.
     * </p>
     * 
     * <p>
     * The following example shows how to send and receive a private message.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onPublicMessage, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           System.out.println("User " +
     *             ((User)evt.getParams().getObj("sender")).getName() +
     *             " sent the following private message: " + evt.getParams().getString("message"));
     *       }
     *   });
     * smartFox.sendPrivateMessage("Hello world!", 25);
     * </pre>
     * </p>
     * 
     * @param message the text of the private message.
     * @param recipientId the id of the recipient user.
     * @param roomId the id of the room from where the message is sent, in case of multi-room join.
     * 
     * @see #sendPublicMessage
     * @see SFSEvent#onPrivateMessage
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendPrivateMessage(String message, int recipientId, int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }
        
        String xmlMsg = "<txt rcp='" + recipientId + "'><![CDATA[" + Entities.encodeEntities(message) + "]]></txt>";
        send(MESSAGE_HEADER_SYSTEM, "prvMsg", roomId, xmlMsg);
    }

    /**
     * Send a Moderator message to the current zone, the current room or a specific user in
     * the current room.
     * 
     * <p>
     * The following example shows how to send a Moderator message.
     * <pre>
     * smartFox.sendModeratorMessage("Greetings from the Moderator", SmartFoxClient.MODMSG_TO_ROOM, smartFox.getActiveRoom());
     * </pre>
     * </p>
     * 
     * <p>
     * In order to send these kind of messages, the user must have Moderator's privileges,
     * which are set by SmartFoxServer when the user logs in (see the {@link #login} method).
     * </p>
     * 
     * @param message the text of the message.
     * @param type the type of message.
     *        The following constants can be passed: {@link #MODMSG_TO_USER}, {@link #MODMSG_TO_ROOM}
     *        and {@link #MODMSG_TO_ZONE}, to send the message to a user, to the current room or
     *        to the entire current zone respectively.
     * @param id the id of the recipient room or user (ignored if the message is sent to the zone).
     * 
     * @see #login
     * @see #MODMSG_TO_USER
     * @see #MODMSG_TO_ROOM
     * @see #MODMSG_TO_ZONE
     * @see SFSEvent#onModeratorMessage
     * 
     * @since	SmartFoxServer Pro v1.4.5
     */
    public void sendModeratorMessage(String message, String type, int id)
    {	
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }
        
        String xmlMsg = "<txt t='" + type + "' id='" + id + "'><![CDATA[" + Entities.encodeEntities(message) + "]]></txt>";
        send(MESSAGE_HEADER_SYSTEM, "modMsg", activeRoomId, xmlMsg);
    }

    /**
     * Send an object to the other users in the current room.
     * <p>
     * This method can be used to send complex/nested data structures to clients,
     * like a game move or a game status change.
     * </p>
     * 
     * <p>
     * The following example shows how to send a simple object to the other users.
     * <pre>
     * SFSObject move = new SFSObject();
     * move.putNumber("x", 150);
     * move.putNumber("y", 250);
     * move.putNumber("speed", 8);
     * smartFox.sendObject(move);
     * </pre>
     * </p>
     * 
     * @param obj the object to be sent. Supported classes are: Boolean, Double, String, SFSObject.
     * 
     * @see #sendObjectToGroup
     * @see SFSEvent#onObjectReceived
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendObject(SFSObject obj)
    {
        sendObject(obj, activeRoomId);
    }

    /**
     * Send an object to the other users in the current room.
     * <p>
     * This method can be used to send complex/nested data structures to clients,
     * like a game move or a game status change.
     * </p>
     * 
     * <p>
     * The following example shows how to send a simple object to the other users.
     * <pre>
     * SFSObject move = new SFSObject();
     * move.putNumber("x", 150);
     * move.putNumber("y", 250);
     * move.putNumber("speed", 8);
     * smartFox.sendObject(move, 25);
     * </pre>
     * </p>
     * 
     * @param obj the object to be sent. Supported classes are: Boolean, Double, String, SFSObject.
     * @param roomId the id of the target room, in case of multi-room join.
     * 
     * @see #sendObjectToGroup
     * @see SFSEvent#onObjectReceived
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendObject(SFSObject obj, int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }

        String xmlData = "<![CDATA[" + SFSObjectSerializer.serialize(obj) + "]]>";

        send(MESSAGE_HEADER_SYSTEM, "asObj", roomId, xmlData);
    }

    /**
     * Send an Actionscript object to a group of users in the room.
     * <p>
     * See {@link #sendObject} for more info.
     * </p>
     * 
     * <p>
     * The following example shows how to send a simple object with primitive data to two users.
     * <pre>
     * SFSObject move = new SFSObject();
     * move.putNumber("x", 150);
     * move.putNumber("y", 250);
     * move.putNumber("speed", 8);
     * smartFox.sendObjectToGroup(move, new int[]{11, 12});
     * </pre>
     * </p>
     * 
     * @param obj the object to be sent. Supported classes are: Boolean, Double, String, SFSObject.
     * @param userList an array containing the id(s) of the recipients.
     * 
     * @see #sendObject
     * @see SFSEvent#onObjectReceived
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendObjectToGroup(SFSObject obj, int[] userList)
    {
        sendObjectToGroup(obj, userList, activeRoomId);
    }

    /**
     * Send an Actionscript object to a group of users in the room.
     * <p>
     * See {@link #sendObject} for more info.
     * </p>
     * 
     * <p>
     * The following example shows how to send a simple object with primitive data to two users.
     * <pre>
     * SFSObject move = new SFSObject();
     * params.put("type", "bullet");
     * move.putNumber("x", 150);
     * move.putNumber("y", 250);
     * move.putNumber("speed", 8);
     * smartFox.sendObjectToGroup(move, new int[]{11, 12}, 25);
     * </pre>
     * </p>
     * 
     * @param obj the object to be sent. Supported classes are: Boolean, Double, String, SFSObject.
     * @param roomId the id of the target room, in case of multi-room join.
     * 
     * @see #sendObject
     * @see SFSEvent#onObjectReceived
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void sendObjectToGroup(SFSObject obj, int[] userList, int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }
        
        String strList = "";

        for(int id : userList)
        {
            strList += id + ",";
        }

        // remove last comma
        strList = strList.substring(0, strList.length());

        obj.put("_$$_", strList);

        String xmlMsg = "<![CDATA[" + SFSObjectSerializer.serialize(obj) + "]]>";

        send(MESSAGE_HEADER_SYSTEM, "asObjG", roomId, xmlMsg);
    }

    /**
     * Send a request to a server side extension using XML protocol.
     *  
     * <p>
     * <b>NOTE</b>: the use JSON instead of XML is highly recommended, as it can save a lot of bandwidth.
     * </p>
     * 
     * <p>
     * The following example shows how to notify a multiplayer game server-side extension that
     * a game action occurred.
     * <pre>
     * // A bullet is being fired
     * SFSObject params = new SFSObject();
     * params.put("type", "bullet");
     * params.putNumber("posx", 100);
     * params.putNumber("posy", 200);
     * params.putNumber("speed", 10);
     * params.putNumber("angle", 45);
     * // Invoke "fire" command on the extension called "gameExt", using XML protocol
     * smartFox.sendXtMessage("gameExt", "fire", params);
     * </pre>
     * </p>
     * 
     * @param xtName the name of the extension (see also the {@link #createRoom} method).
     * @param cmd the name of the action/command to execute in the extension.
     * @param paramObj an object containing the data to be passed to the extension
     *                 (set to empty object if no data is required).
     *                 Supported classes are: Boolean, Double, String, SFSObject.
     * 
     * @see #XTMSG_TYPE_XML
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public void sendXtMessage(String xtName, String cmd, SFSObject paramObj)
    {
        sendXtMessage(xtName, cmd, paramObj, activeRoomId);
    }

    /**
     * Send a request to a server side extension using XML protocol.
     *  
     * <p>
     * <b>NOTE</b>: the use JSON instead of XML is highly recommended, as it can save a lot of bandwidth.
     * </p>
     * 
     * <p>
     * The following example shows how to notify a multiplayer game server-side extension that
     * a game action occurred.
     * <pre>
     * // A bullet is being fired
     * SFSObject params = new SFSObject();
     * params.put("type", "bullet");
     * params.putNumber("posx", 100);
     * params.putNumber("posy", 200);
     * params.putNumber("speed", 10);
     * params.putNumber("angle", 45);
     * // Invoke "fire" command on the extension called "gameExt", using XML protocol
     * smartFox.sendXtMessage("gameExt", "fire", params, 25);
     * </pre>
     * </p>
     * 
     * @param xtName the name of the extension (see also the {@link #createRoom} method).
     * @param cmd the name of the action/command to execute in the extension.
     * @param paramObj an object containing the data to be passed to the extension
     *                 (set to empty object if no data is required).
     *                 Supported classes are: Boolean, Double, String, SFSObject.
     * @param roomId the id of the room where the request was originated, in case of multi-room join.
     * 
     * @see #XTMSG_TYPE_XML
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public void sendXtMessage(String xtName, String cmd, SFSObject paramObj, int roomId)
    {
        if(!checkRoomList())
        {
            return;
        }

        // Encapsulate message
        SFSObject xtReq = new SFSObject();
        xtReq.put("name", xtName);
        xtReq.put("cmd", cmd);
        xtReq.put("param", paramObj);
        String xmlmsg = "<![CDATA[" + SFSObjectSerializer.serialize(xtReq) + "]]>";

        send(MESSAGE_HEADER_EXTENSION, "xtReq", roomId, xmlmsg);
    }

    /**
     * Send a request to a server side extension using JSON protocol. 
     * 
     * <p>
     * <b>NOTE</b>: the use JSON instead of XML is highly recommended, as it can save a lot of bandwidth.
     * </p>
     * 
     * <p>
     * The following example shows how to notify a multiplayer game server-side extension that
     * a game action occurred.
     * <pre>
     * // A bullet is being fired
     * try
     * {
     *     JSONObject params = new JSONObject("{ "type" : "bullet", "posx" : 100, "posy" : 200, "speed" : 10, "angle" : 45 }");
     *     // Invoke "fire" command on the extension called "gameExt", using JSON protocol
     *     smartFox.sendXtMessage("gameExt", "fire", params);
     * }
     * catch(JSONException ex)
     * {
     *     ex.printStackTrace();
     * }
     * </pre>
     * </p>
     * 
     * @param xtName the name of the extension (see also the {@link #createRoom} method).
     * @param cmd the name of the action/command to execute in the extension.
     * @param paramObj an object containing the data to be passed to the extension
     *                 (set to empty object if no data is required).
     * 
     * @see #XTMSG_TYPE_JSON
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public void sendXtMessage(String xtName, String cmd, JSONObject paramObj)
    {
        sendXtMessage(xtName, cmd, paramObj, activeRoomId);
    }

    /**
     * Send a request to a server side extension using JSON protocol. 
     * 
     * <p>
     * <b>NOTE</b>: the use JSON instead of XML is highly recommended, as it can save a lot of bandwidth.
     * </p>
     * 
     * <p>
     * The following example shows how to notify a multiplayer game server-side extension that
     * a game action occurred.
     * <pre>
     * // A bullet is being fired
     * try
     * {
     *     JSONObject params = new JSONObject("{ "type" : "bullet", "posx" : 100, "posy" : 200, "speed" : 10, "angle" : 45 }");
     *     // Invoke "fire" command on the extension called "gameExt", using JSON protocol
     *     smartFox.sendXtMessage("gameExt", "fire", params);
     * }
     * catch(JSONException ex)
     * {
     *     ex.printStackTrace();
     * }
     * </pre>
     * </p>
     * 
     * @param xtName the name of the extension (see also the {@link #createRoom} method).
     * @param cmd the name of the action/command to execute in the extension.
     * @param paramObj an object containing the data to be passed to the extension
     *                 (set to empty object if no data is required).
     * @param roomId the id of the room where the request was originated, in case of multi-room join.
     * 
     * @see #XTMSG_TYPE_JSON
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public void sendXtMessage(String xtName, String cmd, JSONObject paramObj, int roomId)
    {
        if(!checkRoomList())
        {
            return;
        }

        try
        {
            JSONObject body = new JSONObject();
            body.put("x", xtName);
            body.put("c", cmd);
            body.put("r", roomId);
            body.put("p", paramObj);

            JSONObject obj = new JSONObject();
            obj.put("t", "xt");
            obj.put("b", body);

            String msg = obj.toString();
            sendJson(msg);
        }
        catch(JSONException ex)
        {
            debugMessage("Error sending JSON extension message: " + ex.getMessage(), ex, Level.SEVERE);
        }
    }

    /**
     * Send a request to a server side extension using String protocol. 
     * 
     * <p>
     * <b>NOTE</b>: The String-based protocol can be very useful for realtime applications/games where
     * reducing the amount of data is the highest priority.
     * </p>
     * 
     * <p>
     * The following example shows how to notify a multiplayer game server-side extension that
     * a game action occurred.
     * <pre>
     * // A bullet is being fired
     * 
     * // The array contains the type, x and y position, speed and the angle.
     * String[] params = {"bullet", "100", "200", "10", "45"};
     * 
     * // Invoke "fire" command on the extension called "gameExt", using String protocol
     * smartFox.sendXtMessage("gameExt", "fire", params);
     * </pre>
     * </p>
     * 
     * @param xtName the name of the extension (see also the {@link #createRoom} method).
     * @param cmd the name of the action/command to execute in the extension.
     * @param params an array that contains the data to be passed to the extension
     *        (set to empty array if no data is required).
     * 
     * @see #XTMSG_TYPE_STR
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public void sendXtMessage(String xtName, String cmd, String[] params)
    {
        sendXtMessage(xtName, cmd, params, activeRoomId);
    }

    /**
     * Send a request to a server side extension using String protocol. 
     * 
     * <p>
     * <b>NOTE</b>: The String-based protocol can be very useful for realtime applications/games where
     * reducing the amount of data is the highest priority.
     * </p>
     * 
     * <p>
     * The following example shows how to notify a multiplayer game server-side extension that
     * a game action occurred.
     * <pre>
     * // A bullet is being fired
     * 
     * // The array contains the x and y position, speed and the angle.
     * String[] params = {"bullet", "100", "200", "10", "45"};
     * 
     * // Invoke "fire" command on the extension called "gameExt", using String protocol
     * smartFox.sendXtMessage("gameExt", "fire", params, 25);
     * </pre>
     * </p>
     * 
     * @param xtName the name of the extension (see also the {@link #createRoom} method).
     * @param cmd the name of the action/command to execute in the extension.
     * @param params an array that contains the data to be passed to the extension
     *        (set to empty array if no data is required).
     * @param roomId the id of the room where the request was originated, in case of multi-room join.
     * 
     * @see #XTMSG_TYPE_STR
     * @see SFSEvent#onExtensionResponse
     * 
     * @since SmartFoxServer Pro v1.x.x
     */
    public void sendXtMessage(String xtName, String cmd, String[] params, int roomId)
    {
        if(!checkRoomList())
        {
            return;
        }

        String hdr = MSG_STR + "xt" + MSG_STR + xtName + MSG_STR + cmd + MSG_STR + roomId + MSG_STR;

        for(String param : params)
        {
            hdr += param + MSG_STR;
        }

        sendString(hdr);
    }

    /**
     * Block or unblock a user in the buddy list.
     * <p>
     * When a buddy is blocked, SmartFoxServer does not deliver private messages from/to that user.
     * </p>
     * 
     * <p>
     * The following example shows how to block a user from the buddy list.
     * <pre>
     * smartFox.setBuddyBlockStatus("jack", true);
     * </pre>
     * </p>
     * 
     * @param buddyName the name of the buddy to be blocked or unblocked.
     * @param status {@code true} to block the buddy, {@code false} to unblock the buddy.
     * 
     * @see #buddyList
     * 
     * @since SmartFoxServer Pro v1.6.0
     */
    public void setBuddyBlockStatus(String buddyName, boolean status)
    {
        Buddy b = getBuddyByName(buddyName);

        if(b != null)
        {
            if(b.isBlocked() != status)
            {
                b.setBlocked(status);

                String xmlMsg = "<n x='" + (status ? "1" : "0") +"'>" + buddyName + "</n>";
                send(MESSAGE_HEADER_SYSTEM, "setB", -1, xmlMsg);

                // Fire internal update
                SFSObject params = new SFSObject();
                params.put("buddy", b);

                SFSEvent evt = new SFSEvent(SFSEvent.onBuddyListUpdate, params);
                dispatchEvent(evt);
            }
        }
    }

    /**
     * Set the current user's Buddy Variables.
     * <p>
     * This method allows to set a number of properties of the current user as buddy of other users;
     * in other words these variables will be received by the other users who have the current user as a buddy.
     * </p>
     * 
     * <p>
     * Buddy Variables are the best way to share user's informations with all the other users having him/her in
     * their buddy list.: for example the nickname, the current audio track the user is listening to, etc.
     * The most typical usage is to set a variable containing the current user status, like "available", "occupied",
     * "away", "invisible", etc.
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: before the release of SmartFoxServer Pro v1.6.0, Buddy Variables could not be stored,
     * and existed during the user session only. SmartFoxServer Pro v1.6.0 introduced the ability to persist (store) all
     * Buddy Variables and the possibility to save "offline Buddy Variables" (see the following usage notes).
     * </p>
     * 
     * <p>
     * <b>NOTE</b>: let's assume that three users (A, B and C) use an "istant messenger"-like application,
     * and user A is part of the buddy lists of users B and C. <br>
     * If user A sets his own variables (using the {@link #setBuddyVariables} method),
     * the {@link #myBuddyVars} list on his client gets populated and a {@link SFSEvent#onBuddyListUpdate} event is
     * dispatched to users B and C. User B and C can then read those variables in their own buddy lists by means of
     * the <b>variables</b> property on the buddy object (which can be retrieved from the {@link #buddyList} list by
     * means of the {@link #getBuddyById} or {@link #getBuddyByName} methods).
     * 
     * <hr />
     * 
     * If the buddy list's <i>advanced security mode</i> is used (see the SmartFoxServer server-side configuration),
     * Buddy Variables persistence is enabled: in this way regular variables are saved when a user goes offline and
     * they are restored (and dispatched to the other users) when their owner comes back online.
     * Also, setting the {@code <offLineBuddyVariables>} parameter to {@code true}, offline variables can be used:
     * this kind of Buddy Variables is loaded regardless the buddy is online or not, providing further informations for
     * each entry in the buddy list. A typical usage for offline variables is to define a buddy image or additional
     * informations such as country, email, rank, etc. To creare an offline Buddy Variable, the "$" character must be
     * placed before the variable name.
     * </p>
     * 
     * <p>
     * The following example shows how to set three variables containing the user's status,
     * the current audio track the user listening to and the user's rank. The last one is an offline variable.
     * <pre>
     * Map&lt;String, String&gt; bVars = new HashMap();
     * bVars.put("status", "away");
     * bVars.put("track", "One Of These Days");
     * bVars.put("$rank", "guru");
     * smartFox.setBuddyVariables(bVars);
     * </pre>
     * </p>
     * 
     * @param varList an associative array, where the key is the name of the variable and the value is
     *                the variable's value. Buddy Variables should all be strings.
     *                If you need to use other data types you should apply the appropriate type casts.
     * 
     * @see #myBuddyVars
     * @see SFSEvent#onBuddyListUpdate
     * 
     * @since SmartFoxServer Basic (except <i>advanced mode</i>) / Pro v1.x.x
     */
    public void setBuddyVariables(Map<String, String> varList)
    {			
        // Encapsulate Variables
        String xmlMsg = "<vars>";

        // Reference to the user setting the variables
        for(String vName : varList.keySet())
        {
            String vValue = varList.get(vName);

            // if variable is new or updated send it and update locally
            if(!myBuddyVars.get(vName).equals(vValue))
            {
                myBuddyVars.put(vName, vValue);
                xmlMsg += "<var n='" + vName + "'><![CDATA[" + vValue + "]]></var>";
            }
        }

        xmlMsg += "</vars>";

        this.send(MESSAGE_HEADER_SYSTEM, "setBvars", -1, xmlMsg);
    }

    /**
     * Set one or more Room Variables.
     * <p>
     * Room Variables are a useful feature to share data across the clients, keeping it in a centralized place
     * on the server. When a user sets/updates/deletes one or more Room Variables, all the other users in the
     * same room are notified. Allowed data types for Room Variables are Numbers, Strings and Booleans;
     * in order save bandwidth, Arrays and Objects are not supported.
     * Nevertheless, an array of values can be simulated, for example, by using an index in front of
     * the name of each variable (check one of the following examples). <br>
     * If a Room Variable is set to {@code null}, it is deleted from the server.
     * </p>
     * 
     * <p>
     * The following example shows how to save a persistent Room Variable called "score".
     * This variable won't be destroyed when its creator leaves the room.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, false, true);
     * rVars.put("score", variable);
     * smartFox.setRoomVariables(rVars);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to save a persistent Room Variable called "score".
     * This variable won't be destroyed when its creator leaves the room.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, false, true);
     * rVars.put("score", variable);
     * smartFox.setRoomVariables(rVars);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to save two Room Variables at once.
     * The one called "bestTime" is private and no other user except its owner can modify it.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest bestTime = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, true);
     * rVars.put("bestTime", bestTime);
     * RoomVariableRequest bestLap = new RoomVariableRequest("120", SFSVariable.TYPE_NUMBER);
     * rVars.put("bestLap", bestLap);
     * smartFox.setRoomVariables(rVars);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to delete a Room Variable called "bestTime" by
     * setting its value to {@code null}.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest(null, SFSVariable.TYPE_NULL);
     * rVars.put("bestTime", variable);
     * smartFox.setRoomVariables(rVars);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to send an array-like set of data without consuming too much bandwidth.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * String[] names = {"john", "dave", "sam"};
     * for(int i = 0; i &lt; names.lenght; i++)
     * {
     *     RoomVariableRequest variable = new RoomVariableRequest(names[i], SFSVariable.TYPE_STRING);
     *     rVars.put("name" + i, variable);
     * }
     * smartFox.setRoomVariables(rVars);
     * </pre>
     * </p>
     *
     * <p>
     * The following example shows how to handle the data sent in the previous example when the
     * {@link SFSEvent#onRoomVariablesUpdate} event is received.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onRoomVariablesUpdate, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           Set&lt;String&gt; changedVars = (Set&lt;String&gt;)evt.getParams().get("changedVars");
     *           Room room = (Room)evt.getParams().get("room");
     *           for(String v : changedVars)
     *           {
     *               System.out.println(v + " room variable was updated; new value is: " + room.getVariable(v).getValue());
     *           }
     *       }
     *   });
     * </pre>
     * </p>
     * 
     * @param vars {@code Map} containing the room variables name as key and the room variables as value.
     * 
     * @see Room#getVariable
     * @see Room#getVariables
     * @see SFSEvent#onRoomVariablesUpdate
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void setRoomVariables(Map<String, RoomVariableRequest>vars)
    {
        setRoomVariables(vars, activeRoomId, true);
    }

    /**
     * Set one or more Room Variables.
     * <p>
     * Room Variables are a useful feature to share data across the clients, keeping it in a centralized place
     * on the server. When a user sets/updates/deletes one or more Room Variables, all the other users in the
     * same room are notified. Allowed data types for Room Variables are Numbers, Strings and Booleans;
     * in order save bandwidth, Arrays and Objects are not supported.
     * Nevertheless, an array of values can be simulated, for example, by using an index in front of
     * the name of each variable (check one of the following examples). <br>
     * If a Room Variable is set to {@code null}, it is deleted from the server.
     * </p>
     * 
     * <p>
     * The following example shows how to save a persistent Room Variable called "score".
     * This variable won't be destroyed when its creator leaves the room.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, false, true);
     * rVars.put("score", variable);
     * smartFox.setRoomVariables(rVars, 25);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to save a persistent Room Variable called "score".
     * This variable won't be destroyed when its creator leaves the room.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, false, true);
     * rVars.put("score", variable);
     * smartFox.setRoomVariables(rVars, 25);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to save two Room Variables at once.
     * The one called "bestTime" is private and no other user except its owner can modify it.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest bestTime = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, true);
     * rVars.put("bestTime", bestTime);
     * RoomVariableRequest bestLap = new RoomVariableRequest("120", SFSVariable.TYPE_NUMBER);
     * rVars.put("bestLap", bestLap);
     * smartFox.setRoomVariables(rVars, 25);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to delete a Room Variable called "bestTime" by
     * setting its value to {@code null}.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest(null, SFSVariable.TYPE_NULL);
     * rVars.put("bestTime", variable);
     * smartFox.setRoomVariables(rVars, 25);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to send an array-like set of data without consuming too much bandwidth.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * String[] names = {"john", "dave", "sam"};
     * for(int i = 0; i &lt; names.lenght; i++)
     * {
     *     RoomVariableRequest variable = new RoomVariableRequest(names[i], SFSVariable.TYPE_STRING);
     *     rVars.put("name" + i, variable);
     * }
     * smartFox.setRoomVariables(rVars, 25);
     * </pre>
     * </p>
     *
     * <p>
     * The following example shows how to handle the data sent in the previous example when the
     * {@link SFSEvent#onRoomVariablesUpdate} event is received.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onRoomVariablesUpdate, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           Set&lt;String&gt; changedVars = (Set&lt;String&gt;)evt.getParams().get("changedVars");
     *           Room room = (Room)evt.getParams().get("room");
     *           for(String v : changedVars)
     *           {
     *               System.out.println(v + " room variable was updated; new value is: " + room.getVariable(v).getValue());
     *           }
     *       }
     *   });
     * </pre>
     * </p>
     * 
     * @param vars {@code Map} containing the room variables name as key and the room variables as value.
     * @param roomId the id of the room where the variables should be set, in case of molti-room join.
     * 
     * @see Room#getVariable
     * @see Room#getVariables
     * @see SFSEvent#onRoomVariablesUpdate
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void setRoomVariables(Map<String, RoomVariableRequest>vars, int roomId)
    {
        setRoomVariables(vars, roomId, true);
    }

    /**
     * Set one or more Room Variables.
     * <p>
     * Room Variables are a useful feature to share data across the clients, keeping it in a centralized place
     * on the server. When a user sets/updates/deletes one or more Room Variables, all the other users in the
     * same room are notified. Allowed data types for Room Variables are Numbers, Strings and Booleans;
     * in order save bandwidth, Arrays and Objects are not supported.
     * Nevertheless, an array of values can be simulated, for example, by using an index in front of
     * the name of each variable (check one of the following examples). <br>
     * If a Room Variable is set to {@code null}, it is deleted from the server.
     * </p>
     * 
     * <p>
     * The following example shows how to save a persistent Room Variable called "score".
     * This variable won't be destroyed when its creator leaves the room.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, false, true);
     * rVars.put("score", variable);
     * smartFox.setRoomVariables(rVars, activeRoomId, true);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to save a persistent Room Variable called "score".
     * This variable won't be destroyed when its creator leaves the room.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, false, true);
     * rVars.put("score", variable);
     * smartFox.setRoomVariables(rVars, activeRoomId, true);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to save two Room Variables at once.
     * The one called "bestTime" is private and no other user except its owner can modify it.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest bestTime = new RoomVariableRequest("2500", SFSVariable.TYPE_NUMBER, true);
     * rVars.put("bestTime", bestTime);
     * RoomVariableRequest bestLap = new RoomVariableRequest("120", SFSVariable.TYPE_NUMBER);
     * rVars.put("bestLap", bestLap);
     * smartFox.setRoomVariables(rVars, activeRoomId, true);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to delete a Room Variable called "bestTime" by
     * setting its value to {@code null}.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest variable = new RoomVariableRequest(null, SFSVariable.TYPE_NULL);
     * rVars.put("bestTime", variable);
     * smartFox.setRoomVariables(rVars, activeRoomId, true);
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to send an array-like set of data without consuming too much bandwidth.
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * String[] names = {"john", "dave", "sam"};
     * for(int i = 0; i &lt; names.lenght; i++)
     * {
     *     RoomVariableRequest variable = new RoomVariableRequest(names[i], SFSVariable.TYPE_STRING);
     *     rVars.put("name" + i, variable);
     * }
     * smartFox.setRoomVariables(rVars, activeRoomId, true);
     * </pre>
     * </p>
     *
     * <p>
     * The following example shows how to handle the data sent in the previous example when the
     * {@link SFSEvent#onRoomVariablesUpdate} event is received.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onRoomVariablesUpdate, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           Set&lt;String&gt; changedVars = (Set&lt;String&gt;)evt.getParams().get("changedVars");
     *           Room room = (Room)evt.getParams().get("room");
     *           for(String v : changedVars)
     *           {
     *               System.out.println(v + " room variable was updated; new value is: " + room.getVariable(v).getValue());
     *           }
     *       }
     *   });
     * </pre>
     * </p>
     * 
     * <p>
     * The following example shows how to update a Room Variable without affecting the variable's ownership.
     * By default, when a user updates a Room Variable, he becomes the "owner" of that variable.
     * In some cases it could be needed to disable this behavoir by setting the <i>setOwnership</i> property to
     * {@code false}. 
     * <pre>
     * Map&lt;String, RoomVariableRequest&gt; rVars = new HashMap&lt;String, RoomVariableRequest&gt;();
     * RoomVariableRequest shipPosX = new RoomVariableRequest("100", SFSVariable.TYPE_NUMBER);
     * rVars.put("shipPosX", shipPosX);
     * RoomVariableRequest shipPosY = new RoomVariableRequest("200", SFSVariable.TYPE_NUMBER);
     * rVars.put("shipPosY", shipPosY);
     * smartFox.setRoomVariables(rVars, activeRoomId, false);
     * </pre>
     * </p>
     * 
     * @param vars {@code Map} containing the room variables name as key and the room variables as value.
     * @param roomId the id of the room where the variables should be set, in case of molti-room join.
     * @param setOwnership {@code false} to prevent the Room Variable change ownership when its value
     *                     is modified by another user.
     * 
     * @see Room#getVariable
     * @see Room#getVariables
     * @see SFSEvent#onRoomVariablesUpdate
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void setRoomVariables(Map<String, RoomVariableRequest>vars, int roomId, boolean setOwnership)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }
        
       String xmlMsg;

        if(setOwnership)
        {
            xmlMsg = "<vars>";
        }
        else
        {
            xmlMsg = "<vars so='0'>";
        }

        for(String varName : vars.keySet())
        {
            xmlMsg += getXmlRoomVariable(varName, vars.get(varName));
        }

        xmlMsg += "</vars>";

        send(MESSAGE_HEADER_SYSTEM, "setRvars", roomId, xmlMsg);
    }

    /**
     * Set on or more User Variables.
     * <p>
     * User Variables are a useful tool to store user data that has to be shared with other users.
     * When a user sets/updates/deletes one or more User Variables, all the other users in the same room are
     * notified. Allowed data types for User Variables are Numbers, Strings and Booleans;
     * Arrays and Objects are not supported in order save bandwidth.
     * If a User Variable is set to {@code null}, it is deleted from the server.
     * Also, User Variables are destroyed when their owner logs out or gets disconnected.
     * </p>
     * 
     * <p>
     * The following example shows how to save the user data (avatar name and position)
     * in an avatar chat application.
     * <pre>
     * Map&lt;String, SFSVariable&gt; uVars = new HashMap&lt;String, SFSVariable&gt;();
     * uVars.put("myAvatar", new SFSVariable("Homer", SFSVariable.TYPE_STRING));
     * uVars.put("posx", new SFSVariable("100", SFSVariable.TYPE_NUMBER));
     * uVars.put("posy", new SFSVariable("200", SFSVariable.TYPE_NUMBER));
     * smartFox.setUserVariables(uVars);
     * </pre>
     * </p>
     * 
     * @param vars {@code Map} containing the user variables name as key and the room variables as value.
     * 
     * @see User#getVariable
     * @see User#getVariables
     * @see SFSEvent#onUserVariablesUpdate
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void setUserVariables(Map<String, SFSVariable> vars)
    {
        setUserVariables(vars, activeRoomId);
    }

    /**
     * Set on or more User Variables.
     * <p>
     * User Variables are a useful tool to store user data that has to be shared with other users.
     * When a user sets/updates/deletes one or more User Variables, all the other users in the same room are
     * notified. Allowed data types for User Variables are Numbers, Strings and Booleans;
     * Arrays and Objects are not supported in order save bandwidth.
     * If a User Variable is set to {@code null}, it is deleted from the server.
     * Also, User Variables are destroyed when their owner logs out or gets disconnected.
     * </p>
     * 
     * <p>
     * The following example shows how to save the user data (avatar name and position)
     * in an avatar chat application.
     * <pre>
     * Map&lt;String, SFSVariable&gt; uVars = new HashMap&lt;String, SFSVariable&gt;();
     * uVars.put("myAvatar", new SFSVariable("Homer", SFSVariable.TYPE_STRING));
     * uVars.put("posx", new SFSVariable("100", SFSVariable.TYPE_NUMBER));
     * uVars.put("posy", new SFSVariable("200", SFSVariable.TYPE_NUMBER));
     * smartFox.setUserVariables(uVars, 25);
     * </pre>
     * </p>
     * 
     * @param vars {@code Map} containing the user variables name as key and the room variables as value.
     * @param roomId the room id where the request was originated, in case of molti-room join.
     * 
     * @see User#getVariable
     * @see User#getVariables
     * @see SFSEvent#onUserVariablesUpdate
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void setUserVariables(Map<String, SFSVariable>vars, int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }
        
        Room currRoom = getActiveRoom();
        User user = currRoom.getUser(myUserId);

        // Update local client
        user.setVariables(vars);

        // Prepare and send message
        String xmlMsg = getXmlUserVariable(vars);
        send(MESSAGE_HEADER_SYSTEM, "setUvars", roomId, xmlMsg);
    }

    /**
     * Turn a spectator inside a game room into a player. 
     * <p>
     * All spectators have their <b>player id</b> property set to -1; when a spectator becomes a player,
     * his player id gets a number > 0, representing the player number. The player id values are assigned by
     * the server, based on the order in which the players joined the room. If the user joined more than one
     * room, the id of the room where the switch should occurr must be passed to this method.
     * The switch operation is successful only if at least one player slot is available in the room.
     * </p>
     * 
     * <p>
     * The following example shows how to turn a spectator into a player.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onSpectatorSwitched, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           if(evt.getParams().getBool("success"))
     *           {
     *               System.out.println("You have been turned into a player; your player id is " + evt.getParams().get("newId"));
     *           }
     *           else
     *           {
     *               System.out.println("The attempt to switch from spectator to player failed");
     *           }
     *       }
     *   });
     * smartFox.switchSpectator();
     * </pre>
     * </p>
     * 
     * @see User#isSpectator
     * @see SFSEvent#onSpectatorSwitched
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void switchSpectator()
    {
        switchSpectator(activeRoomId);
    }

    /**
     * Turn a spectator inside a game room into a player. 
     * <p>
     * All spectators have their <b>player id</b> property set to -1; when a spectator becomes a player,
     * his player id gets a number > 0, representing the player number. The player id values are assigned by
     * the server, based on the order in which the players joined the room. If the user joined more than one
     * room, the id of the room where the switch should occurr must be passed to this method.
     * The switch operation is successful only if at least one player slot is available in the room.
     * </p>
     * 
     * <p>
     * The following example shows how to turn a spectator into a player.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onSpectatorSwitched, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           if(evt.getParams().getBool("success"))
     *           {
     *               System.out.println("You have been turned into a player; your player id is " + evt.getParams().get("newId"));
     *           }
     *           else
     *           {
     *               System.out.println("The attempt to switch from spectator to player failed");
     *           }
     * 
     *           smartFox.switchSpectator(25);
     *       }
     *   });
     * </pre>
     * </p>
     * 
     * @param roomId the id of the room where the spectator should be switched, in case of molti-room join.
     * 
     * @see User#isSpectator
     * @see SFSEvent#onSpectatorSwitched
     * 
     * @since SmartFoxServer Basic / Pro v1.x.x
     */
    public void switchSpectator(int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }
        
        send(MESSAGE_HEADER_SYSTEM, "swSpec", roomId, "");
    }

    /**
     * Turn a player inside a game room into a spectator.
     * <p>
     * All players have their <b>player id</b> property set to a value > 0;
     * when a spectator becomes a player, his playerId is set to -1.
     * </p>
     * <p>
     * If the user joined more than one room use
     * {@link #switchPlayer(int)} instead.
     * </p>
     * <p>
     * The switch operation is successful only if at least one spectator slot
     * is available in the room.
     * </p>
     *
     * <p>
     * The following example shows how to turn a spectator into a player.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onPlayerSwitched, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           if(evt.getParams().getBool("success"))
     *           {
     *               System.out.println("You have been turned into a player; your id is " + evt.getParams().get("newId"));
     *           }
     *           else
     *           {
     *               System.out.println("The attempt to switch from spectator to player failed");
     *           }
     *
     *           smartFox.switchPlayer();
     *       }
     *   });
     * </pre>
     * </p>
     *
     * @see #switchPlayer(int)
     * @see User#isSpectator()
     * @see SFSEvent#onPlayerSwitched
     * 
     * @since SmartFoxServer Pro v1.6.6
     */
    public void switchPlayer()
    {
        switchPlayer(activeRoomId);
    }

    /**
     * Turn a player inside a game room into a spectator.
     * <p>
     * All players have their <b>player id</b> property set to a value > 0;
     * when a spectator becomes a player, his playerId is set to -1.
     * </p>
     * <p>
     * The switch operation is successful only if at least one spectator slot
     * is available in the room.
     * </p>
     *
     * <p>
     * The following example shows how to turn a spectator into a player.
     * <pre>
     * smartFox.addEventListener(SFSEvent.onPlayerSwitched, new ISFSEventHandler()
     *   {
     *       public void handleEvent(SFSEvent evt)
     *       {
     *           if(evt.getParams().getBool("success"))
     *           {
     *               System.out.println("You have been turned into a player; your id is " + evt.getParams().get("newId"));
     *           }
     *           else
     *           {
     *               System.out.println("The attempt to switch from spectator to player failed");
     *     7      }
     *
     *           smartFox.switchPlayer(35);
     *       }
     *   });
     * </pre>
     * </p>
     * 
     * @param   roomId the id of the room where the player should be switched
     *                  to spectator.
     *
     * @see User#isSpectator()
     * @see SFSEvent#onPlayerSwitched
     * 
     * @since SmartFoxServer Pro v1.6.6
     */
    public void switchPlayer(int roomId)
    {
        if(!checkRoomList() || !checkJoin())
        {
            return;
        }

        send(MESSAGE_HEADER_SYSTEM, "swPl", roomId, "");
    }

    public void __logout()
    {
        initialize(true);
    }

    public void sendString(String strMessage)
    {
        debugMessage("[Sending - STR]: " + strMessage, Level.INFO);

        if(isHttpMode)
        {
            httpConnection.send(strMessage);
        }
        else
        {
            socketConnection.send(strMessage);
        }
    }

    public void sendJson(String jsMessage)
    {
        debugMessage("[Sending - JSON]: " + jsMessage, Level.INFO);

        if (isHttpMode)
        {
            httpConnection.send(jsMessage);
        }
        else
        {
            socketConnection.send(jsMessage);
        }
    }

    public long getBenchStartTime()
    {
        return this.benchStartTime;
    }

    public void clearRoomList()
    {
        this.roomList = new ConcurrentHashMap<Integer, Room>();
    }

    // -------------------------------------------------------
    // Private methods
    // -------------------------------------------------------

    private void initialize()
    {
        initialize(false);
    }
    
    private void initialize(boolean isLogOut)
    {
        // Clear local properties
        this.changingRoom = false;
        this.amIModerator = false;
        this.playerId = -1;
        this.activeRoomId = -1;
        this.myUserId = -1;
        this.myUserName = "";

        // Clear data structures
        this.roomList = new ConcurrentHashMap<Integer, Room>();
        this.buddyList = new CopyOnWriteArrayList<Buddy>();
        this.myBuddyVars = new ConcurrentHashMap<String, String>();

        // Set connection status
        if (!isLogOut)
        {
            this.connected = false;
            this.isHttpMode = false;
        }
    }
    
    private void parseConfig(IXMLElement configXML,  boolean autoConnect)
    {
        IXMLElement ipXML = configXML.getFirstChildNamed("ip");
        if(ipXML != null)
        {
            this.ipAddress = ipXML.getContent();
        }
        else
        {
            onConfigLoadFailure("\"ip\" tag is missing.");
            return;
        }
        
        IXMLElement portXML = configXML.getFirstChildNamed("port");
        if(portXML != null)
        {
            this.port = Integer.parseInt(portXML.getContent());
        }
        else
        {
            this.port = 9339;
        }
        
        IXMLElement zoneXML = configXML.getFirstChildNamed("zone");
        if(zoneXML != null)
        {
            this.defaultZone = zoneXML.getContent();
        }
        else
        {
            onConfigLoadFailure("\"zone\" tag is missing.");
            return;
        }
        
        IXMLElement blueBoxXML = configXML.getFirstChildNamed("blueBoxIpAddress");
        if(blueBoxXML != null)
        {
            this.blueBoxIpAddress = blueBoxXML.getContent();
        }
        
        IXMLElement blueBoxPortXML = configXML.getFirstChildNamed("blueBoxPort");
        if(blueBoxPortXML != null)
        {
            this.blueBoxPort = Integer.parseInt(blueBoxPortXML.getContent());
        }
        
        IXMLElement debugXML = configXML.getFirstChildNamed("debug");
        if(debugXML != null)
        {
            this.debug = debugXML.getContent().toLowerCase().equals("true");
        }

        IXMLElement smartConnectXML = configXML.getFirstChildNamed("smartConnect");
        if(smartConnectXML != null)
        {
            this.smartConnect = smartConnectXML.getContent().toLowerCase().equals("true");
        }

        IXMLElement httpPortXML = configXML.getFirstChildNamed("httpPort");
        if(httpPortXML != null)
        {
            this.httpPort = Integer.parseInt(httpPortXML.getContent());
        }

        IXMLElement httpPollSpeedXML = configXML.getFirstChildNamed("httpPollSpeed");
        if(httpPollSpeedXML != null)
        {
            this.httpConnection.setHttpPollSpeed(
                    Integer.parseInt(httpPollSpeedXML.getContent()));
        }
        
        IXMLElement separatorXML = configXML.getFirstChildNamed("rawProtocolSeparator");
        if(separatorXML != null)
        {
            setRawProtocolSeparator(separatorXML.getContent().charAt(0));
        }

        if(autoConnect)
        {
            this.connect(ipAddress, port);
        }
        else
        {
            // Dispatch onConfigLoadSuccess event
            SFSEvent sfsEvt = new SFSEvent(SFSEvent.onConfigLoadSuccess, new SFSObject());
            dispatchEvent(sfsEvt);
        }
    }

    private void onConfigLoadFailure(String msg)
    {
        debugMessage(msg, Level.SEVERE);
        SFSObject params = new SFSObject();
        params.put("message", msg);
        SFSEvent sfsEvt = new SFSEvent(SFSEvent.onConfigLoadFailure, params);
        dispatchEvent(sfsEvt);
    }

    private void onConfigLoadFailure(String msg, Exception ex)
    {
        debugMessage(msg + ex.getMessage(), ex, Level.SEVERE);
        SFSObject params = new SFSObject();
        params.put("message", ex.getMessage());
        SFSEvent sfsEvt = new SFSEvent(SFSEvent.onConfigLoadFailure, params);
        dispatchEvent(sfsEvt);
    }

    private void setupMessageHandlers()
    {
        sysHandler = new SysHandler(this);
        extHandler = new ExtHandler(this);

        addMessageHandler("sys", sysHandler);
        addMessageHandler("xt", extHandler);
    }


    private void addMessageHandler(String key, IMessageHandler handler)
    {
        if(this.messageHandlers.get(key) == null)
        {
            this.messageHandlers.put(key, handler);
        }
        else
        {
            debugMessage("Warning, message handler called: " + key + " already exist!", Level.WARNING);
        }
    }

    private void debugMessage(String message, Level level)
    {
        if(this.debug)
        {
            logger.log(level, message);

            SFSObject param = new SFSObject();
            param.put("message", message);
            SFSEvent evt = new SFSEvent(SFSEvent.onDebugMessage, param);
            dispatchEvent(evt);
        }
    }
    
    private void debugMessage(String message, Exception ex, Level level)
    {
        if(this.debug)
        {
            logger.log(level, message, ex);

            SFSObject param = new SFSObject();
            param.put("message", message);
            SFSEvent evt = new SFSEvent(SFSEvent.onDebugMessage, param);
            dispatchEvent(evt);
        }
    }

    private void send(String header, String action, int fromRoom, String message)
    {
        // Setup Msg Header
        String xmlMsg = makeXmlHeader(header);

        // Setup Body
        xmlMsg += "<body action='" + action + "' r='" + fromRoom + "'>" + message + "</body>" + closeHeader();

        debugMessage("[Sending]: " + xmlMsg, Level.INFO);

        if (isHttpMode)
        {
            httpConnection.send(xmlMsg);
        }
        else
        {
            socketConnection.send(xmlMsg);
        }
    }

    private String makeXmlHeader(String header)
    {
        String xmlData = "<msg";
        xmlData += " t ='" + header + "'";
        xmlData += ">";
        
        return xmlData;
    }

    private String closeHeader()
    {
        return "</msg>";
    }

    private boolean checkBuddyDuplicates(String buddyName)
    {
        // Check for buddy duplicates in the current buddy list

        boolean res = false;

        for(Buddy buddy : buddyList)
        {
            if(buddy.getName().equals(buddyName))
            {
                res = true;
                break;
            }
        }
        
        return res;
    }

    private void xmlReceived(String msg)
    {
        try
        {
            // Got XML response
            IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
            IXMLReader reader = StdXMLReader.stringReader(msg);
            parser.setReader(reader);
            IXMLElement xmlData = (IXMLElement) parser.parse();

            String handlerId = xmlData.getAttribute("t", null);
            if(handlerId != null)
            {
                IMessageHandler handler = messageHandlers.get(handlerId);

                if(handler != null)
                {
                    handler.handleMessage(xmlData, XTMSG_TYPE_XML);
                }
            }
        }
        catch (XMLException ex)
        {
            debugMessage("Error parsing XML string: " + ex.getMessage(), ex, Level.SEVERE);
        }
        catch(ClassNotFoundException ex)
        {
            debugMessage("Error parsing XML string: " + ex.getMessage(), ex, Level.SEVERE);
        }
        catch(InstantiationException ex)
        {
            debugMessage("Error parsing XML string: " + ex.getMessage(), ex, Level.SEVERE);
        }
        catch(IllegalAccessException ex)
        {
            debugMessage("Error parsing XML string: " + ex.getMessage(), ex, Level.SEVERE);
        }
    }

    private void jsonReceived(String msg)
    {
        try
        {
            
            JSONObject jso = new JSONObject(msg);

            String handlerId = jso.getString("t");
            IMessageHandler handler = messageHandlers.get(handlerId);

            if (handler != null) {
                handler.handleMessage(jso.getJSONObject("b"), XTMSG_TYPE_JSON);
            }
        }
        catch(JSONException ex)
        {
            debugMessage("Error parsing JSON string: " + ex.getMessage(), ex, Level.SEVERE);
        }
    }

    private void strReceived(String msg)
    {
        // Got String response
        String[] params = msg.substring(1).split(String.valueOf(MSG_STR));

        String handlerId = params[0];
        IMessageHandler handler = messageHandlers.get(handlerId);

        if(handler != null)
        {
            String[] spliceParams = new String[params.length - 1];
            System.arraycopy(params, 1, spliceParams, 0, spliceParams.length);
            handler.handleMessage(spliceParams, XTMSG_TYPE_STR);
        }
    }

    private String getXmlRoomVariable(String vName, RoomVariableRequest rVar)
    {
        // Get properties for this var
        String vValue = rVar.getValue();
        String vPrivate = (rVar.isPrivate()) ? "1" : "0";
        String vPersistent = (rVar.isPersistent()) ? "1":"0";

        String t = null;
        String type = rVar.getType();

        // Check type
        if(type.equals("boolean"))
        {
            t = "b";
            vValue = (vValue.equals("true")) ? "1" : "0";
        }
        else if(type.equals("number"))
        {
            t = "n";
        }
        else if(type.equals("string"))
        {
            t = "s";
        }
        else if(vValue == null)
        {
            t = "x";
            vValue = "";
        }

        if(t != null)
        {
            return "<var n='" + vName + "' t='" + t + "' pr='" + vPrivate + "' pe='" + vPersistent + "'><![CDATA[" + vValue + "]]></var>";
        }
        else
        {
            return "";
        }
    }

    private String getXmlUserVariable(Map<String, SFSVariable>uVars)
    {
        String xmlStr = "<vars>";
        String val;
        String t;
        String type;

        for(String key : uVars.keySet())
        {
            val = uVars.get(key).getValue();
            type = uVars.get(key).getType();
            t = null;

            // Check types
            if(type.equals("boolean"))
            {
                t = "b";
                val = (val.equals("true")) ? "1" : "0";
            }
            else if(type.equals("number"))
            {
                t = "n";
            }
            else if(type.equals("string"))
            {
                t = "s";
            }
            else if(val == null)
            {
                t = "x";
                val = "";
            }

            if(t != null)
            {
                xmlStr += "<var n='" + key + "' t='" + t + "'><![CDATA[" + val + "]]></var>";
            }
        }

        xmlStr += "</vars>";

        return xmlStr;
    }

    private boolean checkRoomList()
    {
        boolean success = true;

        if(this.roomList == null || this.roomList.size() == 0)
        {
            success = false;
            errorTrace("The room list is empty!\nThe client API cannot function properly until the room list is populated.\nPlease consult the documentation for more infos.");
        }

        return success;
    }
    
    private boolean checkJoin()
    {
        boolean success = true;

        if(this.activeRoomId < 0)
        {
            success = false;
            errorTrace("You haven't joined any rooms!\nIn order to interact with the server you should join at least one room.\nPlease consult the documentation for more infos.");
        }

        return success;
    }

    private void errorTrace(String msg)
    {
        logger.log(Level.WARNING, "Internal error: " + msg);
    }

    // -------------------------------------------------------
    // Internal Http Event Handlers
    // -------------------------------------------------------

    public void handleEvent(SFSEvent event)
    {
        if(event.getName().equals(HttpEvent.onHttpConnect))
        {
            this.onConnect(true);
            connected = true;
        }
        else if(event.getName().equals(HttpEvent.onHttpClose))
        {
            // Clear data
            initialize();

            // Fire event
            SFSEvent sfse = new SFSEvent(SFSEvent.onConnectionLost, new SFSObject());
            dispatchEvent(sfse);
        }
        else if(event.getName().equals(HttpEvent.onHttpData))
        {
            String data = event.getParams().getString("data");
            String[] messages = data.split(blueBoxSeparator);
            String message;

            if(messages[0].length() > 0)
            {	
                for (int i = 0; i < messages.length; i++)
                {
                    message = messages[i];

                    if (message.length() > 0)
                    {
                        handleMessage(message);
                    }
                }
            }
        }
        else if(event.getName().equals(HttpEvent.onHttpError))
        {
            if (!connected)
            {
                dispatchConnectionError();
            }
        }
    }

    // -------------------------------------------------------
    // Internal Socket Event Handlers
    // -------------------------------------------------------

    public void onConnect(boolean success)
    {
        if(success)
        {
            String xmlMsg = "<ver v='" + this.majVersion + this.minVersion + this.subVersion + "' />";
            send(MESSAGE_HEADER_SYSTEM, "verChk", 0, xmlMsg);
        }
        else
        {
            tryBlueBoxConnection();
        }
    }

    public void onClose()
    {
        // Clear data
        initialize();

        // Fire event
        SFSEvent sfse = new SFSEvent(SFSEvent.onConnectionLost, new SFSObject());
        dispatchEvent(sfse);
    }

    public void onError(Exception e)
    {
        debugMessage("Socket Error: " + e.getMessage(), e, Level.WARNING);
        tryBlueBoxConnection();
    }

    /*
    * New in 1.5.4
    */
    private void tryBlueBoxConnection()
    {
        if(!connected)
        {
            if(smartConnect)
            {
                debugMessage("Socket connection failed. Trying BlueBox", Level.INFO);
                
                isHttpMode = true;
                String __ip = blueBoxIpAddress != null ? blueBoxIpAddress : ipAddress;
                int __port = blueBoxPort != 0 ? blueBoxPort : httpPort;

                httpConnection.connect( __ip, __port );
            }
            else
            {
                dispatchConnectionError();
            }
        }
        else
        {
            debugMessage("[WARN] Connection error.", Level.WARNING);
            dispatchConnectionError();
        }
    }

    public void onData(String message)
    {			
        handleMessage(message);
    }

    /*
     * Analyze incoming message
     */
    private void handleMessage(String msg)
    {
        if(!msg.equals("ok"))
        {
            debugMessage("[ RECEIVED ]: " + msg + ", (len: " + msg.length() + ")", Level.INFO);
        }

        char type = msg.charAt(0);

        if(type == MSG_XML)
        {
            xmlReceived(msg);
        }	
        else if(type == MSG_STR)
        {
            strReceived(msg);
        }	
        else if(type == MSG_JSON)
        {
            jsonReceived(msg);
        }
    }

    private void dispatchConnectionError()
    {
        SFSObject params = new SFSObject();
        params.put("success", false);
        params.put("error", "I/O Error");

        SFSEvent sfse = new SFSEvent(SFSEvent.onConnection, params);
        dispatchEvent(sfse);
    }
}