/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.sip.communicator.service.protocol;

import java.util.*;

import net.java.sip.communicator.util.*;
import net.java.sip.communicator.service.credentialsstorage.*;

import org.jitsi.service.neomedia.*;
import org.osgi.framework.*;

/**
 * The AccountID is an account identifier that, uniquely represents a specific
 * user account over a specific protocol. The class needs to be extended by
 * every protocol implementation because of its protected
 * constructor. The reason why this constructor is protected is mostly avoiding
 * confusion and letting people (using the protocol provider service) believe
 * that they are the ones who are supposed to instantiate the accountid class.
 * <p>
 * Every instance of the <tt>ProtocolProviderService</tt>, created through the
 * ProtocolProviderFactory is assigned an AccountID instance, that uniquely
 * represents it and whose string representation (obtained through the
 * getAccountUID() method) can be used for identification of persistently stored
 * account details.
 * <p>
 * Account id's are guaranteed to be different for different accounts and in the
 * same time are bound to be equal for multiple installations of the same
 * account.
 *
 * @author Emil Ivov
 * @author Lubomir Marinov
 * @author Pawel Domas
 */
public abstract class AccountID
{
    /**
     * The <tt>Logger</tt> used by the <tt>AccountID</tt> class and its
     * instances for logging output.
     */
    private static final Logger logger = Logger.getLogger(AccountID.class);

    /**
     * The default properties key prefix used in lib/jitsi-defaults.properties
     */
    protected static final String DEFAULTS_PREFIX
            = "net.java.sip.communicator.service.protocol.";

    /**
     * The protocol display name. In the case of overridden protocol name this
     * would be the new name.
     */
    private final String protocolDisplayName;

    /**
     * The real protocol name.
     */
    private final String protocolName;

    /**
     * Allows a specific set of account properties to override a given default
     * protocol name (e.g. account registration wizards which want to present a
     * well-known protocol name associated with the account that is different
     * from the name of the effective protocol).
     * <p>
     * Note: The logic of the SIP protocol implementation at the time of this
     * writing modifies <tt>accountProperties</tt> to contain the default
     * protocol name if an override hasn't been defined. Since the desire is to
     * enable all account registration wizards to override the protocol name,
     * the current implementation places the specified
     * <tt>defaultProtocolName</tt> in a similar fashion.
     * </p>
     *
     * @param accountProperties a Map containing any other protocol and
     * implementation specific account initialization properties
     * @param defaultProtocolName the protocol name to be used in case
     * <tt>accountProperties</tt> doesn't provide an overriding value
     * @return the protocol name
     */
    private static final String getOverriddenProtocolName(
            Map<String, String> accountProperties, String defaultProtocolName)
    {
        String key = ProtocolProviderFactory.PROTOCOL;
        String protocolName = accountProperties.get(key);
        if ((protocolName == null) && (defaultProtocolName != null))
        {
            protocolName = defaultProtocolName;
            accountProperties.put(key, protocolName);
        }
        return protocolName;
    }

    /**
     * Contains all implementation specific properties that define the account.
     * The exact names of the keys are protocol (and sometimes implementation)
     * specific.
     * Currently, only String property keys and values will get properly stored.
     * If you need something else, please consider converting it through custom
     * accessors (get/set) in your implementation.
     */
    protected Map<String, String> accountProperties = null;

    /**
     * A String uniquely identifying the user for this particular account.
     */
    private final String userID;

    /**
     * A String uniquely identifying this account, that can also be used for
     * storing and unambiguously retrieving details concerning it.
     */
    private final String accountUID;

    /**
     * The name of the service that defines the context for this account.
     */
    private final String serviceName;

    /**
     * Creates an account id for the specified provider userid and
     * accountProperties.
     * If account uid exists in account properties, we are loading the account
     * and so load its value from there, prevent changing account uid
     * when server changed (serviceName has changed).
     * @param userID a String that uniquely identifies the user.
     * @param accountProperties a Map containing any other protocol and
     * implementation specific account initialization properties
     * @param protocolName the name of the protocol implemented by the provider
     * that this id is meant for.
     * @param serviceName the name of the service (e.g. iptel.org, jabber.org,
     * icq.com) that this account is registered with.
     */
    protected AccountID( String userID,
                         Map<String, String> accountProperties,
                         String protocolName,
                         String serviceName)
    {
        /*
         * Allow account registration wizards to override the default protocol
         * name through accountProperties for the purposes of presenting a
         * well-known protocol name associated with the account that is
         * different from the name of the effective protocol.
         */
        this.protocolDisplayName
            = getOverriddenProtocolName(accountProperties, protocolName);

        this.protocolName = protocolName;
        this.userID = userID;
        this.accountProperties
            = new HashMap<String, String>(accountProperties);
        this.serviceName = serviceName;

        String existingAccountUID =
                accountProperties.get(ProtocolProviderFactory.ACCOUNT_UID);

        if(existingAccountUID == null)
        {
            //create a unique identifier string
            this.accountUID
                = protocolDisplayName
                    + ":"
                    + userID
                    + "@"
                    + ((serviceName == null) ? "" : serviceName);
        }
        else
        {
            this.accountUID = existingAccountUID;
        }
    }

