/*
 * Copyright 2006-2010 Daniel Henninger.  All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */

package net.sf.kraken.session;

import net.sf.kraken.BaseTransport;
import net.sf.kraken.avatars.Avatar;
import net.sf.kraken.muc.MUCTransportSessionManager;
import net.sf.kraken.registration.Registration;
import net.sf.kraken.registration.RegistrationHandler;
import net.sf.kraken.roster.TransportBuddy;
import net.sf.kraken.roster.TransportBuddyManager;
import net.sf.kraken.type.*;

import org.apache.log4j.Logger;
import org.dom4j.Element;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.util.Base64;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.NotFoundException;
import org.xmpp.packet.*;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Interface for a transport session.
 *
 * This outlines all of the functionality that is required for a transport
 * to implement.  These are functions that the XMPP side of things are going
 * interact with.  The legacy transport itself is expected to handle messages
 * going to the Jabber user.
 *
 * @author Daniel Henninger
 */
public abstract class TransportSession<B extends TransportBuddy> {

    static Logger Log = Logger.getLogger(TransportSession.class);

    /**
     * Convenience constructor that includes priority.
     *
     * @param registration Registration this session is associated with.
     * @param jid JID of user associated with this session.
     * @param transport Transport this session is associated with.
     * @param priority Priority associated with session.
     */
    public TransportSession(Registration registration, JID jid, BaseTransport<B> transport, Integer priority) {
        this.jid = new JID(jid.toBareJID());
        this.registration = registration;
        this.transportRef = new WeakReference<BaseTransport<B>>(transport);
        mucSessionManager = new MUCTransportSessionManager<B>(this);
        addResource(jid.getResource(), priority);
        loadAvatar();
        Log.debug("Created "+transport.getType()+" session for "+jid+" as '"+registration.getUsername()+"'");
        
        // note: vcards are supported even if avatars are not!
        setSupportedFeature(SupportedFeature.vcardtemp); 
    }

    /**
     * Registration that this session is associated with.
     */
    public Registration registration;

    /**
     * Transport this session is associated with.
     */
    public WeakReference<BaseTransport<B>> transportRef;

    /**
     * The bare JID the session is associated with.
     */
    public JID jid;

    /**
     * All JIDs (including resources) that are associated with this session.
     */
    public ConcurrentHashMap<String,Integer> resources = new ConcurrentHashMap<String,Integer>();

    /**
     * List of packets that are pending delivery while a session is detached.
     */
    private ArrayList<Packet> pendingPackets = new ArrayList<Packet>();

    /**
     * Current highest resource.
     */
    public String highestResource = null;

    public IQ getRegistrationPacket() {
        return registrationPacket;
    }

    public void setRegistrationPacket(IQ registrationPacket) {
        this.registrationPacket = registrationPacket;
    }

    /**
     * Registration packet that is awaiting a response.
     */
    public IQ registrationPacket = null;

    /**
     * Is the roster locked for sync editing?
     */
    public boolean rosterLocked = false;

    /**
     * Contains a list of specific roster items that are locked.
     */
    public ArrayList<String> rosterItemsLocked = new ArrayList<String>();

    /**
     * The current login status on the legacy network.
     */
    public TransportLoginStatus loginStatus = TransportLoginStatus.LOGGED_OUT;

    /**
     * The current reason behind a connection failure, if one has occurred.
     */
    public ConnectionFailureReason failureStatus = ConnectionFailureReason.NO_ISSUE;

    /**
     * Supported features.
     */
    public ArrayList<SupportedFeature> supportedFeatures = new ArrayList<SupportedFeature>();

    /**
     * Number of reconnection attempts made.
     */
    public Integer reconnectionAttempts = 0;

    /**
     * If set, represents a unix timestamp when the session was detached.  The expectation being it'll get cleaned
     * up if it's been hanging around for too long.
     */
    public long detachTimestamp = 0;

    /**
     * Current presence status.
     */
    public PresenceType presence = PresenceType.unavailable;

    /**
     * Current verbose status.
     */
    public String verboseStatus = "";

    /**
     * Pending presence status
     */
    public PresenceType pendingPresence = null;

    /**
     * Pending verbose status.
     */
    public String pendingVerboseStatus = null;

    /**
     * Transport buddy manager.
     */
    public TransportBuddyManager<B> buddyManager = new TransportBuddyManager<B>(this);

