/**
 * 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.Cursor;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;

import org.lyra.Ledger;
import org.lyra.Lyra;
import org.lyra.Operations;
import org.lyra.Database.Comptroller;
import org.lyra.Host.Status;
import org.lyra.Host.UpdateEvent;
import org.lyra.Host.UpdateListener;
import org.lyra.Host.Updater;
import org.lyra.LastDotFm.Demarcation;
import org.lyra.Widgets.GraphicsUtilities;
import org.lyra.Widgets.HTTPClient;
import org.lyra.Widgets.RapidDialog;


/**
 * Album art display is one of the primary reason why Lyra was integrated with
 * the Last.fm service.   It permits us to get album art and provides other
 * services which add additional value, such as 'Scrobbling,' music purchasing
 * and profile display.
 * <p>
 * @author Martin Foster
 * @version 1.0.4, %Revision, 147%, %LastChangedDate, 06/11/11 4:57PM%
 * @since 1.0.1
 * @see org.lyra.LastDotFm.Demarcation
 * @see org.lyra.Host.UpdateListener
 */
public class AlbumArtPanel extends JPanel {

	
	/*
	 * VISUAL ELEMENTS
	 */

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

	// Icon for Album Art
	private JLabel iconArt;
	
	
	/*
	 * PACAKGE INSTANCES
	 */
	
	private Comptroller Manager;
	private Status Info;
	private Ledger Logger;
	
	// Setting maps
	private Map<String,String> Lang;
	private Map<String,String> Global;
	
	/*
	 * CLASS ELEMENTS
	 */
	private Interact Interface;
	private Demarcation Deploy;	
	