    /**
     * Returns the user id associated with this account.
     *
     * @return A String identifying the user inside this particular service.
     */
    public String getUserID()
    {
        return userID;
    }

    /**
     * Returns a name that can be displayed to the user when referring to this
     * account. WARNING: This property actually refers to
     * <code>ACCOUNT_DISPLAY_NAME</code>!
     *
     * @return A String identifying the user inside this particular service.
     */
    public String getDisplayName()
    {
        // If the ACCOUNT_DISPLAY_NAME property has been set for this account
        // we'll be using it as a display name.
        String key = ProtocolProviderFactory.ACCOUNT_DISPLAY_NAME;
        String accountDisplayName = accountProperties.get(key);
        if (accountDisplayName != null && accountDisplayName.length() > 0)
        {
            return accountDisplayName;
        }

        // Otherwise construct a display name.
        String returnValue = getUserID();
        String protocolName = getProtocolDisplayName();

        if (protocolName != null && protocolName.trim().length() > 0)
            returnValue += " (" + protocolName + ")";

        return returnValue;
    }

    /**
     * Sets {@link ProtocolProviderFactory#DISPLAY_NAME} property value.
     *
     * @param displayName the display name value to set.
     */
    public void setDisplayName(String displayName)
    {
        setOrRemoveIfEmpty(ProtocolProviderFactory.DISPLAY_NAME,
            displayName);
    }

    /**
     * Returns the display name of the protocol.
     *
     * @return the display name of the protocol
     */
    public String getProtocolDisplayName()
    {
        return protocolDisplayName;
    }

    /**
     * Returns the name of the protocol.
     *
     * @return the name of the protocol
     */
    public String getProtocolName()
    {
        return protocolName;
    }

    /**
     * Returns a String uniquely identifying this account, guaranteed to remain
     * the same across multiple installations of the same account and to always
     * be unique for differing accounts.
     * @return String
     */
    public String getAccountUniqueID()
    {
        return accountUID;
    }

    /**
     * Returns a Map containing protocol and implementation account
     * initialization properties.
     * @return a Map containing protocol and implementation account
     * initialization properties.
     */
    public Map<String, String> getAccountProperties()
    {
        return new HashMap<String, String>(accountProperties);
    }

    /**
     * Returns the specific account property.
     *
     * @param key property key
     * @param defaultValue default value if the property does not exist
     * @return property value corresponding to property key
     */
    public boolean getAccountPropertyBoolean(Object key, boolean defaultValue)
    {
        String value = getAccountPropertyString(key);
        if(value == null)
            value = getDefaultString(key.toString());
        return (value == null) ? defaultValue : Boolean.parseBoolean(value);
    }

    /**
     * Gets the value of a specific property as a signed decimal integer. If the
     * specified property key is associated with a value in this
     * <tt>AccountID</tt>, the string representation of the value is parsed into
     * a signed decimal integer according to the rules of
     * {@link Integer#parseInt(String)} . If parsing the value as a signed
     * decimal integer fails or there is no value associated with the specified
     * property key, <tt>defaultValue</tt> is returned.
     *
     * @param key the key of the property to get the value of as a
     * signed decimal integer
     * @param defaultValue the value to be returned if parsing the value of the
     * specified property key as a signed decimal integer fails or there is no
     * value associated with the specified property key in this
     * <tt>AccountID</tt>
     * @return the value of the property with the specified key in this
     * <tt>AccountID</tt> as a signed decimal integer; <tt>defaultValue</tt> if
     * parsing the value of the specified property key fails or no value is
     * associated in this <tt>AccountID</tt> with the specified property name
     */
    public int getAccountPropertyInt(Object key, int defaultValue)
    {
        String stringValue = getAccountPropertyString(key);
        int intValue = defaultValue;

        if ((stringValue == null) || (stringValue.isEmpty()))
        {
            stringValue = getDefaultString(key.toString());
        }

        if ((stringValue != null) && (stringValue.length() > 0))
        {
            try
            {
                intValue = Integer.parseInt(stringValue);
            }
            catch (NumberFormatException ex)
            {
                logger.error("Failed to parse account property " + key
                    + " value " + stringValue + " as an integer", ex);
            }
        }
        return intValue;
    }

