/**
 * Copyright (c) 2011, Martin Foster <martin@ethereal-realms.org>
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice, 
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright 
 *       notice, this list of conditions and the following disclaimer in the 
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Ethereal-Realms.org nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.lyra.Interface;

import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.BoxLayout;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.BorderFactory;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import java.awt.Font;
import java.awt.Color;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.swing.JButton;
import javax.swing.ImageIcon;

import org.lyra.Lyra;
import org.lyra.Host.Options;
import org.lyra.Host.Pendulum;
import org.lyra.Host.PendulumListener;
import org.lyra.Host.Playback;
import org.lyra.Host.Status;
import org.lyra.Host.Terminus;
import org.lyra.Host.UpdateEvent;
import org.lyra.Host.UpdateListener;
import org.lyra.Host.Updater;
import org.lyra.Interface.Menu.VolumeMenu;
import org.lyra.Widgets.TickerSimpleLabel;
import org.lyra.Widgets.TickerBufferedLabel;


/**
 * Compact player interface without the song selection and management
 * capabilities which exists in the regular interface.   Used mostly as a
 * status update capability then an information handler.
 * <p>
 * @author Martin Foster
 * @version 1.0.1, %Revision, 99%, %LastChangedDate, 17/03/11 8:17PM%
 */
public class PlayerCompactFrame extends JFrame {

	// Serial Requirements
	private static final long serialVersionUID = 1L;

	/*
	 * USER INTERFACE ELEMENTS
	 */
	private JPanel jContentPane = null;

	// Control Buttons
	private JPanel panelControl = null;
	private JButton buttonPrevious = null;
	private JButton buttonPlay = null;
	private JButton buttonStop = null;
	private JButton buttonNext = null;
	private JButton buttonVolume = null;
	private JButton buttonFullscreen = null;

	// Display Panel and Elapsed Bar
	private JPanel panelDisplay = null;
	private JProgressBar progressTime = null;
	
	// Artist and Song Information
	private JPanel panelText = null;
	private JLabel labelText = null;

	// Time and Details Section
	private JPanel panelDetails = null;
	private JLabel labelTime = null;

	// Icon Changes
	private ImageIcon iconPlay   = null;
	private ImageIcon iconPause  = null;
	private ImageIcon iconVolume = null;
	private ImageIcon iconMute   = null;
	
	
	/*
	 * PACKAGE INSTANCES
	 */
	private Status Information;
	private Playback Control;
	private Options Modes;
	
	// Volume Control
	private VolumeMenu VolumeControl;
	private JPopupMenu VolumePopup;
	
	// Language and Global Setting Elements
	private Map<String,String> Lang;
	private Map<String,String> Global;
	
	// List of enabled commands
	private Set<String> Enabled;
	
	// Screens
	private Window Fullscreen;
	private Window Compact;
	
	/*
	 * CLASS INSTANCES
	 */
	private Interact Interface;
	
	/*
	 * CONSTANTS
	 */
	private static final int TICKER_WIDTH = 260;
	
