package tmcsim.cadsimulator;

import jaco.mp3.player.MP3Player;
import java.applet.*;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import tmcsim.client.cadclientgui.data.IncidentEvent;

/**
 * SoundPlayer is used to play audio files associated with IncidentEvents that
 * occur during a simulation. When the SoundPlayer has been enabled audio clips
 * are enqueued by calling the enqueueClip() method. The audio files are played
 * in order of receipt and when finished, the IncidentEvent is notified. Audio
 * playing may be disabled or enabled through the setAudioEnabled() method.
 * Disabling audio will cause a currently playing audio clip to stop and be
 * requeued at the front of the queue. Re-enabling the audio will then continue
 * playing all queued clips. The deQueueAll() method is used to clear the list
 * of audio clips that have been queued.
 *
 * @author Matthew Cechini (mcechini@calpoly.edu)
 * @version Date: 2006/06/06 20:46:41
 * @author Nicolas Russo modified to play mp3 audio.
 * @version 2016.07.03
 */
public class SoundPlayer extends Thread
{

    /**
     * Error Logger.
     */
    private Logger soundLogger = Logger.getLogger("tmcsim.cadsimulator");
    /**
     * The base location that wav files are referenced from
     */
    private String baseAudioDir = "";
    /**
     * The private vector of audioClips used during a simulation
     */
    private Vector<AudioClipInfo> enqueuedClips = null;
    /**
     * The audio clip that is currently being played by the AudioPlayer.
     */
    private AudioClipInfo currentClip = null;
    /**
     * Audio stream for playing audio files.
     */
    private AudioClip clip = null;
    private MP3Player mp3Player = null;
    /**
     * Flag to designate when an audio file is playing. This prevents multiple
     * audio files from being played simultaneously. Flag is initialized to
     * false.
     */
    private boolean audioPlaying = false;
    /**
     * Flag to designate whether audio playing is enabled. If audio is disabled,
     * then enquque events will be ignored. Flag is initialized to false.
     */
    private boolean audioEnabled = false;
    /**
     * Timer used to pause audio player during an audio file.
     */
    private Timer timer = null;

    /**
     * Inner class used to contain the audio file's name and duration.
     */
    private class AudioClipInfo
    {

        String fileName;
        int duration;
        IncidentEvent theEvent;

        AudioClipInfo(IncidentEvent ie)
        {
            fileName = ie.waveFile;
            duration = ie.waveLength;
            theEvent = ie;
        }

        AudioClipInfo(String name, int dur)
        {
            fileName = name;
            duration = dur;
            theEvent = null;
        }

        public void wavePlayed()
        {
            if (theEvent != null)
            {
                theEvent.wavePlayed();
            }
        }
    }

    /**
     * Constructor. Establish the base file path for wav file referencing.
     *
     * @param wavFilePath Pathname for where the simulation wav files will be
     * referenced.
     */
    public SoundPlayer(String baseFilePath)
    {
        baseAudioDir = baseFilePath;
        audioPlaying = false;
        audioEnabled = true;
        timer = new Timer();
        enqueuedClips = new Vector<AudioClipInfo>();
    }

    /**
     * Add a audioClipInfo object to the audioClips Vector. The enqueued audio
     * file will play when all previously enqueued audio files have been played.
     * If audio is not enabled, the clip will not be queued, and the incident
     * event will be notified that the audio clip has finished playing to allow
     * the simulation to continue.
     *
     * @param ie The Incident Event to enqueue to the current list of events.
     */
    public void enqueueClip(IncidentEvent ie)
    {

        if (audioEnabled)
        {
            synchronized (enqueuedClips)
            {
                enqueuedClips.add(new AudioClipInfo(ie));
            }
        }
        else
        {
            ie.wavePlayed();
        }
    }

    /**
     * Method called when user presses reset in simulation. All queued events
     * need to be deQueued.
     */
    public void deQueueAll()
    {
        synchronized (enqueuedClips)
        {
            enqueuedClips.clear();
        }
    }

    /**
     * Get the current audio enabled status.
     *
     * @return True if enabled, false if disabled.
     */
    public boolean getAudioEnabled()
    {
        return audioEnabled;
    }

    /**
     * Set the current audio enabled status. A false value will cause all
     * enqueue events to be ignored. A currently playing audio file will be
     * stopped and requeued. All other queued events will remain in the queue.
     *
     * @param enable True if enabling, false if disabling.
     */
    public void setAudioEnabled(boolean enable)
    {

        if (!enable)
        {
            timer.cancel();
            if (clip != null)
            {
                clip.stop();
            }
            if (mp3Player != null)
            {
                mp3Player.stop();
            }

            if (currentClip != null)
            {
                enqueuedClips.add(0, currentClip);
            }

            audioPlaying = false;
        }

        audioEnabled = enable;
    }

    /**
     * Method declaration for the Thread.run() method. While this thread is not
     * interrupted, check if there are enqueued audio files. If so, remove the
     * first one(FIFO) create an AudioClip object, and play the audio file. A
     * timer tis then started for the duration of the audio file. When the timer
     * expires the audioPlaying flag is reset to false and the corresponding
     * incident event is notified of the audio file's completion.
     */
    public void run()
    {
        AudioClipInfo theClip = null;

        while (!isInterrupted())
        {

            synchronized (enqueuedClips)
            {
                if (enqueuedClips.size() > 0 && !audioPlaying && audioEnabled)
                {
                    theClip = enqueuedClips.remove(0);
                }
            }

            //if nothing to play, wait a second
            if (theClip == null)
            {
                try
                {
                    Thread.sleep(1000);
                } catch (Exception e)
                {
                }
            }
            else
            {

                try
                {

                    if (theClip.duration > 0)
                    {
                        String filename = baseAudioDir + theClip.fileName;
                        String fileExt = filename.substring(filename.lastIndexOf('.'));
                        File audioFile = new File(filename);

                        if (audioFile.exists() && fileExt.equals(".mp3"))
                        {
                            mp3Player = new MP3Player(audioFile);
                            mp3Player.play();
                        }
                        else if (audioFile.exists() && fileExt.equals(".wav"))
                        {
                            clip = Applet.newAudioClip(audioFile.toURI().toURL());
                            clip.play();
                        }
                        else
                        {
                            throw new Exception();
                        }
                    }

                    currentClip = theClip;
                    audioPlaying = true;

                    timer = new Timer();
                    timer.schedule(new TimerTask()
                    {
                        public void run()
                        {
                            clipPlayed();
                            audioPlaying = false;
                        }
                    }, theClip.duration * 1000);

                    theClip = null;
                } catch (Exception e)
                {
                    soundLogger.logp(Level.WARNING, "SoundPlayer", "run",
                            "Unable to play audio file: " + baseAudioDir + theClip.fileName, e);

                    theClip.theEvent.wavePlayed();
                    theClip = null;
                    timer.cancel();
                }
            }
        }
    }

    /**
     * Called when audio file finishes playing. Notifies the corresponding
     * Incident Event that its audio file has finished playing.
     */
    private void clipPlayed()
    {
        currentClip.wavePlayed();
    }
}
