/*
 * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package org.graalvm.visualvm.modules.systray;

import java.awt.AWTException;
import java.awt.CheckboxMenuItem;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.openide.DialogDisplayer;
import org.openide.LifecycleManager;
import org.openide.NotifyDescriptor;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.windows.WindowManager;

/**
 *
 * @author Jiri Sedlacek
 */
@NbBundle.Messages({
    "SysTray_NotSupportedUninstall=<html><b>VisualVM tray icon not supported.</b><br><br>Your system can't display VisualVM tray icon.<br>Please uninstall the plugin.</html>",
    "SysTray_Show=Show",
    "SysTray_Hide=Hide",
    "SysTray_AlwaysOnTop=Always on top",
    "SysTray_HideWhenMinimized=Hide when minimized",
    "SysTray_NoTrayicon=No trayicon when showing",
    "SysTray_Exit=Exit",
    "SysTray_Settings=Settings",
    "SysTray_ModalDialog=<html><b>Modal dialog in the way.</b><br><br>Please close all modal dialogs before hiding VisualVM.</html>"
})
class SysTray {

    private static SysTray INSTANCE;

    private TrayIcon trayIcon;
    private Frame mainWindow;
    private WindowStateListener mainWindowListener;

    private PopupMenu trayPopup;
    private MenuItem showHideItem;
    private MenuItem exitItem;
    private CheckboxMenuItem onTopItem;
    private CheckboxMenuItem hideMinimizedItem;
    private CheckboxMenuItem hideTrayIconItem;

    private int lastWindowState;
    private boolean hideWhenMinimized;
    private boolean hideTrayIcon;

    private boolean workaround;


    static synchronized SysTray getInstance() {
        if (INSTANCE == null) INSTANCE = new SysTray();
        return INSTANCE;
    }


    synchronized void initialize() {
        if (SystemTray.isSupported()) {
            mainWindow = WindowManager.getDefault().getMainWindow();
            mainWindowListener = new MainWindowListener();

            lastWindowState = mainWindow.getExtendedState();

            loadSettings();

            if (!hideTrayIcon) showTrayIcon();
            mainWindow.addWindowStateListener(mainWindowListener);
        }
    }

    synchronized void uninitialize() {
        if (trayIcon != null) hideTrayIcon();
        if (mainWindow != null && mainWindowListener != null) {
            mainWindow.removeWindowStateListener(mainWindowListener);
            mainWindow = null;
        }
    }


    private void loadSettings() {
        SysTrayPreferences preferences = SysTrayPreferences.getInstance();
        hideWhenMinimized = preferences.getHideWhenMinimized();
        hideTrayIcon = preferences.getHideTrayIcon();
    }