    /**
     * This session's avatar.
     */
    public Avatar avatar;

    /**
     *  The MUC transport session manager.
     */
    public MUCTransportSessionManager<B> mucSessionManager;

    /**
     * Retrieve the muc session manager.
     *
     * @return muc session manager instance.
     */
    public MUCTransportSessionManager<B> getMUCSessionManager() {
        return mucSessionManager;
    }

    /**
     * Retrieve the buddy manager.
     *
     * @return buddy manager instance.
     */
    public TransportBuddyManager<B> getBuddyManager() {
        return buddyManager;
    }

    /**
     * Associates a resource with the session, and tracks it's priority.
     *
     * @param resource Resource string
     * @param priority Priority of resource
     */
    public void addResource(String resource, Integer priority) {
        resources.put(resource, priority);
        if (highestResource == null || resources.get(highestResource) < priority) {
            highestResource = resource;
        }
    }

    /**
     * Removes an association of a resource with the session.
     *
     * @param resource Resource string
     */
    public void removeResource(String resource) {
        resources.remove(resource);
        JID retJID = new JID(getJID().getNode(),getJID().getDomain(),resource);
        getBuddyManager().sendOfflineForAllAvailablePresences(retJID);
        // Send unavailable message to resource that went offline
        Presence p = new Presence();
        p.setType(Presence.Type.unavailable);
        p.setTo(retJID);
        p.setFrom(getTransport().getJID());
        getTransport().sendPacket(p);
        // Recalculate the highest resource
        if (resource.equals(highestResource)) {
            Integer highestPriority = -255;
            String tmpHighestResource = null;
            for (String res : resources.keySet()) {
                if (resources.get(res) > highestPriority) {
                    tmpHighestResource = res;
                    highestPriority = resources.get(res);
                }
            }
            highestResource = tmpHighestResource;
        }
    }

    /**
     * Updates the priority of a resource.
     *
     * @param resource Resource string
     * @param priority New priority
     */
    public void updateResource(String resource, Integer priority) {
        resources.put(resource, priority);
        Integer highestPriority = -255;
        String tmpHighestResource = null;
        for (String res : resources.keySet()) {
            if (resources.get(res) > highestPriority) {
                tmpHighestResource = res;
                highestPriority = resources.get(res);
            }
        }
        highestResource = tmpHighestResource;
    }

    /**
     * Removes all resources associated with a session.
     */
    public void removeAllResources() {
        for (String resource : resources.keySet()) {
            removeResource(resource); 
        }
    }

    /**
     * Returns the number of active resources.
     *
     * @return Number of active resources.
     */
    public int getResourceCount() {
        return resources.size();
    }

    /**
     * Detaches the session, leaving it running in the background and "suspended".
     */
    public void detachSession() {
        detachTimestamp = new Date().getTime();
    }

    /**
     * Attaches the session, indicating that it's actively used.
     */
    public void attachSession() {
        detachTimestamp = 0;
        for (Packet p : pendingPackets) {
            getTransport().sendPacket(p);
        }
        pendingPackets.clear();
    }

    /**
     * Stores a pending packet for later delivery
     */
    public void storePendingPacket(Packet p) {
        pendingPackets.add(p);
    }

    /**
     * Retrieves the detach timestamp for determining if the session is detached, and if so how long it's been
     * detached.
     */
    public long getDetachTimestamp() {
        return detachTimestamp;
    }

    /**
     * Returns if the roster is currently locked.
     *
     * @return true or false if the roster is locked.
     */
    public boolean isRosterLocked() {
        return rosterLocked;
    }

    /**
     * Returns if a specific roster item is currently locked.
     *
     * Also checks global lock.
     *
     * @param jid JID to check whether it's locked.
     * @return true or false if the roster item is locked.
     */
    public boolean isRosterLocked(String jid) {
        return rosterLocked || rosterItemsLocked.contains(jid);
    }

    /**
     * Locks the roster (typically used for editing during syncing).
     */
    public void lockRoster() {
        rosterLocked = true;
    }

    /**
     * Locks a specific roster item (typically used for direct roster item updates).
     *
     * @param jid JID to lock.
     */
    public void lockRoster(String jid) {
        if (!rosterItemsLocked.contains(jid)) {
            rosterItemsLocked.add(jid);
        }
    }

    /**
     * Unlocks the roster after sync editing is complete.
     */
    public void unlockRoster() {
        rosterLocked = false;
    }

