/**
 * 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 java.awt.Image;
import java.awt.MenuItem;
import java.awt.MenuShortcut;
import java.awt.PopupMenu;
import java.awt.TrayIcon;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.List;
import java.util.Map;

import javax.swing.SwingUtilities;

import org.lyra.Lyra;
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;

/**
 * Class which will create a self-sustained TrayIcon component which can be
 * used to have basic control and display information on Lyra when minimised.
 * This class has no visual editor mode due to the lack of visual editors
 * which supports this feature.
 * <p>
 * @author Martin Foster
 * @version 1.0.4, %Revision, 107%, %LastChangedDate, 14/04.11 9:54PM%
 * @since 1.0.2
 */
public class LyraTrayIcon extends TrayIcon {

	/*
	 * MAIN HOLDER
	 */
	private static LyraTrayIcon myTray = null;
	
	/*
	 * PACKAGE INSTANCES
	 */
	
	private Status Information;
	private Playback Control;
	
	// Language and Global Setting Elements
	private Map<String,String> Lang;
	
	// Screens
	private Window Organiser;
	private Window Compact;
	
	
	/*
	 * CLASS INSTANCES
	 */
	
	// Interactive Ability
	private Interact Interface;
	
	// Popup Menu
	private PopupLyra Popup;
	
	
	/**
	 * Creates a TrayIcon with the specified image. 
	 * <p>
	 * @param image The Image to be used.
	 * @param information State and status information for playback and modes.
	 * @param control Playback control instance.
	 * @param watcher Updater instance to receive changes in player state.
	 * @param lang Language elements.
	 * @param organiser Window to revert to when going back to the player/organiser panel.
	 * @return LyraTrayIcon instance.
	 */
	public static LyraTrayIcon getTrayIcon(
			Image image, 
			Status information, 
			Playback control, 
			Updater watcher, 
			Map<String,String> lang,
			Window organiser) {
		
		// We have to make sure we have an instance
		if (myTray == null) {
			
			// Create new instance to pass on
			myTray = new LyraTrayIcon(
						image, 
						information, 
						control, 
						watcher, 
						lang, 
						organiser
					  );
		}
		
		// Return instance
		return myTray;
	}
	// End of getTrayIcon Method
	
	
	/**
	 * Permit the application to track both windows.
	 * <p>
	 * @param compact Compact window instance.
	 */
	public static void setCompact(Window compact) {
		
		// Keep track of both windows 
		if (myTray != null)
			myTray.Compact = compact;
	}
	// End of setCompact Method
	
	
	/**
	 * Do we have an instance created?
	 * <p>
	 * @return boolean on instance existence.
	 */
	public static boolean isInstanced() {
		
		// If there is an instance we deal with it
		return (myTray != null);
	}
	// End of isInstanced Method
	
	
	/**
	 * Creates an instance TrayIcon with the specified image. 
	 * <p>
	 * @param image The Image to be used.
	 * @param information State and status information for playback and modes.
	 * @param control Playback control instance.
	 * @param watcher Updater instance to receive changes in player state.
	 * @param lang Language elements.
	 * @param organiser Window to revert to when going back to the player/organiser panel.
	 */
	private LyraTrayIcon(
			Image image, 
			Status information, 
			Playback control, 
			Updater watcher, 
			Map<String,String> lang,
			Window organiser) {
		
		/*
		 * Initiate the base system try icon.   We will then add in the
		 * necessary components in order to complete functionality.
		 */
		super(image);

		// Pass up information
		this.Information = information;
		this.Control     = control;
		this.Lang        = lang;
		
		// For now make the screens the same for comparison
		this.Organiser   = organiser;
		this.Compact     = organiser;
		
		/*
		 * The Interaction class provides us with the capability to react to
		 * user generated input for the system tray.
		 */
		this.Interface = new Interact();
		
		/*
		 * We adjust the basic elements from this point forward.  Firstly, we
		 * set the tool tip element to contain the name of the application
		 * and then pass on.   Followed is establishment of the menu structure
		 * allowing someone basic control over playback.
		 */
		
		// Adjust size of icon automatically
		this.setImageAutoSize(true); 
		
		// Adjust Tooltip
		this.setToolTip(
			Lang.get("Tray.Tooltip"));

		// Create instance and attach
		Popup = new PopupLyra();
		this.setPopupMenu(Popup);
		
		/*
		 * Establish the initial settings so that state elements such as the
		 * mute feature are in place.
		 */
		Interface.initSettings();
		
		/*
		 * Now that we are ready to actually start the process we must add
		 * ourselves as a listener.
		 */
		watcher.addUpdateListener(Interface);
		
		
		/*
		 * The icon is nice, but it is nicer to be able to quickly return
		 * to the full screen.  We will monitor for double clicks and handle
		 * accordingly.
		 */
		this.addMouseListener(Interface);
	}
	// End of Constructor Class

	
	/**
	 * Creates the popup menu structure necessary to display along with the
	 * system tray icon.
	 * <p>
	 * @author Martin Foster
	 */
	private class PopupLyra extends PopupMenu {
		