	/**
	 * This is the default constructor.  Used primarily for visual editors as
	 * it will not provide anyone with a interactive elements.
	 */
	@SuppressWarnings("serial")
	public PlayerCompactFrame() {
		
		// Have the super class work
		super();
		
		/*
		 * Under normal circumstances this section will be passed on from the
		 * database.   However Visual Editors do not have this ability to we
		 * create a HashMap that can be used either way.
		 */
		Lang = new HashMap<String,String>(){
			{
				// Player Title
				put("Compact.Window.Title","Lyra");
				
	        	// Current Playlist Control
	        	put("Tooltip.Playback.Previous","Shuffle current playlist...");
	        	put("Tooltip.Playback.Stop","Remove selected entry...");
	        	put("Tooltip.Playback.Play","Clear contents of current playlist...");
	        	put("Tooltip.Playback.Next","Shift selected song up...");
	        	put("Tooltip.Playback.Fullscreen","Return to organizer mode...");
	        	
	        	// Options Level
	        	put("Tooltip.Options.Volume","Adjust volume level...");
	        	
	        	// Default Information Element
	        	put ("Player.Information.Artist", "");
	        	put ("Player.Information.Title",  "");
	        	put ("Player.Information.Time",   "");
			}
		};
		
		// Insert Elements
		initFrame();
	}
	// End of Constructor Method

	
	/**
	 * Primary constructor class for this component.  Will pass on all of the
	 * necessary control elements to make it interactive.
	 * <p>
	 * @param information State and status information for playback and modes.
	 * @param control Playback control instance.
	 * @param modes Options handler for control host Options.
	 * @param watcher Updater instance to receive changes in player state.
	 * @param clock Timer to keep players up to date.
	 * @param lang Language elements.
	 * @param global Global settings.
	 * @param fullscreen Window to revert to when going fullscreen.
	 */
	public PlayerCompactFrame(
			Status information, 
			Playback control, 
			Options modes, 
			Updater watcher, 
			Pendulum clock,  
			Map<String,String> lang,
			Map<String,String> global,
			Window fullscreen) {
		
		// Have the super class do its thing
		super();
		
		// Pass up
		this.Information = information;
		this.Control     = control;
		this.Modes       = modes;
		this.Lang        = lang;
		this.Global      = global;
		
		// Window recording
		this.Fullscreen  = fullscreen;
		this.Compact     = this;
		
		// Enabled Component
		this.Enabled     = Information.getEnabledAsSet();
		
		// Create Instance of Listener
		Interface = new Interact();
		
		// Initialise the contents
		initFrame();
		
		// Update Elements
		Interface.updateState();
		Interface.updateTime();
		
		// Add this interface as a watcher
		watcher.addUpdateListener(Interface);
		clock.addTimerListener(Interface);
	}
	// End of Constructor Class
	
	
	/**
	 * This method establishes the baseline JFrame elements.
	 */
	private void initFrame() {

		// Establish Title
		this.setTitle(Lang.get("Compact.Window.Title"));

		// Adjust Icon
		this.setIconImage(Constants.getIcon());
		
		// Insert Elements
		this.setContentPane(getJContentPane());
		
		/*
		 * Adjust the size to optimal dimension and then lock the display
		 * to prevent resizing.
		 */
		this.pack();
		
		// Adjust minimum size
		this.setMinimumSize(
			this.getPreferredSize());
		
		// Prevent resize
		this.setResizable(false);
		
		
		/*
		 * Establish the default operation so that the window never closes
		 * unless the application ends.
		 */
		this.setDefaultCloseOperation(
				JFrame.HIDE_ON_CLOSE);
		
		/*
		 * Core to the implementation of the panel is the use of PopupMenu 
		 * structures for both the volume and crossfade control.
		 */
		
		// Volume Control
		VolumeControl = new VolumeMenu(Lang);
		VolumePopup = VolumeControl.getPopupVolume();

		// Listener
		VolumeControl.addChangeListener(Interface);

	}
	// End of initFrame Method

	
	/**
	 * This method initialises jContentPane.  Establishes the baseline elements
	 * used in the interface display itself.
	 * <p>
	 * @return javax.swing.JPanel
	 */
	private JPanel getJContentPane() {
		
		// Check for Existence
		if (jContentPane == null) {
			
			// Create Instance
			jContentPane = new JPanel();
			
			jContentPane.setDoubleBuffered(true);
			
			// Establish Layout
			jContentPane.setLayout(new BoxLayout(jContentPane, BoxLayout.Y_AXIS));
			
			// Insert Elements
			jContentPane.add(getPanelDisplay(), null);
			jContentPane.add(getPanelControl(), null);

		}
		
		// Return Instance
		return jContentPane;
	}
	// End of jContentPane Method

	
	/**
	 * This method initialises panelControl.  All of the control buttons for
	 * the compact interface are located here.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelControl() {
		
		// Check for Existence
		if (panelControl == null) {
			
			// Create Instance
			panelControl = new JPanel();
			
			// Establish Layout
			panelControl.setLayout(new BoxLayout(getPanelControl(), BoxLayout.X_AXIS));

			// Insert Elements
			panelControl.add(getButtonPrevious(), null);
			panelControl.add(getButtonPlay(), null);
			panelControl.add(getButtonStop(), null);
			panelControl.add(getButtonNext(), null);
			panelControl.add(getButtonVolume(), null);
			
			// Spacer and Return
			panelControl.add(Box.createHorizontalGlue(), null);
			panelControl.add(getButtonFullscreen(), null);
		}
		
		// Return Instance
		return panelControl;
	}
	// End of getPanelControl Method

	
	/**
	 * This method initialises panelDisplay.   Displays all of the information
	 * for the playback status.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelDisplay() {
		
		// Check for Existence
		if (panelDisplay == null) {
			
			// Create Instance
			panelDisplay = new JPanel();
			
			// Establish Layout and Border
			panelDisplay.setLayout(new BoxLayout(getPanelDisplay(), BoxLayout.Y_AXIS));
			panelDisplay.setBorder(
					BorderFactory.createTitledBorder(
							null, 
							"", 
							TitledBorder.DEFAULT_JUSTIFICATION, 
							TitledBorder.DEFAULT_POSITION, 
							new Font(Font.DIALOG, Font.BOLD, 12), 
							new Color(51, 51, 51)
					  ));
			
			// Insert Elements
			panelDisplay.add(getPanelText(), null);
			panelDisplay.add(getPanelDetails(), null);
			
			// Forced position and spacer
			panelDisplay.add(Box.createHorizontalStrut(TICKER_WIDTH), null);
			panelDisplay.add(getProgressTime(), null);
		}
		
		// Return Instance
		return panelDisplay;
	}
	// End of getPanelDisplay Method
	
	/**
	 * This method initialises progressTime.   Non-interactive progress bar
	 * showing the elapsed time for a song.	
	 * <p>
	 * @return javax.swing.JProgressBar	
	 */
	private JProgressBar getProgressTime() {
		
		// Check for Existence
		if (progressTime == null) {
			
			// Return Instance
			progressTime = new JProgressBar();
			
			// Initial Value
			progressTime.setValue(0);
		}
		
		// Return Instance
		return progressTime;
	}
	// End of getProgressTime Method

	
	/**
	 * This method initialises buttonPrevious.  Goes back a song in the current
	 * playlist if available.	
	 * <p>
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonPrevious() {
		
		// Check for Existence
		if (buttonPrevious == null) {
			
			// Create Instance
			buttonPrevious = new JButton();
			
			// Adjust the Icon and Language
			buttonPrevious.setIcon(new ImageIcon(
				Lyra.getIconByMap("Playback-Previous")));
			buttonPrevious.setToolTipText(
				Lang.get("Tooltip.Playback.Previous"));

			// Identifier and Listener
			buttonPrevious.setName("ACTION.PREVIOUS");
			buttonPrevious.addActionListener(Interface);
			
			// Establish Enabled State
			buttonPrevious.setEnabled(Enabled.contains(
				Terminus.PROTOCOL.get("Player.Previous")));
		}
		
		// Return Instance
		return buttonPrevious;
	}
	// End of getButtonPrevious Method
	

	/**
	 * This method initialises buttonPlay.  Pauses or starts playback.	
	 * <p>
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonPlay() {
		
		// Check for Existence
		if (buttonPlay == null) {
			
			// Create Instance
			buttonPlay = new JButton();
			
			// Language Element
			buttonPlay.setToolTipText(
				Lang.get("Tooltip.Playback.Play"));

			// Identifier and Action Listener
			buttonPlay.setName("ACTION.PLAY");
			buttonPlay.addActionListener(Interface);
			
			// Establish Enabled State
			buttonPlay.setEnabled(Enabled.contains(
				Terminus.PROTOCOL.get("Player.Play")));
		}
		
		// Return Instance
		return buttonPlay;
	}
	// End of getButtonPlay Method

	
	/**
	 * This method initialises buttonStop.  Stops the playback.	
	 * <p>
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonStop() {
		
		// Check for Existence
		if (buttonStop == null) {
			
			// Create of Existence
			buttonStop = new JButton();
			
			// Icon and Language Elements
			buttonStop.setIcon(new ImageIcon(
				Lyra.getIconByMap("Playback-Stop")));
			buttonStop.setToolTipText(
				Lang.get("Tooltip.Playback.Stop"));
			
			// Identifier and Listener
			buttonStop.setName("ACTION.STOP");
			buttonStop.addActionListener(Interface);
			
			// Establish Enabled State
			buttonStop.setEnabled(Enabled.contains(
				Terminus.PROTOCOL.get("Player.Stop")));
			
		}
		
		// Return Instance
		return buttonStop;
	}
	// End of getButtonStop Method

	
	/**
	 * This method initialises buttonNext.  Moves to the next song in the
	 * current playlist.	
	 * <p>
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonNext() {
		
		// Check for Existence
		if (buttonNext == null) {
			
			// Create Instance
			buttonNext = new JButton();
			
			// Icon and Language Element
			buttonNext.setIcon(new ImageIcon(
				Lyra.getIconByMap("Playback-Next")));
			buttonNext.setToolTipText(
				Lang.get("Tooltip.Playback.Next"));
			
			// Identifier and Listener
			buttonNext.setName("ACTION.NEXT");
			buttonNext.addActionListener(Interface);
			
			// Establish Enabled State
			buttonNext.setEnabled(Enabled.contains(
				Terminus.PROTOCOL.get("Player.Next")));
		}
		
		// Return Instance
		return buttonNext;
	}
	// End of getButtoNext Method
	

	/**
	 * This method initialises buttonVolume.  Permits volume control.	
	 * 	
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonVolume() {
		
		// Check for Existence
		if (buttonVolume == null) {
			
			// Create Instance
			buttonVolume = new JButton();
			
			// Icon and Language Element
			buttonVolume.setIcon(new ImageIcon(
				Lyra.getIconByMap("Options-Volume")));
			buttonVolume.setToolTipText(
				Lang.get("Tooltip.Options.Volume"));

			// Identifier and Listener
			buttonVolume.setName("OPTIONS.VOLUME");
			buttonVolume.addActionListener(Interface);
			
			// Establish Enabled State
			buttonVolume.setEnabled(Enabled.contains(
				Terminus.PROTOCOL.get("Mode.Volume")));
		}
		
		// Return Instance
		return buttonVolume;
	}
	// End of getButtonVolume Method

	
	/**
	 * This method initialises panelText.  Displays Song and Artist information
	 * in a ticker fashion.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelText() {
		
		// Check for Existence
		if (panelText == null) {
			
			// Create Instance and Layout
			panelText = new JPanel();
			panelText.setLayout(new BoxLayout(getPanelText(), BoxLayout.X_AXIS));

			// Insert Elements
			panelText.add(getLabelText(), null);
			panelText.add(Box.createHorizontalGlue(), null);
		}
		
		// Return Instance
		return panelText;
	}
	// End of getPanelText Method
	
	
	/**
	 * This method initialises labelText.   Used to display artist and song
	 * information.
	 * <p>
	 * @return javax.swing.JLabel
	 */
	private JLabel getLabelText() {
		
		// Check for Existence
		if (labelText == null) {
			
			/*
			 * We check to see if display is double buffered or not.  If that
			 * is the case, we then chose to make use of a ticker which moves
			 * a pixel at a time and provides a smoother look overall.
			 */
			if (Boolean.parseBoolean(Global.get("Setting.Interface.Smooth"))) {
				
				// Create Instance
				TickerBufferedLabel doubled = new TickerBufferedLabel(
						Lang.get("Player.Information.Title"),
						new Font(Font.DIALOG, Font.BOLD, 12),
						TICKER_WIDTH
					);
				
				// Assign instance
				labelText = doubled;
			}
			
			/*
			 * When all else fails we fall back to a truer implementation
			 * of a ticker.   As in all of the events are swing native without
			 * custom drawing.   However the ticker moves a character at a time
			 * instead of pixel based.
			 */
			else {
				
				// Create Instance
				TickerSimpleLabel doubled = new TickerSimpleLabel(
						Lang.get("Player.Information.Title"),
						new Font(Font.DIALOG, Font.BOLD, 12),
						TICKER_WIDTH
					);
				
				// Adjust Separator
				doubled.setSeparator(
					Lang.get("Player.Information.Separator"));
				
				// Pass up the instance
				labelText = doubled;
			}
		}
		
		// Return Instance
		return labelText;
	}
	// End of getLabelText Method

	
	/**
	 * This method initialises panelDetails.  Time and song metrics.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelDetails() {
		
		// Check for Existence
		if (panelDetails == null) {

			// Create Instance and Layout
			panelDetails = new JPanel();
			panelDetails.setLayout(new BoxLayout(getPanelDetails(), BoxLayout.X_AXIS));

			// Insert Elements
			panelDetails.add(getLabelTime(), null);
			panelDetails.add(Box.createHorizontalGlue(), null);
		}
		return panelDetails;
	}
	// End of getPanelDetails Method

	
	/**
	 * This method initialises labelTime.   Time run down.
	 * <p>
	 * @return javax.swing.JLabel
	 */
	private JLabel getLabelTime() {
		
		// Check for Existence
		if (labelTime == null) {
			
			// Create Instance
			labelTime = new JLabel(
					Lang.get("Player.Information.Time")
				);
		}
		
		// Return Instance
		return labelTime;
	}
	// End of getLabelTime Method
	
	
	/**
	 * This method initialises buttonFullscreen.  Returns the user to
	 * the full screen mode.	
	 * <p>
	 * @return javax.swing.JButton	
	 */
	private JButton getButtonFullscreen() {
		
		// Check for Existence
		if (buttonFullscreen == null) {
			
			// Create Instance
			buttonFullscreen = new JButton();
			
			// Icon and Language
			buttonFullscreen.setIcon(new ImageIcon(
				Lyra.getIconByMap("Playback-Fullscreen")));
			buttonFullscreen.setToolTipText(
				Lang.get("Tooltip.Playback.Fullscreen"));

			// Identifier and Listener
			buttonFullscreen.setName("ACTION.ORGANIZER");
			buttonFullscreen.addActionListener(Interface);
		}
		
		// Return Instance
		return buttonFullscreen;
	}
	// End of getButtonFullscreen Method

	
	/**
	 * This method initialises iconPlay.
	 * <p>
	 * @return javax.swing.ImageIcon
	 */
	private ImageIcon getIconPlay() {
		
		// Check for Existence
		if (iconPlay == null) {
			
			// Create Instance
			iconPlay = new ImageIcon(
					Lyra.getIconByMap("Playback-Play"));
		}
		
		// Return instance
		return iconPlay;
	}
	// End of getIconPlay Method
	
	
	/**
	 * This method initialises iconPause.
	 * <p>
	 * @return javax.swing.ImageIcon
	 */
	private ImageIcon getIconPause() {
		
		// Check for Existence
		if (iconPause == null) {
			
			// Create Instance
			iconPause = new ImageIcon(
					Lyra.getIconByMap("Playback-Pause"));
		}
		
		// Return instance
		return iconPause;
	}
	// End of getIconPause Method
	
	
	/**
	 * This method initialises iconVolume.
	 * <p>
	 * @return javax.swing.ImageIcon
	 */
	private ImageIcon getIconVolume() {
		
		// Check for Existence
		if (iconVolume == null) {
			
			// Create Instance
			iconVolume = new ImageIcon(
					Lyra.getIconByMap("Options-Volume"));
		}
		
		// Return instance
		return iconVolume;
	}
	// End of getIconVolume Method
	
	
	/**
	 * This method initialises iconMute.
	 * <p>
	 * @return javax.swing.ImageIcon
	 */
	private ImageIcon getIconMute() {
		
		// Check for Existence
		if (iconMute == null) {
			
			// Create Instance
			iconMute = new ImageIcon(
					Lyra.getIconByMap("Options-Mute"));
		}
		
		// Return instance
		return iconMute;
	}
	// End of getIconMute Method
	
	
	/*
	 * SETTER ELEMENTS
	 * 
	 * Setter elements exist to act on behalf of the logic elements.  Logic
	 * elements should not deal with the elements directly unless it is to
	 * read certain status information.
	 */
	