    /**
     * Unlocks a specific roster item.
     *
     * @param jid JID to unlock.
     */
    public void unlockRoster(String jid) {
        if (rosterItemsLocked.contains(jid)) {
            rosterItemsLocked.remove(jid);
        }
    }

    /**
     * Retrieves the registration information associated with the session.
     *
     * @return Registration information of the user associated with the session.
     */
    public Registration getRegistration() {
        return registration;
    }

    /**
     * Retrieves the transport associated with the session.
     *
     * @return Transport associated with the session.
     */
    public BaseTransport<B> getTransport() {
        return transportRef.get();
    }

    /**
     * Retrieves the roster associated with the session.
     *
     * @return Roster associated with the session, or null if none.
     */
    public Roster getRoster() {
        try {
            return getTransport().getRosterManager().getRoster(getJID().getNode());
        }
        catch (UserNotFoundException e) {
            return null;
        }
    }

    /**
     * Retrieves the bare jid associated with the session.
     *
     * @return JID of the user associated with this session.
     */
    public JID getJID() {
        return jid;
    }

    /**
     * Retrieves the JID of the highest priority resource.
     *
     * @return Full JID including resource with highest priority.
     */
    public JID getJIDWithHighestPriority() {
        return new JID(jid.getNode(),jid.getDomain(),highestResource);
    }

    /**
     * Given a resource, returns whether it's priority is the highest.
     *
     * @param resource Resource to be checked.
     * @return True or false if the resource is the highest priority.
     */
    public Boolean isHighestPriority(String resource) {
        return (highestResource.equals(resource));
    }

    /**
     * Change the priority of a given resource.
     *
     * @param resource Resource to be changed.
     * @param priority New priority of resource
     */
    public void updatePriority(String resource, Integer priority) {
        boolean currentHighest = false;
        if (isHighestPriority(resource)) {
            currentHighest = true;
        }
        updateResource(resource, priority);
        if (currentHighest && !isHighestPriority(resource)) {
            Presence p = new Presence(Presence.Type.probe);
            p.setTo(getJIDWithHighestPriority());
            p.setFrom(getTransport().getJID());
            getTransport().sendPacket(p);
        }
    }

    /**
     * Retrieves the priority of a given resource.
     *
     * @param resource Resource to be checked.
     * @return Priority of the resource, or null if not found.
     */
    public Integer getPriority(String resource) {
        return resources.get(resource);
    }

    /**
     * Given a resource, returns whether the resource is currently associated with this session.
     *
     * @param resource Resource to be checked.
     * @return True of false if the resource is associated with this session.
     */
    public boolean hasResource(String resource) {
        return (resources.containsKey(resource));
    }

    /**
     * Sets a feature that the client supports.
     *
     * @param feature Feature that the session supports.
     */
    public void setSupportedFeature(SupportedFeature feature) {
        if (!supportedFeatures.contains(feature)) {
            supportedFeatures.add(feature);
        }
    }

    /**
     * Removes a feature that the client supports.
     *
     * @param feature Feature to be removed from the supported list.
     */
    public void removeSupportedFeature(SupportedFeature feature) {
        supportedFeatures.remove(feature);
    }

    /**
     * Clears all of the supported features recorded.
     */
    public void clearSupportedFeatures() {
        supportedFeatures.clear();
    }

    /**
     * Retrieves whether this session supports a specific feature.
     *
     * @param feature Feature to check for support of.
     * @return True or false if the session supports the specified feature.
     */
    public Boolean isFeatureSupported(SupportedFeature feature) {
        return supportedFeatures.contains(feature);
    }

    /**
     * Updates the login status.
     *
     * If there is a pending presence set, it will automatically commit the pending presence.
     *
     * @param status New login status.
     */
    public void setLoginStatus(TransportLoginStatus status) {
        loginStatus = status;
        if (status.equals(TransportLoginStatus.LOGGED_IN)) {
            reconnectionAttempts = 0;
            setFailureStatus(ConnectionFailureReason.NO_ISSUE);
            getRegistration().setLastLogin(new Date());
            if (pendingPresence != null && pendingVerboseStatus != null) {
                setPresenceAndStatus(pendingPresence, pendingVerboseStatus);
                pendingPresence = null;
                pendingVerboseStatus = null;
            }
            if (getRegistrationPacket() != null) {
                new RegistrationHandler(getTransport()).completeRegistration(this);
            }
        }
    }