		// Serial Requirements
		private static final long serialVersionUID = 1L;

		// Display Player/Organiser
		private MenuItem itemShow = null;
		
		// Playback Controls
		private MenuItem itemPlay = null;
		private MenuItem itemStop = null;
		private MenuItem itemNext = null;
		private MenuItem itemPrev = null;
		
		// Exit the application
		private MenuItem itemExit = null;
		
		
		/**
		 * Base constructor for the class.  Instance is a populated popup
		 * menu ready for inclusion.
		 */
		public PopupLyra() {
			
			// Addition of Show Item and Separator
			this.add(getItemShow());
			this.addSeparator();

			// Addition of Playback and Separator
			this.add(getItemPrev());
			this.add(getItemPlay());
			this.add(getItemStop());
			this.add(getItemNext());
			this.addSeparator();

			// Addition of Exit Application
			this.add(getItemExit());
			
		}
		// End of Constructor Class
		
		
		/**
		 * Returns the option menu to display the Player/Organiser window.
		 * <p>
		 * @return java.awt.MenuItem
		 */
		private MenuItem getItemShow() {
			
			// Check for Existence
			if (itemShow == null) {
				
				// Create Instance and Set Language
				itemShow = new MenuItem();
				itemShow.setLabel(Lang.get("Tray.Menu.Show"));
				
				// Quick Key
				itemShow.setShortcut(
					new MenuShortcut(KeyEvent.VK_S));
				
				// Identifier and Action
				itemShow.setName("SHOW");
				itemShow.addActionListener(Interface);
			}
			
			// Return Instance
			return itemShow;
		}
		// End of getItemShow Method
		
		
		/**
		 * Permits a user to pause/play the playback.
		 * <p>
		 * @return java.awt.MenuItem
		 */
		private MenuItem getItemPlay() {
			
			// Check for Existence
			if (itemPlay == null) {
				
				// Create Instance and Set Language
				itemPlay = new MenuItem();
				itemPlay.setLabel(Lang.get("Tray.Menu.Play"));
				
				// Quick Key
				itemPlay.setShortcut(
					new MenuShortcut(KeyEvent.VK_P));
				
				// Identifier and Action
				itemPlay.setName("PLAY");
				itemPlay.addActionListener(Interface);
			}
			
			// Return Instance
			return itemPlay;
		}
		// End of getItemPlay Method
		
		
		/**
		 * Sets the label to the appropriate text.
		 * <p>
		 * @param label String containing label for display.
		 */
		private void setItemPlay(String label) {
			
			// Direct passage
			getItemPlay().setLabel(label);
		}
		// End of setItemPlay Method
		
		
		/**
		 * Adjusts the label to indicate that we can begin playback.
		 */
		public void setItemPlayAsPlay() {
			
			// Direct passage
			setItemPlay(Lang.get("Tray.Menu.Play"));
		}
		// End of setItemPlayAsPlay Method
		
		
		/**
		 * Adjusts the label to indicate that we can pause playback.
		 */
		public void setItemPlayAsPause() {
			
			// Direct passage
			setItemPlay(Lang.get("Tray.Menu.Pause"));
		}
		// End of setItemPauseAsPause Method
		
		
		/**
		 * Permits a user to stop the playback.
		 * <p>
		 * @return java.awt.MenuItem
		 */
		private MenuItem getItemStop() {
			
			// Check for Existence
			if (itemStop == null) {
				
				// Create Instance and Set Language
				itemStop = new MenuItem();
				itemStop.setLabel(Lang.get("Tray.Menu.Stop"));
				
				// Quick Key
				itemStop.setShortcut(
					new MenuShortcut(KeyEvent.VK_S));
				
				// Identifier and Action
				itemStop.setName("STOP");
				itemStop.addActionListener(Interface);
			}
			
			// Return Instance
			return itemStop;
		}
		// End of getItemStop Method
		
		
		/**
		 * Permits playing the next song in the Current Playlist.
		 * <p>
		 * @return java.awt.MenuItem
		 */
		private MenuItem getItemNext() {
			
			// Check for Existence
			if (itemNext == null) {
				
				// Create Instance and Set Language
				itemNext = new MenuItem();
				itemNext.setLabel(Lang.get("Tray.Menu.Next"));
				
				// Quick Key
				itemNext.setShortcut(
					new MenuShortcut(KeyEvent.VK_N));
				
				// Identifier and Action
				itemNext.setName("Next");
				itemNext.addActionListener(Interface);
			}
			
			// Return Instance
			return itemNext;
		}
		// End of getItemNext Method
		
		
		/**
		 * Permits playing the previous song in the Current Playlist.
		 * <p>
		 * @return java.awt.MenuItem
		 */
		private MenuItem getItemPrev() {
			
			// Check for Existence
			if (itemPrev == null) {
				
				// Create Instance and Set Language
				itemPrev = new MenuItem();
				itemPrev.setLabel(Lang.get("Tray.Menu.Prev"));
				
				// Quick Key
				itemPrev.setShortcut(
					new MenuShortcut(KeyEvent.VK_B));
				
				// Identifier and Action
				itemPrev.setName("PREVIOUS");
				itemPrev.addActionListener(Interface);
			}
			
			// Return Instance
			return itemPrev;
		}
		// End of getItemPrev Method
		