	/**
	 * Adjusts the icon for the play button setting it to a pause icon.
	 */
	public void setButtonPlayAsPause() {
		
		// Adjust the icon setting it to pause
		getButtonPlay().setIcon(getIconPause());
	}
	// End of setButtonPlayAsPause Method
	
	
	/**
	 * Adjusts the icon for the play button setting it to a play icon.
	 */
	public void setButtonPlayAsPlay() {
		
		// Adjust the icon setting it to pause
		getButtonPlay().setIcon(getIconPlay());
	}
	// End of setButtonPlayAsPlay Method
	
	
	/**
	 * Adjusts the icon for the volume button setting it to a mute icon.
	 */
	public void setButtonVolumeAsMute() {
		
		// Adjust the icon setting it to pause
		getButtonVolume().setIcon(getIconMute());
	}
	// End of setButtonVolumeAsMute Method
	
	
	/**
	 * Adjusts the icon for the play button setting it to a play icon.
	 */
	public void setButtonVolumeAsVolume() {
		
		// Adjust the icon setting it to pause
		getButtonVolume().setIcon(getIconVolume());
	}
	// End of setButtonVolumeAsVolume Method
	
	
	/**
	 * Sets the slider of the position and capacity.
	 * <p>
	 * @param elapsed Integer carrying current runtime.
	 * @param total Integer representing total play time.
	 */
	public void setSliderProgress(int elapsed, int total) {

		// We only need to adapt the maximum if its different
		if (getProgressTime().getMaximum() != total)
			getProgressTime().setMaximum(total);
		
		// Establish position
		getProgressTime().setValue(elapsed);
	}
	// End of setSliderProgress Method
	
	
	/**
	 * Sets the display label for the title field.
	 * <p>
	 * @param title String carrying title name.
	 */
	public void setLabelText(String title) {
		
		// Confirm difference and set
		if (!getLabelText().getText().equals(title))
			getLabelText().setText(title);
	}
	// End of setLabelText Method
	
	
	/**
	 * Sets the display label for the time field.
	 * <p>
	 * @param elapsed Integer carrying current runtime.
	 * @param total Integer representing total play time.
	 */
	public void setLabelTime(int elapsed, int total) {
		
		// Parse the total elapsed time
		long time_min  = TimeUnit.SECONDS.toMinutes(elapsed);
		long time_sec = TimeUnit.SECONDS.toSeconds(elapsed - (time_min * 60));
		
		// Parse the total minutes for the song
		long total_min  = TimeUnit.SECONDS.toMinutes(total);
		long total_sec = TimeUnit.SECONDS.toSeconds(total - (total_min * 60));
		
		// Confirm difference and set 
		getLabelTime().setText(
			Lang.get("Player.Information.Template")
				.replaceAll("MEMIN", Interface.getNumber(time_min))
				.replaceAll("MESEC", Interface.getNumber(time_sec))
				.replaceAll("MTMIN", Interface.getNumber(total_min))
				.replaceAll("MTSEC", Interface.getNumber(total_sec))
		    );
	}
	// End of setLabelTime Method