    /**
     * Retrieves the current login status.
     *
     * @return Login status of session.
     */
    public TransportLoginStatus getLoginStatus() {
        return loginStatus;
    }


    public ConnectionFailureReason getFailureStatus() {
        return failureStatus;
    }

    public void setFailureStatus(ConnectionFailureReason failureStatus) {
        this.failureStatus = failureStatus;
    }

    /**
     * Returns true only if we are completely logged in.
     *
     * @return True or false whether we are currently completely logged in.
     */
    public Boolean isLoggedIn() {
        return (loginStatus == TransportLoginStatus.LOGGED_IN);
    }

    /**
     * Should be called when a session has been disconnected.
     *
     * This can be anything from a standard logout to a forced disconnect from the server.
     *
     * @param errorMessage Error message to send, or null if no message.  (only sent on full disconnect)
     */
    public void sessionDisconnected(String errorMessage) {
        reconnectionAttempts++;
        if (getRegistrationPacket() != null || !JiveGlobals.getBooleanProperty("plugin.gateway."+getTransport().getType()+"reconnect", true) || (reconnectionAttempts > JiveGlobals.getIntProperty("plugin.gateway."+getTransport().getType()+"reconnectattempts", 3))) {
            sessionDisconnectedNoReconnect(errorMessage);
        }
        else {
            cleanUp();
            Log.debug("Session "+getJID()+" disconnected from "+getTransport().getJID()+".  Reconnecting... (attempt "+reconnectionAttempts+")");
            setLoginStatus(TransportLoginStatus.RECONNECTING);
            ClientSession session = XMPPServer.getInstance().getSessionManager().getSession(getJIDWithHighestPriority());
            if (session != null) {
                logIn(getTransport().getPresenceType(session.getPresence()), null);
            }
            else {
                sessionDisconnectedNoReconnect(errorMessage);
            }
        }
    }

    /**
     * Should be called when a session has been disconnected but no reconnect attempt should be made.
     *
     * It is also called internally by sessionDisconnected to handle total failed attempt.
     *
     * @param errorMessage Error message to send, or null if no message.
     */
    public void sessionDisconnectedNoReconnect(String errorMessage) {
        Log.debug("Disconnecting session "+getJID()+" from "+getTransport().getJID());
        try {
            cleanUp();
        }
        catch (Exception e) {
            Log.info("sessionDisconnectedNoReconnect: Error="+ e);
        }
        setLoginStatus(TransportLoginStatus.LOGGED_OUT);
        if (getRegistrationPacket() != null) {
            new RegistrationHandler(getTransport()).completeRegistration(this);
        }
        else {
            Presence p = new Presence(Presence.Type.unavailable);
            p.setTo(getJID());
            p.setFrom(getTransport().getJID());
            getTransport().sendPacket(p);
            if (errorMessage != null) {
                getTransport().sendMessage(
                        getJIDWithHighestPriority(),
                        getTransport().getJID(),
                        errorMessage,
                        Message.Type.error
                );
            }
            getBuddyManager().sendOfflineForAllAvailablePresences(getJID());
        }
        buddyManager.resetBuddies();
        getTransport().getSessionManager().removeSession(getJID());
    }

    /**
     * Retrieves the current status.
     *
     * @return Current status setting.
     */
    public PresenceType getPresence() {
        return presence;
    }

    /**
     * Sets the current status.
     *
     * @param newpresence New presence to set to.
     */
    public void setPresence(PresenceType newpresence) {
        if (newpresence == null) {
            newpresence = PresenceType.unknown;
        }
        if (newpresence.equals(PresenceType.unavailable)) {
            verboseStatus = "";
        }
        if (!presence.equals(newpresence)) {
            Presence p = new Presence();
            p.setTo(getJID());
            p.setFrom(getTransport().getJID());
            getTransport().setUpPresencePacket(p, newpresence);
            if (!verboseStatus.equals("")) {
                p.setStatus(verboseStatus);
            }
            getTransport().sendPacket(p);
        }
        presence = newpresence;
    }

    /**
     * Retrieves the current verbose status.
     *
     * @return Current verbose status.
     */
    public String getVerboseStatus() {
        return verboseStatus;
    }

