/*
 * 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.awt.image.*;
import java.net.*;
import java.util.HashMap;
import java.util.Map;

import javax.swing.*;
import javax.swing.event.*;

import net.java.sip.communicator.impl.osdependent.*;
import net.java.sip.communicator.impl.osdependent.systemtray.SystemTray;
import net.java.sip.communicator.impl.osdependent.systemtray.TrayIcon;
import net.java.sip.communicator.impl.osdependent.windows.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.systray.*;
import net.java.sip.communicator.service.systray.event.*;
import net.java.sip.communicator.util.Logger;

import org.apache.commons.lang3.tuple.Pair;
import org.jitsi.util.*;
import org.osgi.framework.*;

import com.apple.eawt.*;

/**
 * The <tt>Systray</tt> provides a Icon and the associated <tt>TrayMenu</tt>
 * in the system tray using the Jdic library.
 *
 * @author Nicolas Chamouard
 * @author Yana Stamcheva
 * @author Lyubomir Marinov
 * @author Symphorien Wanko
 * @author Pawel Domas
 */
public class SystrayServiceJdicImpl
    extends AbstractSystrayService
{
    /**
     * The systray.
     */
    private final SystemTray systray;

    /**
     * The icon in the system tray.
     */
    private TrayIcon trayIcon;

    /**
     * The menu that spring with a right click.
     */
    private Object menu;

    /**
     * The <tt>Logger</tt> used by the <tt>SystrayServiceJdicImpl</tt> class and
     * its instances for logging output.
     */
    private static final Logger logger
        = Logger.getLogger(SystrayServiceJdicImpl.class);

    /**
     * The various icons used on the systray
     */
    private ImageIcon currentIcon;

    private ImageIcon logoIcon;

    private ImageIcon logoIconOffline;

    private ImageIcon logoIconAway;

    private ImageIcon logoIconExtendedAway;

    private ImageIcon logoIconFFC;

    private ImageIcon logoIconDND;

    private ImageIcon logoIconWhite;

    /**
     * The dock Icons used only in Mac version
     */
    private URL dockIconOnline;

    private URL dockIconOffline;

    private URL dockIconAway;

    private URL dockIconExtendedAway;

    private URL dockIconFFC;

    private URL dockIconDND;

    private Image originalDockImage = null;

    private boolean initialized = false;

    /**
     * The listener which gets notified about pop-up message events (e.g. clicks
     * on the pop-up).
     */
    private final SystrayPopupMessageListener popupMessageListener
        = new SystrayPopupMessageListenerImpl();

    /**
     * Initializes a new <tt>SystrayServiceJdicImpl</tt> instance.
     */
    public SystrayServiceJdicImpl()
    {
        super(OsDependentActivator.bundleContext);

        SystemTray systray;
        try
        {
            systray = SystemTray.getSystemTray();
        }
        catch (Throwable t)
        {
            if (t instanceof ThreadDeath)
                throw (ThreadDeath) t;
            else
            {
                systray = null;
                if (!GraphicsEnvironment.isHeadless())
                    logger.error("Failed to create a systray!", t);
            }
        }

        this.systray = systray;
        if (this.systray != null)
        {
            initSystray();
        }
    }

    @Override
    public Map<String, String> getSystrayModes()
    {
        return new HashMap<String, String>()
        {{
            put("disabled", "service.systray.mode.DISABLED");
            if (java.awt.SystemTray.isSupported())
            {
                put("native", "service.systray.mode.NATIVE");
            }

            if (!OSUtils.IS_MAC && !OSUtils.IS_WINDOWS)
            {
                put("appindicator",
                    "service.systray.mode.APPINDICATOR");
                put("appindicator_static",
                    "service.systray.mode.APPINDICATOR_STATIC");
            }
        }};
    }

    @Override
    public String getActiveSystrayMode()
    {
        return SystemTray.getSystemTrayMode();
    }

    /**
     * Initializes the systray icon and related listeners.
     */
    private void initSystray()
    {
        UIService uiService = OsDependentActivator.getUIService();

        if (uiService == null)
        {
            /*
             * Delay the call to the #initSystray() method until the UIService
             * implementation becomes available.
             */
            try
            {
                OsDependentActivator.bundleContext.addServiceListener(
                        new DelayedInitSystrayServiceListener(),
                        '('
                            + Constants.OBJECTCLASS
                            + '='
                            + UIService.class.getName()
                            + ')');
            }
            catch (InvalidSyntaxException ise)
            {
                /*
                 * Oh, it should not really happen. Besides, it is not clear at
                 * the time of this writing what is supposed to happen in the
                 * case of such an exception here.
                 */
            }
            return;
        }

        Pair<Object, Object> createdMenu = TrayMenuFactory.createTrayMenu(
            this,
            systray.useSwingPopupMenu(),
            systray.supportsDynamicMenu());
        menu = createdMenu.getLeft();

        boolean isMac = OSUtils.IS_MAC;

        logoIcon = Resources.getImage("service.systray.TRAY_ICON_WINDOWS");
        logoIconOffline = Resources.getImage(
            "service.systray.TRAY_ICON_WINDOWS_OFFLINE");
        logoIconAway = Resources.getImage(
            "service.systray.TRAY_ICON_WINDOWS_AWAY");
        logoIconExtendedAway = Resources.getImage(
            "service.systray.TRAY_ICON_WINDOWS_EXTENDED_AWAY");
        logoIconFFC = Resources.getImage(
            "service.systray.TRAY_ICON_WINDOWS_FFC");
        logoIconDND = Resources.getImage(
            "service.systray.TRAY_ICON_WINDOWS_DND");

        // If we're running under Mac OS X, we use special black and white
        // icons without background.
        if (isMac)
        {
            logoIcon = Resources.getImage("service.systray.TRAY_ICON_MACOSX");
            logoIconWhite = Resources.getImage(
                "service.systray.TRAY_ICON_MACOSX_WHITE");
        }

        /*
         * Default to set offline , if any protocols become online will set it
         * to online.
         */
        currentIcon = isMac ? logoIcon : logoIconOffline;

        trayIcon
            = systray.createTrayIcon(
                    currentIcon,
                    Resources.getApplicationString(
                            "service.gui.APPLICATION_NAME"),
                    menu);
        trayIcon.setIconAutoSize(true);

        if (isMac)
        {
            // init dock Icons
            dockIconOnline = Resources.getImageURL(
                "service.systray.DOCK_ICON_ONLINE");
            dockIconOffline = Resources.getImageURL(
                "service.systray.DOCK_ICON_OFFLINE");
            dockIconAway = Resources.getImageURL(
                "service.systray.DOCK_ICON_AWAY");
            dockIconExtendedAway = Resources.getImageURL(
                "service.systray.DOCK_ICON_EXTENDED_AWAY");
            dockIconFFC =
                    Resources.getImageURL("service.systray.DOCK_ICON_FFC");
            dockIconDND =
                    Resources.getImageURL("service.systray.DOCK_ICON_DND");
        }

        //Show/hide the contact list when user clicks on the systray.
        final Object defaultActionItem;
        if (systray.useSwingPopupMenu())
        {
            defaultActionItem = ((JMenuItem) createdMenu.getRight());
        }
        else
        {
            defaultActionItem = ((MenuItem) createdMenu.getRight());
        }

        /*
         * Change the Mac OS X icon with the white one when the pop-up menu
         * appears.
         */
        if (isMac)
        {
            TrayMenuFactory.addPopupMenuListener(
                    menu,
                    new PopupMenuListener()
                    {
                        public void popupMenuWillBecomeVisible(PopupMenuEvent e)
                        {
                            ImageIcon newIcon = logoIconWhite;
                            trayIcon.setIcon(newIcon);
                            currentIcon = newIcon;
                        }

                        public void popupMenuWillBecomeInvisible(
                                PopupMenuEvent e)
                        {
                            ImageIcon newIcon = logoIcon;
                            getTrayIcon().setIcon(newIcon);
                            currentIcon = newIcon;
                        }

                        public void popupMenuCanceled(PopupMenuEvent e)
                        {
                            popupMenuWillBecomeInvisible(e);
                        }
                    });
        }

        PopupMessageHandler pmh = null;

        if (!isMac)
        {
            pmh = new PopupMessageHandlerTrayIconImpl(trayIcon);
            addPopupHandler(pmh);
            OsDependentActivator.bundleContext.registerService(
                    PopupMessageHandler.class.getName(),
                    pmh,
                    null);
        }

        initHandlers();

        /*
         * Either we have an incorrect configuration value or the default pop-up
         * handler is not available yet. We will use the available pop-up
         * handler and will automatically switch to the configured one when it
         * becomes available. We will be aware of it since we listen for new
         * registered services in the BundleContext.
         */
        if ((getActivePopupMessageHandler() == null) && (pmh != null))
            setActivePopupMessageHandler(pmh);

        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                systray.addTrayIcon(trayIcon);
                trayIcon.setDefaultAction(defaultActionItem);
            }
        });

        initialized = true;
        uiService.setMainWindowCanHide(true);
    }

    /**
     * Sets a new Systray icon.
     *
     * @param imageType the type of the image to set.
     */
    public void setSystrayIcon(int imageType)
    {
        if (!checkInitialized())
            return;

        boolean isMac = OSUtils.IS_MAC;
        ImageIcon systrayIconToSet = null;

        switch (imageType)
        {
        case SystrayService.SC_IMG_TYPE:
            systrayIconToSet
                = (isMac && TrayMenuFactory.isVisible(menu))
                    ? logoIconWhite
                    : logoIcon;
            break;
        case SystrayService.SC_IMG_OFFLINE_TYPE:
            if (!isMac)
                systrayIconToSet = logoIconOffline;
            break;
        case SystrayService.SC_IMG_AWAY_TYPE:
            if (!isMac)
                systrayIconToSet = logoIconAway;
            break;
        case SystrayService.SC_IMG_EXTENDED_AWAY_TYPE:
            if (!isMac)
                systrayIconToSet = logoIconExtendedAway;
            break;
        case SystrayService.SC_IMG_FFC_TYPE:
            if (!isMac)
                systrayIconToSet = logoIconFFC;
            break;
        case SystrayService.SC_IMG_DND_TYPE:
            if (!isMac)
                systrayIconToSet = logoIconDND;
            break;
        }

        if (systrayIconToSet != null)
        {
            this.trayIcon.setIcon(systrayIconToSet);
            this.currentIcon = systrayIconToSet;
        }

        if (isMac)
        {
            URL dockIconURLToSet;

            switch (imageType)
            {
            case SystrayService.SC_IMG_TYPE:
                dockIconURLToSet = dockIconOnline;
                break;
            case SystrayService.SC_IMG_OFFLINE_TYPE:
                dockIconURLToSet = dockIconOffline;
                break;
            case SystrayService.SC_IMG_AWAY_TYPE:
                dockIconURLToSet = dockIconAway;
                break;
            case SystrayService.SC_IMG_EXTENDED_AWAY_TYPE:
                dockIconURLToSet = dockIconExtendedAway;
                break;
            case SystrayService.SC_IMG_FFC_TYPE:
                dockIconURLToSet = dockIconFFC;
                break;
            case SystrayService.SC_IMG_DND_TYPE:
                dockIconURLToSet = dockIconDND;
                break;
            default:
                dockIconURLToSet = null;
                break;
            }
            try
            {
                Application application = Application.getApplication();

                if (originalDockImage == null)
                    originalDockImage = application.getDockIconImage();

                if (dockIconURLToSet != null)
                {
                    application.setDockIconImage(
                            Toolkit.getDefaultToolkit().getImage(
                                    dockIconURLToSet));
                }
                else if (originalDockImage != null)
                {
                    application.setDockIconImage(originalDockImage);
                }
            }
            catch (Exception e)
            {
                logger.error("failed to change dock icon", e);
            }
        }
    }

    @Override
    public boolean checkInitialized()
    {
        return initialized;
    }

    /**
     * Set the number of pending notifications to the the application icon
     * (Dock on OSX, TaskBar on Windows, nothing on Linux currently).
     */
    @Override
    public void setNotificationCount(int count)
    {
        if (OSUtils.IS_MAC)
        {
            Application application = Application.getApplication();
            application.setDockIconBadge(new Integer(count).toString());
        }
        else if (OSUtils.IS_WINDOWS)
        {
            UIService uiService = OsDependentActivator.getUIService();
            if (uiService == null)
            {
                return;
            }

            ExportedWindow mainWindow =
                uiService.getExportedWindow(ExportedWindow.MAIN_WINDOW);
            if (mainWindow == null
                || !(mainWindow.getSource() instanceof Component))
            {
                return;
            }

            BufferedImage img = null;
            if (count > 0)
            {
                img = createOverlayImage(new Integer(count).toString());
            }

            try
            {
                TaskBarList3.getInstance().SetOverlayIcon(
                    (Component) mainWindow.getSource(), img, null);
            }
            catch (Exception ex)
            {
                logger.error("Could not set the notification count.", ex);
            }
        }
    }

    private BufferedImage createOverlayImage(String text)
    {
        int size = 16;
        BufferedImage image =
            new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

        //background
        g.setPaint(new Color(0, 0, 0, 102));
        g.fillRoundRect(0, 0, size, size, size, size);

        //filling
        int mainRadius = 14;
        g.setPaint(new Color(255, 98, 89));
        g.fillRoundRect(size / 2 - mainRadius / 2, size / 2 - mainRadius / 2,
            mainRadius, mainRadius, size, size);

        //text
        Font font = g.getFont();
        g.setFont(new Font(font.getName(), Font.BOLD, 9));
        FontMetrics fontMetrics = g.getFontMetrics();
        int textWidth = fontMetrics.stringWidth(text);
        g.setColor(Color.white);
        g.drawString(text, size / 2 - textWidth / 2,
            size / 2 - fontMetrics.getHeight() / 2 + fontMetrics.getAscent());

        return image;
    }

    /**
     * @return the trayIcon
     */
    public TrayIcon getTrayIcon()
    {
        return trayIcon;
    }

    /**
     * Set the handler which will be used for popup message
     * @param newHandler the handler to set. providing a null handler is like
     * disabling popup.
     * @return the previously used popup handler
     */
    public PopupMessageHandler setActivePopupMessageHandler(
            PopupMessageHandler newHandler)
    {
        PopupMessageHandler oldHandler = getActivePopupHandler();

        if (oldHandler != null)
            oldHandler.removePopupMessageListener(popupMessageListener);
        if (newHandler != null)
            newHandler.addPopupMessageListener(popupMessageListener);

        return super.setActivePopupMessageHandler(newHandler);
    }

    /** our listener for popup message click */
    private static class SystrayPopupMessageListenerImpl
        implements SystrayPopupMessageListener
    {

        /**
         * Handles a user click on a systray popup message. If the popup
         * notification was the result of an incoming message from a contact,
         * the chat window with that contact will be opened, if not already, and
         * brought to front.
         *
         * @param evt the event triggered when user clicks on a systray popup
         * message
         */
        public void popupMessageClicked(SystrayPopupMessageEvent evt)
        {
            Object o = evt.getTag();

            if (o instanceof Contact)
                OsDependentActivator.getUIService().
                    getChat((Contact) o).setChatVisible(true);
        }
    }

    /**
     * Implements a <tt>ServiceListener</tt> which waits for an
     * <tt>UIService</tt> implementation to become available, invokes
     * {@link #initSystray()} and unregisters itself.
     */
    private class DelayedInitSystrayServiceListener
        implements ServiceListener
    {
        public void serviceChanged(ServiceEvent serviceEvent)
        {
            if (serviceEvent.getType() == ServiceEvent.REGISTERED)
            {
                UIService uiService = OsDependentActivator.getUIService();

                if (uiService != null)
                {
                    /*
                     * This ServiceListener has successfully waited for an
                     * UIService implementation to become available so it no
                     * longer need to listen.
                     */
                    OsDependentActivator.bundleContext.removeServiceListener(
                            this);

                    if (!initialized && systray != null)
                        initSystray();
                }
            }
        }
    }
}