		/**
		 * Returns the option menu to display the Player/Organiser window.
		 * <p>
		 * @return java.awt.MenuItem
		 */
		private MenuItem getItemExit() {
			
			// Check for Existence
			if (itemExit == null) {
				
				// Create Instance and Set Language
				itemExit = new MenuItem();
				itemExit.setLabel(Lang.get("Tray.Menu.Exit"));
				
				// Quick Key
				itemExit.setShortcut(
					new MenuShortcut(KeyEvent.VK_Q));
				
				// Identifier and Action
				itemExit.setName("EXIT");
				itemExit.addActionListener(Interface);
			}
			
			// Return Instance
			return itemExit;
		}
		// End of getItemQuit Method
	}
	// End of PopupLyra Class
	
	
	
	/**
	 * 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, MouseListener, UpdateListener {

		/*
		 * CLASS INSTANCES
		 */
		
		// Holders
		private Map<String,String> State;
		private List<Map<String,String>> Playlist;
		
		
		/* (non-Javadoc)
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		public void actionPerformed(ActionEvent evt) {
			
			/*
			 * Menu items make up most of the.
			 */
			if (evt.getSource() instanceof MenuItem) {
				
				// Extract Identifier
				String ident = ((MenuItem)evt.getSource()).getName();
		
				
				/*
				 * We close the application through the normal process.  This
				 * is important to note that it may very well prompt prior to
				 * closing.
				 */
				if (ident.equals(Popup.getItemExit().getName()))
					Lyra.closeApplication(Organiser);
				
				/*
				 * We will set the visibility aspect to true whenever prompted
				 * no matter if it has been set prior or not.
				 */
				else if (ident.equals(Popup.getItemShow().getName())) {
					
					/*
					 * The Organiser/Player view is the default so users should
					 * not be surprised when it appears instead of compact.
					 */
					if ((!Organiser.isVisible())
					 && (!Compact.isVisible()))
						Organiser.setVisible(true);
				}
				
				/*
				 * Playback features are straight forward, with exception of
				 * pause, all other features will simply call their functions
				 * directly.
				 */
				
				// Previous Song
				else if (ident.equals(Popup.getItemPrev().getName()))
					Control.playPrevious();
				
				// Next Song
				else if (ident.equals(Popup.getItemNext().getName()))
					Control.playNext();
				
				// Start Playback
				else if (ident.equals(Popup.getItemPlay().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();
				}
				
				// Stop Playback
				else if (ident.equals(Popup.getItemStop().getName()))
					Control.playStop();
			}
		}
		// End of actionPerformed Method

