/**
 * 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.util.ArrayDeque;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;


/**
 * Play an audible chime and periodic bell.
 */
class ChimePlayer
{
    /** timer refresh interval */
    private static final long TIMER_REFRESH_INTERVAL_MILLISECONDS = 200L;

    /** chime audio clip */
    private Clip chime = null;

    /** periodic bell audio clip */
    private Clip periodicBell = null;

    /** timer for playing the chime */
    private final Timer timer;

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

    /** should the periodic bell be played? */
    private volatile boolean periodicBellPlay = false;

    /** time at which the periodic bell was last played */
    private volatile long periodicBellLastPlayed = 0L;

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


    /**
    * Constructor.
    *
    * @param parent
    *      parent GmailAssistant object
    */
    ChimePlayer(
            final GmailAssistant parent)
    {
        /* load chime audio clip */
        try
        {
            final AudioInputStream stream = AudioSystem.getAudioInputStream(
                    ChimePlayer.class.getResource("resources/bells_call_2.wav"));

            final AudioFormat format = stream.getFormat();

            this.chime = (Clip) AudioSystem.getLine(new DataLine.Info(
                    Clip.class,
                    format,
                    (int) (stream.getFrameLength() * format.getFrameSize())));

            this.chime.open(stream);
        }
        catch (Exception e)
        {
            this.chime = null;

            SwingManipulator.showErrorDialog(parent, "Initialization Error - " + GmailAssistant.NAME,
                    "Failed to load chime audio clip (" + e + ").\n" +
                    GmailAssistant.NAME + " will proceed to run without playing the chime audio clip.");
        }

        /* load periodic bell audio clip */
        try
        {
            final AudioInputStream stream = AudioSystem.getAudioInputStream(
                    ChimePlayer.class.getResource("resources/desk_bell.wav"));

            final AudioFormat format = stream.getFormat();

            this.periodicBell = (Clip) AudioSystem.getLine(new DataLine.Info(
                    Clip.class,
                    format,
                    (int) (stream.getFrameLength() * format.getFrameSize())));

            this.periodicBell.open(stream);
        }
        catch (Exception e)
        {
            this.periodicBell = null;

            SwingManipulator.showErrorDialog(parent, "Initialization Error - " + GmailAssistant.NAME,
                    "Failed to load periodic bell audio clip (" + e + ").\n" +
                    GmailAssistant.NAME + " will proceed to run without playing the periodic bell audio clip.");
        }

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

        this.timer = new Timer("Chime-Player-Timer", true);

        this.timer.schedule(new TimerTask()
        {
            @Override
            public void run()
            {
                ActionType action;

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

                if (action == ActionType.CHIME)
                {
                    playClipAndWait(ChimePlayer.this.chime);
                    ChimePlayer.this.periodicBellLastPlayed = System.currentTimeMillis();
                }
                else if (action == ActionType.TEST_CHIME)
                {
                    playClipAndWait(ChimePlayer.this.chime);
                }
                else if (action == ActionType.TEST_PERIODIC_BELL)
                {
                    playClipAndWait(ChimePlayer.this.periodicBell);
                }
                else if (action == ActionType.TERMINATE)
                {
                    ChimePlayer.this.timer.cancel();
                    ChimePlayer.this.timerCancelled = true;
                }

                /* play periodic bell, if necessary */
                if (ChimePlayer.this.periodicBellPlay &&
                        ((System.currentTimeMillis() - ChimePlayer.this.periodicBellLastPlayed) >=
                        ((Long) parent.getProperty(GmailAssistant.Property.ALERT_PERIODIC_BELL_INTERVAL_MILLISECONDS))))
                {
                    playClipAndWait(ChimePlayer.this.periodicBell);
                    ChimePlayer.this.periodicBellLastPlayed = System.currentTimeMillis();
                }
            }
        }, 0L, ChimePlayer.TIMER_REFRESH_INTERVAL_MILLISECONDS);
    }


    /**
    * Play the chime.
    */
    void playChime()
    {
        synchronized (this.actions)
        {
            this.actions.add(ActionType.CHIME);
        }
    }


    /**
    * Test the chime.
    */
    void testChime()
    {
        synchronized (this.actions)
        {
            this.actions.add(ActionType.TEST_CHIME);
        }
    }


    /**
    * Test the periodic bell.
    */
    void testPeriodicBell()
    {
        synchronized (this.actions)
        {
            this.actions.add(ActionType.TEST_PERIODIC_BELL);
        }
    }


    /**
    * Cancel all pending chimes.
    */
    void cancelAll()
    {
        synchronized (this.actions)
        {
            this.actions.clear();
        }
    }


    /**
    * Start the periodic bell.
    */
    void startPeriodicBell()
    {
        this.periodicBellPlay = true;
    }


    /**
    * Stop the periodic bell.
    */
    void stopPeriodicBell()
    {
        this.periodicBellPlay = false;
    }


    /**
    * 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(ChimePlayer.TIMER_REFRESH_INTERVAL_MILLISECONDS / 10);
            }
            catch (Exception e)
            {
                /* ignore */
            }
        }
        while (true);
    }


    /**
    * Play the specified audio clip, and wait for it to finish.
    *
    * @param c
    *      audio clip to be played
    */
    void playClipAndWait(
            final Clip c)
    {
        if (c != null)
        {
            /* rewind the clip and start playing */
            c.setFramePosition(0);
            c.start();

            while (c.isActive())
            {
                try
                {
                    Thread.sleep(ChimePlayer.TIMER_REFRESH_INTERVAL_MILLISECONDS / 10);
                }
                catch (Exception e)
                {
                    /* ignore */
                }
            }
        }
    }

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

    /**
    * Types of action.
    */
    private enum ActionType
    {
        CHIME,
        TEST_CHIME,
        TEST_PERIODIC_BELL,
        TERMINATE
    }
}