/**
 * 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.Box;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.ImageIcon;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.text.NumberFormat;
import java.util.Map;

import org.lyra.Ledger;
import org.lyra.Lyra;
import org.lyra.Interface.Constants;
import org.lyra.LastDotFm.Demarcation;
import org.lyra.Listener.WindowSizeListener;
import org.lyra.Widgets.RapidDialog;
import org.lyra.Widgets.ResizeIcon;


/**
 * Interface to the Last.fm service providing a profile of a specific album
 * that the user has selected to see.   Since there is a potential for a lot
 * of information to be missing elements may be hidden or never put into place.
 * <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
 */
public class ProfileFrame extends JFrame {

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

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

	// Amplification Pane
	private JPanel panelAmplification = null;
	private JLabel iconAlbum = null;

	// Information Pane
	private JScrollPane scrollInformation = null;
	private JPanel panelInformation = null;
	
	// Album Title
	private JPanel panelTitle = null;
	
	// Artist Descriptor
	private JPanel panelArtist = null;
	
	// Release Descriptor
	private JPanel panelRelease = null;
	
	// Song Statistics
	private JPanel panelStatistics = null;
	
	// Tags Display
	private JPanel panelTags = null;
	
	// Similar Display
	private JPanel panelSimilar = null;
	
	// Summary and Full Content
	private JTextArea areaSummary = null;
	private JTextArea areaContent = null;
	
	// Expansion Section
	private JPanel panelExpand = null;
	private JLabel labelExpand = null;
	
	// Window Resizing
	private JPanel panelStatus = null;
	private JLabel iconResize = null;
	
	
	/*
	 * PACKAGE ELEMENTS
	 */
	
	private Ledger Logger;
	
	// Setting maps
	private Map<String,String> Lang;
	private Map<String,String> Global;
	
	/*
	 * CLASS ELEMENTS
	 */
	private Interact Interface = new Interact();
	private Demarcation Deploy;	
	
	// Default Colour for Hyper links
	private Color Hyperlink = Color.BLUE;
	
	// Map containing query details
	private Map<String,Object> Details;
	
	// Information Collection
	private String myAlbum;
	private String myArtist;
	
	/*
	 * CONSTANTS
	 */
	private static final int ROW_LIMIT = 350;
	