		/* (non-Javadoc)
		 * @see org.lyra.Host.UpdateListener#hostUpdated(org.lyra.Host.UpdateEvent)
		 */
		public void hostUpdated(UpdateEvent update) {
			
			// Do nothing unless its
			if ((!update.isPlayer())
			 && (!update.isCurrent()))
				return;
				
			// Capture the state information
			State 	 = Information.getState();
			Playlist = Information.getCurrent();
			

			/*
			 * Player updates include changes in the playback.   This does not
			 * include changes in the volume control as this falls under the 
			 * mixer changes.
			 */
			if (update.isPlayer()) {
			
				// Invoke the following in the worker thread
				SwingUtilities.invokeLater(new Runnable() {

					/* (non-Javadoc)
					 * @see java.lang.Runnable#run()
					 */
					public void run() {
						
						// Updates the player information
						updatePlayer();
					}
					
				});
			}
		}
		// End of hostUpdated Method
		
		
		/**
		 * Updates player information on the system tray to match current
		 * playback.
		 */
		private void updatePlayer() {
			
			/*
			 * The Play menu and how it looks changes based on the state.
			 * When the state is PLAY we can pause, any other time the play
			 * control will be displayed.
			 */
			
			if (State.get("state").equals(Terminus.STATE_PLAY)) {
				
				// Pause Control
				Popup.setItemPlayAsPause();
				
				
				/*
				 * 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")));
					
					/*
					 * Create a string entry that contains enough information 
					 * to notify the user that a certain song is being played.
					 */
					StringBuffer sb = new StringBuffer();
					
					// Append Artist
					sb.append(song.get("Artist"));
					
					// Append Album
					if (song.get("Album") != null) {
						
						sb.append("\n");
						sb.append(song.get("Album"));
					}
						
					// Append Title
					if (song.get("Title") != null) {
						
						sb.append("\n");
						sb.append(song.get("Title"));
					}
					
					
					/*
					 * With the String generated, we can move onto actually 
					 * showing the information.
					 */
					if ((!Organiser.isVisible())
					 && (!Compact.isVisible()))
						displayMessage(
							Lang.get("Tray.Message.Title"), 
							sb.toString(), 
							TrayIcon.MessageType.INFO
						  );
					
					// Change the tooltip
					setToolTip(sb.toString());
				}
				
				/*
				 * Resets the tooltip element to contain a default value since
				 * no song is currently playing.
				 */
				else if (State.get("song") == null)
					setToolTip(Lang.get("Tray.Tooltip"));
			}
			
			// Play as Default
			else Popup.setItemPlayAsPlay();
		}
		// End of updatePlayer Method
		
		
		/**
		 * The initial settings method is designed specifically to push
		 * elements into place.
		 */
		public void initSettings() {
			
			// Capture the state information
			State 	 = Information.getState();
			Playlist = Information.getCurrent();
			
			// Call updates
			updatePlayer();
		}
		// End of initSettings Method

		/*
		 * MOUSE EVENTS
		 */
		
		
		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
		 */
		public void mouseClicked(MouseEvent evt) {

			/*
			 * We are only going to look events that trigger a double click
			 * and with that only one item has a mouse listener attached so
			 * we have no need to check which specifically.
			 */
			if (evt.getClickCount() == 2) {		
				
				/*
				 * The Organiser/Player view is the default so users should
				 * not be surprised when it appears instead of compact.
				 */
				if ((!Organiser.isVisible())
				 && (!Compact.isVisible()))
					Organiser.setVisible(true);
			}
		}

		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
		 */
		public void mouseEntered(MouseEvent e) {}

		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
		 */
		public void mouseExited(MouseEvent e) {}

		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
		 */
		public void mousePressed(MouseEvent e) {}

		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
		 */
		public void mouseReleased(MouseEvent e) {}
	}
	// End of Interact Class

}