	/* (non-Javadoc)
	 * @see java.awt.Window#setVisible(boolean)
	 */
	public void setVisible(boolean state) {
		
		/*
		 * When the window is made visible or hidden we extract information
		 * or make use of the information to lay position on the window.  
		 */
		
		Point pos;
		
		// Position visible window
		if (state) {
			
			// Extract point
			pos = new Point(
				Integer.parseInt(Global.get("State.Compact.Window.XCoord")),
				Integer.parseInt(Global.get("State.Compact.Window.YCoord"))
			  );
			
			// Position window
			this.setLocation(pos);
		}
		
		// Extract Information
		else {
			
			// Extract Position
			pos = this.getLocationOnScreen();
			
			// X Coordinate
			Global.put(
				"State.Compact.Window.XCoord",
				Integer.toString(pos.x)
			  );
			
			// Y Coordinate
			Global.put(
				"State.Compact.Window.YCoord",
				Integer.toString(pos.y)	
			  );
			
			// Set the parent visible
			Fullscreen.setVisible(true);
		}

		// Adjust the settings
		super.setVisible(state);
	}
	// End of setVisible Method
	
	
	/**
	 * The code separation from the user interface.   This Class handles all
	 * of the complexities of making the interface interactive.
	 * <p>
	 * @author Martin Foster
	 */
	private class Interact implements 
		ActionListener,
		ChangeListener,
		UpdateListener, 
		PendulumListener {

		
		/*
		 * Storage elements which contain all the state information we
		 * require.
		 */
		private Map<String,String> State;
		private List<Map<String,String>> Playlist;
		
	
		/**
		 * Base constructor for the class.
		 */
		public Interact() {}
		
		
		/**
		 * Simple method which will take a number and convert it to a string
		 * maintaining elements such as an additional zero for formatting.
		 * <p>
		 * @param value Number to convert.
		 * @return String of converted and formatted number.
		 */
		public String getNumber(Long value) {
			
			// Do a quick check
			String num = (value < 10)
				? "0" + Long.toString(value)
				: Long.toString(value);
			
			// Return the number
			return num;
		}
		// End of getNumber Method
		
		
		/**
		 * Method which is used after the base is in to ensure that all of the
		 * time elements are updated to match initial state. 
		 */
		private void updateTime() {
			/*
			 * Now that we have the song and state tells us what is playing
			 * we can now discern the time related elements and pass it on
			 * for processing.
			 */
			if (!State.get("state").equals(Terminus.STATE_STOP)) {
				
				// Extract currently playing song
				Map<String,String> song 
				  = Playlist.get(Integer.parseInt(State.get("song")));
				
				// Calculate elements
				int elapsed = Integer.parseInt(State.get("time").split(":")[0]);
				int total   = Integer.parseInt(song.get("Time"));

				// Time element
				setLabelTime(elapsed, total);
			
				// Slider element
				setSliderProgress(elapsed, total);
			}
		}
		// End of updateTime Method
		
		
		/**
		 * This method will update various elements to reflect current state
		 * information from the Host itself. 
		 */
		private void updateState() {
			
			// Change over the state information
			State    = Information.getState();
			Playlist = Information.getCurrent();
			
			// Update all Elements
			statePlayer();
			stateMixer();
		}
		// End of updateState Method
		

		/**
		 * Provides an updated set of coordinates to the popup menu in order to
		 * appear as though it is attached to a widget directly.
		 * <p>
		 * @param caller Caller component to place against.
		 * @param popup Popup menu to spawn.
		 */
		private void updatePosition(JComponent caller, JPopupMenu popup) {
			
			// Extract the point
			Point pos = caller.getLocationOnScreen();
			
			// Adjust the X coordinate position
			int xpos = (int)(caller.getWidth() + pos.getX());
			int ypos = (int)pos.getY();
			
			// Adjust the position
			popup.setLocation(xpos, ypos);
		}
		// End of updatePosition Method
		
		
		/**
		 * Updates Player controls in the panel.
		 */
		private void statePlayer() {
			
			/*
			 * The Play button and how it looks changes based on the state.
			 * When playing it will display itself as a pause button while in
			 * all other states will make use of the play icon as would 
			 * normally be expected.
			 */
			
			// Pause
			if (State.get("state").equals(Terminus.STATE_PLAY))
				setButtonPlayAsPause();
			
			// Play as Default
			else setButtonPlayAsPlay();

			
			/*
			 * Update the label elements in the song display component.  Artist
			 * Time and Label are all components that we will require to update.
			 */
			if (State.get("song") != null) {
				
				// Extract currently playing song
				Map<String,String> song 
				  = Playlist.get(Integer.parseInt(State.get("song")));
				
				// Establish texts
				setLabelText(
					song.get("Artist") + 
					Lang.get("Player.Information.Separator") + 
					song.get("Title")
				  );
			}
			
			/*
			 * Update the label elements in the song display component.  Artist
			 * Time and Label are all components that we will require to update.
			 */
			else {
				
				// Adjust Title
				setLabelText(Lang.get("Player.Information.Title"));
				
				// Adjust time
				getLabelTime().setText(Lang.get("Player.Information.Time"));
			}
		}
		// End of statePlayer Method
		
		
		/**
		 * Updates Mixer elements in the panel.
		 */
		private void stateMixer() {
			
			// Safety reset
			if (!State.containsKey("volume"))
				return;
			
			/*
			 * Volume control requires an icon change based on the volume
			 * level.  If its the case we will make sure that elements are
			 * properly in place.
			 */
			
			// Volume Control
			VolumeControl.setState(
				Integer.parseInt(State.get("volume"))
			  );
			
			// Adjustment to icon look and feel
			if (VolumeControl.getState() > 0)
				setButtonVolumeAsVolume();
			
			// Mute icon
			else setButtonVolumeAsMute();
		}
		// End of stateVolume Method
		
		
		/*
		 * LISTENERS
		 * 
		 * Listeners are how we get updates and event notifications.  This is
		 * where logic will start to play a part.
		 */

		/* (non-Javadoc)
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		public void actionPerformed(ActionEvent evt) {
			
			/*
			 * In this case we only care about buttons and will use this as a
			 * way proceed.  We use abstract button since it will catch toggles
			 * and normal buttons.
			 */
			if (evt.getSource() instanceof AbstractButton) {
				
				// Extract source name
				String name = ((AbstractButton)evt.getSource()).getName();
				
				
				/*
				 * Time to compare and see which element was actually put into
				 * use.  Very useful for us to know ahead of time.
				 * 
				 * This specific section deals with elements that are really
				 * one-to-one with the commands they would call.  No inherent
				 * complexity.
				 */
				
				// Push back an element
				if (name.equals(getButtonPrevious().getName()))
					Control.playPrevious();
				
				// Advance an Element
				else if (name.equals(getButtonNext().getName()))
					Control.playNext();
				
				// Stop playback altogether
				else if (name.equals(getButtonStop().getName()))
					Control.playStop();
				
				/*
				 * The Play/Pause button requires a lot more thought in the
				 * matter because the same button will change based on state
				 * and as a result needs to be tracked.
				 * 
				 * Also the play button does not normally start without knowing
				 * a position.  So we typically track what we are doing.
				 */
				else if (name.equals(getButtonPlay().getName())) {
					
					/*
					 * Stopped states makes things fairly straight forward 
					 * over all since it will simply start playback from the
					 * top of the list.
					 */
					if (State.get("state").equals(Terminus.STATE_STOP)) {
						
						// Discover position
						int pos = (State.get("song") != null)
							? Integer.parseInt(State.get("song"))
							: 0;
							
						// Start at designated position
						Control.playStart(pos);
					}
					
					/*
					 * Pause can be fairly straight forward as well since it
					 * will restart the song currently playing.  Hence we
					 * just make use of state information to recover.
					 */
					else if (State.get("state").equals(Terminus.STATE_PAUSE))
						Control.playResume();

					/*
					 * When playing the only option we have left is to pause
					 * the elements in play.
					 */
					else if (State.get("state").equals(Terminus.STATE_PLAY))
						Control.playPause();
				}
				
				/*
				 * Popup windows provide certain capabilities to the display
				 * that would be cumbersome to implement directly.   Hence we
				 * have them in hidden window.
				 */
				
				// Volume Control
				else if (name.equals(getButtonVolume().getName())) {
					
					// Inverse visibility in case
					VolumePopup.setVisible(!VolumePopup.isVisible());

					// Adjust Position
					if (VolumePopup.isVisible())
						updatePosition(
							getButtonVolume(), 
							VolumePopup
						  );
				}
				
				/*
				 * This option will revert the view back to the full screen
				 * organiser which contains all of the features and options.
				 */
				else if (name.equals(getButtonFullscreen().getName())) {
					
					// Return visibility to the organiser
					Fullscreen.setVisible(true);
					
					// Hide the compact view
					Compact.setVisible(false);
				}
			}
		}
		// End of actionPerformed Method


