/*
 * Copyright (C) 2009-2017 Alistair Neil <info@dazzleships.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package lib;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.Window.Type;
import java.awt.event.AWTEventListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.regex.Pattern;
import javax.swing.BorderFactory;
import javax.swing.JDialog;
import javax.swing.JTextArea;
import javax.swing.JToolTip;
import javax.swing.JWindow;
import javax.swing.UIManager;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

/**
 *
 * @author Alistair Neil <info@dazzleships.net>
 */
public final class SwingTrayIcon {

    private InfoTip infoToolTip;
    private JDialog jdiagPopup;
    private TrayPopupMenu trayPopupMenu;
    private AWTEventListener awtEventListen;
    private volatile boolean mouseExited = false;
    private SystemTray st = null;
    private GraphicsConfiguration gc = null;
    private Point mouseloc;
    private TrayIcon ti;
    private boolean loaded;

    public SwingTrayIcon(GraphicsConfiguration gc, String resourcepath) {
        loaded = false;
        if (!SystemTray.isSupported()) {
            return;
        }
        try {
            st = SystemTray.getSystemTray();
            ClassLoader cl = this.getClass().getClassLoader();
            ti = new TrayIcon(new javax.swing.ImageIcon(cl.getResource(resourcepath)).getImage());
        } catch (Exception ex) {
            return;
        }
        ti.setImageAutoSize(true);
        this.gc = gc;
        initDiagPopup();
        if (OSFunction.isLinux()) {
            infoToolTip = new InfoTip();
            infoToolTip.setFocusable(false);
        }
        initMouseHandler();
    }