    /**
     * Sets the current verbose status.
     *
     * @param newstatus New verbose status.
     */
    public void setVerboseStatus(String newstatus) {
        if (newstatus == null) {
            newstatus = "";
        }
        if (!verboseStatus.equals(newstatus)) {
            Presence p = new Presence();
            p.setTo(getJID());
            p.setFrom(getTransport().getJID());
            getTransport().setUpPresencePacket(p, presence);
            if (!newstatus.equals("")) {
                p.setStatus(newstatus);
            }
            getTransport().sendPacket(p);
        }
        verboseStatus = newstatus;
    }

    /**
     * Convenience routine to set both presence and verbose status at the same time.
     *
     * @param newpresence New presence to set to.
     * @param newstatus New verbose status.
     */
    public void setPresenceAndStatus(PresenceType newpresence, String newstatus) {
        Log.debug("Updating status ["+newpresence+","+newstatus+"] for "+this);
        if (newpresence == null) {
            newpresence = PresenceType.unknown;
        }
        if (newstatus == null) {
            newstatus = "";
        }
        if (newpresence.equals(PresenceType.unavailable)) {
            newstatus = "";
        }
        if (!presence.equals(newpresence) || !verboseStatus.equals(newstatus)) {
            Presence p = new Presence();
            p.setTo(getJID());
            p.setFrom(getTransport().getJID());
            getTransport().setUpPresencePacket(p, newpresence);
            if (!newstatus.equals("")) {
                p.setStatus(newstatus);
            }
            getTransport().sendPacket(p);
        }
        presence = newpresence;
        verboseStatus = newstatus;
    }

    /**
     * Sets a pending presence and verbose status that will trigger when login status is LOGGED_IN.
     *
     * @param newpresence New presence to set to.
     * @param newstatus New verbose status.
     */
    public void setPendingPresenceAndStatus(PresenceType newpresence, String newstatus) {
        if (newpresence == null) {
            newpresence = PresenceType.unknown;
        }
        if (newstatus == null) {
            newstatus = "";
        }
        if (newpresence.equals(PresenceType.unavailable)) {
            newstatus = "";
        }
        pendingPresence = newpresence;
        pendingVerboseStatus = newstatus;
    }

    /**
     * Sends the current presence to the session user.
     *
     * @param to JID to send presence updates to.
     */
    public void sendPresence(JID to) {
        Presence p = new Presence();
        p.setTo(to);
        p.setFrom(getTransport().getJID());
        getTransport().setUpPresencePacket(p, presence);
        p.setStatus(verboseStatus);
        getTransport().sendPacket(p);
    }

    /**
     * Sends the current presence only if it's not unavailable.
     *
     * @param to JID to send presence updates to.
     */
    public void sendPresenceIfAvailable(JID to) {
       if (!presence.equals(PresenceType.unavailable)) {
           sendPresence(to);
       }
    }

    /**
     * Retrieves the avatar for this session.
     *
     * @return Avatar instance associated with the JID of this session.
     */
    public Avatar getAvatar() {
        return avatar;
    }

    /**
     * Sets the avatar associated with this session.
     *
     * @param avatar instance to associate with this session.
     */
    public void setAvatar(Avatar avatar) {
        this.avatar = avatar;
    }

    /**
     * Loads an avatar if one is available.
     *
     * Pulls from cache.  If nothing is in cache, we attempt to check their vcard info.
     */
    private void loadAvatar() {
        if (JiveGlobals.getBooleanProperty("plugin.gateway."+getTransport().getType()+".avatars", true)) {
            try {
                this.avatar = new Avatar(jid);
            }
            catch (NotFoundException e) {
                Element vcardElem = VCardManager.getInstance().getVCard(jid.getNode());
                if (vcardElem != null) {
                    Element photoElem = vcardElem.element("PHOTO");
                    if (photoElem != null) {
                        Element typeElem = photoElem.element("TYPE");
                        Element binElem = photoElem.element("BINVAL");
                        if (typeElem != null && binElem != null) {
                            byte[] imageData = Base64.decode(binElem.getText());
                            this.avatar = new Avatar(jid, imageData);
                        }
                    }
                }
            }
        }
    }

    /**
     * Provides a "neat" string representation of the session.
     */
    @Override
    public String toString() {
        return "TransportSession["+getJID()+"]";
    }

    /**
     * Updates status on legacy service.
     *
     * @param presenceType Type of presence.
     * @param verboseStatus Longer status description.
     */
    public abstract void updateStatus(PresenceType presenceType, String verboseStatus);