	/**
	 * Visual Editor constructor which contains all of the elements necessary
	 * to display an image with no interaction.
	 */
	@SuppressWarnings("serial")
	public AlbumArtPanel() {
		
		/*
		 * 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("Art.Tooltip.Cover","Click to expand options menu...");
				
				// Artist Profile
				put("Art.Menu.Profile","Artist Profile");
				put("Art.Tooltip.Profile","Display artist profile from Last.fm...");
				
				// Album Details
				put("Art.Menu.Details","Album Details");
				put("Art.Tooltip.Details","Display album details from Last.fm...");
				
				// Clear Cover Art
				put("Art.Menu.Clear","Clear Cover Art");
				put("Art.Tooltip.Clear","Remove currently displayed cover art...");
				
				// Select Cover Art
				put("Art.Menu.Select","Select Cover Art");
				put("Art.Tooltip.Select","Select cover art from existing images...");
			}
		};
		
		// Establish Baseline
		initPanel();
	}
	// End of Constructor Method
	
	
	/**
	 * Default constructor for the class.  Provides all of the necessary 
	 * elements dynamic display and interaction.
	 * <p>
	 * @param manager Database query and update manager.
	 * @param info Lyra host status information.
	 * @param logger Ledger instance for logging and status.
	 * @param watcher Listener interface for changes from the host.
	 * @param lang Language map for language independence.
	 * @param global Global settings map.
	 */
	public AlbumArtPanel(
			Comptroller manager, 
			Status info, 
			Ledger logger, 
			Updater watcher,
			Map<String,String> lang, 
			Map<String,String> global) {
		
		// Pass Up
		this.Manager = manager;
		this.Info    = info;
		this.Logger  = logger;
		this.Lang    = lang;
		this.Global  = global;
		
		// Create Instance
		this.Interface = new Interact();
		
		// Add in listener
		watcher.addUpdateListener(Interface);
		
		// Create Instance
		this.Deploy = Demarcation.getInstance(Logger, Lang, Global);
		
		// Establish Baseline
		initPanel();
		
		// Update Information
		this.Interface.updateArt();
	}
	// End of Constructor Method
	
	
	/**
	 * Method which creates the baseline interface. 
	 */
	private void initPanel() {
		
		// Establish layout
		this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		
		
		/*
		 * The album images may be square however the real estate they tie up
		 * may have room to grow.  So adding spacers on both extremities will
		 * ensure that it comes out centred.
		 */
		
		// Insert Spacer
		this.add(Box.createVerticalGlue(), null);
		
		// Insert Visual Element
		this.add(getIconArt(), null);
		
		// Insert Spacer
		this.add(Box.createVerticalGlue(), null);
		
	}
	// End of initPanel Method
	
	
	/**
	 * Initialises the base display for the Album Cover Art.  The initial image
	 * is an icon for the Last.fm service as a way to promote the site as we
	 * wait for the proper image.
	 * <p>
	 * @return javax.swing.JLabel
	 */
	private JLabel getIconArt() {
		
		// Check for Existence
		if (iconArt == null) {
			
			// Create Instance
			iconArt = new JLabel();
			
			// Establish Image
			iconArt.setIcon(new ImageIcon(
						getClass().getResource(
							Lyra.getIconByMap("LastDotFm-Small"))));
			
			// Language Element
			iconArt.setToolTipText(
					Lang.get("Art.Tooltip.Cover"));
			
			// Establish Icon to Show Interactivity
			iconArt.setCursor(
				new Cursor(Cursor.HAND_CURSOR));
			
			// Identifier and Action
			iconArt.setName("ALBUM_ART");
			iconArt.addMouseListener(Interface);
		}
		
		// Return Instance
		return iconArt;
	}
	// End of getIconAlbum Method
	
	
	/**
	 * Adjusts the currently displayed icon to the one provided.
	 * <p>
	 * @param img ImageIcon instance.
	 */
	private void setIconArt(ImageIcon img) {
		
		// Set and change the icon
		getIconArt().setIcon(img);
	}
	// End of setIconArt Method
	
	
	/**
	 * Adjusts the currently displayed icon using the link provided.
	 * <p>
	 * @param link Location on the file system with the image.
	 */
	private void setIconArt(String link) {
		
		// See if this will work
		try {
			
			// Extract a scaled instance of the image
			Image process = GraphicsUtilities.getScaledInstance(
					new ImageIcon(link).getImage(), 
					Constants.getArtSize(), 
					Constants.getArtSize(), 
					RenderingHints.VALUE_INTERPOLATION_BILINEAR, 
					true
				);
			
			
			// Establish Image
			setIconArt(new ImageIcon(process));
		}
		
		/*
		 * There is a chance that above failed due to an error in the image
		 * itself.   With this assumption, we will revert to the default
		 * image.
		 */
		catch (NullPointerException err) {
			
			// Establish Image
			setIconArt(new ImageIcon(
					getClass().getResource(
							Lyra.getIconByMap("LastDotFm-Small"))));
		}
	}
	// End of setIconArt Method
	
	
	/**
	 * Adjusts the currently displayed icon using a default image.
	 */
	private void setIconArt() {
		
		// Establish Image
		setIconArt(new ImageIcon(
				getClass().getResource(
						Lyra.getIconByMap("LastDotFm-Small"))));
	}
	// End of setIconArt Method
	
	
	/**
	 * The code separation from the user interface.   This Class handles all
	 * of the complexities of making the interface interactive.
	 * <p>
	 * @author Martin Foster
	 */
	public class Interact implements ActionListener, MouseListener, Runnable, UpdateListener{

		// File Tracking
		private Map<String,String> cFile;
		private Map<String,String> nFile;
		
		// Current State
		private Map<String,String> State;
		
		// Menu Structure
		private AlbumArtPopup Popup;
		
		// Standard file operations
		private Operations Ops;
		
		
		// Logger Identifier
		private static final String CLASS = "org.lyra.Interface.AlbumArtPanel.Interact";
		
		
		/**
		 * Base constructor for class. 
		 */
		public Interact() {
			
			// Extract Initial State
			State = Info.getState();
			
			// Extract Current File and Copy
			cFile = new HashMap<String,String>(
						updateDetails(State));
			
			// Generate instance
			Ops = new Operations(Logger);
		}
		// End of Constructor Class
		
		
		/**
		 * Updates the Album Art image based on existing information. 
		 */
		public void updateArt() {
			
			// Initial Update
			if (cFile.size() > 1)
				updateArt(cFile);
		}
		// End of updateArt Method 
		
		
		/**
		 * Updates the Album Art image based on provided information.  
		 * <p>
		 * @param current Current song information.
		 */
		private void updateArt(Map<String, String> current) {
			
			/*
			 * Here we have the necessary information to continue. We
			 * know the filename, so the first step is to look up and
			 * see if we have a cached image.  If this fails, we will
			 * be extracting a fresh image from Last.fm.
			 */
			
			// Loop up cached version
			String cache = Manager.getSongArt(current.get("file"));
			
			// No cache exists time to request from host
			if (cache == null) {
			
				/*
				 * We choose to launch a thread to deal with the
				 * new cached image handling.  This should prevent
				 * locking of the display and deal with it at its 
				 * own time.
				 */
				Thread thread = new Thread(this);
				thread.start();
			}
								
			else {
				
				// Extract full name
				String dest = Lyra.getCacheByFile(cache);
				
				// Everything is good we can access the file
				if (new File(dest).canRead()) {
					
					// Set art to our cached version
					setIconArt(dest);
					
					// Log newly created cache
					Logger.Info(CLASS,
						"hostUpdated",
						Lang.get("Art.Log.Cache")
							.replaceAll("MFILE", cache)
					  );
				}
				
				// We better reset
				else {
					
					// Set art to our cached version
					setIconArt();
					
					// Reset the entry
					Manager.resetCache(
						current.get("Artist"), 
						current.get("Album")		
					  );
					
					// Log newly created cache
					Logger.Info(CLASS,
						"hostUpdated",
						Lang.get("Art.Log.Reset")
						  .replaceAll("MFILE", cache)
					  );
				}
			}
		}
		// End of updateArt Method
		
		
		/**
		 * Extracts the information from the org.lyra.Host.Status class and extracts
		 * the currently playing song.   Based on state and currently playing 
		 * elements.
		 * <p>
		 * @return Map containing current song details.
		 */
		private Map<String,String> updateDetails(Map<String,String> state) {
			
			// Confirm this element exists
			if (state.get("song") != null) {
				
				// Extract and parse song
				int num = Integer.parseInt(state.get("song"));
				
				// Extract the specific song
				if (Info.getCurrent().get(num) != null) {
					
					// Extract song detail
					return Info.getCurrent().get(num);
				}
			}
			
			// Return nothing if any of the above fails
			return new HashMap<String,String>();
		}
		// End of updateFilename Method
		

		/*
		 * MENU DRIVEN COMMANDS
		 */
		
		/* (non-Javadoc)
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		public void actionPerformed(ActionEvent evt) {

			// Only dealing with menu items
			if (evt.getSource() instanceof JMenuItem) {
			
				// Hide the popup menu
				Popup.setVisible(false);
				
				// Extract Identifier
				String ident = ((JMenuItem)evt.getSource()).getName();
				
				// Log Action
				Logger.Info(CLASS,
						"actionPerformed",
						Lang.get("Art.Action.Popup")
						    .replaceAll("MOPTION",ident)
				  );
				
				
				/*
				 * Loads up a new window with the profile containing the artist
				 * profile.
				 */
				if (ident.equals(Popup.getItemProfile().getName())) {
					
					// We need to display valid information
					if (cFile.size() > 1) {
						
						/*
						 * Tying into the Last.fm service can be a fairly slow
						 * process.  So we throw this into a thread in order
						 * to just let the process take as long as it needs
						 * without locking the interface.
						 */
						Thread open = new Thread() {
							
							/* (non-Javadoc)
							 * @see java.lang.Thread#run()
							 */
							public void run() {
								
								// Create New Instance
								final JFrame frame = new ProfileFrame(
									Logger,
									Lang,
									Global,
									cFile.get("Artist")
								  );
								
								/*
								 * Once ready we have to throw this element back
								 * into the worker thread in order to prevent
								 * some weird errors.
								 */
								SwingUtilities.invokeLater(new Runnable() {
									
									/* (non-Javadoc)
									 * @see java.lang.Runnable#run()
									 */
									public void run() {
										
										// Display
										frame.setVisible(true);										
									}
								});
								
							}
						};
						
						// Run the process in background
						open.start();
					}
				}
				
				/*
				 * Loads up a new window with the profile containing the album
				 * information.   
				 */
				else if (ident.equals(Popup.getItemDetails().getName())) {
					
					// We need to display valid information
					if (cFile.size() > 1) {
						
						/*
						 * Tying into the Last.fm service can be a fairly slow
						 * process.  So we throw this into a thread in order
						 * to just let the process take as long as it needs
						 * without locking the interface.
						 */
						Thread open = new Thread() {
							
							/* (non-Javadoc)
							 * @see java.lang.Thread#run()
							 */
							public void run() {
								
								// Create New Instance
								final JFrame frame = new ProfileFrame(
									Logger,
									Lang,
									Global,
									cFile.get("Artist"),
									cFile.get("Album")
								  );
								
								/*
								 * Once ready we have to throw this element back
								 * into the worker thread in order to prevent
								 * some weird errors.
								 */
								SwingUtilities.invokeLater(new Runnable() {
									
									/* (non-Javadoc)
									 * @see java.lang.Runnable#run()
									 */
									public void run() {
										
										// Display
										frame.setVisible(true);										
									}
								});
								
							}
						
						};
						
						// Run the process in background
						open.start();
					}
				}
				
				
				/*
				 * Clears out the currently displayed album art, replacing it
				 * with a blank template.
				 */
				else if (ident.equals(Popup.getItemClear().getName())) {
				
					/*
					 * The complexity lies in the fact that we cannot simply
					 * remove the reference, since it will initiate another
					 * extraction from the service.  So we replace it with a
					 * blank template.
					 */
					if (cFile.size() > 1) {
						
						// Quickly extract song information
						String artist = cFile.get("Artist");
						String album  = cFile.get("Album");
						
						// Establish target file
						File target = new File(Lyra.getCacheByFile(Lyra.BLANK));
						
						// Update Database
						Manager.insertCache(
								artist, 
								album, 
								target.getName()
							);
						
						// Pass on for display
						setIconArt(target.getAbsolutePath());
					}
				}
				
				/*
				 * The user has opted to select album art from their own file
				 * system instead of relying on the Last.fm process to do so
				 * for them. 
				 */
				else if (ident.equals(Popup.getItemSelect().getName())) {
					
					/*
					 * The JFileChooser will permit the user in selecting the
					 * actual image file he wants to work with.  
					 */
					  
					// Create Instances
					ImageFilter imf = new ImageFilter();
					JFileChooser fc = new JFileChooser();
					
					// Adjust filter
					fc.setFileFilter(imf);
					
					// We only want to select files
					fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
					
					/*
					 * We only care if the user has decided to choose a file.
					 * Otherwise we ignore it completely.
					 */
					if (fc.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
						
						// Get the source file
						File source = fc.getSelectedFile();
						
						// Source is valid
						if ((source.canRead()) 
						 && (cFile.size() > 1)) {
							
							// One way or the other we get a name
							String amalgam;
							
							// Quickly extract song information
							String artist = cFile.get("Artist");
							String album  = cFile.get("Album");
							
							/*
							 * We have everything we need, however there are 
							 * issues with assuming an artist/album combination
							 * when dealing with compilations.  This alleviates
							 * the problem.
							 * 
							 * This part is easy, we create a base filename for
							 * saving the image and pass it on to be saved.  
							 * Once complete it will be passed on.
							 */
							RapidDialog rd = new RapidDialog(Lang);
							
							// Extract choice
							Boolean choice = rd.noticeAlbumArt(null);
							
							/*
							 * We only look for this option as any
							 * other is assumed to be
							 */
							if (choice) {
								
								// Make the filename character safe
								amalgam = album
								          .toLowerCase()
								          .replaceAll("[\\s\\W\\+]+","_");
								
								// Finally add in extension
								amalgam = amalgam + Operations.extractFileExtension(source);
							}
							
							else {
								
								// Combine the two elements
								amalgam = artist + "+" + album;
								
								// Make the filename character safe
								amalgam = amalgam
								          .toLowerCase()
								          .replaceAll("[\\s\\W\\+]+","_");
								
								// Finally add in extension
								amalgam = amalgam + Operations.extractFileExtension(source);
							}
							
							// Establish target file
							File target = new File(Lyra.getCacheByFile(amalgam));
							
							/*
							 * We have to ensure that the file can be
							 * either written to or created.
							 */
							try {
							
								// Confirm that we can write
								if (((target.exists()) && (target.canWrite()))
								 || (target.createNewFile())) {
									
									// Copy over wholesale
									Ops.fileCopy(source, target);
									

									/*
									 * Here we will make a group update and
									 * use the filename appropriately.
									 */
									if (choice)
											
										// Update Database
										Manager.insertCache(
												album, 
												target.getName()
											);
									}
									
									/*
									 * We do a pair update one way or the other
									 * unless they choose global explicitly.
									 */
									else {
										
										// Update Database
										Manager.insertCache(
												artist, 
												album, 
												target.getName()
											);
									}

									// Pass on for display
									setIconArt(target.getAbsolutePath());	
							} 
							catch (IOException e) {}
						}
					}
				}
			}
		}
		// End of actionPerformed Method
		
		
		/*
		 * HOST UPDATES
		 */
		
