/*
 * 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.osdependent.jdic;

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

import javax.swing.*;

import net.java.sip.communicator.impl.osdependent.*;
import net.java.sip.communicator.plugin.desktoputil.presence.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.globalstatus.*;
import net.java.sip.communicator.util.*;

import org.osgi.framework.*;

/**
 * The <tt>StatusSubMenu</tt> provides a menu which allow to select the status
 * for each of the protocol providers registered when the menu appears
 *
 * @author Nicolas Chamouard
 * @author Lyubomir Marinov
 */
public class StatusSubMenu
    implements ProviderPresenceStatusListener,
               RegistrationStateChangeListener,
               ActionListener,
               ItemListener
{
    /**
     * Contains all accounts and corresponding menus.
     */
    private final Map<AccountID, Object> accountSelectors =
        new Hashtable<AccountID, Object>();

    private final Object menu;

    /**
     * Hide accounts from accounts status list.
     */
    private static boolean hideAccountStatusSelectors = false;

    /**
     * Initializes a new <tt>StatusSubMenu</tt> instance.
     *
     * @param swing <tt>true</tt> to represent this instance with a Swing
     * <tt>JMenu</tt>; <tt>false</tt> to use an AWT <tt>Menu</tt>
     */
    public StatusSubMenu(boolean swing, boolean accountMenuSupported)
    {
        String text = Resources.getString("impl.systray.SET_STATUS");

        if (swing)
        {
            JMenu menu = new JMenu(text);

            menu.setIcon(
                    Resources.getImage("service.systray.STATUS_MENU_ICON"));
            /* makes the menu look better */
            menu.setPreferredSize(new java.awt.Dimension(28, 24));

            this.menu = menu;
        }
        else
        {
            this.menu = new Menu(text);
        }

        if (accountMenuSupported)
        {
            String hideAccountStatusSelectorsProperty
                = "impl.gui.HIDE_ACCOUNT_STATUS_SELECTORS";
            String hideAccountsStatusDefaultValue =
                OsDependentActivator.getResources()
                    .getSettingsString(hideAccountStatusSelectorsProperty);

            if(hideAccountsStatusDefaultValue != null)
                hideAccountStatusSelectors = Boolean.parseBoolean(
                    hideAccountsStatusDefaultValue);

            hideAccountStatusSelectors =
                OsDependentActivator.getConfigurationService()
                    .getBoolean(
                        hideAccountStatusSelectorsProperty,
                        hideAccountStatusSelectors);
        }
        else
        {
            hideAccountStatusSelectors = true;
        }

        PresenceStatus offlineStatus = null;
        // creates menu item entry for every global status
        for(GlobalStatusEnum status : GlobalStatusEnum.globalStatusSet)
        {
            createMenuItem(status, swing);

            if(status.getStatus() < 1)
                offlineStatus = status;
        }
        // initially it is offline
        selectItemFromStatus(offlineStatus.getStatus());

        if (accountMenuSupported)
        {
            this.addSeparator();
            addMenuItem(menu, new GlobalStatusMessageMenu(swing).getMenu());
        }

        if(!hideAccountStatusSelectors)
            this.addSeparator();

        this.init();
    }

    public Object getMenu()
    {
        return menu;
    }

    /**
     * Creates a menu item with the given <tt>textKey</tt>, <tt>iconID</tt> and
     * <tt>name</tt>.
     * @return the created <tt>JCheckBoxMenuItem</tt>
     */
    private Object createMenuItem(
        GlobalStatusEnum status,
        boolean swing)
    {
        Object menuItem = null;

        if(swing)
        {
            JCheckBoxMenuItem mItem = new JCheckBoxMenuItem(
                GlobalStatusEnum.getI18NStatusName(status),
                new ImageIcon(status.getStatusIcon()));

            mItem.setName(status.getStatusName());
            mItem.addActionListener(this);
            menuItem = mItem;
        }
        else
        {
            CheckboxMenuItem mItem = new CheckboxMenuItem(
                GlobalStatusEnum.getI18NStatusName(status));
            mItem.setName(status.getStatusName());
            mItem.addItemListener(this);
            menuItem = mItem;
        }
        addMenuItem(getMenu(), menuItem);


        return menuItem;
    }

    /**
     * Adds separator to underlying menu implementation.
     */
    private void addSeparator()
    {
        if (menu instanceof JMenu)
            ((JMenu) menu).addSeparator();
        else
            ((Menu) menu).addSeparator();
    }

    /**
     * Adds the account corresponding to the given protocol provider to this
     * menu.
     *
     * @param protocolProvider the protocol provider corresponding to the
     *            account to add
     */
    private void addAccount(ProtocolProviderService protocolProvider)
    {
        if(protocolProvider.getAccountID().isStatusMenuHidden())
            return;

        OperationSetPresence presence
            = protocolProvider.getOperationSet(OperationSetPresence.class);

        boolean swing = (menu instanceof JComponent);

        if (presence == null)
        {
            StatusSimpleSelector simpleSelector =
                new StatusSimpleSelector(protocolProvider, swing);

            this.accountSelectors.put(protocolProvider.getAccountID(),
                simpleSelector);
            if(!hideAccountStatusSelectors)
                addMenuItem(menu, simpleSelector.getMenu());

            protocolProvider.addRegistrationStateChangeListener(this);
        }
        else
        {
            StatusSelector statusSelector
                = new StatusSelector(protocolProvider, presence, swing);

            this.accountSelectors.put(protocolProvider.getAccountID(),
                statusSelector);
            if(!hideAccountStatusSelectors)
                addMenuItem(menu, statusSelector.getMenu());

            presence.addProviderPresenceStatusListener(this);
        }
    }

    static void addMenuItem(Object menu, Object menuItem)
    {
        if (menu instanceof Container)
            ((Container) menu).add((Component) menuItem);
        else
            ((Menu) menu).add((MenuItem) menuItem);
    }

    /**
     * Removes the account corresponding to the given protocol provider from
     * this menu.
     *
     * @param protocolProvider the protocol provider corresponding to the
     *            account to remove.
     */
    private void removeAccount(ProtocolProviderService protocolProvider)
    {
        Object selector =
            this.accountSelectors.get(protocolProvider.getAccountID());

        // no such provider added
        if(selector == null)
            return;

        Object selectorMenu;
        if (selector instanceof StatusSimpleSelector)
            selectorMenu = ((StatusSimpleSelector) selector).getMenu();
        else
            selectorMenu = ((StatusSelector) selector).getMenu();

        if (menu instanceof Container)
            ((Container) menu).remove((Component) selectorMenu);
        else
            ((MenuContainer) menu).remove((MenuComponent) selectorMenu);

        /*
         * Remove the listeners installed in
         * addAccount(ProtocolProviderService).
         */
        OperationSetPresence presence
            = protocolProvider.getOperationSet(OperationSetPresence.class);

        if (presence != null)
            presence.removeProviderPresenceStatusListener(this);
        else
            protocolProvider.removeRegistrationStateChangeListener(this);
    }

    /**
     * We fill the protocolProviderTable with all
     * running protocol providers at the start of
     * the bundle.
     */
    private void init()
    {
        OsDependentActivator.bundleContext
            .addServiceListener(new ProtocolProviderServiceListener());

        for(ProtocolProviderService provider : getProtocolProviders())
        {
            if(!provider.getAccountID().isHidden())
                this.addAccount(provider);
        }
    }

    /**
     * Obtains all currently registered ProtocolProviderServices.
     * @return all currently registered ProtocolProviderServices.
     */
    private List<ProtocolProviderService> getProtocolProviders()
    {
        BundleContext bundleContext = OsDependentActivator.bundleContext;
        Collection<ServiceReference<ProtocolProviderService>> ppsRefs
            = ServiceUtils.getServiceReferences(
                    bundleContext,
                    ProtocolProviderService.class);
        List<ProtocolProviderService> protocolProviders
            = new ArrayList<ProtocolProviderService>();

        // in case we found any
        if ((ppsRefs != null) && !ppsRefs.isEmpty())
        {
            for (ServiceReference<ProtocolProviderService> ppsRef : ppsRefs)
                protocolProviders.add(bundleContext.getService(ppsRef));
        }
        return protocolProviders;
    }

    /**
     * Fired when an account has changed its status. We update the icon
     * in the menu.
     *
     * @param evt
     */
    public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt)
    {
        ProtocolProviderService pps = evt.getProvider();
        StatusSelector selectorBox
            = (StatusSelector) accountSelectors.get(pps.getAccountID());

        if (selectorBox != null)
            selectorBox.updateStatus(evt.getNewStatus());

        this.updateGlobalStatus();
    }

    /*
     * ImplementsProviderPresenceStatusListener#providerStatusMessageChanged(
     * PropertyChangeEvent).
     */
    public void providerStatusMessageChanged(PropertyChangeEvent evt)
    {
    }

    /*
     * ImplementsRegistrationStateChangeListener#registrationStateChanged(
     * RegistrationStateChangeEvent). Updates the status of accounts which do
     * not support presence.
     */
    public void registrationStateChanged(RegistrationStateChangeEvent evt)
    {
        ProtocolProviderService pps = evt.getProvider();
        StatusSimpleSelector selectorBox
            = (StatusSimpleSelector) accountSelectors.get(pps.getAccountID());

        if (selectorBox != null)
            selectorBox.updateStatus();

        this.updateGlobalStatus();
    }

    /**
     * Updates the global status by picking the most connected protocol provider
     * status.
     */
    private void updateGlobalStatus()
    {
        int status = 0;
        boolean hasAvailableProvider = false;

        for(ProtocolProviderService protocolProvider : getProtocolProviders())
        {
            // We do not show hidden protocols in our status bar, so we do not
            // care about their status here.
            if (protocolProvider.getAccountID().isHidden())
                continue;

            if (!protocolProvider.isRegistered())
                continue;

            OperationSetPresence presence
                = protocolProvider.getOperationSet(OperationSetPresence.class);

            if(presence == null)
            {
                hasAvailableProvider = true;
                continue;
            }

            int presenceStatus
                = (presence == null)
                    ? PresenceStatus.AVAILABLE_THRESHOLD
                    : presence.getPresenceStatus().getStatus();

            if (status < presenceStatus)
                status = presenceStatus;
        }

        // if we have at least one online provider
        if(status == 0 && hasAvailableProvider)
            status = PresenceStatus.AVAILABLE_THRESHOLD;

        selectItemFromStatus(status);
    }

    /**
     * Selects the menu item corresponding to the given status.
     * For status constants we use here the values defined in the
     * <tt>PresenceStatus</tt>, but this is only for convenience.
     *
     * @param status the status to which the item should correspond
     */
    private void selectItemFromStatus(int status)
    {
        String nameToSelect;
        if(status < PresenceStatus.ONLINE_THRESHOLD)
        {
            nameToSelect = GlobalStatusEnum.OFFLINE_STATUS;
        }
        else if(status < PresenceStatus.EXTENDED_AWAY_THRESHOLD)
        {
            nameToSelect = GlobalStatusEnum.DO_NOT_DISTURB_STATUS;
        }
        else if(status < PresenceStatus.AWAY_THRESHOLD)
        {
            nameToSelect = GlobalStatusEnum.EXTENDED_AWAY_STATUS;
        }
        else if(status < PresenceStatus.AVAILABLE_THRESHOLD)
        {
            nameToSelect = GlobalStatusEnum.AWAY_STATUS;
        }
        else if(status < PresenceStatus.EAGER_TO_COMMUNICATE_THRESHOLD)
        {
            nameToSelect = GlobalStatusEnum.ONLINE_STATUS;
        }
        else if(status < PresenceStatus.MAX_STATUS_VALUE)
        {
            nameToSelect = GlobalStatusEnum.FREE_FOR_CHAT_STATUS;
        }
        else
        {
            nameToSelect = GlobalStatusEnum.OFFLINE_STATUS;
        }

        if(menu instanceof Menu)
        {
            Menu theMenu = (Menu) menu;
            for(int i =0; i < theMenu.getItemCount(); i++)
            {
                MenuItem item = theMenu.getItem(i);

                if(item instanceof CheckboxMenuItem)
                {
                    if(item.getName().equals(nameToSelect))
                    {
                        ((CheckboxMenuItem)item).setState(true);
                    }
                    else
                    {
                        ((CheckboxMenuItem)item).setState(false);
                    }
                }
            }
        }
        else if(menu instanceof JMenu)
        {
            JMenu theMenu = (JMenu) menu;
            for(int i =0; i < theMenu.getItemCount(); i++)
            {
                JMenuItem item = theMenu.getItem(i);

                if(item instanceof JCheckBoxMenuItem)
                {
                    if(item.getName().equals(nameToSelect))
                        item.setSelected(true);
                    else
                        item.setSelected(false);
                }
            }
        }
    }

    /**
     * Change the status of the protocol according to the menu item selected
     *
     * @param evt the event containing the menu item name
     */
    public void actionPerformed(ActionEvent evt)
    {
        Object source = evt.getSource();
        if(source instanceof JMenuItem)
            changeStatusFromName(((JMenuItem)source).getName());
    }

    /**
     * Listens for changes in item state (CheckboxMenuItem)s.
     * @param e the event.
     */
    public void itemStateChanged(ItemEvent e)
    {
        Object sourceItem = e.getSource();
        if(e.getStateChange() == ItemEvent.SELECTED)
        {
            if(sourceItem instanceof CheckboxMenuItem)
            {
                changeStatusFromName(((CheckboxMenuItem)sourceItem).getName());
            }
        }
        else if(e.getStateChange() == ItemEvent.DESELECTED)
        {
            if(sourceItem instanceof CheckboxMenuItem)
            {
                ((CheckboxMenuItem)sourceItem).setState(true);
            }
        }
    }

    /**
     * Changes global status from selected item name.
     * @param itemName the item name that was selected.
     */
    private void changeStatusFromName(String itemName)
    {
        OsDependentActivator.getGlobalStatusService().publishStatus(
                    GlobalStatusEnum.getStatusByName(itemName));
    }

    /**
     * Listens for <tt>ServiceEvent</tt>s indicating that a
     * <tt>ProtocolProviderService</tt> has been registered and completes the
     * account status menu.
     */
    private class ProtocolProviderServiceListener
        implements ServiceListener
    {
        /**
         * When a service is registered or unregistered, we update
         * the provider tables and add/remove listeners (if it supports
         * BasicInstantMessaging implementation)
         *
         * @param event ServiceEvent
         */
        public void serviceChanged(ServiceEvent event)
        {
            //if the event is caused by a bundle being stopped, we don't want to
            //know
            ServiceReference<?> serviceRef = event.getServiceReference();

            if(serviceRef.getBundle().getState() == Bundle.STOPPING)
                return;

            Object service
                = OsDependentActivator.bundleContext.getService(serviceRef);

            if (! (service instanceof ProtocolProviderService))
                return;

            ProtocolProviderService provider = (ProtocolProviderService)service;

            switch (event.getType())
            {
            case ServiceEvent.REGISTERED:
                addAccount(provider);
                break;

            case ServiceEvent.UNREGISTERING:
                removeAccount(provider);
                break;
            }
        }
    }
}
