package it.gotoandplay.smartfoxclient.handlers;

import it.gotoandplay.smartfoxclient.SFSEvent;
import it.gotoandplay.smartfoxclient.SmartFoxClient;
import it.gotoandplay.smartfoxclient.data.Buddy;
import it.gotoandplay.smartfoxclient.data.Room;
import it.gotoandplay.smartfoxclient.data.SFSObject;
import it.gotoandplay.smartfoxclient.data.User;
import it.gotoandplay.smartfoxclient.data.SFSVariable;
import it.gotoandplay.smartfoxclient.util.Entities;
import it.gotoandplay.smartfoxclient.util.SFSObjectSerializer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import net.n3.nanoxml.IXMLElement;

/*
 * SysHandler class: handles "sys" type messages.
 * 
 * @version 1.2.0
 * 
 * @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 SysHandler implements IMessageHandler
{
    private SmartFoxClient sfs;

    public SysHandler(SmartFoxClient sfs)
    {
        this.sfs = sfs;
    }

    public void handleMessage(Object msgObj, String type)
    {
        IXMLElement xmlData = (IXMLElement)msgObj;
        IXMLElement body = (IXMLElement)xmlData.getChildrenNamed("body").elementAt(0);
        String action = body.getAttribute("action", "");
        
        if(action.equals("apiOK"))
        {
            handleApiOK(xmlData);
        }
        else if(action.equals("apiKO"))
        {
            handleApiKO(xmlData);
        }        
        else if(action.equals("logOK"))
        {
            handleLoginOk(xmlData);
        }        
        else if(action.equals("logKO"))
        {
            handleLoginKo(xmlData);
        }        
        else if(action.equals("logout"))
        {
            handleLogout(xmlData);
        }        
        else if(action.equals("rmList"))
        {
            handleRoomList(xmlData);
        }        
        else if(action.equals("uCount"))
        {
            handleUserCountChange(xmlData);
        }        
        else if(action.equals("joinOK"))
        {
            handleJoinOk(xmlData);
        }        
        else if(action.equals("joinKO"))
        {
            handleJoinKo(xmlData);
        }        
        else if(action.equals("uER"))
        {
            handleUserEnterRoom(xmlData);
        }        
        else if(action.equals("userGone"))
        {
            handleUserLeaveRoom(xmlData);
        }        
        else if(action.equals("pubMsg"))
        {
            handlePublicMessage(xmlData);
        }        
        else if(action.equals("prvMsg"))
        {
            handlePrivateMessage(xmlData);
        }        
        else if(action.equals("dmnMsg"))
        {
            handleAdminMessage(xmlData);
        }        
        else if(action.equals("modMsg"))
        {
            handleModMessage(xmlData);
        }        
        else if(action.equals("dataObj"))
        {
            handleASObject(xmlData);
        }        
        else if(action.equals("rVarsUpdate"))
        {
            handleRoomVarsUpdate(xmlData);
        }        
        else if(action.equals("roomAdd"))
        {
            handleRoomAdded(xmlData);
        }        
        else if(action.equals("roomDel"))
        {
            handleRoomDeleted(xmlData);
        }        
        else if(action.equals("rndK"))
        {
            handleRandomKey(xmlData);
        }        
        else if(action.equals("roundTripRes"))
        {
            handleRoundTripBench(xmlData);
        }        
        else if(action.equals("uVarsUpdate"))
        {
            handleUserVarsUpdate(xmlData);
        }        
        else if(action.equals("createRmKO"))
        {
            handleCreateRoomError(xmlData);
        }        
        else if(action.equals("bList"))
        {
            handleBuddyList(xmlData);
        }        
        else if(action.equals("bUpd"))
        {
            handleBuddyListUpdate(xmlData);
        }        
        else if(action.equals("bAdd"))
        {
            handleBuddyAdded(xmlData);
        }        
        else if(action.equals("roomB"))
        {
            handleBuddyRoom(xmlData);
        }        
        else if(action.equals("leaveRoom"))
        {
            handleLeaveRoom(xmlData);
        }        
        else if(action.equals("swSpec"))
        {
            handleSpectatorSwitched(xmlData);
        }        
        else if(action.equals("swPl"))
        {
            handlePlayerSwitched(xmlData);
        }
        else if(action.equals("bPrm"))
        {
            handleAddBuddyPermission(xmlData);
        }        
        else if(action.equals("remB"))
        {
            handleRemoveBuddy(xmlData);
        }        
        else
        {
            System.out.println("Unknown sys command: " + action);
        }
    }
    
    // Handle correct API
    public void handleApiOK(IXMLElement xmlData)
    {
        sfs.setConnected(true);
        SFSObject params = new SFSObject();
        params.put("success", true);
        SFSEvent evt = new SFSEvent(SFSEvent.onConnection, params);
        sfs.dispatchEvent(evt);
    }


    // Handle obsolete API
    public void handleApiKO(IXMLElement xmlData)
    {
        SFSObject params = new SFSObject();
        params.put("success", false);
        params.put("error", "API are obsolete, please upgrade");

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


    // Handle successfull login
    public void handleLoginOk(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement login = body.getFirstChildNamed("login");
        int uid = Integer.parseInt(login.getAttribute("id", "-1"));
        int mod = Integer.parseInt(login.getAttribute("mod", "0"));
        String name = login.getAttribute("n", "");

        sfs.amIModerator = (mod == 1);
        sfs.myUserId = uid;
        sfs.myUserName = name;
        sfs.playerId = -1;

        SFSObject params = new SFSObject();
        params.put("success", true);
        params.put("name", name);
        params.put("error", "");

        SFSEvent evt = new SFSEvent(SFSEvent.onLogin, params);
        sfs.dispatchEvent(evt);

        // Request room list
        sfs.getRoomList();
    }

    // Handle unsuccessfull login
    public void handleLoginKo(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement login = body.getFirstChildNamed("login");
        String error = login.getAttribute("e", "");
        
        SFSObject params = new SFSObject();
        params.put("success", false);
        params.put("error", error);
        
        SFSEvent evt = new SFSEvent(SFSEvent.onLogin, params);
        sfs.dispatchEvent(evt);
    }

    // Handle successful logout
    public void handleLogout(IXMLElement xmlData)
    {
        sfs.__logout();

        SFSEvent evt = new SFSEvent(SFSEvent.onLogout, new SFSObject());
        sfs.dispatchEvent(evt);
    }

    // Populate the room list for this zone and fire the event
    public void handleRoomList(IXMLElement xmlData)
    {
        //sfs.clearRoomList();
        Map<Integer, Room> roomList = sfs.getAllRooms();
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement rmList = body.getFirstChildNamed("rmList");
        Vector<IXMLElement> rooms = rmList.getChildrenNamed("rm");
        
        for(Iterator<IXMLElement> i = rooms.iterator(); i.hasNext();)
        {
            IXMLElement roomXml = i.next();
            int roomId = Integer.parseInt(roomXml.getAttribute("id", "-1"));
            String roomName = roomXml.getFirstChildNamed("n").getContent();
            int maxu = Integer.parseInt(roomXml.getAttribute("maxu", "0"));
            int maxs = Integer.parseInt(roomXml.getAttribute("maxs", "0"));
            int temp = Integer.parseInt(roomXml.getAttribute("temp", "0"));
            int game = Integer.parseInt(roomXml.getAttribute("game", "0"));
            int priv = Integer.parseInt(roomXml.getAttribute("priv", "0"));
            int lmb = Integer.parseInt(roomXml.getAttribute("lmb", "0"));
            int ucnt = Integer.parseInt(roomXml.getAttribute("ucnt", "0"));
            int scnt = Integer.parseInt(roomXml.getAttribute("scnt", "0"));
            
            Room room = new Room(roomId, roomName, maxu, maxs,
                    temp == 1, game == 1, priv == 1, lmb == 1, ucnt, scnt);

            // Handle Room Variables
            if(roomXml.getFirstChildNamed("vars") != null && roomXml.getFirstChildNamed("vars").hasChildren())
            {
                populateVariables(room.getVariables(), roomXml);
            }

            //Merge with current room list data, to avoid destroying previous data
	    //@since 1.6.0
            Room oldRoom = roomList.get(room.getId());
            if(oldRoom != null)
            {
                room.setVariables(oldRoom.getVariables());
                room.setUserList(oldRoom.getUserList());
            }

            // Add room
            roomList.put(roomId, room);
        }
        
        SFSObject params = new SFSObject();
        params.put("roomList", roomList);

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

    // Handle the user count change in a room
    public void handleUserCountChange(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int uCount = Integer.parseInt(body.getAttribute("u", "0"));
        int sCount = Integer.parseInt(body.getAttribute("s", "0"));
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));

        Room room = sfs.getAllRooms().get(roomId);

        if(room != null)
        {
            room.setUserCount(uCount);
            room.setSpectatorCount(sCount);

            SFSObject params = new SFSObject();
            params.put("room", room);

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


    // Successfull room Join
    public void handleJoinOk(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        IXMLElement roomVarsXml = body;
        Vector<IXMLElement> userListXml = body.getFirstChildNamed("uLs").getChildrenNamed("u");
        IXMLElement playerIdXML = body.getFirstChildNamed("pid");
        int playerId = 0;
        if(playerIdXML != null)
        {
            playerId = Integer.parseInt(playerIdXML.getAttribute("id", "0"));
        }

        // Set current active room
        sfs.activeRoomId = roomId;

        // get current Room and populates usrList
        Room currRoom = sfs.getRoom(roomId);

        // Clear the old data, we need to start from a clean list
        currRoom.clearUserList();

        // Set the player ID
        // -1 = no game room
        sfs.playerId = playerId;

        // Also set the myPlayerId in the room
        // for multi-room applications
        currRoom.setMyPlayerIndex(playerId);

        // Handle Room Variables
        if(roomVarsXml.getFirstChildNamed("vars") != null && roomVarsXml.getFirstChildNamed("vars").hasChildren())
        {
                currRoom.clearVariables();
                populateVariables(currRoom.getVariables(), roomVarsXml);
        }

        // Populate Room userList
        for(Iterator<IXMLElement> i = userListXml.iterator(); i.hasNext();)
        {
            IXMLElement usr = i.next();
            // grab the user properties
            String name = usr.getFirstChildNamed("n").getContent();
            int id = Integer.parseInt(usr.getAttribute("i", "-1"));
            int mod = Integer.parseInt(usr.getAttribute("m", "0"));
            int spec = Integer.parseInt(usr.getAttribute("s", "0"));                
            boolean isMod = mod == 1;
            boolean isSpec = spec == 1;
            int pId = Integer.parseInt(usr.getAttribute("p", "-1"));

            // Create and populate User
            User user = new User(id, name);
            user.setModerator(isMod);
            user.setIsSpectator(isSpec);
            user.setPlayerId(pId);

            // Handle user variables
            if(usr.getFirstChildNamed("vars") != null && usr.getFirstChildNamed("vars").hasChildren())
            {
                populateVariables(user.getVariables(), usr);
            }

            // Add user
            currRoom.addUser(user, id);
        }

        // operation completed, release lock
        sfs.changingRoom = false;

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("room", currRoom);

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

    // Failed room Join
    public void handleJoinKo(IXMLElement xmlData)
    {
        sfs.changingRoom = false;

        SFSObject params = new SFSObject();
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement error = body.getFirstChildNamed("error");
        String errorMsg = error.getAttribute("msg", "");
        params.put("error", errorMsg);

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

    // New user enters the room
    public void handleUserEnterRoom(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        IXMLElement user = body.getFirstChildNamed("u");
        int usrId = Integer.parseInt(user.getAttribute("i", "-1"));
        IXMLElement usrNameXML = user.getFirstChildNamed("n");
        String usrName;
        if(usrNameXML != null)
        {
            usrName = usrNameXML.getContent();
        }
        else
        {
            usrName = "";
        }
        int mod = Integer.parseInt(user.getAttribute("m", "0"));
        int spec = Integer.parseInt(user.getAttribute("s", "0"));
        boolean isMod = mod == 1;
        boolean isSpec = spec == 1;
        int pid = Integer.parseInt(user.getAttribute("p", "-1"));

        Room currRoom = sfs.getRoom(roomId);

        // Create new user object
        User newUser = new User(usrId, usrName);
        newUser.setModerator(isMod);
        newUser.setIsSpectator(isSpec);
        newUser.setPlayerId(pid);

        // Populate user vars
        if(user.getFirstChildNamed("vars") != null && user.getFirstChildNamed("vars").hasChildren())
        {
            populateVariables(newUser.getVariables(), user);
        }

        // Add user to room
        currRoom.addUser(newUser, usrId);

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("roomId", roomId);
        params.put("user", newUser);

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

    // User leaves a room
    public void handleUserLeaveRoom(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        IXMLElement user = body.getFirstChildNamed("user");
        int userId = Integer.parseInt(user.getAttribute("id", "-1"));

        // Get room
        Room theRoom = sfs.getRoom(roomId);

        // Get user name
        String uName = theRoom.getUser(userId).getName();

        // Remove user
        theRoom.removeUser(userId);

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("roomId", roomId);
        params.put("userId", userId);
        params.put("userName", uName);

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

    public void handlePublicMessage(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        String message = body.getFirstChildNamed("txt").getContent();
        IXMLElement user = body.getFirstChildNamed("user");
        int userId = Integer.parseInt(user.getAttribute("id", "-1"));

        User sender = sfs.getRoom(roomId).getUser(userId);

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("message", Entities.decodeEntities(message));
        params.put("sender", sender);
        params.put("roomId", roomId);

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

    public void handlePrivateMessage(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        String message = body.getFirstChildNamed("txt").getContent();
        IXMLElement user = body.getFirstChildNamed("user");
        int userId = Integer.parseInt(user.getAttribute("id", "-1"));

        User sender = sfs.getRoom(roomId).getUser(userId);

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("message", Entities.decodeEntities(message));
        params.put("sender", sender);
        params.put("roomId", roomId);
        params.put("userId", userId);

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

    public void handleAdminMessage(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        String message = body.getFirstChildNamed("txt").getContent();

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("message", Entities.decodeEntities(message));

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

    public void handleModMessage(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        String message = body.getFirstChildNamed("txt").getContent();
        IXMLElement user = body.getFirstChildNamed("user");
        int userId = Integer.parseInt(user.getAttribute("id", "-1"));
        
        User sender = null;
        Room room = sfs.getRoom(roomId);

        if(room != null)
        {
            sender = sfs.getRoom(roomId).getUser(userId);
        }

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("message", Entities.decodeEntities(message));
        params.put("sender", sender);

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

    public void handleASObject(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        String xmlStr = body.getFirstChildNamed("dataObj").getContent();
        IXMLElement user = body.getFirstChildNamed("user");
        int userId = Integer.parseInt(user.getAttribute("id", "-1"));
        
        User sender = sfs.getRoom(roomId).getUser(userId);
        SFSObject asObj = SFSObjectSerializer.deserialize(xmlStr);

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("obj", asObj);
        params.put("sender", sender);

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

    public void handleRoomVarsUpdate(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));

        Room currRoom = sfs.getRoom(roomId);
        Set<String> changedVars = new HashSet<String>();

        // Handle Room Variables
        if(body.getFirstChildNamed("vars") != null && body.getFirstChildNamed("vars").hasChildren())
        {
            populateVariables(currRoom.getVariables(), body, changedVars);
        }

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("room", currRoom);
        params.put("changedVars", changedVars);

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

    public void handleUserVarsUpdate(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement user = body.getFirstChildNamed("user");
        int userId = Integer.parseInt(user.getAttribute("id", "-1"));
        
        Set<String> changedVars = new HashSet<String>();
        User varOwner = null;
        User returnUser = null;

        if(body.getFirstChildNamed("vars") != null && body.getFirstChildNamed("vars").hasChildren())
        {
            // Search the userId in all available rooms
            for(Room room : sfs.getAllRooms().values())
            {
                varOwner = room.getUser(userId);
				// found a user with the passed userId, populate his variables
                if(varOwner != null)
                {
                    if(returnUser == null)
                    {
                        returnUser = varOwner;
                    }
                    populateVariables(varOwner.getVariables(), body, changedVars);
                }
            }
        }

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("user", returnUser);
        params.put("changedVars", changedVars);

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

    private void handleRoomAdded(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement rm = body.getFirstChildNamed("rm");
        int rId = Integer.parseInt(rm.getAttribute("id", "-1"));
        String rName = rm.getFirstChildNamed("name").getContent();
        int rMax = Integer.parseInt(rm.getAttribute("max", "0"));
        int rSpec = Integer.parseInt(rm.getAttribute("spec", "0"));
        int temp = Integer.parseInt(rm.getAttribute("temp", "0"));
        boolean isTemp = temp == 1;
        int game = Integer.parseInt(rm.getAttribute("game", "0"));
        boolean isGame = game == 1;
        int priv = Integer.parseInt(rm.getAttribute("priv", "0"));
        boolean isPriv = priv == 1;
        int limbo = Integer.parseInt(rm.getAttribute("limbo", "0"));
        boolean isLimbo = limbo == 1;

        // Create room obj
        Room newRoom = new Room(rId, rName, rMax, rSpec, isTemp, isGame, isPriv, isLimbo, 0, 0);

        Map<Integer, Room> rList = sfs.getAllRooms();

        // Handle Room Variables
        if(rm.getFirstChildNamed("vars") != null && rm.getFirstChildNamed("vars").hasChildren())
        {
            populateVariables(newRoom.getVariables(), rm);
        }

        rList.put(rId, newRoom);

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("room", newRoom);

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

    private void handleRoomDeleted(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement rm = body.getFirstChildNamed("rm");
        int roomId = Integer.parseInt(rm.getAttribute("id", "-1"));

        Map<Integer, Room> roomList = sfs.getAllRooms();

        // Pass the last reference to the upper level
        // If there's no other references to this room in the upper level
        // This is the last reference we're keeping

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("room", roomList.get(roomId));

        // Remove reference from main room list
        roomList.remove(roomId);

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


    private void handleRandomKey(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        String key = body.getFirstChildNamed("k").getContent();

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

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

    private void handleRoundTripBench(IXMLElement xmlData)
    {
        long now = System.currentTimeMillis();
        long res = now - sfs.getBenchStartTime();

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("elapsed", res);

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

    private void handleCreateRoomError(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement room = body.getFirstChildNamed("room");
        String errMsg = room.getAttribute("e", "");

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("error", errMsg);

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

    private void handleBuddyList(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement bList = body.getFirstChildNamed("bList");
        IXMLElement myVars = body.getFirstChildNamed("mv");
        Buddy buddy;
        SFSObject params = new SFSObject();
        SFSEvent evt = null;

        // Get my buddy variables
        if(myVars != null && myVars.hasChildren())
        {
            Vector<IXMLElement> v = myVars.getChildrenNamed("v");
            for(Iterator<IXMLElement> i = v.iterator(); i.hasNext();)
            {
                IXMLElement myVar = i.next();
                String varName = myVar.getAttribute("n", "");
                sfs.myBuddyVars.put(varName, myVar.getContent());
            }
        }

        if(bList != null)
        {
            if(bList.hasChildren())
            {
                Vector<IXMLElement> bXML = bList.getChildrenNamed("b");
                for(Iterator<IXMLElement> i = bXML.iterator(); i.hasNext();)
                {
                    IXMLElement b = i.next();
                    buddy = new Buddy();
                    int online = Integer.parseInt(b.getAttribute("s", "0"));
                    buddy.setOnline(online == 1);
                    String name = b.getFirstChildNamed("n").getContent();
                    buddy.setName(name);
                    int id = Integer.parseInt(b.getAttribute("i", "-1"));
                    buddy.setId(id);
                    int blocked = Integer.parseInt(b.getAttribute("x", "0"));
                    buddy.setBlocked(blocked == 1);
                    
                    //Runs through buddy variables
                    IXMLElement bVarsXML = b.getFirstChildNamed("vs");

                    if(bVarsXML != null && bVarsXML.hasChildren())
                    {
                        Vector<IXMLElement> bVars = b.getChildrenNamed("v");
                        Map<String, String> variables = new HashMap<String, String>();
                        for(Iterator<IXMLElement> j = bVars.iterator(); j.hasNext();)
                        {
                            IXMLElement bVar = j.next();
                            String bVarName = bVar.getAttribute("n", "");
                            variables.put(bVarName, bVar.getContent());
                        }
                        buddy.setVariables(variables);
                    }

                    sfs.buddyList.add(buddy);
                }
            }

            // Fire event!
            params.put("list", sfs.buddyList);
            evt = new SFSEvent(SFSEvent.onBuddyList, params);
            sfs.dispatchEvent(evt);
        }
        // Buddy List load error!
        else
        {
            // Fire event!
            IXMLElement err = body.getFirstChildNamed("err");
            params.put("error", err.getContent());
            evt = new SFSEvent(SFSEvent.onBuddyListError, params);
            sfs.dispatchEvent(evt);
        }
    }

    private void handleBuddyListUpdate(IXMLElement xmlData)
    {
        SFSObject params = new SFSObject();
        SFSEvent evt = null;

        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement b = body.getFirstChildNamed("b");
        
        if(b != null)
        {
            Buddy buddy = new Buddy();
            int online = Integer.parseInt(b.getAttribute("s", "0"));
            buddy.setOnline(online == 1);
            String name = b.getFirstChildNamed("n").getContent();
            buddy.setName(name);
            int id = Integer.parseInt(b.getAttribute("i", "-1"));
            buddy.setId(id);
            int blocked = Integer.parseInt(b.getAttribute("x", "0"));
            buddy.setBlocked(blocked == 1);

            // Runs through buddy variables
            IXMLElement bVars = b.getFirstChildNamed("vs");

            Buddy tempB = null;
            boolean found = false;

            for(ListIterator<Buddy> i = sfs.buddyList.listIterator(); i.hasNext();)
            {
                tempB = i.next();

                if(tempB.getName().equals(buddy.getName()))
                {
                    buddy.setBlocked(tempB.isBlocked());
                    buddy.setVariables(tempB.getVariables());

                    //add/modify variables
                    if(bVars != null && bVars.hasChildren())
                    {
                        Vector<IXMLElement> bVarXML = bVars.getChildrenNamed("v");
                        for(Iterator<IXMLElement> j = bVarXML.iterator(); j.hasNext();)
                        {
                            IXMLElement bVar = j.next();
                            String bVarName = bVar.getAttribute("n", "");
                            buddy.getVariables().put(bVarName, bVar.getContent());
                        }
                    }
                    
                    // swap objects
                    sfs.buddyList.set(sfs.buddyList.indexOf(tempB), buddy);

                    found = true;
                    break;
                }
            }

            // Fire event!
            if(found)
            {
                params.put("buddy", buddy);

                evt = new SFSEvent(SFSEvent.onBuddyListUpdate, params);
                sfs.dispatchEvent(evt);
            }
        }
        // Buddy List load error!
        else
        {
            // Fire event!
            IXMLElement err = body.getFirstChildNamed("err");
            params.put("error", err.getContent());
            evt = new SFSEvent(SFSEvent.onBuddyListError, params);
            sfs.dispatchEvent(evt);
        }
    }

    private void handleAddBuddyPermission(IXMLElement xmlData)
    {	
        // Fire event!
        SFSObject params = new SFSObject();
        IXMLElement body = xmlData.getFirstChildNamed("body");
        params.put("sender", body.getFirstChildNamed("n").getContent());
        String message = "";
        IXMLElement txt = body.getFirstChildNamed("txt");

        if(txt != null)
        {
            message = Entities.decodeEntities(txt.getContent());
        }

        params.put("message", message);
        SFSEvent evt = new SFSEvent(SFSEvent.onBuddyPermissionRequest, params);
        sfs.dispatchEvent(evt);
    }

    private void handleBuddyAdded(IXMLElement xmlData)
    {
        Buddy buddy = new Buddy();
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement b = body.getFirstChildNamed("b");
        int online = Integer.parseInt(b.getAttribute("s", "0"));
        buddy.setOnline(online == 1);
        String name = b.getFirstChildNamed("n").getContent();
        buddy.setName(name);
        int id = Integer.parseInt(b.getAttribute("i", "-1"));
        buddy.setId(id);
        int blocked = Integer.parseInt(b.getAttribute("x", "0"));
        buddy.setBlocked(blocked == 1);
        
        //Runs through buddy variables
        IXMLElement bVarsXML = b.getFirstChildNamed("vs");

        if(bVarsXML != null && bVarsXML.hasChildren())
        {
            Vector<IXMLElement> bVars = b.getChildrenNamed("v");
            Map<String, String> variables = new HashMap<String, String>();
            for(Iterator<IXMLElement> j = bVars.iterator(); j.hasNext();)
            {
                IXMLElement bVar = j.next();
                String bVarName = bVar.getAttribute("n", "");
                variables.put(bVarName, bVar.getContent());
            }
            buddy.setVariables(variables);
        }

        sfs.buddyList.add(buddy);

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

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

    private void handleRemoveBuddy(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        String buddyName = body.getFirstChildNamed("n").getContent();

        Buddy buddy = null;

        for(Iterator<Buddy> i = sfs.buddyList.iterator(); i.hasNext();)
        {
            buddy = i.next();

            if(buddy.getName().equals(buddyName))
            {
                sfs.buddyList.remove(buddy);

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

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

                break;
            }
        }
    }

    private void handleBuddyRoom(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        String roomIds = body.getFirstChildNamed("br").getAttribute("r", "");
        String[] idStr = roomIds.split(",");
        int[] ids = new int[idStr.length];

        for(int i = 0; i < ids.length; i++)
        {
            ids[i] = Integer.parseInt(idStr[i]);
        }

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("idList", ids);

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

    private void handleLeaveRoom(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        IXMLElement rm = body.getFirstChildNamed("rm");
        int roomLeft = Integer.parseInt(rm.getAttribute("id", "-1"));

        // Fire event!
        SFSObject params = new SFSObject();
        params.put("roomId", roomLeft);

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

    private void handleSpectatorSwitched(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        IXMLElement pid = body.getFirstChildNamed("pid");
        int playerId = Integer.parseInt(pid.getAttribute("id", "-1"));

        // Synch user count, if switch successful
        Room theRoom = sfs.getRoom(roomId);

        if(playerId > 0)
        {
            theRoom.setUserCount(theRoom.getUserCount() + 1);
            theRoom.setSpectatorCount(theRoom.getSpectatorCount() - 1);
        }

        //
        // Update another user, who was turned into a player
        //
        String u = pid.getAttribute("u", null);
        if(u != null)
        {
            int userId = Integer.parseInt(u);
            User user = theRoom.getUser(userId);

            if(user != null)
            {
                user.setIsSpectator(false);
                user.setPlayerId(playerId);
            }
        }
        //
        // Update myself
        //
        else
        {
            sfs.playerId = playerId;

            // Fire event!
            SFSObject params = new SFSObject();
            params.put("success", sfs.playerId > 0);
            params.put("newId", sfs.playerId);
            params.put("room", theRoom);

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

    private void handlePlayerSwitched(IXMLElement xmlData)
    {
        IXMLElement body = xmlData.getFirstChildNamed("body");
        int roomId = Integer.parseInt(body.getAttribute("r", "-1"));
        IXMLElement pid = body.getFirstChildNamed("pid");
        int playerId = Integer.parseInt(pid.getAttribute("id", null));

        // Sync user count, if switch successful
        Room theRoom = sfs.getRoom(roomId);

        if(playerId == -1)
        {
            theRoom.setUserCount(theRoom.getUserCount() - 1);
            theRoom.setSpectatorCount(theRoom.getSpectatorCount() + 1);
        }

        //
        // Update another user, who was turned into a spectator
        //
        String u = pid.getAttribute("u", null);
        if(u != null)
        {
            int userId = Integer.parseInt(u);
            User user = theRoom.getUser(userId);

            if(user != null)
            {
                user.setIsSpectator(true);
                user.setPlayerId(playerId);
            }
        }
        //
        // Update myself
        //
        else
        {
            sfs.playerId = playerId;

            // Fire event!
            SFSObject params = new SFSObject();
            params.put("success", sfs.playerId == -1);
            params.put("newId", sfs.playerId);
            params.put("room", theRoom);

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


    //=======================================================================
    // Other class methods
    //=======================================================================
    /*
     * Takes an SFS variables XML node and store it in an array
     * Usage: for parsing room and user variables
     * 
     * @param variables the {@code Map) where the variable are stored:
     *                  Key is the variable name and the value is the variable.
     * @param xmlData the XML variables node
    */	
    private void populateVariables(Map<String, SFSVariable> variables, IXMLElement xmlData)
    {
        populateVariables(variables, xmlData, null);
    }
    
    /*
     * Takes an SFS variables XML node and store it in an array
     * Usage: for parsing room and user variables
     * 
     * @param variables the {@code Map) where the variable are stored:
     *                  Key is the variable name and the value is the variable.
     * @param xmlData the XML variables node
     * @param changedVars {@code Set} that will be populated with the names of the changed vars.
     *                    {code null} if not needed.
    */	
    private void populateVariables(Map<String, SFSVariable> variables, IXMLElement xmlData, Set<String> changedVars)
    {		
        IXMLElement vars = xmlData.getFirstChildNamed("vars");
        Vector<IXMLElement> var = vars.getChildrenNamed("var");
        for(Iterator<IXMLElement> i = var.iterator(); i.hasNext();)
        {
            IXMLElement v = i.next();
            String vName = v.getAttribute("n", "");
            String vType = v.getAttribute("t", "");
            String vValue = v.getContent();

            if(changedVars != null)
            {
                changedVars.add(vName);
            }
            
            if(vType.equals("x"))
            {
                variables.remove(vName);
            }
            else
            {
                variables.put(vName, new SFSVariable(vValue, vType));
            }
        }
    }
}