	/**
	 * This is the default constructor for the Album profile.  Due to the 
	 * dynamic nature of the information, there is no Visual Editor 
	 * constructor available.
	 * <p>
	 * @param logger Ledger instance for logging and status.
	 * @param lang Language map for language independence.
	 * @param global Global settings map.
	 * @param artist Artist name.
	 * @param album Artist name.
	 */
	public ProfileFrame(
			Ledger logger, 
			Map<String,String> lang, 
			Map<String,String> global,
			String artist,
			String album) {
		
		// Initialise Super Class
		super();
		
		// Pass Up
		this.Logger   = logger;
		this.Lang     = lang;
		this.Global   = global;
		this.myArtist = artist;
		this.myAlbum  = album;
		
		
		/*
		 * Unlike most user interface elements in Lyra this one requires
		 * external knowledge prior to actually beginning.  So we extract the
		 * details hash.  
		 */
		
		// Create Instance
		this.Deploy = Demarcation.getInstance(Logger, Lang, Global);
		
		// Extract Details
		this.Details = Deploy.extractAlbumInfo(
			myAlbum, 
			myArtist, 
			Global.get("Program.Language")
		  );
			
		// Establish Baseline Look and Feel
		initFrame();
	}
	// End of Constructor Class

	
	/**
	 * This is the default constructor for artist profiles.  Due to the dynamic
	 * nature of the information, there is no Visual Editor constructor 
	 * available.
	 * <p>
	 * @param logger Ledger instance for logging and status.
	 * @param lang Language map for language independence.
	 * @param global Global settings map.
	 * @param artist Artist name.
	 */
	public ProfileFrame(
			Ledger logger, 
			Map<String,String> lang, 
			Map<String,String> global,
			String artist) {
		
		// Initialise Super Class
		super();
		
		// Pass Up
		this.Logger   = logger;
		this.Lang     = lang;
		this.Global   = global;
		this.myArtist = artist;
		
		
		/*
		 * Unlike most user interface elements in Lyra this one requires
		 * external knowledge prior to actually beginning.  So we extract the
		 * details hash.  
		 */
		
		// Create Instance
		this.Deploy = Demarcation.getInstance(Logger, Lang, Global);
		
		// Extract Details
		this.Details = Deploy.extractArtistInfo(
			myArtist, 
			Global.get("Program.Language")
		  );
			
		// Establish Baseline Look and Feel
		initFrame();
	}
	// End of Constructor Class
	
	
	/**
	 * This method initialises this.  Establishes the baseline features of
	 * look and feel for the frame itself.
	 */
	private void initFrame() {

		/*
		 * We have created pseudo hyper link elements that are used to perform
		 * tasks.  In order to make sure the colours always work, we make use
		 * of the icon colour sets.
		 */
		
		// Orange
		if (Global.get("Setting.Interface.Icons").equals("ORANGE"))
			Hyperlink = Color.ORANGE;
		
		// Green
		else if (Global.get("Setting.Interface.Icons").equals("GREEN"))
			Hyperlink = Color.GREEN;
				
		
		/*
		 * Time to determine which title we are going to display on the window
		 * since we know that an absence of Album information is key to an 
		 * Artist profile we work based on that.
		 */
		
		// Album Profile
		if (myAlbum != null) {
			this.setTitle(
				Lang.get("Art.Profile.Album.Window")
					.replaceAll("MALBUM", myAlbum)
					.replaceAll("MARTIST", myArtist)
			  );
		}

		// Artist Profile
		else {
			this.setTitle(
				Lang.get("Art.Profile.Artist.Window")
					.replaceAll("MARTIST", myArtist)
			  );
		}

		/*
		 * Additional user interface elements.  These are not dynamic like
		 * the elements above.
		 */
		
		// Adjust Icon
		this.setIconImage(new ImageIcon(
				getClass().getResource(
						Lyra.IMG_SOURCE + Lyra.LYRA)).getImage());
		
		// Make sure we dispose when closed
		this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		
		// Add in User Interface Elements
		this.setContentPane(getJContentPane());
		
		// Pack Contents
		this.pack();
		
		// Now we can adjust the position
		this.setLocationRelativeTo(null);
	}
	// End of initFrame Method

	
	/**
	 * This method initialises jContentPane.  Primary container which contains
	 * all of the other user interface elements.
	 * <p>
	 * @return javax.swing.JPanel
	 */
	private JPanel getJContentPane() {
		
		// Check for Existence
		if (jContentPane == null) {
			
			// Create Instance
			jContentPane = new JPanel();
			
			// Establish Layout
			jContentPane.setLayout(new BorderLayout());

			// Insert Elements
			jContentPane.add(getPanelContainer(), BorderLayout.CENTER);
			jContentPane.add(getPanelStatus(), BorderLayout.SOUTH);
		}
		
		// Return Instance
		return jContentPane;
	}
	// End of getJContentPane Method

	
	/**
	 * This method initialises panelAmplification.  Contains user interface
	 * elements that are not necessary for display but provide additional 
	 * amplification.  Such as the album picture and purchase information.
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelAmplification() {
		
		// Check for Existence
		if (panelAmplification == null) {
			
			// Create Instance
			panelAmplification = new JPanel();
			
			// Establish Layout
			panelAmplification.setLayout(new BoxLayout(getPanelAmplification(), BoxLayout.Y_AXIS));
			
			
			/*
			 * Album Art is fairly straight forward.  We pass on the URL and it
			 * will in turn ensure that the URL is either correct for not
			 * display the element.
			 */
			try {
				
				// Insert Album Art
				panelAmplification.add(
					getIconAlbum(
						new URL(Details.get("Image").toString())), null);
			} 
			
			// We ignore this aspect
			catch (MalformedURLException e) {}
			catch (NullPointerException e) {}
			
			
			
