/*
 * 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.impl.gui;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.lang.ref.*;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;

import net.java.sip.communicator.impl.gui.event.*;
import net.java.sip.communicator.impl.gui.lookandfeel.*;
import net.java.sip.communicator.impl.gui.main.*;
import net.java.sip.communicator.impl.gui.main.account.*;
import net.java.sip.communicator.impl.gui.main.call.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
import net.java.sip.communicator.impl.gui.main.chat.conference.*;
import net.java.sip.communicator.impl.gui.main.chat.history.*;
import net.java.sip.communicator.impl.gui.main.chatroomslist.*;
import net.java.sip.communicator.impl.gui.main.configforms.*;
import net.java.sip.communicator.impl.gui.main.contactlist.*;
import net.java.sip.communicator.impl.gui.main.login.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.impl.gui.utils.Constants;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.gui.Container;
import net.java.sip.communicator.service.gui.event.*;
import net.java.sip.communicator.service.muc.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.shutdown.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.Logger;
import net.java.sip.communicator.util.account.*;
import net.java.sip.communicator.util.skin.*;

import org.jitsi.service.resources.*;
import org.jitsi.util.*;
import org.osgi.framework.*;

import com.sun.jna.platform.WindowUtils;

/**
 * An implementation of the <tt>UIService</tt> that gives access to other
 * bundles to this particular swing ui implementation.
 *
 * @author Yana Stamcheva
 * @author Lyubomir Marinov
 * @author Dmitri Melnikov
 * @author Adam Netocny
 * @author Hristo Terezov
 */