    /**
     * Returns the account property string corresponding to the given key.
     *
     * @param key the key, corresponding to the property string we're looking
     * for
     * @return the account property string corresponding to the given key
     */
    public String getAccountPropertyString(Object key)
    {
        return getAccountPropertyString(key, null);
    }

    /**
     * Returns the account property string corresponding to the given key.
     *
     * @param key the key, corresponding to the property string we're looking
     *        for
     * @param defValue the default value returned when given <tt>key</tt>
     *        is not present
     * @return the account property string corresponding to the given key
     */
    public String getAccountPropertyString(Object key, String defValue)
    {
        String value = accountProperties.get(key);
        if(value == null)
            value = getDefaultString(key.toString());
        return (value == null) ? defValue : value;
    }

    /**
     * Adds a property to the map of properties for this account identifier.
     *
     * @param key the key of the property
     * @param value the property value
     */
    public void putAccountProperty(String key, String value)
    {
        accountProperties.put(key, value);
    }

    /**
     * Adds property to the map of properties for this account
     * identifier.
     * @param key the key of the property
     * @param value the property value
     */
    public void putAccountProperty(String key, Object value)
    {
        accountProperties.put(key, String.valueOf(value));
    }

    /**
     * Removes specified account property.
     * @param key the key to remove.
     */
    public void removeAccountProperty(String key)
    {
        accountProperties.remove(key);
    }

    /**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hashtables such as those provided by
     * <tt>java.util.Hashtable</tt>.
     * <p>
     * @return  a hash code value for this object.
     * @see     java.lang.Object#equals(java.lang.Object)
     * @see     java.util.Hashtable
     */
    @Override
    public int hashCode()
    {
        return (accountUID == null)? 0 : accountUID.hashCode();
    }

    /**
     * Indicates whether some other object is "equal to" this account id.
     * <p>
     * @param   obj   the reference object with which to compare.
     * @return  <tt>true</tt> if this object is the same as the obj
     *          argument; <tt>false</tt> otherwise.
     * @see     #hashCode()
     * @see     java.util.Hashtable
     */
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;