    private void showTrayIcon() {
        try {
            SystemTray tray = SystemTray.getSystemTray();
            if (tray != null) {
                trayIcon = createTrayIcon();
                if (trayIcon != null) {
                    try {
                        tray.add(trayIcon);
                    } catch (AWTException e) {
                        trayIcon = null;
                        DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message(
                            Bundle.SysTray_NotSupportedUninstall(), NotifyDescriptor.ERROR_MESSAGE));
                        System.err.println("Exception showing tray icon: " + e); // NOI18N
                    } catch (Exception e) {
                        trayIcon = null;
                        Exceptions.printStackTrace(e);
                    }
                }
            }
        } catch (Exception e) {
            Exceptions.printStackTrace(e);
        }
    }

    private void hideTrayIcon() {
        SystemTray tray = SystemTray.getSystemTray();
        if (tray != null) {
            try {
                tray.remove(trayIcon);
            } catch (Exception e) {
                Exceptions.printStackTrace(e);
            }
        }
        trayIcon = null;
    }


    private TrayIcon createTrayIcon() {
        Image image = createTrayImage();
        String tooltip = createTrayTooltip();
        trayPopup = createTrayPopup();
        TrayIcon icon = new TrayIcon(image, tooltip, trayPopup);
        icon.setImageAutoSize(true);

        icon.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        if (trayPopup.isEnabled()) toggleWindowVisibility();
                    }
                });
            }
        });

        return icon;
    }

    private String createTrayTooltip() {
        return mainWindow.getTitle();
    }

    private Image createTrayImage() {
        Dimension iconDimension = SystemTray.getSystemTray().getTrayIconSize();
        int iconWidth = iconDimension.width;
        int iconHeight = iconDimension.height;

        if (iconWidth <= 16 && iconHeight <= 16)
            return ImageUtilities.loadImage("org/graalvm/visualvm/modules/systray/resources/icon16.png"); // NOI18N

        if (iconWidth <= 32 && iconHeight <= 32)
            return ImageUtilities.loadImage("org/graalvm/visualvm/modules/systray/resources/icon32.png"); // NOI18N

        return ImageUtilities.loadImage("org/graalvm/visualvm/modules/systray/resources/icon48.png"); // NOI18N
    }

    private PopupMenu createTrayPopup() {

        // "Show / Hide" menu item
        showHideItem = new MenuItem(mainWindow.isVisible() ? Bundle.SysTray_Hide() : Bundle.SysTray_Show());
        showHideItem.setFont(UIManager.getFont("MenuItem.font").deriveFont(Font.BOLD)); // NOI18N
        showHideItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        toggleWindowVisibility();
                    }
                });
            }
        });

        // "Always on top" menu item
        if (Toolkit.getDefaultToolkit().isAlwaysOnTopSupported() && mainWindow.isAlwaysOnTopSupported()) {
            onTopItem = new CheckboxMenuItem(Bundle.SysTray_AlwaysOnTop(), SysTrayPreferences.getInstance().getAlwaysOnTop());
            onTopItem.setFont(UIManager.getFont("MenuItem.font")); // NOI18N
            onTopItem.addItemListener(new ItemListener() {
                public void itemStateChanged(ItemEvent e) {
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            toggleAlwaysOnTop();
                        }
                    });
                }
            });
            toggleAlwaysOnTop(); // Sets initial state
        }

        // "Hide when minimized" menu item
        hideMinimizedItem = new CheckboxMenuItem(Bundle.SysTray_HideWhenMinimized(), hideWhenMinimized);
        hideMinimizedItem.setFont(UIManager.getFont("MenuItem.font")); // NOI18N
        hideMinimizedItem.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        toggleHideWhenMinimized();
                    }
                });
            }
        });

        // "No trayicon when showing" menu item
        hideTrayIconItem = new CheckboxMenuItem(Bundle.SysTray_NoTrayicon(), hideTrayIcon);
        hideTrayIconItem.setFont(UIManager.getFont("MenuItem.font")); // NOI18N
        hideTrayIconItem.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        toggleNoShowingIcon();
                    }
                });
            }
        });

        // "Exit" menu item
        exitItem = new MenuItem(Bundle.SysTray_Exit());
        exitItem.setFont(UIManager.getFont("MenuItem.font")); // NOI18N
        exitItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        LifecycleManager.getDefault().exit();
                    }
                });
            }
        });

        // "Settings" submenu
        PopupMenu settingsItem = new PopupMenu(Bundle.SysTray_Settings());
        settingsItem.setFont(UIManager.getFont("MenuItem.font")); // NOI18N
        if (onTopItem != null) settingsItem.add(onTopItem);
        settingsItem.add(hideMinimizedItem);
        settingsItem.add(hideTrayIconItem);


        PopupMenu popupMenu = new PopupMenu();
        popupMenu.add(showHideItem);
        popupMenu.add(settingsItem);
        popupMenu.addSeparator();
        popupMenu.add(exitItem);
        return popupMenu;
    }

    private void toggleWindowVisibility() {
        if (mainWindow.isVisible()) hideWindow(); // May not hide window when modal dialog(s) in the way
        else showWindow();
    }

    private void hideWindow() {
        Window[] windows = mainWindow.getOwnedWindows();
        for (Window window : windows) {
            if (window.isVisible() && window instanceof Dialog)
                if (((Dialog)window).isModal()) {
                    trayPopup.setEnabled(false);
                    DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(
                            Bundle.SysTray_ModalDialog(), NotifyDescriptor.WARNING_MESSAGE));
                    trayPopup.setEnabled(true);
                    return;
                }
        }

        mainWindow.setVisible(false);
        if (!Utilities.isWindows() && (mainWindow.getExtendedState() & Frame.ICONIFIED) != 0) {
            workaround = true;
        }
        if (showHideItem != null) showHideItem.setLabel(Bundle.SysTray_Show());
    }

    private void showWindow() {
        mainWindow.setVisible(true);
        mainWindow.setExtendedState(lastWindowState);
        showHideItem.setLabel(Bundle.SysTray_Hide());
        mainWindow.toFront();
    }

    private void toggleAlwaysOnTop() {
        mainWindow.setAlwaysOnTop(onTopItem.getState());
        SysTrayPreferences.getInstance().setAlwaysOnTop(onTopItem.getState());
    }

    private void toggleHideWhenMinimized() {
        hideWhenMinimized = hideMinimizedItem.getState();
        if (hideWhenMinimized && (mainWindow.getExtendedState() & Frame.ICONIFIED) != 0)
            hideWindow(); // May not hide window when modal dialog(s) in the way
        SysTrayPreferences.getInstance().setHideWhenMinimized(hideWhenMinimized);
    }

    private void toggleNoShowingIcon() {
        hideTrayIcon = hideTrayIconItem.getState();
        int windowState = mainWindow.getExtendedState();
        if ((windowState & Frame.ICONIFIED) != 0) {
            if (hideTrayIcon && trayIcon == null) showTrayIcon();
        } else {
            if (hideTrayIcon && trayIcon != null) hideTrayIcon();
        }
        SysTrayPreferences.getInstance().setHideTrayIcon(hideTrayIcon);
    }


    private SysTray() {}


    private class MainWindowListener implements WindowStateListener {

        public void windowStateChanged(WindowEvent e) {
            int windowState = e.getNewState();
            if ((windowState & Frame.ICONIFIED) != 0) {
                if (workaround) {
                    workaround = false;
                    mainWindow.setExtendedState(lastWindowState);
                } else {
                    workaround = false;
                    if (hideWhenMinimized || hideTrayIcon) hideWindow(); // May not hide window when modal dialog(s) in the way
                    if (!mainWindow.isVisible() && hideTrayIcon && trayIcon == null) showTrayIcon();
                }
            } else {
                lastWindowState = windowState;
                if (hideTrayIcon && trayIcon != null) hideTrayIcon();
            }
        }

    }

}