public class UIServiceImpl
    implements UIService,
               ShutdownService,
               ServiceListener,
               PropertyChangeListener,
               UINotificationListener
{
    /**
     * The <tt>Logger</tt> used by the <tt>UIServiceImpl</tt> class and its
     * instances for logging output.
     */
    private static final Logger logger = Logger.getLogger(UIServiceImpl.class);

    private PopupDialogImpl popupDialog;

    private AccountRegWizardContainerImpl wizardContainer;

    /**
     * The <tt>PluginComponentListener</tt>s interested into when
     * <tt>PluginComponent</tt>s gets added and removed.
     * <p>
     * Because <tt>UIServiceImpl</tt> is global with respect to the lifetime of
     * the application and, consequently, <tt>PluginComponentListener</tt>s get
     * leaked, the listeners are referenced by <tt>WeakReference</tt>s.
     * </p>
     */
    private final List<WeakReference<PluginComponentListener>>
        pluginComponentListeners
            = new ArrayList<WeakReference<PluginComponentListener>>();

    private static final List<Container> supportedContainers
        = new ArrayList<Container>();

    static
    {
        supportedContainers.add(Container.CONTAINER_MAIN_TOOL_BAR);
        supportedContainers.add(Container.CONTAINER_CONTACT_RIGHT_BUTTON_MENU);
        supportedContainers.add(Container.CONTAINER_GROUP_RIGHT_BUTTON_MENU);
        supportedContainers.add(Container.CONTAINER_TOOLS_MENU);
        supportedContainers.add(Container.CONTAINER_HELP_MENU);
        supportedContainers.add(Container.CONTAINER_CHAT_TOOL_BAR);
        supportedContainers.add(Container.CONTAINER_CALL_HISTORY);
        supportedContainers.add(Container.CONTAINER_MAIN_TABBED_PANE);
        supportedContainers.add(Container.CONTAINER_CHAT_HELP_MENU);
    }

    private static final Hashtable<WindowID, ExportedWindow> exportedWindows
        = new Hashtable<WindowID, ExportedWindow>();

    private MainFrame mainFrame;

    private LoginManager loginManager;

    private final ChatWindowManager chatWindowManager
        = new ChatWindowManager();

    private final ConferenceChatManager conferenceChatManager
        = new ConferenceChatManager();

    private ConfigurationFrame configurationFrame;

    private final HistoryWindowManager historyWindowManager
        = new HistoryWindowManager();

    private SingleWindowContainer singleWindowContainer;

    /**
     * Creates an instance of <tt>UIServiceImpl</tt>.
     */
    public UIServiceImpl()
    {
        UINotificationManager.addNotificationListener(this);
    }

    /**
     * Initializes all frames and panels and shows the GUI.
     */
    void loadApplicationGui()
    {
        this.setDefaultThemePack();

        // Initialize the single window container if we're in this case. This
        // should be done before initializing the main window, because he'll
        // search for it.
        if (ConfigurationUtils.isSingleWindowInterfaceEnabled())
            singleWindowContainer = new SingleWindowContainer();

        // Initialize the main window.
        this.mainFrame = new MainFrame();

        if (UIManager.getLookAndFeel() instanceof SIPCommLookAndFeel)
            initCustomFonts();

        /*
         * The mainFrame isn't fully ready without the MetaContactListService so
         * make sure it's set before allowing anything, such as LoginManager, to
         * use the mainFrame. Otherwise, LoginManager, for example, will call
         * back from its event listener(s) into the mainFrame and cause a
         * NullPointerException.
         */
        mainFrame.setContactList(GuiActivator.getContactListService());

        // Initialize main window bounds.
        this.mainFrame.initBounds();

        // Register the main window as an exported window, so that other bundles
        // could access it through the UIService.
        GuiActivator.getUIService().registerExportedWindow(mainFrame);

        // Initialize the login manager.
        this.loginManager = new LoginManager(new LoginRendererSwingImpl());

        this.popupDialog = new PopupDialogImpl();

        this.wizardContainer = new AccountRegWizardContainerImpl(mainFrame);

        if (ConfigurationUtils.isTransparentWindowEnabled())
        {
            try
            {
                WindowUtils.setWindowTransparent(mainFrame, true);
            }
            catch (UnsupportedOperationException ex)
            {
                logger.error(ex.getMessage(), ex);
                ConfigurationUtils.setTransparentWindowEnabled(false);
            }
        }

        if(ConfigurationUtils.isApplicationVisible()
            || Boolean.getBoolean("disable-tray")
            || ConfigurationUtils.isMinimizeInsteadOfHide())
        {
            mainFrame.setFrameVisible(true);
        }

        SwingUtilities.invokeLater(new RunLoginGui());

        this.initExportedWindows();

        KeyboardFocusManager focusManager
            = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        focusManager.addKeyEventDispatcher(
                new KeyBindingsDispatching(focusManager));
    }

    /**
     * Implements <code>UISercie.getSupportedContainers</code>. Returns the
     * list of supported containers by this implementation .
     *
     * @see UIService#getSupportedContainers()
     * @return an Iterator over all supported containers.
     */
    public Iterator<Container> getSupportedContainers()
    {
        return Collections.unmodifiableList(supportedContainers).iterator();
    }

    /**
     * Creates the corresponding PluginComponentEvent and notifies all
     * <tt>ContainerPluginListener</tt>s that a plugin component is added or
     * removed from the container.
     *
     * @param factory the plugin component factory that is added to the
     *            container.
     * @param eventID one of the PLUGIN_COMPONENT_XXX static fields indicating
     *            the nature of the event.
     */
    private void firePluginEvent(PluginComponentFactory factory,
                                 int eventID)
    {
        PluginComponentEvent evt = new PluginComponentEvent(factory, eventID);

        if (logger.isDebugEnabled())
            logger.debug("Will dispatch the following plugin component event: "
            + evt);

        synchronized (pluginComponentListeners)
        {
            Iterator<WeakReference<PluginComponentListener>> i
                = pluginComponentListeners.iterator();

            while (i.hasNext())
            {
                PluginComponentListener l = i.next().get();

                if (l == null)
                    i.remove();
                else
                {
                    switch (evt.getEventID())
                    {
                    case PluginComponentEvent.PLUGIN_COMPONENT_ADDED:
                        l.pluginComponentAdded(evt);
                        break;
                    case PluginComponentEvent.PLUGIN_COMPONENT_REMOVED:
                        l.pluginComponentRemoved(evt);
                        break;
                    default:
                        logger.error("Unknown event type " + evt.getEventID());
                        break;
                    }
                }
            }
        }
    }

    /**
     * Implements <code>isVisible</code> in the UIService interface. Checks if
     * the main application window is visible.
     *
     * @return <code>true</code> if main application window is visible,
     *         <code>false</code> otherwise
     * @see UIService#isVisible()
     */
    public boolean isVisible()
    {
        return mainFrame.isFrameVisible();
    }

    /**
     * Implements <code>setVisible</code> in the UIService interface. Shows or
     * hides the main application window depending on the parameter
     * <code>visible</code>.
     *
     * @param isVisible true if we are to show the main application frame and
     * false otherwise.
     *
     * @see UIService#setVisible(boolean)
     */
    public void setVisible(final boolean isVisible)
    {
        this.mainFrame.setFrameVisible(isVisible);
    }

    /**
     * Locates the main application window to the new x and y coordinates.
     *
     * @param x The new x coordinate.
     * @param y The new y coordinate.
     */
    public void setLocation(int x, int y)
    {
        mainFrame.setLocation(x, y);
    }

    /**
     * Returns the current location of the main application window. The returned
     * point is the top left corner of the window.
     *
     * @return The top left corner coordinates of the main application window.
     */
    public Point getLocation()
    {
        return mainFrame.getLocation();
    }

    /**
     * Returns the size of the main application window.
     *
     * @return the size of the main application window.
     */
    public Dimension getSize()
    {
        return mainFrame.getSize();
    }

    /**
     * Sets the size of the main application window.
     *
     * @param width The width of the window.
     * @param height The height of the window.
     */
    public void setSize(int width, int height)
    {
        mainFrame.setSize(width, height);
    }

    /**
     * Implements <code>minimize</code> in the UIService interface. Minimizes
     * the main application window.
     *
     * @see UIService#minimize()
     */
    public void minimize()
    {
        this.mainFrame.minimize();
    }

    /**
     * Implements <code>maximize</code> in the UIService interface. Maximizes
     * the main application window.
     *
     * @see UIService#maximize()
     */
    public void maximize()
    {
        this.mainFrame.maximize();
    }

    /**
     * Implements <code>restore</code> in the UIService interface. Restores
     * the main application window.
     *
     * @see UIService#restore()
     */
    public void restore()
    {
        if (mainFrame.isFrameVisible())
        {
            if (mainFrame.getState() == JFrame.ICONIFIED)
                mainFrame.setState(JFrame.NORMAL);

            mainFrame.toFront();
        }
        else
            mainFrame.setFrameVisible(true);
    }

    /**
     * Implements <code>resize</code> in the UIService interface. Resizes the
     * main application window.
     *
     * @param height the new height of tha main application frame.
     * @param width the new width of the main application window.
     *
     * @see UIService#resize(int, int)
     */
    public void resize(int width, int height)
    {
        this.mainFrame.setSize(width, height);
    }

    /**
     * Implements <code>move</code> in the UIService interface. Moves the main
     * application window to the point with coordinates - x, y.
     *
     * @param x the value of X where the main application frame is to be placed.
     * @param y the value of Y where the main application frame is to be placed.
     *
     * @see UIService#move(int, int)
     */
    public void move(int x, int y)
    {
        this.mainFrame.setLocation(x, y);
    }

    /**
     * Brings the focus to the main application window.
     */
    public void bringToFront()
    {
        if (mainFrame.getState() == Frame.ICONIFIED)
            mainFrame.setState(Frame.NORMAL);
        // Because toFront() method gives us no guarantee that our frame would
        // go on top we'll try to also first request the focus and set our
        // window always on top to put all the chances on our side.
        mainFrame.requestFocus();
        mainFrame.setAlwaysOnTop(true);
        mainFrame.toFront();
        mainFrame.setAlwaysOnTop(false);
    }

    /**
     * Called from the systray service when a tray has been initialized and
     * hiding (instead of minimizing or exiting) is possible). If hiding is
     * possible and the option to minimize is not selected, the application
     * gets hidden on clicking 'X'.
     * 
     * @param true if a tray icon was loaded.
     */
    public void setMainWindowCanHide(boolean canHide)
    {
        mainFrame.updateCloseAction(canHide);
    }

    /**
     * Adds all <tt>ExportedWindow</tt>s to the list of application windows,
     * which could be used from other bundles. Once registered in the
     * <tt>UIService</tt> this window could be obtained through the
     * <tt>getExportedWindow(WindowID)</tt> method and could be shown,
     * hidden, resized, moved, etc.
     */
    public void initExportedWindows()
    {
        registerExportedWindow(new AddContactDialog(mainFrame));
    }

    /**
     * Registers the given <tt>ExportedWindow</tt> to the list of windows that
     * could be accessed from other bundles.
     *
     * @param window the window to be exported
     */
    public void registerExportedWindow(ExportedWindow window)
    {
        exportedWindows.put(window.getIdentifier(), window);
    }

    /**
     * Unregisters the given <tt>ExportedWindow</tt> from the list of windows
     * that could be accessed from other bundles.
     *
     * @param window the window to no longer be exported
     */
    public void unregisterExportedWindow(ExportedWindow window)
    {
        WindowID identifier = window.getIdentifier();
        ExportedWindow removed = exportedWindows.remove(identifier);

        /*
         * In case the unexpected happens and we happen to have the same
         * WindowID for multiple ExportedWindows going through
         * #registerExportedWindow(), we have to make sure we're not
         * unregistering some other ExportedWindow which has overwritten the
         * registration of the specified window.
         */
        if ((removed != null) && !removed.equals(window))
        {

            /*
             * We accidentally unregistered another window so bring back its
             * registration.
             */
            exportedWindows.put(identifier, removed);

            /* Now unregister the right window. */
            for (Iterator<Map.Entry<WindowID, ExportedWindow>> entryIt =
                exportedWindows.entrySet().iterator(); entryIt.hasNext();)
            {
                Map.Entry<WindowID, ExportedWindow> entry = entryIt.next();
                if (window.equals(entry.getValue()))
                    entryIt.remove();
            }
        }
    }

    /**
     * Adds the given <tt>PluginComponentListener</tt> to the list of component
     * listeners registered in this <tt>UIService</tt> implementation.
     *
     * @param listener the <tt>PluginComponentListener</tt> to add
     */
    public void addPluginComponentListener(PluginComponentListener listener)
    {
        synchronized (pluginComponentListeners)
        {
            Iterator<WeakReference<PluginComponentListener>> i
                = pluginComponentListeners.iterator();
            boolean contains = false;

            while (i.hasNext())
            {
                PluginComponentListener l = i.next().get();

                if (l == null)
                    i.remove();
                else if (l.equals(listener))
                    contains = true;
            }
            if (!contains)
                pluginComponentListeners.add(
                        new WeakReference<PluginComponentListener>(listener));
        }
    }

    /**
     * Removes the given <tt>PluginComponentListener</tt> from the list of
     * component listeners registered in this <tt>UIService</tt> implementation.
     *
     * @param listener the <tt>PluginComponentListener</tt> to remove
     */
    public void removePluginComponentListener(PluginComponentListener listener)
    {
        synchronized (pluginComponentListeners)
        {
            Iterator<WeakReference<PluginComponentListener>> i
                = pluginComponentListeners.iterator();

            while (i.hasNext())
            {
                PluginComponentListener l = i.next().get();

                if ((l == null) || l.equals(listener))
                    i.remove();
            }
        }
    }

    /**
     * Implements <code>getSupportedExportedWindows</code> in the UIService
     * interface. Returns an iterator over a set of all windows exported by
     * this implementation.
     *
     * @return an Iterator over all windows exported by this implementation of
     * the UI service.
     *
     * @see UIService#getSupportedExportedWindows()
     */
    public Iterator<WindowID> getSupportedExportedWindows()
    {
        return Collections.unmodifiableMap(exportedWindows).keySet().iterator();
    }

    /**
     * Implements the <code>getExportedWindow</code> in the UIService interface.
     * Returns the window corresponding to the given <tt>WindowID</tt>.
     *
     * @param windowID the id of the window we'd like to retrieve.
     * @param params the params to be passed to the returned window.
     * @return a reference to the <tt>ExportedWindow</tt> instance corresponding
     *         to <tt>windowID</tt>.
     * @see UIService#getExportedWindow(WindowID)
     */
    public ExportedWindow getExportedWindow(WindowID windowID, Object[] params)
    {
        ExportedWindow win = exportedWindows.get(windowID);

        if (win != null)
            win.setParams(params);

        return win;
    }

    /**
     * Implements the <code>getExportedWindow</code> in the UIService
     * interface. Returns the window corresponding to the given
     * <tt>WindowID</tt>.
     *
     * @param windowID the id of the window we'd like to retrieve.
     *
     * @return a reference to the <tt>ExportedWindow</tt> instance corresponding
     * to <tt>windowID</tt>.
     * @see UIService#getExportedWindow(WindowID)
     */
    public ExportedWindow getExportedWindow(WindowID windowID)
    {
        return getExportedWindow(windowID, null);
    }

    /**
     * Implements the <code>UIService.isExportedWindowSupported</code> method.
     * Checks if there's an exported component for the given
     * <tt>WindowID</tt>.
     *
     * @param windowID the id of the window that we're making the query for.
     *
     * @return true if a window with the corresponding windowID is exported by
     * the UI service implementation and false otherwise.
     *
     * @see UIService#isExportedWindowSupported(WindowID)
     */
    public boolean isExportedWindowSupported(WindowID windowID)
    {
        return exportedWindows.containsKey(windowID);
    }

    /**
     * Implements <code>getPopupDialog</code> in the UIService interface.
     * Returns a <tt>PopupDialog</tt> that could be used to show simple
     * messages, warnings, errors, etc.
     *
     * @return a <tt>PopupDialog</tt> that could be used to show simple
     * messages, warnings, errors, etc.
     *
     * @see UIService#getPopupDialog()
     */
    public PopupDialog getPopupDialog()
    {
        return this.popupDialog;
    }

    /**
     * Implements {@link UIService#getChat(Contact)}. If a chat for the given
     * contact exists already, returns it; otherwise, creates a new one.
     *
     * @param contact the contact that we'd like to retrieve a chat window for.
     * @return the <tt>Chat</tt> corresponding to the specified contact.
     * @see UIService#getChat(Contact)
     */
    public ChatPanel getChat(Contact contact)
    {
        return this.getChat(contact, null);
    }

    /**
     * Implements {@link UIService#getChat(Contact)}. If a chat for the given
     * contact exists already, returns it; otherwise, creates a new one.
     *
     * @param contact the contact that we'd like to retrieve a chat window for.
     * @param escapedMessageUID the message ID of the message that should be
     * excluded from the history when the last one is loaded in the chat
     * @return the <tt>Chat</tt> corresponding to the specified contact.
     * @see UIService#getChat(Contact)
     */
    public ChatPanel getChat(Contact contact, String escapedMessageUID)
    {
        MetaContact metaContact
            = GuiActivator.getContactListService()
                .findMetaContactByContact(contact);

        if(metaContact == null)
            return null;

        return chatWindowManager.getContactChat(
            metaContact,
            true,
            escapedMessageUID);
    }

    /**
     * Returns the <tt>Chat</tt> corresponding to the given <tt>ChatRoom</tt>.
     *
     * @param chatRoom the <tt>ChatRoom</tt> for which the searched chat is
     * about.
     * @return the <tt>Chat</tt> corresponding to the given <tt>ChatRoom</tt>.
     */
    public ChatPanel getChat(ChatRoom chatRoom)
    {
        return chatWindowManager.getMultiChat(chatRoom, true);
    }

    /**
     * Returns the selected <tt>Chat</tt>.
     *
     * @return the selected <tt>Chat</tt>.
     */
    public ChatPanel getCurrentChat()
    {
        return chatWindowManager.getSelectedChat();
    }

    /**
     * Returns the phone number currently entered in the phone number field.
     *
     * @return the phone number currently entered in the phone number field.
     */
    public String getCurrentPhoneNumber()
    {
        return null;
    }

    /**
     * Changes the phone number currently entered in the phone number field.
     *
     * @param phoneNumber the phone number to enter in the phone number field.
     */
    public void setCurrentPhoneNumber(String phoneNumber)
    {

    }

    /**
     * Implements the <code>UIService.isContainerSupported</code> method.
     * Checks if the plugable container with the given Container is supported
     * by this implementation.
     *
     * @param containderID the id of the container that we're making the query
     * for.
     *
     * @return true if the container with the specified id is exported by the
     * implementation of the UI service and false otherwise.
     *
     * @see UIService#isContainerSupported(Container)
     */
    public boolean isContainerSupported(Container containderID)
    {
        return supportedContainers.contains(containderID);
    }

    /**
     * Implements the <code>UIService.getAccountRegWizardContainer</code>
     * method. Returns the current implementation of the
     * <tt>AccountRegistrationWizardContainer</tt>.
     *
     * @see UIService#getAccountRegWizardContainer()
     *
     * @return a reference to the currently valid instance of
     * <tt>AccountRegistrationWizardContainer</tt>.
     */
    public WizardContainer getAccountRegWizardContainer()
    {
        return this.wizardContainer;
    }

    /**
     * Returns a default implementation of the <tt>SecurityAuthority</tt>
     * interface that can be used by non-UI components that would like to launch
     * the registration process for a protocol provider. Initially this method
     * was meant for use by the systray bundle and the protocol URI handlers.
     *
     * @param protocolProvider the <tt>ProtocolProviderService</tt> for which
     * the authentication window is about.
     *
     * @return a default implementation of the <tt>SecurityAuthority</tt>
     * interface that can be used by non-UI components that would like to launch
     * the registration process for a protocol provider.
     */
    public SecurityAuthority getDefaultSecurityAuthority(
                    ProtocolProviderService protocolProvider)
    {
        SecurityAuthority secAuthority = GuiActivator.getSecurityAuthority(
            protocolProvider.getProtocolName());

        if (secAuthority == null)
            secAuthority = GuiActivator.getSecurityAuthority();

        if (secAuthority == null)
            secAuthority = new DefaultSecurityAuthority(protocolProvider);

        return secAuthority;
    }

    /**
     * Returns the LoginManager.
     * @return the LoginManager
     */
    public LoginManager getLoginManager()
    {
        return loginManager;
    }

    /**
     * Returns the chat conference manager.
     *
     * @return the chat conference manager.
     */
    public ConferenceChatManager getConferenceChatManager()
    {
        return conferenceChatManager;
    }

    /**
     * Returns the chat window manager.
     *
     * @return the chat window manager.
     */
    public ChatWindowManager getChatWindowManager()
    {
        return chatWindowManager;
    }

    /**
     * Returns the <tt>HistoryWindowManager</tt>.
     * @return the <tt>HistoryWindowManager</tt>
     */
    public HistoryWindowManager getHistoryWindowManager()
    {
        return historyWindowManager;
    }

    /**
     * Returns the <tt>MainFrame</tt>. This is the class defining the main
     * application window.
     *
     * @return the <tt>MainFrame</tt>
     */
    public MainFrame getMainFrame()
    {
        return mainFrame;
    }

    /**
     * The <tt>RunLogin</tt> implements the Runnable interface and is used to
     * shows the login windows in a seperate thread.
     */
    private class RunLoginGui implements Runnable {
        public void run() {
            loginManager.runLogin();
        }
    }

    /**
     * Sets the look&feel and the theme.
     */
    private void setDefaultThemePack()
    {
        // Show tooltips immediately and specify a custom background.
        ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
        UIManager.put(
                "ToolTip.background",
                new Color(
                        GuiActivator.getResources().getColor(
                                "service.gui.TOOLTIP_BACKGROUND")));
        toolTipManager.setInitialDelay(500);
        toolTipManager.setDismissDelay(60000);
        toolTipManager.setEnabled(true);

        // we need to set the UIDefaults class loader so that it may access
        // resources packed inside OSGI bundles
        UIManager.put("ClassLoader", getClass().getClassLoader());

        /*
         * Attempt to use the OS-native LookAndFeel instead of
         * SIPCommLookAndFeel.
         */
        String laf = UIManager.getSystemLookAndFeelClassName();
        boolean lafIsSet = false;

        /*
         * SIPCommLookAndFeel used to be set in case the system L&F was the same
         * as the cross-platform L&F. Unfortunately, SIPCommLookAndFeel is now
         * broken because its classes are loaded by different ClassLoaders which
         * results in exceptions. That's why the check
         * !laf.equals(UIManager.getCrossPlatformLookAndFeelClassName()) is
         * removed from the if statement bellow and thus the cross-platform L&F
         * is preferred over SIPCommLookAndFeel.
         */
        if (laf != null)
        {
            /*
             * Swing does not have a LookAndFeel which integrates with KDE and
             * the cross-platform LookAndFeel is plain ugly. KDE integrates with
             * GTK+ so try to use the GTKLookAndFeel when running in KDE.
             */
            String gtkLookAndFeel
                = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";

            if ((OSUtils.IS_FREEBSD || OSUtils.IS_LINUX)
                    && laf.equals(
                            UIManager.getCrossPlatformLookAndFeelClassName()))
            {
                try
                {
                    String kdeFullSession = System.getenv("KDE_FULL_SESSION");

                    if ((kdeFullSession != null)
                            && (kdeFullSession.length() != 0))
                    {
                        for (LookAndFeelInfo lafi
                                : UIManager.getInstalledLookAndFeels())
                        {
                            if (gtkLookAndFeel.equals(lafi.getClassName()))
                            {
                                laf = gtkLookAndFeel;
                                break;
                            }
                        }
                    }
                }
                catch (Throwable t)
                {
                    if (t instanceof ThreadDeath)
                        throw (ThreadDeath) t;
                }
            }

            try
            {
                UIManager.setLookAndFeel(laf);
                lafIsSet = true;

                UIDefaults uiDefaults = UIManager.getDefaults();
                if (OSUtils.IS_WINDOWS)
                    fixWindowsUIDefaults(uiDefaults);

                // Workaround for SC issue #516
                // "GNOME SCScrollPane has rounded and rectangular borders"
                if (laf.equals(gtkLookAndFeel)
                        || laf.equals("com.sun.java.swing.plaf.motif.MotifLookAndFeel"))
                {
                    uiDefaults.put(
                        "ScrollPaneUI",
                        new javax.swing.plaf.metal.MetalLookAndFeel()
                            .getDefaults().get("ScrollPaneUI"));
                }
            }
            catch (ClassNotFoundException ex)
            {
                /*
                 * Ignore the exceptions because we're only trying to set the
                 * native LookAndFeel and, if it fails, we'll use
                 * SIPCommLookAndFeel.
                 */
            }
            catch (InstantiationException ex)
            {
            }
            catch (IllegalAccessException ex)
            {
            }
            catch (UnsupportedLookAndFeelException ex)
            {
            }
        }

        if (!lafIsSet)
        {
            try
            {
                SIPCommLookAndFeel lf = new SIPCommLookAndFeel();
                SIPCommLookAndFeel.setCurrentTheme(new SIPCommDefaultTheme());

                // Check the isLookAndFeelDecorated property and set the
                // appropriate default decoration.
                if (Boolean.parseBoolean(
                        GuiActivator.getResources().getSettingsString(
                                "impl.gui.IS_LOOK_AND_FEEL_DECORATED")))
                {
                    JFrame.setDefaultLookAndFeelDecorated(true);
                    JDialog.setDefaultLookAndFeelDecorated(true);
                }

                UIManager.setLookAndFeel(lf);
            }
            catch (UnsupportedLookAndFeelException ex)
            {
                logger.error("The provided Look & Feel is not supported.", ex);
            }
        }
    }

    private void fixWindowsUIDefaults(UIDefaults uiDefaults)
    {

        /*
         * Windows actually uses different fonts for the controls in windows and
         * the controls in dialogs. Unfortunately, win.defaultGUI.font may not
         * be the font Windows will use for controls in windows but the one to
         * be used for dialogs. And win.messagebox.font will be the font for
         * windows but the L&F will use it for OptionPane which in turn should
         * rather use the font for dialogs. So swap the meanings of the two to
         * get standard fonts in the windows while compromizing that dialogs may
         * appear in it as well (if the dialogs are created as non-OptionPanes
         * and in this case SIP Communicator will behave as Mozilla Firfox and
         * Eclipse with respect to using the window font for the dialogs).
         */
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Object menuFont = toolkit.getDesktopProperty("win.menu.font");
        Object messageboxFont
            = toolkit.getDesktopProperty("win.messagebox.font");
        if ((messageboxFont != null) && messageboxFont.equals(menuFont))
        {
            Object defaultGUIFont
                = toolkit.getDesktopProperty("win.defaultGUI.font");
            if ((defaultGUIFont != null)
                    && !defaultGUIFont.equals(messageboxFont))
            {
                Object messageFont = uiDefaults.get("OptionPane.font");
                Object controlFont = uiDefaults.get("Panel.font");
                if ((messageFont != null) && !messageFont.equals(controlFont))
                {
                    uiDefaults.put("OptionPane.font", controlFont);
                    uiDefaults.put("OptionPane.messageFont", controlFont);
                    uiDefaults.put("OptionPane.buttonFont", controlFont);

                    uiDefaults.put("Button.font", messageFont);
                    uiDefaults.put("CheckBox.font", messageFont);
                    uiDefaults.put("ComboBox.font", messageFont);
                    uiDefaults.put("EditorPane.font", messageFont);
                    uiDefaults.put("FormattedTextField.font", messageFont);
                    uiDefaults.put("Label.font", messageFont);
                    uiDefaults.put("List.font", messageFont);
                    uiDefaults.put("RadioButton.font", messageFont);
                    uiDefaults.put("Panel.font", messageFont);
                    uiDefaults.put("PasswordField.font", messageFont);
                    uiDefaults.put("ProgressBar.font", messageFont);
                    uiDefaults.put("ScrollPane.font", messageFont);
                    uiDefaults.put("Slider.font", messageFont);
                    uiDefaults.put("Spinner.font", messageFont);
                    uiDefaults.put("TabbedPane.font", messageFont);
                    uiDefaults.put("Table.font", messageFont);
                    uiDefaults.put("TableHeader.font", messageFont);
                    uiDefaults.put("TextField.font", messageFont);
                    uiDefaults.put("TextPane.font", messageFont);
                    uiDefaults.put("TitledBorder.font", messageFont);
                    uiDefaults.put("ToggleButton.font", messageFont);
                    uiDefaults.put("Tree.font", messageFont);
                    uiDefaults.put("Viewport.font", messageFont);
                }
            }
        }

        // Workaround for bug 6396936 (http://bugs.sun.com): WinL&F : font for
        // text area is incorrect.
        uiDefaults.put("TextArea.font", uiDefaults.get("TextField.font"));
    }

    /**
     * Notifies all plugin containers of a <tt>PluginComponent</tt>
     * registration.
     * @param event the <tt>ServiceEvent</tt> that notified us
     */
    public void serviceChanged(ServiceEvent event)
    {
        Object sService = GuiActivator.bundleContext.getService(
            event.getServiceReference());

        // we don't care if the source service is not a plugin component
        if (! (sService instanceof PluginComponentFactory))
            return;

        PluginComponentFactory factory = (PluginComponentFactory) sService;

        switch (event.getType())
        {
        case ServiceEvent.REGISTERED:
            if (logger.isInfoEnabled())
                logger.info("Handling registration of a new Plugin Component.");

            this.firePluginEvent(factory,
                PluginComponentEvent.PLUGIN_COMPONENT_ADDED);
            break;

        case ServiceEvent.UNREGISTERING:
            this.firePluginEvent(factory,
                PluginComponentEvent.PLUGIN_COMPONENT_REMOVED);
            break;
        }
    }

    /**
     * Returns the corresponding <tt>BorderLayout</tt> constraint from the given
     * <tt>Container</tt> constraint.
     *
     * @param containerConstraints constraints defined in the <tt>Container</tt>
     * @return the corresponding <tt>BorderLayout</tt> constraint from the given
     * <tt>Container</tt> constraint.
     */
    public static Object getBorderLayoutConstraintsFromContainer(
        Object containerConstraints)
    {
        Object layoutConstraint = null;
        if (containerConstraints == null)
            return null;

        if (containerConstraints.equals(Container.START))
            layoutConstraint = BorderLayout.LINE_START;
        else if (containerConstraints.equals(Container.END))
            layoutConstraint = BorderLayout.LINE_END;
        else if (containerConstraints.equals(Container.TOP))
            layoutConstraint = BorderLayout.NORTH;
        else if (containerConstraints.equals(Container.BOTTOM))
            layoutConstraint = BorderLayout.SOUTH;
        else if (containerConstraints.equals(Container.LEFT))
            layoutConstraint = BorderLayout.WEST;
        else if (containerConstraints.equals(Container.RIGHT))
            layoutConstraint = BorderLayout.EAST;

        return layoutConstraint;
    }

    /**
     * Indicates that a <tt>PropertyChangeEvent</tt> has occurred.
     *
     * @param evt the <tt>PropertyChangeEvent</tt> that notified us
     */
    public void propertyChange(PropertyChangeEvent evt)
    {
        String propertyName = evt.getPropertyName();

        if (propertyName.equals(
            "impl.gui.IS_TRANSPARENT_WINDOW_ENABLED"))
        {
            String isTransparentString = (String) evt.getNewValue();

            boolean isTransparentWindowEnabled
                = Boolean.parseBoolean(isTransparentString);

            try
            {
                WindowUtils.setWindowTransparent(   mainFrame,
                    isTransparentWindowEnabled);
            }
            catch (UnsupportedOperationException ex)
            {
                logger.error(ex.getMessage(), ex);

                if (isTransparentWindowEnabled)
                {
                    ResourceManagementService resources
                        = GuiActivator.getResources();

                    new ErrorDialog(
                            mainFrame,
                            resources.getI18NString("service.gui.ERROR"),
                            resources.getI18NString(
                                    "service.gui.TRANSPARENCY_NOT_ENABLED"))
                        .showDialog();
                }

                ConfigurationUtils.setTransparentWindowEnabled(false);
            }
        }
        else if (propertyName.equals(
            "impl.gui.WINDOW_TRANSPARENCY"))
        {
            mainFrame.repaint();
        }
    }

    /**
     * Initialize main window font.
     */
    private void initCustomFonts()
    {
        JComponent layeredPane = mainFrame.getLayeredPane();

        ResourceManagementService resources = GuiActivator.getResources();
        String fontName = resources.getSettingsString("service.gui.FONT_NAME");
        int fontSize = resources.getSettingsInt("service.gui.FONT_SIZE");
        Font font = new Font(fontName, Font.BOLD, fontSize);

        for (int i = 0; i < layeredPane.getComponentCount(); i++)
            layeredPane.getComponent(i).setFont(font);
    }

    /**
     * Implements UIService#useMacOSXScreenMenuBar(). Indicates that the Mac OS
     * X screen menu bar is to be used on Mac OS X and the Windows-like
     * per-window menu bars are to be used on non-Mac OS X operating systems.
     *
     * @return <tt>true</tt> to indicate that MacOSX screen menu bar should be
     * used, <tt>false</tt> - otherwise
     */
    public boolean useMacOSXScreenMenuBar()
    {
        return OSUtils.IS_MAC;
    }

    /**
     * Implements ShutdownService#beginShutdown(). Disposes of the mainFrame
     * (if it exists) and then instructs Felix to start shutting down the
     * bundles so that the application can gracefully quit.
     */
    public void beginShutdown()
    {
        try
        {
            // close chats, so we do not start removing plugins one by one
            // when we start to stop the application
            for(ChatPanel cp : chatWindowManager.getAllChats())
                chatWindowManager.closeChat(cp);

            if (mainFrame != null)
                mainFrame.dispose();
        }
        finally
        {
            // Just exit. The shutdown hook in felix ensures that we stop
            // everything nicely.
            System.exit(0);
        }
    }

    /**
     * Returns the <tt>ConfigurationContainer</tt> associated with this
     * <tt>UIService</tt>.
     *
     * @return the <tt>ConfigurationContainer</tt> associated with this
     * <tt>UIService</tt>
     */
    public ConfigurationContainer getConfigurationContainer()
    {
        if (configurationFrame == null)
        {
            if(!SwingUtilities.isEventDispatchThread())
            {
                try
                {
                    SwingUtilities.invokeAndWait(new Runnable()
                    {
                        public void run()
                        {
                            getConfigurationContainer();
                        }
                    });
                }
                catch(Throwable e)
                {
                    logger.error("Error creating config frame in swing thread");
                    // if still no frame create it outside event dispatch thread
                    if(configurationFrame == null)
                        configurationFrame = new ConfigurationFrame(mainFrame);
                }

                return configurationFrame;
            }

            configurationFrame = new ConfigurationFrame(mainFrame);
        }

        return configurationFrame;
    }

    /**
     * Dispatcher which ensures that our custom keybindings will
     * be executed before any other focused(or not focused) component
     * will consume our key event. This way we override some components
     * keybindings.
     */
    private static class KeyBindingsDispatching
        implements KeyEventDispatcher
    {
        private final KeyboardFocusManager focusManager;

        KeyBindingsDispatching(KeyboardFocusManager focusManager)
        {
            this.focusManager = focusManager;
        }

        public boolean dispatchKeyEvent(KeyEvent e)
        {
            if(e.getID() == KeyEvent.KEY_PRESSED)
            {
                Window w = focusManager.getActiveWindow();
                JRootPane rpane = null;

                if(w instanceof JFrame)
                    rpane = ((JFrame)w).getRootPane();

                if(w instanceof JDialog)
                    rpane = ((JDialog)w).getRootPane();

                if(rpane == null)
                    return false;

                Object binding = rpane.
                    getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
                        get(KeyStroke.getKeyStrokeForEvent(e));

                if(binding == null)
                    return false;

                Object actObj = rpane.getActionMap().get(binding);

                if(actObj != null && actObj instanceof UIAction)
                {
                    ((UIAction)actObj).actionPerformed(
                        new ActionEvent(w, -1, (String)binding));
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * Returns a list containing all open Chats
     *
     * @return  A list of all open Chats.
     */
    public List<Chat> getChats()
    {
        return new ArrayList<Chat>(chatWindowManager.getChatPanels());
    }

    /**
     * Get the MetaContact corresponding to the chat.
     * The chat must correspond to a one on one conversation, otherwise this
     * method will return null.
     *
     * @param chat  The chat to get the MetaContact from
     * @return      The MetaContact corresponding to the chat or null in case
     *              it is a chat with more then one person.
     */
    public MetaContact getChatContact(Chat chat)
    {
        Object contact = ((ChatPanel) chat).getChatSession().getDescriptor();
        // If it is a one on one conversation this would be a MetaContact
        if (contact instanceof MetaContact)
            return (MetaContact) contact;
        // If not, we are talking to more then one person and we return null
        else
            return null;
    }

    /**
     * Adds the given <tt>WindowListener</tt> to the main application window.
     * @param l the <tt>WindowListener</tt> to add
     */
    public void addWindowListener(WindowListener l)
    {
        mainFrame.addWindowListener(l);
    }

    /**
     * Removes the given <tt>WindowListener</tt> from the main application
     * window.
     * @param l the <tt>WindowListener</tt> to remove
     */
    public void removeWindowListener(WindowListener l)
    {
        mainFrame.removeWindowListener(l);
    }

    /**
     * Provides all currently instantiated <tt>Chats</tt>.
     *
     * @return all active <tt>Chats</tt>.
     */
    public Collection <Chat> getAllChats()
    {
        return new ArrayList <Chat> (getChatWindowManager().getAllChats());
    }

    /**
     * Returns the single window container if such one is available (i.e. we're
     * in a single window mode).
     *
     * @return the single window container if such one is available, otherwise
     * returns null
     */
    public SingleWindowContainer getSingleWindowContainer()
    {
        return singleWindowContainer;
    }

    /**
     * Registers a <tt>NewChatListener</tt> to be informed when new
     * <tt>Chats</tt> are created.
     * @param listener listener to be registered
     */
    public void addChatListener(ChatListener listener)
    {
        getChatWindowManager().addChatListener(listener);
    }

    /**
     * Removes the registration of a <tt>NewChatListener</tt>.
     * @param listener listener to be unregistered
     */
    public void removeChatListener(ChatListener listener)
    {
        getChatWindowManager().removeChatListener(listener);
    }

    /**
     * Repaints and revalidates the whole UI Tree.
     *
     * Calls {@link SwingUtilities#updateComponentTreeUI(Component c)}
     * for every window owned by the application which cause UI skin and
     * layout repaint.
     */
    public void repaintUI()
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                public void run()
                {
                    repaintUI();
                }
            });
            return;
        }

        if (UIManager.getLookAndFeel() instanceof SIPCommLookAndFeel)
            ((SIPCommLookAndFeel) UIManager.getLookAndFeel()).loadSkin();

        Constants.reload();
        ImageLoader.clearCache();

        Window[] windows
            = net.java.sip.communicator.plugin.desktoputil.WindowUtils
                .getWindows();

        for(Window win : windows)
        {
            reloadComponents(win);
            ComponentUtils.updateComponentTreeUI(win);
        }
    }

    /**
     * Reloads reload-able children of the given <tt>window</tt>.
     *
     * @param window the window, which components to reload
     */
    private void reloadComponents(Window window)
    {
        if (window instanceof Skinnable)
            ((Skinnable) window).loadSkin();

        reloadComponents((java.awt.Container) window);
    }

    /**
     * Reloads all children components of the given <tt>container</tt> in depth.
     *
     * @param container the container, which children to reload
     */
    private void reloadComponents(java.awt.Container container)
    {
        for (int i = 0; i < container.getComponentCount(); i++)
        {
            Component c = container.getComponent(i);

            if (c instanceof Skinnable)
                ((Skinnable) c).loadSkin();

            if (c instanceof JComponent)
            {
                JPopupMenu jpm = ((JComponent) c).getComponentPopupMenu();
                if(jpm != null && jpm.isVisible()
                        && jpm.getInvoker() == (JComponent)c)
                {
                    if (jpm instanceof Skinnable)
                        ((Skinnable) jpm).loadSkin();

                    reloadComponents(jpm);
                }
            }

            if (c instanceof JMenu)
            {
                Component[] children = null;
                children = ((JMenu)c).getMenuComponents();
                for(int ii = 0; ii < children.length; ii++)
                {
                    if (children[ii] instanceof Skinnable)
                        ((Skinnable) children[ii]).loadSkin();

                    if (children[ii] instanceof java.awt.Container)
                        reloadComponents((java.awt.Container) children[ii]);
                }
            }
            else if (c instanceof java.awt.Container)
                reloadComponents((java.awt.Container) c);
        }
    }

    /**
     * Returns the create account window.
     *
     * @return the create account window
     */
    public CreateAccountWindow getCreateAccountWindow()
    {
        return new NewAccountDialog();
    }

    /**
     * Creates a new <tt>Call</tt> with a specific set of participants.
     * <p>
     * The current implementation provided by <tt>UIServiceImpl</tt> supports a
     * single participant at the time of this writing.
     * </p>
     *
     * @param participants an array of <tt>String</tt> values specifying the
     * participants to be included into the newly created <tt>Call</tt>
     * @see UIService#createCall(String[])
     */
    public void createCall(String[] participants)
    {
        if (participants.length == 1)
            CallManager.createCall(participants[0], null);
        else
            throw new IllegalArgumentException("participants");
    }

    /**
     * Starts a new <tt>Chat</tt> with a specific set of participants.
     * <p>
     * The current implementation provided by <tt>UIServiceImpl</tt> supports a
     * single participant at the time of this writing.
     * </p>
     *
     * @param participants an array of <tt>String</tt> values specifying the
     * participants to be included into the newly created <tt>Chat</tt>
     * @see UIService#startChat(String[])
     */
    public void startChat(String[] participants)
    {
        startChat(participants, false);
    }

    /**
     * Starts a new <tt>Chat</tt> with a specific set of participants.
     *
     * @param participants an array of <tt>String</tt> values specifying the
     * participants to be included into the newly created <tt>Chat</tt>
     * @param isSmsEnabled whether sms option should be enabled if possible
     */
    public void startChat(String[] participants, boolean isSmsEnabled)
    {
        if (participants.length == 1)
            getChatWindowManager().startChat(participants[0], isSmsEnabled);
        else
            throw new IllegalArgumentException("participants");
    }
    
    /**
     * Opens a chat room window for the given <tt>ChatRoomWrapper</tt> instance.
     * 
     * @param chatRoom the chat room associated with the chat room window
     */
    public void openChatRoomWindow(ChatRoomWrapper chatRoom)
    {
        ChatWindowManager chatWindowManager
            = getChatWindowManager();
        ChatPanel chatPanel
            = chatWindowManager.getMultiChat(chatRoom, true);
    
        chatWindowManager.openChat(chatPanel, true);
    }
    
    /**
     * Closes the chat room window for the given <tt>ChatRoomWrapper</tt> 
     * instance.
     * 
     * @param chatRoom the chat room associated with the chat room window.
     */
    public void closeChatRoomWindow(ChatRoomWrapper chatRoom)
    {
        ChatWindowManager chatWindowManager
            = getChatWindowManager();
        ChatPanel chatPanel
            = chatWindowManager.getMultiChat(chatRoom, false);
    
        if (chatPanel != null)
            chatWindowManager.closeChat(chatPanel);
    }

    /**
     * Creates a contact list component.
     *
     * @param clContainer the parent contact list container
     * @return the created <tt>ContactList</tt>
     */
    public ContactList createContactListComponent(
        ContactListContainer clContainer)
    {
        return new TreeContactList(clContainer);
    }

    /**
     * Returns a collection of all currently in progress calls.
     *
     * @return a collection of all currently in progress calls.
     */
    public Collection<Call> getInProgressCalls()
    {
        return CallManager.getInProgressCalls();
    }
    
    /**
     * Shows Add chat room dialog.
     */
    public void showAddChatRoomDialog()
    {
        ChatRoomTableDialog.showChatRoomTableDialog();
    }
    
    /**
     * Shows chat room open automatically configuration dialog.
     * @param chatRoomId the chat room id of the chat room associated with the 
     * dialog 
     * @param pps the protocol provider service of the chat room
     */
    public void showChatRoomAutoOpenConfigDialog(
        ProtocolProviderService pps, String chatRoomId)
    {
        ChatRoomAutoOpenConfigDialog.showChatRoomAutoOpenConfigDialog(
            pps, chatRoomId);
    }

    /**
     * Counts the number of unread notifications and forwards the sum to the
     * systray service.
     */
    @Override
    public void notificationReceived(UINotification notification)
    {
        forwardNotificationCount();
    }

    /**
     * Counts the number of unread notifications and forwards the sum to the
     * systray service.
     */
    @Override
    public void notificationCleared(UINotification notification)
    {
        forwardNotificationCount();
    }

    private void forwardNotificationCount()
    {
        int count = 0;
        for (UINotificationGroup g : UINotificationManager
            .getNotificationGroups())
        {
            Iterator<UINotification> it =
                UINotificationManager.getUnreadNotifications(g);
            while (it.hasNext())
            {
                count += it.next().getUnreadObjects();
            }
        }

        GuiActivator.getSystrayService().setNotificationCount(count);
    }
}