		/* (non-Javadoc)
		 * @see org.lyra.Host.UpdateListener#hostUpdated(org.lyra.Host.UpdateEvent)
		 */
		public void hostUpdated(UpdateEvent update) {

			/*
			 * We only care if there is an update to the player itself.  This
			 * can mean however a change in state or song played, so we will
			 * have to narrow it down to song changes.
			 */
			if (update.isPlayer()) {
				
				// Always get proper information
				State = Info.getState();
				
				// There is a song to look into
				if (State.containsKey("songid")) {
					
					// Extract Newly Passed Filename
					nFile = updateDetails(State);
					
					// Is there a current song?
					if (nFile.size() > 0) {
					
						// We ignore when the file is the same
						if ((cFile.size() > 0)
						 && (cFile.get("file").equals(nFile.get("file"))))
							return;
						
						// Pass on for use
						cFile = nFile;
						
						// Process an update on the new information
						updateArt(nFile);
						
						// Return
						return;
					}
					
					// We reset to defaults
					else {

						// We reset to default
						setIconArt();
					}
					
					// Extract Current File and Copy
					cFile = nFile;
				}

				/*
				 * There is no song currently listed in the information.  Hence
				 * time to reset the information.
				 */
				else {

					// We reset to default
					setIconArt();	
				}
			}
		}
		// End of hostUpdated
		
		
		/*
		 * MOUSE LISTENERS
		 */
		
		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
		 */
		public void mouseClicked(MouseEvent evt) {

			/*
			 * This is the only way we can elicit a response from a JLabel
			 * element.  So we implement an action based on a click which
			 * could mimic a button.
			 */
			if ((evt.getSource() instanceof JLabel) 
			 && (evt.getClickCount() == 1)) {
				
				// Safety
				if (Popup == null)
					Popup = new AlbumArtPopup();
				
				// Make Menu Disappear
				if (Popup.isVisible()) {
					
					// Hide
					Popup.setVisible(false);
				}

				// Make Menu Re-Appear
				else {
					
					// Extract location on screen
					Point src = evt.getPoint();
					
					// Display
					Popup.show(getIconArt(), src.x, src.y);
				}
			}
		}
		// End of mouseClicked Method

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

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

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

		
		/*
		 * RUNNABLE
		 */