			/*
			 * Purchase Items are unknown until we get results from the service.
			 * So there may be multiple sites to purchase from or a singular
			 * one.
			 */
			if ((Details.containsKey("Purchase"))
			 && (Details.get("Purchase") instanceof Map<?,?>)) {

				// Extract Instance
				Map<?,?> purchase = (Map<?,?>)Details.get("Purchase");
				
				// Loop through keys
				for (Object key: purchase.keySet()) {
					
					// If this fails we simply will not display
					try {
						
						// Insert Purchase Items
						panelAmplification.add(
							getPanelPurchase(
								key.toString(), 
								new URL(purchase.get(key).toString())));
					} 
					
					// We handle by not displaying
					catch (MalformedURLException e) {}
					catch (NullPointerException e) {}
				}
			}
		}
		return panelAmplification;
	}
	// End of getPanelAmplification Method
	
	
	/**
	 * Returns the Album Art for a specific album.  Passed a specific URL
	 * which will cause a request to the Internet.
	 * <p>
	 * @param link URL to the image itself.
	 * @return javax.swing.JLabel
	 */
	private JLabel getIconAlbum(URL link) {
		
		// Check for Existence
		if (iconAlbum == null) {
			
			// Create new Instance
			iconAlbum = new JLabel();

			// Establish Border
			iconAlbum.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));

			// Set Icon
			iconAlbum.setIcon(new ImageIcon(link));
		}
		
		// Return Instance
		return iconAlbum;
	}
	// End of getIconAlbum Method
	
	
	/**
	 * Contains the information pane allowing it to expand without having to
	 * resize the window to view all of the contents.
	 * <p>
	 * @return javax.swing.JScrollPane
	 */
	private JScrollPane getScrollInformation() {

		// Check for Existence
		if (scrollInformation == null) {
			
			// Create Instance
			scrollInformation = new JScrollPane();
			
			// Scroll Bar Policy
			scrollInformation.setHorizontalScrollBarPolicy(
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
			scrollInformation.setVerticalScrollBarPolicy(
				JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 
			
			// Insert the scrolling element
			scrollInformation.setViewportView(getPanelInformation());
			
			/*
			 * This operation should guarantee that the scroll bar is always at 
			 * the top and forces the user to scroll down for additional 
			 * content.
			 */
			scrollInformation
				.getViewport()
				.setViewPosition(
					new Point(0,0)
			  );
		}
		
		// Return Method
		return scrollInformation;
	}
	// End of getScrollInformation Method
	
	
	/**
	 * This method initialises panelInformation.  The information panel
	 * contains the bulk of the profile information.  Most of these are
	 * subsequently in their own panels in order to enforce justification.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelInformation() {
		
		// Check for Existence
		if (panelInformation == null) {
			
			// Create Instance
			panelInformation = new JPanel();
			
			// Establish Layout and Border
			panelInformation.setLayout(new BoxLayout(getPanelInformation(), BoxLayout.Y_AXIS));
			panelInformation.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));

			// Insert Elements
			panelInformation.add(getPanelTitle(), null);
			
			// Artist profile specific elements
			if (myAlbum != null) {
				
				// Associated Artist Information
				panelInformation.add(getPanelArtist(), null);
			
				// Spacer and Statistics
				panelInformation.add(Box.createVerticalStrut(Constants.getNormalGap()));
				panelInformation.add(getPanelRelease(), null);
			}
			
			// Spacer and Statistics
			panelInformation.add(Box.createVerticalStrut(Constants.getNormalGap()));
			panelInformation.add(getPanelStatistics(), null);
			
			// Spacer and Tags
			panelInformation.add(Box.createVerticalStrut(Constants.getNormalGap()));
			panelInformation.add(getPanelTags(), null);				

			// Spacer and Similar
			if (myAlbum == null) {
				panelInformation.add(Box.createVerticalStrut(Constants.getNormalGap()));
				panelInformation.add(getPanelSimilar(), null);				
			}
			
			// Spacer and Contents
			panelInformation.add(Box.createVerticalStrut(Constants.getNormalGap()));
			panelInformation.add(getAreaSummary(), null);
			panelInformation.add(getAreaContent(), null);
			panelInformation.add(getPanelExpand(), null);
		}
		
		// Return Instance
		return panelInformation;
	}
	// End of getPanelInformation Method

	
	/**
	 * This method initialises panelPurchase.  These elements are generated
	 * dynamically.  So there is no particular need to tie up resources up
	 * at the class level for their instances.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelPurchase(String name, URL link) {
		
		/*
		 * The label is designed to look like a hyper link, so we make use of
		 * HTML in order to create an underlined entry.
		 */
		JLabel labelPurchase = new JLabel();
		labelPurchase.setText(
				Lang.get("Art.Profile.Hyperlink")
				    .replaceAll("MLINK", name)
			);
		
		// Emulate a Hyper Link
		labelPurchase.setForeground(Hyperlink);
		labelPurchase.setCursor(new Cursor(Cursor.HAND_CURSOR));

		// Force Sizing
		labelPurchase.setMaximumSize(
			labelPurchase.getPreferredSize());
		
		// Identifier and Action
		labelPurchase.setName(link.toString());
		labelPurchase.addMouseListener(Interface);
		
		
		/*
		 * The panel itself contains all of the necessary elements in order to
		 * display correctly and permit the user to click on the interface.
		 */

		// Create Instance
		JPanel panelPurchase = new JPanel();
		
		// Establish Layout
		panelPurchase.setLayout(new BoxLayout(panelPurchase, BoxLayout.X_AXIS));

		// Insert Elements with Spacers
		panelPurchase.add(Box.createHorizontalGlue());
		panelPurchase.add(labelPurchase, null);
		panelPurchase.add(Box.createHorizontalGlue());

		// Return Instance
		return panelPurchase;
	}
	// End of getPanelPurchase Method

	
	/**
	 * This method initialises panelTitle.  Displays the album title.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelTitle() {
		
		// Check for Existence
		if (panelTitle == null) {

			// We need an instance regardless
			panelTitle = new JPanel();

			
			/*
			 * First we create the label instance and use information extracted
			 * in order to properly display.
			 */
			try {
				
				// Create Label and Font
				JLabel labelTitle = new JLabel();
				labelTitle.setFont(new Font("Dialog", Font.BOLD, 18));

				// Attempt to establish text
				labelTitle.setText(Details.get("Title").toString());
				
				// Disguise as Hyper Link
				labelTitle.setForeground(Hyperlink);
				labelTitle.setCursor(new Cursor(Cursor.HAND_CURSOR));
				
				// Identifier and Action Listener
				labelTitle.setName(Details.get("Address").toString());
				labelTitle.addMouseListener(Interface);
				
				
				// Establish Layout
				panelTitle.setLayout(new BoxLayout(getPanelTitle(), BoxLayout.X_AXIS));

				// Add in Element and Spacer
				panelTitle.add(labelTitle, null);
				panelTitle.add(Box.createHorizontalGlue(), null);
			}
	
			// We will not display the element if the above failed
			catch (NullPointerException e) {
				
				// Hide the element and forget
				panelTitle.setVisible(false);
			};
		}
		
		// Return Instance
		return panelTitle;
	}
	// End of getPanelTitle Method

	
	/**
	 * This method initialises panelArtist.  Artist is only slightly more
	 * complex then Album as it has a prefix element that is required.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelArtist() {
		
		// Check for Existence 
		if (panelArtist == null) {
			
			// We return an instance regardless
			panelArtist = new JPanel();

			// We hide when we fail
			try {
				
				/*
				 * Extract the string content and run a quick check in order to
				 * confirm that there is indeed content.
				 */
				String artist = Details.get("Artist").toString();
				
				// Check and hide
				if (artist.trim().isEmpty())
					panelArtist.setVisible(false);
				
				
				/*
				 * The Label is internal to this application only and contains
				 * two elements.  The actual name of the artist and a prefix.
				 */
				
				// Create Prefix Label
				JLabel labelArtist = new JLabel();
				
				// Make the font bold
				labelArtist.setFont(
					labelArtist.getFont()
					           .deriveFont(Font.BOLD));
				
				labelArtist.setText(
					Lang.get("Art.Profile.Album.Artist")
					    .replaceAll("MARTIST", artist));

				// Establish Layout
				panelArtist.setLayout(new BoxLayout(getPanelArtist(), BoxLayout.X_AXIS));

				// Instances and Spacer
				panelArtist.add(labelArtist, null);
				panelArtist.add(Box.createHorizontalGlue());
			}

			/*
			 * If the above fails, we simply opt to hide the instance and
			 * return that.   The display will automatically wrap itself
			 * around missing elements.
			 */
			catch (NullPointerException e) {
				
				// Hide Ourselves
				panelArtist.setVisible(false);
			}
		}
		
		// Return Instance
		return panelArtist;
	}
	// End of getPanelArtist Method

	
	/**
	 * This method initialises panelRelease.  Release is only slightly more
	 * complex then Album as it has a prefix element that is required.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelRelease() {
		
		// Check for Existence 
		if (panelRelease == null) {
			
			// We return an instance regardless
			panelRelease = new JPanel();

			// We hide when we fail
			try {
				
				/*
				 * Extract the string content and run a quick check in order to
				 * confirm that there is indeed content.
				 */
				String release = Details.get("Release").toString();
				
				// Check and hide
				if (release.trim().isEmpty())
					panelRelease.setVisible(false);
				
				/*
				 * The Label is internal to this application only and contains
				 * two elements.  The actual name of the Release and a prefix.
				 */
				
				// Create Prefix Label
				JLabel labelRelease = new JLabel();
				
				// Set the Text
				labelRelease.setText(
					Lang.get("Art.Profile.Album.Release")
					    .replaceAll("MRELEASE", release));

				// Establish Layout
				panelRelease.setLayout(new BoxLayout(getPanelRelease(), BoxLayout.X_AXIS));

				// Instances and Spacer
				panelRelease.add(labelRelease, null);
				panelRelease.add(Box.createHorizontalGlue());
			}

			/*
			 * If the above fails, we simply opt to hide the instance and
			 * return that.   The display will automatically wrap itself
			 * around missing elements.
			 */
			catch (NullPointerException e) {
				
				// Hide Ourselves
				panelRelease.setVisible(false);
			}
		}
		
		// Return Instance
		return panelRelease;
	}
	// End of getPanelRelease Method
	
	
	/**
	 * This method initialises panelStatistics.  Basic information on the hits
	 * on an album, in this case the amount of plays and listeners.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelStatistics() {
		
		// Check for Existence
		if (panelStatistics == null) {
			
			// We always return an Instance
			panelStatistics = new JPanel();
			
			try {
			
				/*
				 * The label is internal to this panel directly.   It is also the
				 * most likely to fail in its operations due to the fact that
				 * it is dealing with external data.
				 */

				Long lplays  = Long.decode(Details.get("Plays").toString());
				Long llisten = Long.decode(Details.get("Listeners").toString());
				
				// Format Numbers
				NumberFormat nf = NumberFormat.getNumberInstance();
				String plays    = nf.format(lplays);
				String listen   = nf.format(llisten);
				
				// Create Label
				JLabel labelStatistics = new JLabel();
				labelStatistics.setText(
					Lang.get("Art.Profile.Album.Statistics")
						.replaceAll("MPLAYS", plays)
						.replaceAll("MLISTENERS", listen)
				  );

				
				// Establish Layout
				panelStatistics.setLayout(new BoxLayout(getPanelStatistics(), BoxLayout.X_AXIS));

				// Insert Label and Spacer
				panelStatistics.add(labelStatistics, null);
				panelStatistics.add(Box.createHorizontalGlue());
			}
			
			/*
			 * If the above fails, then we simply hide the element in order to
			 * make sure that empty information is not displayed.
			 */
			catch (NullPointerException e) {
			
				// Hide and Forget
				panelStatistics.setVisible(false);
			}
		}
		
		// Return Instance
		return panelStatistics;
	}
	// End of getPanelStatistics Method


	/**
	 * This method initialises panelTags.  Tags provide an additional criteria
	 * with which a user can use to find similarities. 	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelTags() {
		
		// Check for Existence
		if (panelTags == null) {
			
			// Establish JLabel Elements
			JLabel labelPrefix = new JLabel();
			labelPrefix.setFont(
				labelPrefix.getFont()
					       .deriveFont(Font.BOLD));
			labelPrefix.setText(Lang.get("Art.Profile.Album.Tags"));

			// Extract proper row_limit
			int limit = ROW_LIMIT 
			          - labelPrefix.getFontMetrics(labelPrefix.getFont())
			                       .stringWidth(Lang.get("Art.Profile.Album.Tags"));

			
			/*
			 * Contains the prefix and a spacer allowing it to appear justified
			 * at the top in case we get multiple rows.
			 */
			
			// Create Instance
			JPanel panelPrefix = new JPanel();
			
			// Establish Layout
			panelPrefix.setLayout(new BoxLayout(panelPrefix, BoxLayout.Y_AXIS));
			
			// Insert Elements
			panelPrefix.add(labelPrefix, null);
			panelPrefix.add(Box.createVerticalGlue());

			
			/*
			 * Contains all of the stored tags.  At least the rows that contains
			 * the elements themselves.
			 */
			
			// Create Instance
			JPanel panelContents = new JPanel();
			
			// Establish Layout
			panelContents.setLayout(new BoxLayout(panelContents, BoxLayout.Y_AXIS));
			
			
			/*
			 * Main container for all the elements.  This will create a
			 * look that is justified.
			 */
			
			// Create Instance
			panelTags = new JPanel();
			
			// Establish Layout Flow will permit wrapping
			panelTags.setLayout(new BoxLayout(getPanelTags(), BoxLayout.X_AXIS));

			// Insert Containers
			panelTags.add(panelPrefix);
			panelTags.add(Box.createHorizontalStrut(Constants.getSmallGap()));
			panelTags.add(panelContents);
			
			
			// Keep track of elements
			int elements = 0;
			int width    = 0;
			
			// Font Metrics
			FontMetrics fm = null;
			
			/*
			 * We have no idea how many tags may actually be available for
			 * display.  So we generate each item independently.
			 */
			if ((Details.containsKey("Tags"))
			 && (Details.get("Tags") instanceof Map<?,?>)) {
			
				// Extract Instance
				Map<?,?> tags = (Map<?,?>)Details.get("Tags");

				
				// Create Instance
				JPanel panelRow = new JPanel();
				
				// Establish Layout Flow will permit wrapping
				panelRow.setLayout(new BoxLayout(panelRow, BoxLayout.X_AXIS));
				
				
				// Loop through and add in tags
				for (Object key: tags.keySet()) {
					
					// We have no guarantee the information is valid
					try {						
												
						// Insert a new tag
						JLabel tag = getLabelTags(
							key.toString(),
							new URL(tags.get(key).toString()));

						// Extract Font
						if (fm == null)
							fm = tag.getFontMetrics(tag.getFont());
						
						// Capture Size
						int size = fm.stringWidth(key.toString());
						
						/*
						 * Since we need to create the concept of rows we will
						 * track width and reset as necessary.
						 */
						if ((width + size) > limit) {
							
							// Final append
							panelRow.add(Box.createHorizontalGlue());
							
							// Add in the elements
							panelContents.add(panelRow, null);
							
							
							// Create new Instance
							panelRow = new JPanel();
								
							// Establish Layout Flow will permit wrapping
							panelRow.setLayout(new BoxLayout(panelRow, BoxLayout.X_AXIS));

							// Reset width
							width = 0;
						}

						// Insert spacer if required
						if (width > 0)
							panelRow.add(new JLabel(Lang.get("Art.Profile.Separator")), null);

						// Insert element
						panelRow.add(tag, null);
						
						// Account for width
						width += size;
						
						// Increment
						elements++;
					} 
					
					// We simply will not add the elements
					catch (MalformedURLException e) {}	
					catch (NullPointerException e) {}
				}
				
				// Append Panel
				if (width > 0) {
					
					// Final append
					panelRow.add(Box.createHorizontalGlue());
					
					// Append Item
					panelContents.add(panelRow, null);
				}
				
				// Should always be needed
				panelContents.add(Box.createVerticalGlue(), null);
			}
				
			// Force to the left
			if (elements == 0)
				panelTags.setVisible(false);
		}
		
		// Return Instance
		return panelTags;
	}
	// End of getPanelTags Method

	
	/**
	 * This method initialises panelSimilar.  Similar provide an additional criteria
	 * with which a user can use to find common interests.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelSimilar() {
		
		// Check for Existence
		if (panelSimilar == null) {
			
			// Establish JLabel Elements
			JLabel labelPrefix = new JLabel();
			labelPrefix.setFont(
				labelPrefix.getFont()
					       .deriveFont(Font.BOLD));
			labelPrefix.setText(Lang.get("Art.Profile.Artist.Similar"));

			// Extract proper row_limit
			int limit = ROW_LIMIT 
			              - labelPrefix.getFontMetrics(labelPrefix.getFont())
			                           .stringWidth(Lang.get("Art.Profile.Artist.Similar"));
			
			
			/*
			 * Contains the prefix and a spacer allowing it to appear justified
			 * at the top in case we get multiple rows.
			 */
			
			// Create Instance
			JPanel panelPrefix = new JPanel();
			
			// Establish Layout
			panelPrefix.setLayout(new BoxLayout(panelPrefix, BoxLayout.Y_AXIS));
			
			// Insert Elements
			panelPrefix.add(labelPrefix, null);
			panelPrefix.add(Box.createVerticalGlue());

			
			/*
			 * Contains all of the stored Similar.  At least the rows that contains
			 * the elements themselves.
			 */
			
			// Create Instance
			JPanel panelContents = new JPanel();
			
			// Establish Layout
			panelContents.setLayout(new BoxLayout(panelContents, BoxLayout.Y_AXIS));
			
			
			/*
			 * Main container for all the elements.  This will create a
			 * look that is justified.
			 */
			
			// Create Instance
			panelSimilar = new JPanel();
			
			// Establish Layout Flow will permit wrapping
			panelSimilar.setLayout(new BoxLayout(getPanelSimilar(), BoxLayout.X_AXIS));

			// Insert Containers
			panelSimilar.add(panelPrefix);
			panelSimilar.add(Box.createHorizontalStrut(Constants.getSmallGap()));
			panelSimilar.add(panelContents);
			
			
			// Keep track of elements
			int elements = 0;
			int width    = 0;
			
			// Font Metrics
			FontMetrics fm = null;
			
			/*
			 * We have no idea how many Similar may actually be available for
			 * display.  So we generate each item independently.
			 */
			if ((Details.containsKey("Similar"))
			 && (Details.get("Similar") instanceof Map<?,?>)) {
			
				// Extract Instance
				Map<?,?> Similar = (Map<?,?>)Details.get("Similar");

				
				// Create Instance
				JPanel panelRow = new JPanel();
				
				// Establish Layout Flow will permit wrapping
				panelRow.setLayout(new BoxLayout(panelRow, BoxLayout.X_AXIS));
				
				
				// Loop through and add in Similar
				for (Object key: Similar.keySet()) {
					
					// We have no guarantee the information is valid
					try {						
												
						// Insert a new tag
						JLabel tag = getLabelTags(
							key.toString(),
							new URL(Similar.get(key).toString()));

						// Extract Font
						if (fm == null)
							fm = tag.getFontMetrics(tag.getFont());
						
						// Capture Size
						int size = fm.stringWidth(key.toString());
						
						/*
						 * Since we need to create the concept of rows we will
						 * track width and reset as necessary.
						 */
						if ((width + size) > limit) {
							
							// Final append
							panelRow.add(Box.createHorizontalGlue());
							
							// Add in the elements
							panelContents.add(panelRow, null);
							
							
							// Create new Instance
							panelRow = new JPanel();
								
							// Establish Layout Flow will permit wrapping
							panelRow.setLayout(new BoxLayout(panelRow, BoxLayout.X_AXIS));

							// Reset width
							width = 0;
						}

						// Insert spacer if required
						if (width > 0)
							panelRow.add(new JLabel(Lang.get("Art.Profile.Separator")), null);

						// Insert element
						panelRow.add(tag, null);
						
						// Account for width
						width += size;
						
						// Increment
						elements++;
					} 
					
					// We simply will not add the elements
					catch (MalformedURLException e) {}	
					catch (NullPointerException e) {}
				}
				
				// Append Panel
				if (width > 0) {
					
					// Final append
					panelRow.add(Box.createHorizontalGlue());
					
					// Append Item
					panelContents.add(panelRow, null);
				}
				
				// Should always be needed
				panelContents.add(Box.createVerticalGlue(), null);
			}
				
			// Force to the left
			if (elements == 0)
				panelSimilar.setVisible(false);
		}
		
		// Return Instance
		return panelSimilar;
	}
	// End of getPanelSimilar Method
	
	
	/**
	 * Creates a tag that looks like a hyper link and can be clicked in order
	 * to bring up a browser to the Last.fm service information.
	 * <p>
	 * @param name Name of the tag.
	 * @param link URL instance to the site.
	 * @return javax.swing.JLabel; 
	 */
	private JLabel getLabelTags(String name, URL link) {
		
		// Create Instance
		JLabel labelTag = new JLabel();
		
		// Establish Text
		labelTag.setText(
			Lang.get("Art.Profile.Hyperlink")
			    .replaceAll("MLINK", name)
		  );
		
		// Disguise as Hyper Link
		labelTag.setForeground(Hyperlink);
		labelTag.setCursor(new Cursor(Cursor.HAND_CURSOR));
		
		// Adjust Size
		labelTag.setMaximumSize(
			labelTag.getPreferredSize());
		
		// Identifier and Action Listener
		labelTag.setName(link.toString());
		labelTag.addMouseListener(Interface);
		
		// Return Instance
		return labelTag;
	}
	// End of getLabelTags Method
	
	
	/**
	 * This method initialises areaSummary.  Contains a summary of the wiki entry
	 * allowing the user to expand if they so choose.	
	 * <p>
	 * @return javax.swing.JTextArea	
	 */
	private JTextArea getAreaSummary() {
		
		// Check for Existence
		if (areaSummary == null) {
			
			// Create Instance
			areaSummary = new JTextArea();
			
			/*
			 * We have no idea if the content actually exists, so we will
			 * try to go through the motions.  If they fail, we simply hide the
			 * elements.
			 */
			try {
				
				// Extract String
				String contents = Details.get("Summary").toString();

				// Make this to look like a Label
				areaSummary.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
				areaSummary.setMargin(new Insets(2, 2, 2, 2));
				areaSummary.setBackground(new Color(0,0,0,0));
				areaSummary.setOpaque(false);
				
				// Cannot be modified
				areaSummary.setEditable(false);
				
				// Wrapping Methods
				areaSummary.setWrapStyleWord(true);
				areaSummary.setLineWrap(true);

				// Establish Text
				areaSummary.setText(contents);
				
				// Safety Hide
				if (contents.trim().isEmpty())
					areaSummary.setVisible(false);
			}
			
			// Failed, so we hide
			catch (NullPointerException e) {
			
				// Hide Element
				areaSummary.setVisible(false);
			}
		}
		
		// Return Instance
		return areaSummary;
	}
	// End of getAreaSummary Method


	/**
	 * This method initialises areaContent.  Contains the full content of the
	 * wiki on the album.
	 * <p>
	 * @return javax.swing.JTextArea	
	 */
	private JTextArea getAreaContent() {
		
		// Check for Existence
		if (areaContent == null) {
			
			// Create Instance
			areaContent = new JTextArea();
			
			/*
			 * We have no idea if the content actually exists, so we will
			 * try to go through the motions.  If they fail, we simply hide the
			 * elements.
			 */
			try {

				// Make this to look like a Label
				areaContent.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
				areaContent.setMargin(new Insets(2, 2, 2, 2));
				areaContent.setBackground(new Color(0,0,0,0));
				areaContent.setOpaque(false);
				
				// Cannot be modified
				areaContent.setEditable(false);
				
				// Wrapping Methods
				areaContent.setWrapStyleWord(true);
				areaContent.setLineWrap(true);

				// Establish Text
				areaContent.setText(Details.get("Content").toString());

				// Always Hide on Startup
				areaContent.setVisible(false);
			}
			
			// Failed, so we hide
			catch (NullPointerException e) {
			
				// Hide Element
				areaContent.setVisible(false);
			}
		}
		
		// Return Instance
		return areaContent;
	}
	// End of getAreaContent Method

	
	/**
	 * This method initialises panelExpand.  Provides an expansion capability
	 * to bring forth the full text.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelExpand() {
		
		// Check for Existence
		if (panelExpand == null) {
			
			// Create Instance
			panelExpand = new JPanel();
			
			// Establish Layout
			panelExpand.setLayout(new BoxLayout(getPanelExpand(), BoxLayout.X_AXIS));
	
			// Spacers and Label
			panelExpand.add(Box.createHorizontalGlue(), null);
			panelExpand.add(getLabelExpand(), null);
			panelExpand.add(Box.createHorizontalStrut(Constants.getIndentGap()), null);
		}
		
		// Return Instance
		return panelExpand;
	}
	// End of getPanelExpand Method

	
	/**
	 * This method initialises labelExpand.  Permits the user to expand the
	 * summary element.
	 * <p>
	 * @return javax.swing.JLabel
	 */
	private JLabel getLabelExpand() {
		
		// Check for Existence
		if (labelExpand == null) {
			
			// Create Instance
			labelExpand = new JLabel();

			// Emulate a Hyper Link
			labelExpand.setForeground(Hyperlink);
			labelExpand.setCursor(new Cursor(Cursor.HAND_CURSOR));

			// Establish Text
			labelExpand.setText(
				Lang.get("Art.Profile.Hyperlink")
				    .replaceAll("MLINK", Lang.get("Art.Profile.Expand")));
			
			// Visibility Based on Requirement
			labelExpand.setVisible(
				getAreaSummary().isVisible());
			
			// Action Listener
			labelExpand.addMouseListener(Interface);
		}
		
		// Return Instance
		return labelExpand;
	}
	// End of getLabelExpand Method
	
	
	
	/**
	 * This method initialises panelContainer.  Contains the primary interface
	 * in order to have the necessary layout.  The window resize elements fall
	 * outside of this particular layout.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelContainer() {
		
		// Check for Existence
		if (panelContainer == null) {
			
			// Create Instance
			panelContainer = new JPanel();
			
			// Establish Layout
			panelContainer.setLayout(new BoxLayout(getPanelContainer(), BoxLayout.X_AXIS));

			// Insert Elements and spacer
			panelContainer.add(getPanelAmplification(), null);
			panelContainer.add(Box.createHorizontalStrut(Constants.getNormalGap()));
			panelContainer.add(getScrollInformation(), null);
		}
		
		// Return Instance
		return panelContainer;
	}
	// End of getPanelContainer Method

	
	/**
	 * This method initialises panelWindow.  Contains nothing more then the
	 * resize element for creating a consistent look and feel.	
	 * <p>
	 * @return javax.swing.JPanel	
	 */
	private JPanel getPanelStatus() {
		
		// Check for Existence
		if (panelStatus == null) {
			
			// Create Instance
			panelStatus = new JPanel();
			panelStatus.setLayout(new BoxLayout(getPanelStatus(), BoxLayout.X_AXIS));

			// Add in resizing label/icon
			panelStatus.add(Box.createHorizontalGlue());
			panelStatus.add(getIconResize(), null);
		}
		
		// Return Instance
		return panelStatus;
	}
	// End of getPanelStatus Method
	
	
	/**
	 * This method initialises iconResize.  
	 * <p>
	 * @return javax.swing.JLabel
	 */
	private JLabel getIconResize() {
		
		// Verify existence of element
		if (iconResize == null) {

			// Create and establish baseline
			iconResize = new JLabel(new ResizeIcon());
			iconResize.setOpaque(false);
			
			/*
			 * Making this icon do something of use is one of the core reasons
			 * why it was added to the display itself.  Here we create an 
			 * instance and add the listener, since it is not used anywhere else
			 * we don't worry out creating an instance earlier.
			 */
			WindowSizeListener sizer = new WindowSizeListener(this);
			
			// Add in listener elements
			iconResize.addMouseListener(sizer);
			iconResize.addMouseMotionListener(sizer);
		}
		
		// Return label
		return iconResize;
	}
	// End of getIconResize 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 MouseListener {

		/**
		 * There are two elements that require a browser to load up in order
		 * to proceed.  Since the require a fair amount of similar code we
		 * throw it in here.
		 * <p>
		 * @param link Address to send to browser.
		 */
		private void loadBrowser(String link) {
			
			// Launch a browser
			try {
				
				// Send off link
				Desktop.getDesktop().browse(
					URI.create(link));
			} 

			// Something failed above
			catch (IOException e) {
				
				// Create Instance
				RapidDialog rapid = new RapidDialog(Lang);
				
				// Notify User
				rapid.noticeDialog(
					null,
					Lang.get("Options.Log.Browser.Fail")
					    .replaceAll("MADDRESS", link));
			}
		}
		// End of loadBrowser Method
		
		
		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
		 */
		public void mouseClicked(MouseEvent evt) {
			
			// We only deal with JLabels
			if (evt.getSource() instanceof JLabel) {
			
				// Extract Source
				JLabel source = (JLabel)evt.getSource();
				
				
				/*
				 * Normally we assign meaningful values to the getName/SetName
				 * methods in order to readily identify the elements.  However
				 * in this case most are dynamic, so we just put in the URL
				 * for use.
				 */
				if (source.getName() != null) {
				
					// Just pass on the value
					loadBrowser(source.getName());
				}
					
				/*
				 * There is only one instance that does not have an assigned
				 * value.  This one has to do with expansion, so we just
				 * go through the motions.
				 */
				else {
					
					// Hide Summary
					getAreaSummary().setVisible(false);
					getPanelExpand().setVisible(false);
					
					// Show Full Entry
					getAreaContent().setVisible(true);
				}
			}
		}
		// End of MouseEvent Method

		/* (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 Interface Class
	
}
