/**
 * GmailAssistant 1.1 (2008-03-16)
 * Copyright 2008 Zach Scrivena
 * zachscrivena@gmail.com
 * http://gmailassistant.sourceforge.net/
 *
 * Notifier for multiple Gmail 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 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 java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;


/**
 * Represent a simple system tray icon.
 */
class SimpleTrayIcon
        extends TrayIcon
{
    /** timer refresh interval */
    private static final long TIMER_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;

    /** parent frame */
    private final JFrame parent;

    /** timer for displaying the system tray icon */
    private final Timer timer;

    /** is the timer cancelled? */
    private volatile boolean timerCancelled = false;

    /** 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 JFrame parent)
            throws IOException
    {
        super(ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/blank.png")));
        this.parent = parent;
        setToolTip(GmailAssistant.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("/gmailassistant/resources/blank.png"));
        final Image normalIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_normal_" + suffix + ".png"));
        final Image hotIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_hot_" + suffix + ".png"));
        final Image normalErrorIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_normal_error_" + suffix + ".png"));
        final Image hotErrorIcon = ImageIO.read(SimpleTrayIcon.class.getResource("/gmailassistant/resources/ga_tray_hot_error_" + suffix + ".png"));

        setNormalIcon();

        /***************************
        * INITIALIZE TIMER THREAD *
        ***************************/

        this.timer = new Timer("Simple-Tray-Icon-Timer", true);

        this.timer.schedule(new TimerTask()
        {
            /** 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;


            @Override
            public void run()
            {
                ActionType action;

                synchronized (SimpleTrayIcon.this.actions)
                {
                    action = SimpleTrayIcon.this.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 = SimpleTrayIcon.this.getImage();
                            }
                        });

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

                            Thread.sleep(SimpleTrayIcon.BLINK_INTERVAL_MILLISECONDS);

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

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

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


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


            /**
            * 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;
                }
            }

        }, 0L, SimpleTrayIcon.TIMER_REFRESH_INTERVAL_MILLISECONDS);
    }


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


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


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


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


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


    /**
    * Terminate the timer thread.
    * This method blocks until the thread is cancelled.
    */
    void terminate()
    {
        synchronized (this.actions)
        {
            this.actions.clear();
        }

        do
        {
            synchronized (this.actions)
            {
                this.actions.add(ActionType.TERMINATE);
            }

            if (this.timerCancelled)
            {
                return;
            }

            try
            {
                Thread.sleep(SimpleTrayIcon.TIMER_REFRESH_INTERVAL_MILLISECONDS / 10);
            }
            catch (Exception e)
            {
                /* ignore */
            }
        }
        while (true);
    }

    /*****************
    * INNER CLASSES *
    *****************/

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