/**
 * GmailAssistant 2.0 (2008-09-07)
 * Copyright 2008 Zach Scrivena
 * zachscrivena@gmail.com
 * http://gmailassistant.sourceforge.net/
 *
 * Notifier for multiple Gmail and Google Apps email accounts.
 *
 * TERMS AND CONDITIONS:
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

package org.freeshell.zs.gmailassistant;

import java.awt.Dimension;
import java.awt.Image;
import java.awt.TrayIcon;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.swing.SwingUtilities;
import org.freeshell.zs.common.Debug;


/**
 * Represent a simple system tray icon.
 */
class SimpleTrayIcon
        extends TrayIcon
{
    /** refresh interval in milliseconds */
    private static final long REFRESH_INTERVAL_MILLISECONDS = 200L;

    /** number of iterations to blink */
    private static final int BLINK_ITERATIONS = 5;

    /** blinking interval in milliseconds */
    private static final long BLINK_INTERVAL_MILLISECONDS = 200L;

    /** queued actions to be performed by the timer */
    private final Queue<ActionType> actions = new ArrayDeque<ActionType>();


    /**
    * Constructor.
    *
    * @param parent
    *     parent GmailAssistant object
    * @throws java.io.IOException
    *     if an I/O error occurs while reading the icon image resources
    */
    SimpleTrayIcon(
            final GmailAssistant parent)
            throws IOException
    {
        super(ImageIO.read(SimpleTrayIcon.class.getResource("/org/freeshell/zs/gmailassistant/resources/blank.png")));
        setToolTip(parent.name);
        setImageAutoSize(true);

        final Dimension d = getSize();
        final int w = Math.max(d.height, d.width);
        final String suffix = (w <= 16) ? "16" : "24";

        final Image blankIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/org/freeshell/zs/gmailassistant/resources/blank.png"));
        final Image normalIcon = ImageIO.read(SimpleTrayIcon.class.getResource(String.format("/org/freeshell/zs/gmailassistant/resources/ga_tray_normal_%s.png", suffix)));
        final Image hotIcon = ImageIO.read(SimpleTrayIcon.class.getResource(String.format("/org/freeshell/zs/gmailassistant/resources/ga_tray_hot_%s.png", suffix)));
        final Image normalErrorIcon = ImageIO.read(SimpleTrayIcon.class.getResource(String.format("/org/freeshell/zs/gmailassistant/resources/ga_tray_normal_error_%s.png", suffix)));
        final Image hotErrorIcon = ImageIO.read(SimpleTrayIcon.class.getResource(String.format("/org/freeshell/zs/gmailassistant/resources/ga_tray_hot_error_%s.png", suffix)));

        setNormalIcon();

        /**************************************************
        * INITIALIZE THREAD FOR HANDLING TRAY ICON STATE *
        **************************************************/

        new Thread(new Runnable()
        {
            /** temporary icon used for blinking effect */
            private Image tempIcon;

            /** "normal" state of icon */
            private boolean normalState = true;

            /** "error" state of icon */
            private boolean errorState = false;


            /**
            * Return the icon corresponding to the specified "normal" and "error" states.
            */
            private Image pickIcon(
                    final boolean normal,
                    final boolean error)
            {
                if (normal)
                {
                    return (error) ? normalErrorIcon : normalIcon;
                }
                else
                {
                    return (error) ? hotErrorIcon : hotIcon;
                }
            }


            @Override
            public void run()
            {
                while (true)
                {
                    final ActionType action;

                    synchronized (actions)
                    {
                        action = actions.poll();
                    }

                    if (action == ActionType.NORMAL)
                    {
                        normalState = true;
                    }
                    else if (action == ActionType.HOT)
                    {
                        normalState = false;
                    }
                    else if (action == ActionType.SET_ERROR)
                    {
                        errorState = true;
                    }
                    else if (action == ActionType.CLEAR_ERROR)
                    {
                        errorState = false;
                    }
                    else if (action == ActionType.BLINK)
                    {
                        try
                        {
                            SwingUtilities.invokeAndWait(new Runnable()
                            {
                                public void run()
                                {
                                    tempIcon = getImage();
                                }
                            });

                            for (int i = 0; i < BLINK_ITERATIONS; i++)
                            {
                                SwingUtilities.invokeAndWait(new Runnable()
                                {
                                    public void run()
                                    {
                                        setImage(blankIcon);
                                    }
                                });

                                Thread.sleep(BLINK_INTERVAL_MILLISECONDS);

                                SwingUtilities.invokeAndWait(new Runnable()
                                {
                                    public void run()
                                    {
                                        setImage(tempIcon);
                                    }
                                });

                                Thread.sleep(BLINK_INTERVAL_MILLISECONDS);
                            }
                        }
                        catch (Exception e)
                        {
                            /* ignore */
                        }

                        try
                        {
                            SwingUtilities.invokeAndWait(new Runnable()
                            {
                                public void run()
                                {
                                    setImage(tempIcon);
                                }
                            });
                        }
                        catch (Exception e)
                        {
                            /* ignore */
                        }
                    }
                    else if (action == ActionType.TERMINATE)
                    {
                        return;
                    }

                    if ((action == ActionType.NORMAL) ||
                            (action == ActionType.HOT) ||
                            (action == ActionType.SET_ERROR) ||
                            (action == ActionType.CLEAR_ERROR))
                    {
                        try
                        {
                            SwingUtilities.invokeAndWait(new Runnable()
                            {
                                public void run()
                                {
                                    setImage(pickIcon(normalState, errorState));
                                }
                            });
                        }
                        catch (Exception e)
                        {
                            /* ignore */
                        }
                    }

                    Debug.sleep(REFRESH_INTERVAL_MILLISECONDS);
                }
            }
        }).start();
    }


    /**
    * Set the tray icon to the "normal" state.
    */
    void setNormalIcon()
    {
        synchronized (actions)
        {
            actions.add(ActionType.NORMAL);
        }
    }


    /**
    * Set the tray icon to the "hot" state.
    */
    void setHotIcon()
    {
        synchronized (actions)
        {
            actions.add(ActionType.HOT);
        }
    }


    /**
    * Set the tray icon to the "error" state.
    */
    void setErrorIcon()
    {
        synchronized (actions)
        {
            actions.add(ActionType.SET_ERROR);
        }
    }


    /**
    * Clear the "error" state of the tray icon.
    */
    void clearErrorIcon()
    {
        synchronized (actions)
        {
            actions.add(ActionType.CLEAR_ERROR);
        }
    }


    /**
    * Blink the tray icon.
    */
    void blinkIcon()
    {
        synchronized (actions)
        {
            actions.add(ActionType.BLINK);
        }
    }


    /**
    * Terminate the thread for handling tray icon state.
    */
    void terminate()
    {
        synchronized (actions)
        {
            actions.clear();
            actions.add(ActionType.TERMINATE);
        }
    }

    /******************
    * NESTED CLASSES *
    ******************/

    /**
    * Types of action.
    */
    private enum ActionType
    {
        NORMAL,
        HOT,
        SET_ERROR,
        CLEAR_ERROR,
        BLINK,
        TERMINATE
    }
}