    private void initMouseHandler() {
        // We need a global mouse listener due to the way TrayIcon intercepts 
        // mousemovements but doesnt feed all the events back to us
        awtEventListen = new java.awt.event.AWTEventListener() {
            @Override
            public void eventDispatched(AWTEvent event) {
                // Catch mouse event so we can easily get coords
                final MouseEvent e = (MouseEvent) event;
                if (!(e.getSource() instanceof TrayIcon)) {
                    return;
                }
                mouseloc = MouseInfo.getPointerInfo().getLocation();
                switch (e.getID()) {
                    case MouseEvent.MOUSE_ENTERED:
                        if (infoToolTip == null || trayPopupMenu.isVisible()) {
                            return;
                        }
                        mouseExited = false;
                        // Raise tooltip
                        Thread t = new Thread(new java.lang.Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException ex) {
                                }
                                if (!trayPopupMenu.isVisible() && !mouseExited) {
                                    infoToolTip.setLocation(getPlacementPosition(mouseloc, infoToolTip.getWidth(), infoToolTip.getHeight(), true));
                                    infoToolTip.setVisible(true);
                                } else {
                                    infoToolTip.setVisible(false);
                                }
                            }
                        });
                        t.start();
                        break;

                    case MouseEvent.MOUSE_EXITED:
                        mouseExited = true;
                        if (infoToolTip != null) {
                            infoToolTip.setVisible(false);
                        }
                        break;

                    case MouseEvent.MOUSE_RELEASED:
                        if (e.getButton() == MouseEvent.BUTTON3) {
                            if (infoToolTip != null) {
                                infoToolTip.setVisible(false);
                            }
                            mouseExited = true;
                            raisePopupMenu(mouseloc);
                        }
                        break;
                }

            }
        };
        Toolkit.getDefaultToolkit().addAWTEventListener(awtEventListen, AWTEvent.MOUSE_EVENT_MASK);
    }

    /**
     * Initialise our hidden dialog which we use to grab focus so that the
     * popumenu can be closed whenever we click on the desktop
     */
    private void initDiagPopup() {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        jdiagPopup = new JDialog();
        jdiagPopup.setUndecorated(true);
        jdiagPopup.setMinimumSize(new Dimension(0, 0));
        jdiagPopup.setBounds(new Rectangle(0, 0));
        jdiagPopup.setType(Type.POPUP);
        jdiagPopup.setFocusable(false);
        jdiagPopup.setAutoRequestFocus(false);
        try {
            jdiagPopup.setLocation(new Point(screenSize.width, screenSize.height));
            jdiagPopup.setOpacity(0);
        } catch (Exception ex) {
        }
        jdiagPopup.addComponentListener(new java.awt.event.ComponentListener() {

            @Override
            public void componentResized(ComponentEvent e) {
            }

            @Override
            public void componentMoved(ComponentEvent e) {
            }

            @Override
            public void componentShown(ComponentEvent e) {
                trayPopupMenu.setVisible(true);
            }

            @Override
            public void componentHidden(ComponentEvent e) {
            }
        });
    }

    public void dispose() {
        // Remove from system tray       
        unload();
        // Cleanup assets that may prevent shutdown
        if (ti != null) {
            for (MouseListener ml : ti.getMouseListeners()) {
                ti.removeMouseListener(ml);
            }
        }
        if (awtEventListen != null) {
            Toolkit.getDefaultToolkit().removeAWTEventListener(awtEventListen);
            awtEventListen = null;
        }
        if (trayPopupMenu != null) {
            for (PopupMenuListener pml : trayPopupMenu.getPopupMenuListeners()) {
                trayPopupMenu.removePopupMenuListener(pml);
            }
            trayPopupMenu = null;
        }
        if (jdiagPopup != null) {
            for (ComponentListener cl : jdiagPopup.getComponentListeners()) {
                jdiagPopup.removeComponentListener(cl);
            }
            jdiagPopup.dispose();
            jdiagPopup = null;
        }
        if (infoToolTip != null) {
            infoToolTip.dispose();
            infoToolTip = null;
        }
        st = null;
    }

    public void disable() {
        unload();
        st = null;
    }

    /**
     * Set tray right click popup
     *
     * @param tpm
     */
    public void setTrayPopupMenu(TrayPopupMenu tpm) {
        trayPopupMenu = tpm;
        if (tpm == null || ti == null) {
            return;
        }
        trayPopupMenu.setFocusable(false);
        // Add popup menu listener to detect when it is hidden
        trayPopupMenu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {

            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
                jdiagPopup.setVisible(false);
            }

            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {
                jdiagPopup.setVisible(false);
            }
        });
        trayPopupMenu.setEnabled(true);
        trayPopupMenu.setInvoker(jdiagPopup);
    }

    /**
     * Open tray popup
     *
     */
    private void raisePopupMenu(Point mouseloc) {
        if (trayPopupMenu != null) {
            // Set onscreen location of trayPopup
            Point p = getPlacementPosition(mouseloc, trayPopupMenu.getVisibleRect().width, trayPopupMenu.getHeight(), false);
            trayPopupMenu.setLocation(p);
            jdiagPopup.setVisible(true);
        }
    }

    /**
     * Calculate onscreen placement position based on mouse pointer position
     *
     * @param boxwidth
     * @param boxheight
     * @param centre
     * @return Point
     */
    private Point getPlacementPosition(Point coords, int boxwidth, int boxheight, boolean istooltip) {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Insets screenInsets;
        Point result = new Point();
        double x, y, limit;
        int yoff = 0;

        if (OSFunction.isLinux()) {
            screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
            int i = screenInsets.bottom + screenInsets.left + screenInsets.right
                    + screenInsets.top;
            // If all insets are zero then offset them all by 32, bit of a hack
            // for desktops that allow intelligent hiding of tray panel
            if (i == 0) {
                screenInsets = new Insets(32, 32, 32, 32);
            }
        } else {
            screenInsets = new Insets(0, 0, 0, 0);
        }

        // Calculate X axis positioning
        x = coords.x;
        if (x > (screenSize.getWidth() / 2)) {
            limit = screenSize.width - screenInsets.right;
            if (x + boxwidth > limit) {
                if (screenInsets.right == 0) {
                    x -= boxwidth;
                } else {
                    x = limit - boxwidth;
                }
            }
        } else {
            limit = screenInsets.left;
            if (x < limit) {
                x = limit;
            }
        }

        // Calculate Y axis positioning
        y = coords.y;
        if (y > (screenSize.getHeight() / 2)) {
            // Bottom
            if (istooltip && screenInsets.bottom == 0) {
                yoff = (int) ((ti.getSize().getHeight() + 1) / 2);
            }
        } else // Bottom
        {
            if (istooltip && screenInsets.top == 0) {
                yoff = (int) ((ti.getSize().getHeight() + 1) / 2);
            }
        }

        if (screenInsets.top != 0 && y < screenInsets.top) {
            y += (screenInsets.top - y);
        }
        limit = screenSize.height - screenInsets.bottom;
        if (y + yoff + boxheight > limit) {
            if (yoff > 0) {
                y -= (boxheight + yoff);
            } else if (screenInsets.bottom == 0) {
                y -= (boxheight + yoff);
            } else {
                y = limit - boxheight;
            }
        } else {
            y += yoff;
        }
        result.setLocation(x, y);
        return result;
    }

    /**
     * Set info tip text
     *
     * @param text
     */
    public void setInfoTip(String text) {
        if (ti == null) {
            return;
        }
        if (OSFunction.isLinux()) {
            // Check to see if tay icon need reloaded
            if (SystemTray.isSupported() && isLoaded() && st.getTrayIcons().length == 0) {
                try {
                    st.add(ti);
                } catch (Exception ex) {
                }
            }
            // Update tooltip (Linux only)
            if (infoToolTip != null) {
                infoToolTip.setText(text);
                if (infoToolTip.isVisible()) {
                    infoToolTip.setLocation(getPlacementPosition(mouseloc, infoToolTip.getWidth(), infoToolTip.getHeight(), true));
                }
            }
        } else {
            // Update tooltip (Windows Only)
            ti.setToolTip(text);
        }
    }

    /**
     * Display desktop message (Windows only)
     *
     * @param title
     * @param body
     */
    public void displayMessage(String title, String body) {
        if (ti != null) {
            ti.displayMessage(title, body, TrayIcon.MessageType.INFO);
        }
    }

    /**
     * Load tray icon
     */
    public void load() {
        // I know this looks odd but it is required for onscreen positioning
        // of the tray popup to work
        if (trayPopupMenu != null) {
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
            trayPopupMenu.setLocation(screenSize.height, screenSize.width);
            trayPopupMenu.setVisible(true);
            trayPopupMenu.setVisible(false);
        }
        // Add this tray icon to system tray
        try {
            if (st != null) {
                st.add(ti);
                loaded = true;
            }
        } catch (Exception ex) {
            loaded = false;
        }
    }

    /**
     * Unload Tray icon
     */
    public void unload() {
        // Remove the tray icon from system tray
        if (st != null) {
            st.remove(ti);
        }
        loaded = false;
    }

    /**
     * Add a mouse listener
     *
     * @param ml
     */
    public void addMouseListener(MouseListener ml) {
        if (ti != null) {
            ti.addMouseListener(ml);
        }
    }

    /**
     * Is tray icon loaded
     *
     * @return ture if it is
     */
    public boolean isLoaded() {
        return loaded;
    }

    public boolean isSupported() {
        return st != null;
    }

    /**
     * Info tip object
     */
    private static final class InfoTip extends JWindow {

        JTextArea jta;
        Pattern p = Pattern.compile("\n");

        public InfoTip() {
            initInfoTip();
        }

        private void initInfoTip() {
            setType(Type.POPUP);
            jta = new JTextArea();
            add(jta);
            JToolTip jtt = jta.createToolTip();
            jta.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
            jta.setBackground(new Color(jtt.getBackground().getRGB()));
            jta.setForeground(new Color(jtt.getForeground().getRGB()));
            setBackground(jta.getBackground());
            jta.setOpaque(true);
            jta.setEditable(false);
            jta.setLineWrap(true);
            jta.setWrapStyleWord(true);
            jta.setVisible(true);
            setEnabled(true);
        }

        /**
         * Set info tip text
         *
         * @param text
         */
        public void setText(String text) {
            String[] strs = p.split(text);
            String largest = "";
            int longest = 0;
            int width;
            for (String s : strs) {
                width = jta.getFontMetrics(jta.getFont()).stringWidth(s);
                if (width > longest) {
                    largest = s;
                    longest = width;
                }
            }
            jta.setText("  " + text.replace("\n", "\n  "));
            width = jta.getFontMetrics(jta.getFont()).stringWidth(largest + "XXX");
            int lineheight = jta.getFontMetrics(jta.getFont()).getHeight() + 1;
            int boxheight = jta.getLineCount() * (lineheight);
            setSize(width, boxheight);
        }

    }

}