        return (obj != null)
            && getClass().isInstance(obj)
            && accountUID.equals(((AccountID)obj).accountUID);
    }

    /**
     * Returns a string representation of this account id (same as calling
     * getAccountUniqueID()).
     *
     * @return  a string representation of this account id.
     */
    @Override
    public String toString()
    {
        return getAccountUniqueID();
    }

    /**
     * Returns the name of the service that defines the context for this
     * account. Often this name would be an sqdn or even an ipaddress but this
     * would not always be the case (e.g. p2p providers may return a name that
     * does not directly correspond to an IP address or host name).
     * <p>
     * @return the name of the service that defines the context for this
     * account.
     */
    public String getService()
    {
        return this.serviceName;
    }

    /**
     * Returns a string that could be directly used (or easily converted to) an
     * address that other users of the protocol can use to communicate with us.
     * By default this string is set to userid@servicename. Protocol
     * implementors should override it if they'd need it to respect a different
     * syntax.
     *
     * @return a String in the form of userid@service that other protocol users
     * should be able to parse into a meaningful address and use it to
     * communicate with us.
     */
    public String getAccountAddress()
    {
        String userID = getUserID();
        return (userID.indexOf('@') > 0) ? userID
            : (userID + "@" + getService());
    }

    /**
     * Indicates if this account is currently enabled.
     * @return <tt>true</tt> if this account is enabled, <tt>false</tt> -
     * otherwise.
     */
    public boolean isEnabled()
    {
        return !getAccountPropertyBoolean(
            ProtocolProviderFactory.IS_ACCOUNT_DISABLED, false);
    }

    /**
     * The address of the server we will use for this account
     *
     * @return String
     */
    public String getServerAddress()
    {
        return getAccountPropertyString(ProtocolProviderFactory.SERVER_ADDRESS);
    }

    /**
     * Returns the password of the account.
     *
     * @return the password of the account.
     */
    public String getPassword()
    {
        return getAccountPropertyString(ProtocolProviderFactory.PASSWORD);
    }

    /**
     * Sets the password of the account.
     *
     * @param password the password of the account.
     */
    public void setPassword(String password)
    {
        setOrRemoveIfEmpty(ProtocolProviderFactory.PASSWORD, password);
    }

    /**
     * The authorization name
     *
     * @return String auth name
     */
    public String getAuthorizationName()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.AUTHORIZATION_NAME);
    }

    /**
     * Sets authorization name.
     *
     * @param authName String
     */
    public void setAuthorizationName(String authName)
    {
        setOrRemoveIfEmpty(
                ProtocolProviderFactory.AUTHORIZATION_NAME,
                authName);
    }

    /**
     * The port on the specified server
     *
     * @return int
     */
    public String getServerPort()
    {
        return getAccountPropertyString(ProtocolProviderFactory.SERVER_PORT);
    }

    /**
     * Sets the server port.
     *
     * @param port int
     */
    public void setServerPort(String port)
    {
        setOrRemoveIfEmpty(ProtocolProviderFactory.SERVER_PORT, port);
    }

    /**
     * Sets the server
     *
     * @param serverAddress String
     */
    public void setServerAddress(String serverAddress)
    {
        setOrRemoveIfEmpty(ProtocolProviderFactory.SERVER_ADDRESS,
                serverAddress);
    }

    /**
     * Returns <tt>true</tt> if server was overriden.
     * @return <tt>true</tt> if server was overriden.
     */
    public boolean isServerOverridden()
    {
        return getAccountPropertyBoolean(
                ProtocolProviderFactory.IS_SERVER_OVERRIDDEN, false);
    }

    /**
     * Sets <tt>isServerOverridden</tt> property.
     * @param isServerOverridden indicates if the server is overridden
     */
    public void setServerOverridden(boolean isServerOverridden)
    {
        putAccountProperty(
                ProtocolProviderFactory.IS_SERVER_OVERRIDDEN,
                isServerOverridden);
    }

    /**
     * Returns the protocol icon path stored under
     * {@link ProtocolProviderFactory#PROTOCOL_ICON_PATH} key.
     *
     * @return the protocol icon path.
     */
    public String getProtocolIconPath()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.PROTOCOL_ICON_PATH);
    }

    /**
     * Sets the protocl icon path that will be held under
     * {@link ProtocolProviderFactory#PROTOCOL_ICON_PATH} key.
     *
     * @param iconPath a path to the protocol icon to set.
     */
    public void setProtocolIconPath(String iconPath)
    {
        putAccountProperty(
                ProtocolProviderFactory.PROTOCOL_ICON_PATH,
                iconPath);
    }

    /**
     * Returns the protocol icon path stored under
     * {@link ProtocolProviderFactory#ACCOUNT_ICON_PATH} key.
     *
     * @return the protocol icon path.
     */
    public String getAccountIconPath()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.ACCOUNT_ICON_PATH);
    }

    /**
     * Sets the account icon path that will be held under
     * {@link ProtocolProviderFactory#ACCOUNT_ICON_PATH} key.
     *
     * @param iconPath a path to the account icon to set.
     */
    public void setAccountIconPath(String iconPath)
    {
        putAccountProperty(
                ProtocolProviderFactory.ACCOUNT_ICON_PATH,
                iconPath);
    }

    /**
     * Returns the DTMF method.
     *
     * @return the DTMF method.
     */
    public String getDTMFMethod()
    {
        return getAccountPropertyString(ProtocolProviderFactory.DTMF_METHOD);
    }

    /**
     * Sets the DTMF method.
     *
     * @param dtmfMethod the DTMF method to set
     */
    public void setDTMFMethod(String dtmfMethod)
    {
        putAccountProperty(ProtocolProviderFactory.DTMF_METHOD, dtmfMethod);
    }

    /**
     * Returns the minimal DTMF tone duration.
     *
     * @return The minimal DTMF tone duration.
     */
    public String getDtmfMinimalToneDuration()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.DTMF_MINIMAL_TONE_DURATION);
    }

    /**
     * Sets the minimal DTMF tone duration.
     *
     * @param dtmfMinimalToneDuration The minimal DTMF tone duration to set.
     */
    public void setDtmfMinimalToneDuration(String dtmfMinimalToneDuration)
    {
        putAccountProperty( ProtocolProviderFactory.DTMF_MINIMAL_TONE_DURATION,
                            dtmfMinimalToneDuration );
    }

    /**
     * Gets the ID of the client certificate configuration.
     * @return the ID of the client certificate configuration.
     */
    public String getTlsClientCertificate()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.CLIENT_TLS_CERTIFICATE);
    }

    /**
     * Sets the ID of the client certificate configuration.
     * @param id the client certificate configuration template ID.
     */
    public void setTlsClientCertificate(String id)
    {
        setOrRemoveIfEmpty(ProtocolProviderFactory.CLIENT_TLS_CERTIFICATE, id);
    }

    /**
     * Checks if the account is hidden.
     * @return <tt>true</tt> if this account is hidden or <tt>false</tt>
     * otherwise.
     */
    public boolean isHidden()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.IS_PROTOCOL_HIDDEN) != null;
    }

    /**
     * Checks if the account config is hidden.
     * @return <tt>true</tt> if the account config is hidden or <tt>false</tt>
     * otherwise.
     */
    public boolean isConfigHidden()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.IS_ACCOUNT_CONFIG_HIDDEN) != null;
    }

    /**
     * Checks if the account status menu is hidden.
     * @return <tt>true</tt> if the account status menu is hidden or
     * <tt>false</tt> otherwise.
     */
    public boolean isStatusMenuHidden()
    {
        return getAccountPropertyString(
            ProtocolProviderFactory.IS_ACCOUNT_STATUS_MENU_HIDDEN) != null;
    }

    /**
     * Checks if the account is marked as readonly.
     * @return <tt>true</tt> if the account is marked as readonly or
     * <tt>false</tt> otherwise.
     */
    public boolean isReadOnly()
    {
        return getAccountPropertyString(
                ProtocolProviderFactory.IS_ACCOUNT_READ_ONLY) != null;
    }

    /**
     * Returns the first <tt>ProtocolProviderService</tt> implementation
     * corresponding to the preferred protocol
     *
     * @return the <tt>ProtocolProviderService</tt> corresponding to the
     * preferred protocol
     */
    public boolean isPreferredProvider()
    {
        String preferredProtocolProp
                = getAccountPropertyString(
                        ProtocolProviderFactory.IS_PREFERRED_PROTOCOL);

        if (preferredProtocolProp != null
                && preferredProtocolProp.length() > 0
                && Boolean.parseBoolean(preferredProtocolProp))
        {
            return true;
        }

        return false;
    }

    /**
     * Set the account properties.
     *
     * @param accountProperties the properties of the account
     */
    public void setAccountProperties(Map<String, String> accountProperties)
    {
        this.accountProperties = accountProperties;
    }

    /**
     * Returns if the encryption protocol given in parameter is enabled.
     *
     * @param encryptionProtocolName The name of the encryption protocol
     * ("ZRTP", "SDES" or "MIKEY").
     */
    public boolean isEncryptionProtocolEnabled(SrtpControlType type)
    {
        // The default value is false, except for ZRTP.
        boolean defaultValue = type == SrtpControlType.ZRTP;

        return
            getAccountPropertyBoolean(
                    ProtocolProviderFactory.ENCRYPTION_PROTOCOL_STATUS
                        + "."
                        + type.toString(),
                    defaultValue);
    }

    /**
     * Returns the list of STUN servers that this account is currently
     * configured to use.
     *
     * @return the list of STUN servers that this account is currently
     * configured to use.
     */
    public List<StunServerDescriptor> getStunServers(
                                                    BundleContext bundleContext)
    {
        Map<String, String> accountProperties = getAccountProperties();
        List<StunServerDescriptor> stunServerList
            = new ArrayList<StunServerDescriptor>();

        for (int i = 0; i < StunServerDescriptor.MAX_STUN_SERVER_COUNT; i ++)
        {
            StunServerDescriptor stunServer
                = StunServerDescriptor.loadDescriptor(
                        accountProperties,
                        ProtocolProviderFactory.STUN_PREFIX + i);

            // If we don't find a stun server with the given index, it means
            // there are no more servers left in the table so we've nothing
            // more to do here.
            if (stunServer == null)
                break;

            String password
                = loadStunPassword(
                        bundleContext,
                        this,
                        ProtocolProviderFactory.STUN_PREFIX + i);

            if(password != null)
                stunServer.setPassword(password);

            stunServerList.add(stunServer);
        }

        return stunServerList;
    }

    /**
     * Returns the password for the STUN server with the specified prefix.
     *
     * @param bundleContext the OSGi bundle context that we are currently
     * running in.
     * @param accountID account ID
     * @param namePrefix name prefix
     *
     * @return password or null if empty
     */
    protected static String loadStunPassword(BundleContext bundleContext,
                                             AccountID     accountID,
                                             String        namePrefix)
    {
        ProtocolProviderFactory providerFactory
                = ProtocolProviderFactory.getProtocolProviderFactory(
                        bundleContext,
                        accountID.getSystemProtocolName());

        String password = null;
        String className = providerFactory.getClass().getName();
        String packageSourceName
                = className.substring(0, className.lastIndexOf('.'));

        String accountPrefix = ProtocolProviderFactory.findAccountPrefix(
                bundleContext,
                accountID, packageSourceName);

        CredentialsStorageService credentialsService
                = ServiceUtils.getService(
                bundleContext,
                CredentialsStorageService.class);

        try
        {
            password = credentialsService.
                    loadPassword(accountPrefix + "." + namePrefix);
        }
        catch(Exception e)
        {
            return null;
        }

        return password;
    }

    /**
     * Determines whether this account's provider is supposed to auto discover
     * STUN and TURN servers.
     *
     * @return <tt>true</tt> if this provider would need to discover STUN/TURN
     * servers and false otherwise.
     */
    public boolean isStunServerDiscoveryEnabled()
    {
        return getAccountPropertyBoolean(
                    ProtocolProviderFactory.AUTO_DISCOVER_STUN,
                    true);
    }

    /**
     * Determines whether this account's provider uses UPnP (if available).
     *
     * @return <tt>true</tt> if this provider would use UPnP (if available),
     * <tt>false</tt> otherwise
     */
    public boolean isUPNPEnabled()
    {
        return getAccountPropertyBoolean(
                                        ProtocolProviderFactory.IS_USE_UPNP,
                                        true);
    }

    /**
     * Determines whether this account's provider uses the default STUN server
     * provided by Jitsi (stun.jitsi.net) if there is no other STUN/TURN server
     * discovered/configured.
     *
     * @return <tt>true</tt> if this provider would use the default STUN server,
     * <tt>false</tt> otherwise
     */
    public boolean isUseDefaultStunServer()
    {
        return
            getAccountPropertyBoolean(
                    ProtocolProviderFactory.USE_DEFAULT_STUN_SERVER,
                    true);
    }

    /**
     * Returns the actual name of the protocol used rather than a branded
     * variant. The method is primarily meant for open protocols such as SIP
     * or XMPP so that it would always return SIP or XMPP even in branded
     * protocols who otherwise return things like GTalk and ippi for
     * PROTOCOL_NAME.
     *
     * @return the real non-branded name of the protocol.
     */
    public String getSystemProtocolName()
    {
        return getProtocolName();
    }

    /**
     * Sorts the enabled encryption protocol list given in parameter to match
     * the preferences set for this account.
     *
     * @return Sorts the enabled encryption protocol list given in parameter to
     * match the preferences set for this account.
     */
    public List<SrtpControlType> getSortedEnabledEncryptionProtocolList()
    {
        Map<String, Integer> encryptionProtocols
            = getIntegerPropertiesByPrefix(
                    ProtocolProviderFactory.ENCRYPTION_PROTOCOL,
                    true);
        Map<String, Boolean> encryptionProtocolStatus
            = getBooleanPropertiesByPrefix(
                    ProtocolProviderFactory.ENCRYPTION_PROTOCOL_STATUS,
                    true,
                    false);

        // If the account is not yet configured, then ZRTP is activated by
        // default.
        if(encryptionProtocols.size() == 0)
        {
            encryptionProtocols.put(
                    ProtocolProviderFactory.ENCRYPTION_PROTOCOL + ".ZRTP",
                    0);
            encryptionProtocolStatus.put(
                    ProtocolProviderFactory.ENCRYPTION_PROTOCOL_STATUS
                        + ".ZRTP",
                    true);
        }

        List<SrtpControlType> sortedEncryptionProtocols
            = new ArrayList<SrtpControlType>(encryptionProtocols.size());

        // First: add all protocol in the right order.
        for (Map.Entry<String, Integer> e : encryptionProtocols.entrySet())
        {
            int index = e.getValue();

            // If the key is set.
            if (index != -1)
            {
                if (index > sortedEncryptionProtocols.size())
                    index = sortedEncryptionProtocols.size();

                String name =
                    e.getKey()
                        .substring(
                            ProtocolProviderFactory.ENCRYPTION_PROTOCOL
                                .length() + 1);

                try
                {
                    sortedEncryptionProtocols.add(index,
                        SrtpControlType.fromString(name));
                }
                catch(IllegalArgumentException exc)
                {
                    logger.error(
                        "Failed to get SRTP control type for name: '"
                            + name + "', key: '" + e.getKey() + "'", exc);
                }
            }
        }

        // Second: remove all disabled protocols.
        for (Iterator<SrtpControlType> i = sortedEncryptionProtocols.iterator();
                i.hasNext();)
        {
            String name = i.next().toString();

            if (!encryptionProtocolStatus.get(
                    ProtocolProviderFactory.ENCRYPTION_PROTOCOL_STATUS
                        + "."
                        + name))
            {
                i.remove();
            }
        }

        return sortedEncryptionProtocols;
    }

    /**
     * Returns a <tt>java.util.Map</tt> of <tt>String</tt>s containing the
     * all property names that have the specified prefix and <tt>Boolean</tt>
     * containing the value for each property selected. Depending on the value
     * of the <tt>exactPrefixMatch</tt> parameter the method will (when false)
     * or will not (when exactPrefixMatch is true) include property names that
     * have prefixes longer than the specified <tt>prefix</tt> param.
     * <p>
     * Example:
     * <p>
     * Imagine a configuration service instance containing 2 properties
     * only:<br>
     * <code>
     * net.java.sip.communicator.PROP1=value1<br>
     * net.java.sip.communicator.service.protocol.PROP1=value2
     * </code>
     * <p>
     * A call to this method with a prefix="net.java.sip.communicator" and
     * exactPrefixMatch=true would only return the first property -
     * net.java.sip.communicator.PROP1, whereas the same call with
     * exactPrefixMatch=false would return both properties as the second prefix
     * includes the requested prefix string.
     * <p>
     * @param prefix a String containing the prefix (the non dotted non-caps
     * part of a property name) that we're looking for.
     * @param exactPrefixMatch a boolean indicating whether the returned
     * property names should all have a prefix that is an exact match of the
     * the <tt>prefix</tt> param or whether properties with prefixes that
     * contain it but are longer than it are also accepted.
     * @param defaultValue the default value if the key is not set.
     * @return a <tt>java.util.Map</tt> containing all property name String-s
     * matching the specified conditions and the corresponding values as
     * Boolean.
     */
    public Map<String, Boolean> getBooleanPropertiesByPrefix(
            String prefix,
            boolean exactPrefixMatch,
            boolean defaultValue)
    {
        String propertyName;
        List<String> propertyNames
            = getPropertyNamesByPrefix(prefix, exactPrefixMatch);
        Map<String, Boolean> properties
            = new HashMap<String, Boolean>(propertyNames.size());

        for(int i = 0; i < propertyNames.size(); ++i)
        {
            propertyName = propertyNames.get(i);
            properties.put(
                    propertyName,
                    getAccountPropertyBoolean(propertyName, defaultValue));
        }

        return properties;
    }

    /**
     * Returns a <tt>java.util.Map</tt> of <tt>String</tt>s containing the
     * all property names that have the specified prefix and <tt>Integer</tt>
     * containing the value for each property selected. Depending on the value
     * of the <tt>exactPrefixMatch</tt> parameter the method will (when false)
     * or will not (when exactPrefixMatch is true) include property names that
     * have prefixes longer than the specified <tt>prefix</tt> param.
     * <p>
     * Example:
     * <p>
     * Imagine a configuration service instance containing 2 properties
     * only:<br>
     * <code>
     * net.java.sip.communicator.PROP1=value1<br>
     * net.java.sip.communicator.service.protocol.PROP1=value2
     * </code>
     * <p>
     * A call to this method with a prefix="net.java.sip.communicator" and
     * exactPrefixMatch=true would only return the first property -
     * net.java.sip.communicator.PROP1, whereas the same call with
     * exactPrefixMatch=false would return both properties as the second prefix
     * includes the requested prefix string.
     * <p>
     * @param prefix a String containing the prefix (the non dotted non-caps
     * part of a property name) that we're looking for.
     * @param exactPrefixMatch a boolean indicating whether the returned
     * property names should all have a prefix that is an exact match of the
     * the <tt>prefix</tt> param or whether properties with prefixes that
     * contain it but are longer than it are also accepted.
     * @return a <tt>java.util.Map</tt> containing all property name String-s
     * matching the specified conditions and the corresponding values as
     * Integer.
     */
    public Map<String, Integer> getIntegerPropertiesByPrefix(
            String prefix,
            boolean exactPrefixMatch)
    {
        String propertyName;
        List<String> propertyNames
            = getPropertyNamesByPrefix(prefix, exactPrefixMatch);
        Map<String, Integer> properties
            = new HashMap<String, Integer>(propertyNames.size());

        for(int i = 0; i < propertyNames.size(); ++i)
        {
            propertyName = propertyNames.get(i);
            properties.put(
                    propertyName,
                    getAccountPropertyInt(propertyName, -1));
        }

        return properties;
    }

    /**
     * Returns a <tt>java.util.List</tt> of <tt>String</tt>s containing the
     * all property names that have the specified prefix. Depending on the value
     * of the <tt>exactPrefixMatch</tt> parameter the method will (when false)
     * or will not (when exactPrefixMatch is true) include property names that
     * have prefixes longer than the specified <tt>prefix</tt> param.
     * <p>
     * Example:
     * <p>
     * Imagine a configuration service instance containing 2 properties
     * only:<br>
     * <code>
     * net.java.sip.communicator.PROP1=value1<br>
     * net.java.sip.communicator.service.protocol.PROP1=value2
     * </code>
     * <p>
     * A call to this method with a prefix="net.java.sip.communicator" and
     * exactPrefixMatch=true would only return the first property -
     * net.java.sip.communicator.PROP1, whereas the same call with
     * exactPrefixMatch=false would return both properties as the second prefix
     * includes the requested prefix string.
     * <p>
     * @param prefix a String containing the prefix (the non dotted non-caps
     * part of a property name) that we're looking for.
     * @param exactPrefixMatch a boolean indicating whether the returned
     * property names should all have a prefix that is an exact match of the
     * the <tt>prefix</tt> param or whether properties with prefixes that
     * contain it but are longer than it are also accepted.
     * @return a <tt>java.util.List</tt>containing all property name String-s
     * matching the specified conditions.
     */
    public List<String> getPropertyNamesByPrefix(
            String prefix,
            boolean exactPrefixMatch)
    {
        List<String> resultKeySet = new LinkedList<String>();

        for (String key : accountProperties.keySet())
        {
            int ix = key.lastIndexOf('.');

            if(ix == -1)
                continue;

            String keyPrefix = key.substring(0, ix);

            if(exactPrefixMatch)
            {
                if(prefix.equals(keyPrefix))
                    resultKeySet.add(key);
            }
            else
            {
                if(keyPrefix.startsWith(prefix))
                    resultKeySet.add(key);
            }
        }

        return resultKeySet;
    }

    /**
     * Sets the property a new value, but only if it's not <tt>null</tt> or
     * the property is removed from the map.
     *
     * @param key the property key
     * @param value the property value
     */
    public void setOrRemoveIfNull(String key, String value)
    {
        if(value != null)
        {
            putAccountProperty(key, value);
        }
        else
        {
            removeAccountProperty(key);
        }
    }

    /**
     * Puts the new property value if it's not <tt>null</tt> nor empty.
     * @param key the property key
     * @param value the property value
     */
    public void setOrRemoveIfEmpty(String key, String value)
    {
        setOrRemoveIfEmpty(key, value, false);
    }

    /**
     * Puts the new property value if it's not <tt>null</tt> nor empty. If
     * <tt>trim</tt> parameter is set to <tt>true</tt> the string will be
     * trimmed, before checked for emptiness.
     *
     * @param key the property key
     * @param value the property value
     * @param trim <tt>true</tt> if the value  will be trimmed, before
     *             <tt>isEmpty()</tt> is called.
     */
    public void setOrRemoveIfEmpty(String key, String value, boolean trim)
    {
        if( value != null
            && (trim ? !value.trim().isEmpty() : !value.isEmpty()) )
        {
            putAccountProperty(key, value);
        }
        else
        {
            removeAccountProperty(key);
        }
    }

    /**
     * Stores configuration properties held by this object into given
     * <tt>accountProperties</tt> map.
     *
     * @param protocolIconPath  the path to the protocol icon is used
     * @param accountIconPath   the path to the account icon if used
     * @param accountProperties output properties map
     */
    public void storeProperties( String protocolIconPath,
                                 String accountIconPath,
                                 Map<String, String> accountProperties )
    {
        if(protocolIconPath != null)
            setProtocolIconPath(protocolIconPath);

        if(accountIconPath != null)
            setAccountIconPath(accountIconPath);

        mergeProperties(this.accountProperties, accountProperties);

        // Removes encrypted password property, as it will be restored during
        // account storage, but only if the password property is present.
        accountProperties.remove("ENCRYPTED_PASSWORD");
    }

    /**
     * Gets default property value for given <tt>key</tt>.
     *
     * @param key the property key
     * @return default property value for given<tt>key</tt>
     */
    protected String getDefaultString(String key)
    {
        return getDefaultStr(key);
    }

    /**
     * Gets default property value for given <tt>key</tt>.
     *
     * @param key the property key
     * @return default property value for given<tt>key</tt>
     */
    public static String getDefaultStr(String key)
    {
        return ProtocolProviderActivator
                .getConfigurationService()
                .getString(DEFAULTS_PREFIX +key);
    }

    /**
     * Copies all properties from <tt>input</tt> map to <tt>output</tt> map.
     * @param input source properties map
     * @param output destination properties map
     */
    public static void mergeProperties( Map<String, String> input,
                                        Map<String, String> output )
    {
        for(String key : input.keySet())
        {
            output.put(key, input.get(key));
        }
    }
}