		/* (non-Javadoc)
		 * @see java.lang.Runnable#run()
		 */
		public void run() {
			
			// Quickly extract song information
			String artist = cFile.get("Artist");
			String album  = cFile.get("Album");
			

			/*
			 * We have two ways of getting information for album art.  The
			 * first is the proper method, in which we extract the actual
			 * cover art.  The second is just an artist cover.
			 */
			
			// Proper Cover
			String link = Deploy.extractAlbumInfo_Art(album,artist);

			// Artist Image Instead
			if ((link == null) || (link.equals("")))
				link = Deploy.extractArtistInfo_Art(artist);
			
			
			// Safety
			if ((link == null) || (link.equals(""))) {
				
				// Log newly created cache
				Logger.Info(CLASS,
					"run",
					Lang.get("Art.Log.Empty")
						.replaceAll("MARTIST", cFile.get("Artist"))
						.replaceAll("MALBUM",  cFile.get("Album"))
				  );
				
				// Set art to a base default
				SwingUtilities.invokeLater(new Runnable(){

					/* (non-Javadoc)
					 * @see java.lang.Runnable#run()
					 */
					public void run() {
						
						// Run in main worker thread
						setIconArt();
					}
				});
				
				// Return 
				return;
			}
			
			
			/*
			 * This part is easy, we create a base filename for
			 * saving the image and pass it on to be saved.  Once
			 * complete it will be passed on.
			 */
			String amalgam = artist + "+" + album;
			
			// Make the filename character safe
			amalgam = amalgam
			          .toLowerCase()
			          .replaceAll("[\\s\\W\\+]+","_");
			
			// Append the extension
			amalgam += Operations.extractFileExtension(link);
			
			
			/*
			 * Connect to the site and extract the image itself.  This will
			 * be transferred to our cache for use.
			 */
			
			// Create Instance and Extract Image
			HTTPClient fetch = new HTTPClient(Logger, Lang);
			
			// Extract Image
			final String filename = 
				fetch.fetchImage(link, new File(Lyra.getCacheByFile(amalgam)));
			
			// Safety
			if (filename == null)
				return;
			
			
			/*
			 * Now that we have the proper filename, we can save
			 * the information to the database.   In this case we
			 * match up all instances of an Artist and Album
			 * combination.
			 */
			Manager.insertCache(artist, album, filename);
			
			// Set art to our cached version
			SwingUtilities.invokeLater(new Runnable(){

				/* (non-Javadoc)
				 * @see java.lang.Runnable#run()
				 */
				public void run() {
					
					// Run in main worker thread
					setIconArt(Lyra.getCacheByFile(filename));
				}
			});
			
			
			// Log newly created cache
			Logger.Info(CLASS,
				"run",
				Lang.get("Art.Log.Create")
					.replaceAll("MFILE", filename)
			  );
		}
		// End of run Method
		
	}
	// End of Interact Class
	
	
	/**
	 * Menu structure that will appear when the user clicks on the Album Cover
	 * Art, providing basic functionality.   This feature is not enabled 
	 * exclusively by a right-mouse click, since not all platforms support
	 * that feature.
	 * <p>
	 * @author Martin Foster
	 */
	private class AlbumArtPopup extends JPopupMenu {

		// Serial Requirements
		private static final long serialVersionUID = 1L;
		
		// Visual Elements
		private JMenuItem itemProfile = null;
		private JMenuItem itemDetails = null;
		private JMenuItem itemClear = null;
		private JMenuItem itemSelect = null;
	
		
		/**
		 * Base constructor for class.
		 */
		public AlbumArtPopup() {
			
			// Initialise components
			initPopup();
		}
		// End of Constructor Method
		
		/**
		 * This method initialises popupArtOptions.  Primary structure	
		 * which establishes all of the menu structure details.
		 */
		private void initPopup() {

			// Information Elements
			this.add(getItemProfile());
			this.add(getItemDetails());
			
			// Separate the two components
			this.addSeparator() ;
			
			// Album Art Controls
			this.add(getItemClear());
			this.add(getItemSelect());
		}
		// End of initPopup Method

		
		/**
		 * This method initialises itemProfile.  Option to display the
		 * artist profile information from the Last.fm Service.	
		 * <p>
		 * @return javax.swing.JMenuItem	
		 */
		private JMenuItem getItemProfile() {
			
			// Check for Existence
			if (itemProfile == null) {
				
				// Create Instance
				itemProfile = new JMenuItem();
				
				// Language Elements
				itemProfile.setText("Artist Profile");
				itemProfile.setToolTipText("Open up the artist profile...");
				
				// Identifier and Action Listener
				itemProfile.setName("ART.PROFILE");
				itemProfile.addActionListener(Interface);
			}
			
			// Return Instance
			return itemProfile;
		}
		// End of getItemProfile Method

		
		/**
		 * This method initialises itemDetails.  Details provides the ability
		 * for the user to extract album information from the Last.fm service.
		 * <p>
		 * @return javax.swing.JMenuItem	
		 */
		private JMenuItem getItemDetails() {
			
			// Check for Existence
			if (itemDetails == null) {
				
				// Create Instance
				itemDetails = new JMenuItem();
				
				// Language Elements
				itemDetails.setText("Album Details");
				itemDetails.setToolTipText("Display the album details...");

				// Identifier and Listener
				itemDetails.setName("ART.DETAILS");
				itemDetails.addActionListener(Interface);
			}
			
			// Check for Existence
			return itemDetails;
		}
		// End of getItemDetails Method
		

		/**
		 * This method initialises itemClear.  Enables a user to clear out the
		 * cached image. 	
		 * <p>
		 * @return javax.swing.JMenuItem	
		 */
		private JMenuItem getItemClear() {
			
			// Check for Existence
			if (itemClear == null) {
				
				// Create Instance
				itemClear = new JMenuItem();
				
				// Language Elements
				itemClear.setText("Clear Album Art");
				itemClear.setToolTipText("Clears the currently displayed album art and reverts to defaults...");

				// Identifier and Action Listener
				itemClear.setName("ART.CLEAR");
				itemClear.addActionListener(Interface);
			}
			
			// Return Instance
			return itemClear;
		}
		// End of getItemClear Method

		
		/**
		 * This method initialises itemSelect.  Enabled a user to select a 
		 * copy of the cover art on the local file system.  	
		 * <p>
		 * @return javax.swing.JMenuItem	
		 */
		private JMenuItem getItemSelect() {
			
			// Check for Existence
			if (itemSelect == null) {
				
				// Create Instance
				itemSelect = new JMenuItem();
				
				// Language Elements
				itemSelect.setText("Select Art");
				itemSelect.setToolTipText("Select Art to Display from File System");

				// Identifier and Listener
				itemSelect.setName("ART.SELECT");
				itemSelect.addActionListener(Interface);
			}
			
			// Return Instance
			return itemSelect;
		}
		// End of getItemSelect Method
	}
	// End of AlbumArtPopup Class
	
	
	/**
	 * Creates a filter element that we can use to restrict the amount of
	 * file choices a user will see.  Since we have limited file support it
	 * was opted to list those we wish to support.
	 * <p>
	 * @author Martin Foster
	 */
	@SuppressWarnings("serial")
	public class ImageFilter extends FileFilter {

		/*
		 * We throw in all of the extensions we care about into this set.  Makes
		 * it easier overall to simply check if its what we want.
		 */
		private Set<String> Extensions = new TreeSet<String>(){
			  {
				add(".tif");
				add(".tiff");
				add(".gif");
				add(".jpeg");
				add(".jpg");
				add(".png");
			  }
		};

	 
	    /* (non-Javadoc)
	     * @see javax.swing.filechooser.FileFilter#accept(java.io.File)
	     */
	    public boolean accept(File f) {
	    	
	    	// Always permit directories in order to navigate
	        if (f.isDirectory())
	            return true;

	        // Extract the File Extension
	        String ext = Operations.extractFileExtension(f.getName());
	        
	        // Is it contained in our set
	        return Extensions.contains(ext);
	    }
	    // End of Accept Method


	    /* (non-Javadoc)
	     * @see javax.swing.filechooser.FileFilter#getDescription()
	     */
	    public String getDescription() {
	    	
	    	// Return Language Element
	        return Lang.get("Art.Filter.Support");
	    }
	    // End of getDescription Method
	}
	// End of ImageFilter Class
}