		/* (non-Javadoc)
		 * @see org.lyra.Host.UpdateListener#hostUpdated(org.lyra.Host.UpdateEvent)
		 */
		public void hostUpdated(UpdateEvent update) {
			
			// Change over the state information
			State    = Information.getState();
			Playlist = Information.getCurrent();
			
			
			/*
			 * Since this update can come from another thread we have no choice
			 * but to ensure that the update happens within the Swing thread.
			 * 
			 * We are more discerning when it comes to what updates are
			 * triggered at one time.  This should make updates a little less
			 * wasteful of resources.
			 */
			
			// Player Updates
			if ((update.isPlayer())
			 || (update.isCurrent()))

				SwingUtilities.invokeLater(new Runnable() {
					
					/* (non-Javadoc)
					 * @see java.lang.Runnable#run()
					 */
					public void run() {
						statePlayer();
					}
				});
					
			// Mixer Elements	
			if (update.isMixer())
				SwingUtilities.invokeLater(new Runnable() {
							
					/* (non-Javadoc)
					 * @see java.lang.Runnable#run()
					 */
					public void run() {
						stateMixer();
					}
				}); 
		}
		// End of hostUpdated Method

		
		/* (non-Javadoc)
		 * @see org.lyra.Host.PendulumListener#pendulumUpdated(int, int)
		 */
		public void pendulumUpdated(int elapsed, int total) {
			
			/*
			 * Since the pendulum runs in a separate thread we can't have it
			 * make changes to Swing elements without going through the 
			 * Swing worker thread.
			 */
			
			// The thread requires finals when we pass on variables
			final int pelapsed = elapsed;
			final int ptotal = total;
			
			// Execute when worker thread has a chance
			SwingUtilities.invokeLater(new Runnable() {
				
				
				/* (non-Javadoc)
				 * @see java.lang.Runnable#run()
				 */
				public void run() {
					
					// Call both elements for update
					setLabelTime(pelapsed, ptotal);
					setSliderProgress(pelapsed, ptotal);
				}
			});
		}
		// End of pendulumUpdated Method
	
		
		/* (non-Javadoc)
		 * @see java.awt.event.ChangeListener#stateChanged(java.awt.event.ChangeEvent)
		 */
		public void stateChanged(ChangeEvent evt) {

			// We only deal with JSlider events
			if (evt.getSource() instanceof JSlider) {
				
				// Extract Source
				JSlider source = (JSlider)evt.getSource();
				
				// We only deal once settled
				if ((!source.getValueIsAdjusting()) 
				 && (source.getName().equals(VolumeControl.getName()))) {
					Modes.setVolume(VolumeControl.getState());
					
				}
			}
		}
		// End of stateChanged Method

	}
	// End of Interact Class
}