    /**
     * Adds a legacy contact to the legacy service.
     *
     * @param jid JID associated with the legacy contact.
     * @param nickname Nickname associated with the legacy contact.
     * @param groups Groups associated with the legacy contact.
     */
    public abstract void addContact(JID jid, String nickname, ArrayList<String> groups);

    /**
     * Removes a legacy contact from the legacy service.
     *
     * @param contact Transport buddy item associated with the legacy contact.
     */
    public abstract void removeContact(B contact);

    /**
     * Updates a legacy contact on the legacy service.
     *
     * @param contact Transport buddy item associated with the legacy contact.
     */
    public abstract void updateContact(B contact);

    /**
     * Accept a legacy contact's add friend request.
     *
     * @param jid JID associated with the target contact.
     */
    public abstract void acceptAddContact(JID jid);

    /**
     * Sends an outgoing message through the legacy service.
     *
     * @param jid JID associated with the target contact.
     * @param message Message to be sent.
     */
    public abstract void sendMessage(JID jid, String message);

    /**
     * Sends a chat state message through the legacy service.
     *
     * Not all chat states have to be handled.  Note that composing message event
     * is sent through this as well.  (XEP-0022)  Primarily this is used with XEP-0085.
     *
     * @param jid JID associated with the target contact.
     * @param chatState Chat state to be reflected in the legacy service.
     */
    public abstract void sendChatState(JID jid, ChatStateType chatState);

    /**
     * Sends a buzz notification through the legacy service.
     *
     * If the legacy service does not support this, ignore it.  Though sometimes a message
     * might be included and you may want to handle that in some sort of special way.
     *
     * @param jid JID associated with the target contact.
     * @param message Message tied to the buzz.
     */
    public abstract void sendBuzzNotification(JID jid, String message);

    /**
     * Updates the session's avatar on the legacy service.
     *
     * If the legacy service does not support this, ignore it.
     *
     * @param type Mime type of image.
     * @param data Binary data (byte array) of image.
     */
    public abstract void updateLegacyAvatar(String type, byte[] data);

    /**
     * Should be called when the service is to be logged into.
     *
     * This is expected to check for current logged in status and log in if appropriate.
     *
     * @param presenceType Initial status (away, available, etc) to be set upon logging in.
     * @param verboseStatus Descriptive status to be set upon logging in.
     */
    public abstract void logIn(PresenceType presenceType, String verboseStatus);

    /**
     * Should be called when the service is to be disconnected from.
     *
     * This is expected to check for current logged in status and log out if appropriate.
     */
    public abstract void logOut();

    /**
     * Clean up session pieces for either a log out or in preparation for a reconnection.
     */
    public abstract void cleanUp();

    /**
     * Retrieves a list of rooms (MUCTransportRoom) that are on the server the session is attached to.
     *
     * Because of the nature of the query, the legacy service is expected to send a response itself,
     * instead of returning a list of information.  The sendRooms command in BaseMUCTransport exists to
     * facilitate this easily.
     *
     * This will never get called unless MUC support is implemented and is optional for non-MUC transports.
     */
    public void getRooms() {
        getTransport().getMUCTransport().cancelPendingRequest(getJID(), getTransport().getMUCTransport().getJID(), NameSpace.DISCO_ITEMS);
    }


    /**
     * Retrieves information about a specific room (MUCTransportRoom).
     *
     * Because of the nature of the query, the legacy service is expected to send a response itself,
     * instead of returning a list of information.  The sendRoomInfo command in BaseMUCTransport exists to
     * facilitate this easily.
     *
     * Override this if you support it.
     *
     * @param room Room to get information about.
     */
    public void getRoomInfo(String room) {
        getTransport().getMUCTransport().cancelPendingRequest(getJID(), getTransport().getMUCTransport().convertIDToJID(room, null), NameSpace.DISCO_INFO);
    }

    /**
     * Retrieves members of a specific room (MUCTransportRoom).
     *
     * Because of the nature of the query, the legacy service is expected to send a response itself,
     * instead of returning a list of information.  The sendRoomMembers command in BaseMUCTransport exists to
     * facilitate this easily.
     *
     * Override this if you support it.
     *
     * @param room Room to get members of.
     */
    public void getRoomMembers(String room) {
        getTransport().getMUCTransport().cancelPendingRequest(getJID(), getTransport().getMUCTransport().convertIDToJID(room, null), NameSpace.DISCO_ITEMS);
    }

}
