/**
 * 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.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;


/**
 * Blink the keyboard num-lock LED.
 */
class KeyboardLedBlinker
{
    /** interval between keyboard num-lock LED blinking */
    private static final long BLINK_INTERVAL_MILLISECONDS = 200L;

    /** number of blinking iterations when performing a test */
    private static final int TEST_ITERATIONS = 2;

    /** timer refresh interval */
    private static final long TIMER_REFRESH_INTERVAL_MILLISECONDS = BLINK_INTERVAL_MILLISECONDS;

    /** timer for blinking the keyboard num-lock LED */
    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.
    */
    KeyboardLedBlinker()
    {
        /***************************
        * INITIALIZE TIMER THREAD *
        ***************************/

        this.timer = new Timer("Keyboard-Blinker-Timer", true);

        this.timer.schedule(new TimerTask()
        {
            /** toolkit for accessing num-lock state */
            private final Toolkit toolkit = Toolkit.getDefaultToolkit();

            /** robot for pressing num-lock key */
            private Robot robot = null;

            /** current num-lock state */
            private boolean state;

            /** saved num-lock state, to be restored */
            private boolean savedState;

            /** is the blinker active? */
            private boolean blinking = false;


            /**
            * Set the num-lock state to the specified state.
            *
            * @param state
            *     desired num-lock state
            */
            private void setNumLockState(
                    final boolean state)
            {
                try
                {
                    this.toolkit.setLockingKeyState(
                            KeyEvent.VK_NUM_LOCK,
                            state);
                }
                catch (Exception e)
                {
                    if (this.robot == null)
                    {
                        try
                        {
                            this.robot = new Robot();
                        }
                        catch (Exception ee)
                        {
                            /* ignore */
                        }
                    }

                    if (this.robot != null)
                    {
                        this.robot.keyPress(KeyEvent.VK_NUM_LOCK);
                        this.robot.keyRelease(KeyEvent.VK_NUM_LOCK);
                    }
                }
            }


            @Override
            public void run()
            {
                ActionType action;

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

                if (action == ActionType.START)
                {
                    if (!this.blinking)
                    {
                        try
                        {
                            this.savedState = this.toolkit.getLockingKeyState(KeyEvent.VK_NUM_LOCK);
                        }
                        catch (Exception e)
                        {
                            this.savedState = false;
                        }

                        this.state = this.savedState;
                        this.blinking = true;
                    }
                }
                else if (action == ActionType.STOP)
                {
                    if (this.blinking)
                    {
                        setNumLockState(this.savedState);
                        this.blinking = false;
                    }
                }
                else if (action == ActionType.TEST)
                {
                    for (int i = 0; i < KeyboardLedBlinker.TEST_ITERATIONS; i++)
                    {
                        setNumLockState(true);

                        try
                        {
                            Thread.sleep(KeyboardLedBlinker.BLINK_INTERVAL_MILLISECONDS);
                        }
                        catch (InterruptedException e)
                        {
                            /* ignore */
                        }

                        setNumLockState(false);

                        try
                        {
                            Thread.sleep(KeyboardLedBlinker.BLINK_INTERVAL_MILLISECONDS);
                        }
                        catch (InterruptedException e)
                        {
                            /* ignore */
                        }
                    }
                }
                else if (action == ActionType.TERMINATE)
                {
                    KeyboardLedBlinker.this.timer.cancel();
                    KeyboardLedBlinker.this.timerCancelled = true;
                }

                if (this.blinking)
                {
                    this.state = !this.state;
                    setNumLockState(this.state);
                }
            }
        }, 0, KeyboardLedBlinker.BLINK_INTERVAL_MILLISECONDS);
    }


    /**
    * Start the blinker, if it has not already been started.
    */
    void start()
    {
        synchronized (this.actions)
        {
            this.actions.add(ActionType.START);
        }
    }


    /**
    * Stop the blinker, if it is blinking.
    */
    void stop()
    {
        synchronized (this.actions)
        {
            this.actions.add(ActionType.STOP);
        }
    }


    /**
    * Test the blinker.
    */
    void test()
    {
        synchronized (this.actions)
        {
            this.actions.add(ActionType.TEST);
        }
    }


    /**
    * Stop the blinker now, if it is blinking.
    */
    void cancelAll()
    {
        synchronized (this.actions)
        {
            this.actions.clear();
            this.actions.add(ActionType.STOP);
        }
    }


    /**
    * 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.STOP);
                this.actions.add(ActionType.TERMINATE);
            }

            if (this.timerCancelled)
            {
                return;
            }

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

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

    /**
    * Types of action.
    */
    private enum ActionType
    {
        START,
        STOP,
        TEST,
        TERMINATE
    }
}