/**
 * 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;

import java.awt.Window;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;

import org.lyra.Block.InfiniteProgressImpl;
import org.lyra.Database.Audit;
import org.lyra.Database.Comptroller;
import org.lyra.Database.Interface;
import org.lyra.Database.Library;
import org.lyra.Host.Options;
import org.lyra.Host.Pendulum;
import org.lyra.Host.Playback;
import org.lyra.Host.Playlist;
import org.lyra.Host.Status;
import org.lyra.Host.Terminus;
import org.lyra.Host.Updater;
import org.lyra.Interface.CloseApplicationDialog;
import org.lyra.Interface.Constants;
import org.lyra.Interface.HostCreateDialog;
import org.lyra.Interface.PlayerOrganiserFrame;
import org.lyra.LastDotFm.Snooper;
import org.lyra.Widgets.InfiniteProgressPanel;
import org.lyra.Widgets.RapidDialog;

/**
 * This is the primary class used to create the original instance of the 
 * application.  Certain constants are located here to provide a central
 * location.
 * <p>
 * @author Martin Foster
 * @version 1.0.1, %Revision, 99%, %LastChangedDate, 17/03/11 8:17PM%
 */
public class Lyra {

	/*
	 * CONFIGURATION HASHES
	 */
	
	/**
	 * Map containing the global settings for the environment.  Normally 
	 * generated from user preferences.
	 */
	public static Map<String, String> Global = new HashMap<String, String>();
	
	/**
	 * Language Map used to contain the language specific elements for display
	 * on the application. 
	 */
	public Map<String, String> Language = new HashMap<String, String>();
	
	
	/*
	 * DIRECTORY STRUCTURES
	 */
	
	
	/**
	 * Provides a safety mechanism which prevents the removal of the the
	 * currently active host. 
	 */
	public static String activeHost;
	
	/**
	 * Directory which leads to the program root data files.   Elements related
	 * to the database, languages and images located under this root.
	 */
	public static File rootProgram;
	
	/**
	 * The user directory will normally point to the <i>.lyra</i> directory
	 * located in the users HOME directory.
	 */
	public static File rootUser;
	
	/**
	 * The location of icons for retrieval based on the selection the user
	 * has made.
	 */
	public static String pathIcon;
	
	/**
	 * The location of help files for retrieval based on the selection of
	 * language element by the user.
	 */
	public static String pathDocs;
	
	/**
	 * The location of the cached images for retrieval of album art.  These
	 * are not host specific, meaning that if you have the same Artist and
	 * Album instance they will used the cached version,
	 */
	public static String pathCache;
	
	
    /**
     * For the sake of a clean structure all of the user copies will be
     * located within a certain structure.
     */
    public static final String PROG = "/.lyra/";
	

	/**
	 * Sub-directory under <i>.rootUser</i> and <i>rootProgram</i> which contains
	 * user modified and baseline settings and preferences for the application
	 * respectively.
	 */
	public static final String CONF = "/conf/";
	
	/**
	 * Sub-directory under <i>.rootUser</i> and <i>rootProgram</i> which contains
	 * host information and a default template respectively.
	 */
	public static final String HOST = "/host/";
	
	/**
	 * Sub-directory under <i>rootProgram</i> which contains document information.
	 */
	public static final String DOCS = "/docs/";
	
	/**
	 * Directory storying icons for display.  Normally there is a sub-directory
	 * for colour section located within.
	 */
	public static final String ICON = "/icons/";
	
	/**
	 * Sub-directory located under <i>rootProgram</i> exclusively containing all
	 * supported language databases.
	 */
    public static final String LANG = "/lang/";
    
    /**
     * Sub-directory under <i>rootUser</i> which contains generated log files.
     */
    public static final String LOGS = "/logs/";
	
    
    /**
     * Sub-directory under <i>rootUser</i> which contains cached images.
     */
    public static final String CACHE = "/cache/";
    
    
    /**
     * The template file forms the base for other configuration files that
     * will be copied for new configurations.
     */
    public static final String TEMPLATE = "template.db3";
    
    
    /**
     * The database structure necessary for all features and tracking of
     * hosts is specified as follows.
     */
    public static final String DATABASE = "lyra.db3";
	
    
    /**
     * The Lyra icon used to decorate windows.
     */
    public static final String LYRA = "Lyra.png";
    
    /**
     * The Lyra blank CD art cover.
     */
	public static final String BLANK = "LastDotFm-Blank.png";
    
	/**
	 * A reference to the instance created by the static <i>main()</i> instance.
	 * Making use of this instance will ensure that we can always return to the
	 * root and kill the application properly.
	 */
	public static Lyra Self;
	
	
	/*
	 * PACKAGE INSTANCES
	 */
	private Comptroller Manager	= null;
	private Interface Db        = null;
	private Ledger Logger       = null;
	
	private Audit Recorder      = null;
	private Pendulum Clock      = null;
	private Playback Control    = null;
	private Terminus Host       = null;
	private Status Information  = null;
	private Playlist Lists      = null;
	private Library Songs       = null;
	private Updater Watcher     = null;
	private Options Modes       = null;
	private Snooper Snoop		= null;
	
	// Display elements
	private PlayerOrganiserFrame myOrg;
	private InfiniteProgressPanel myPane;

	// Class generating updates
	private static final String CLASS = "org.lyra.Lyra";
	

    /**
     * The main entry point into the application.  Exclusively creates an
     * instance to the constructor of this class.
     * <p>
     * @param args String of arguments passed on the command line.
     */
    public static void main(String[] args) {
    	
    	/* 
    	 * We only create a new instance of the application class if it has
    	 * not been defined already.  Otherwise we process nothing.
    	 */
    	if (Self == null)
    		Self = new Lyra(args);

    }
    // End of Main Method
    
    
    /**
     * Primary constructor of the class, will call initialisation and user
     * components of the application.
     * <p>
     * @param args String of arguments passed on the command line.
     */
    public Lyra(String[] args) {
    	
    	/*
    	 * We establish the base classes that will be carried forth and used
    	 * throughout the program.   The Ledger is a wrapper for the built-in
    	 * logging capability of Java and will function to a defaulted extent
    	 * once it has been configured.
    	 * 
    	 * This is why the Database Class requires the use of a Ledger 
    	 * instance despite the fact that it will contain all of the
    	 * information necessary to configure it properly.
    	 * 
    	 */
    	
    	// Ledger Instance
    	Logger = new Ledger();
    	
    	// Database Instance
    	Db = new Interface(Logger);
    	
    	
    	/*
    	 * Initialisation of the primary classes.  Failure in this component
    	 * will result in termination of the code.
    	 */
    	if (progInitialise(args))  {
    		Logger.Info(
    			CLASS,
    			"Lyra",
    			Language.get("Program.Initialise")
    		  );
    	
    		
    		/*
    		 * Create an instance of the core frame.  Even through it is not tied
    		 * to anything as of yet, it can be used to block and provide a method
    		 * of accessing the window in case its focus was stolen by another
    		 * application.
    		 */
    		myOrg = new PlayerOrganiserFrame(Language, Global);
    		
    		myOrg.setVisible(true);
    		myOrg.setLocationRelativeTo(null);

    		
    		/*
    		 * At times it is necessary to block the main display while we are
    		 * doing some processing.  The InfiniteProgressPanel will do that for
    		 * us and provide a clean look as well.
    		 */
    		myPane = new InfiniteProgressPanel(Language.get("Host.Sync.Message.Initial"));

    		// Set the pane for the Organiser
    		myOrg.setGlassPane(myPane);
    		
   
    		/*
    		 * Time to figure out the intricacies of the host connection
    		 * process.   Without this component there is no point in even
    		 * displaying the main user interface.
    		 */
    		boolean loop = true;
    		
    		// Keep looking until told otherwise
    		while (loop) {
    			
    			/*
    			 * This is where we will make sure that the host elements are
    			 * in place, selected and can connect to the site.
    			 */
        		if (hostInitialise()) {
        			
        			// Log the successful completion of a host
            		Logger.Info(
            			CLASS,
            			"Lyra",
            			Language.get("Host.Initialised")
            		  );
            		
            		// Everything we need is done
            		return;
        		}
        		
        		/*
        		 * When the connection fails we provide the user with a prompt
        		 * that will allow them to select leaving the application or
        		 * creating a new instance.
        		 */
        		else {
        			
        			// Create instance
        			RapidDialog dialog = new RapidDialog(Language);
        			
        			// Extract result
        			loop = dialog.noticeHostFail(myOrg);

        			/*
        			 * The user has opted to create a new host process.  So we
        			 * will kill the host instance that currently exists if there
        			 * is only one available.
        			 */
    				if (loop) {
    					
    					/*
    					 * Always good to confirm this element and then
    					 * move on.
    					 */
    					if (Manager.getHostCount() == 1) {
        					
               				// Create a file handle
               				File current = new File(
               						rootUser + 
               						HOST + 
               						Manager.getHostSingle());
               				
               				// Remove the instance file
               				if (current.exists()) current.delete();
               				
               				// Remove associated entries
               				Manager.deleteHosts();               				
    					}
    				}
           			 
        			else {
        				
        				// Kill off the application without saving state
        				completeExecution();
        			}

        		}
    		}
    	}
    	
		/*
		 * Present the user with a list of options that they can specify at
		 * the command line.  The user can then correct their command line
		 * switches if need be.
		 */
    	else {
			System.out.println("Invalid switch: ./reference [-X] [Argument]");
			System.out.println();
			System.out.println("-d : Debugging level: SEVERE, WARNING, INFO, CONFIG, FINE");
			System.out.println("-l : Program Language Support. Default: EN");
			System.out.println("-p : Program directory where database and defaults are located. Default: ./");
			System.out.println("-u : User directory where user specific files exist. Default: $HOME/.lyra/");
			System.out.println();
    	}
    }
    // End of Constructor Method
    
    
	/**
	 * Handles the basics of host initialisation, making sure a host exists,
	 * is selected and populate/update the database if necessary.
	 * <p>
	 * @return Boolean indicating success of overall operation.
	 */
	private boolean hostInitialise() {
		
		// How many hosts do we actually have
		int hosts = Manager.getHostCount();
		
		// What are the details of the connection
		Map<String,String> details;


		/*
		 * No host entries have been created at the moment.  So we will display
		 * the host creation page and allow the user to create a new instance.
		 */
		if (hosts < 1) {
			
			// Successful operation
			boolean success = false;
			
			// Number of tries
			int attempts = 0;
			
			/*
			 * Here we will begin the process of prompting the user for host
			 * information so that they may configure it to their needs.
			 */	
			while ((!success) 
			 && (attempts < 3)) {
			
				// Request user details
				details = HostCreateDialog.getHostDetails(
						Logger, 
						Language, 
						myOrg
					);

				// Determine successful operation
				success = Operations.hostCreate(
						Logger, 
						Manager, 
						Language, 
						details
					);
				
				// Increase attempts
				attempts++;
			} 
		}
		
		/*
		 * Having more then one host to permit connection raises problems with
		 * which one the user may wish to use.  So we will prompt the user to
		 * select one of the hosts.
		 */
		if (hosts > 1) {
			/*
			 * Extract a list of all identifiers and then transfer it over to
			 * an array for direct use by the showInputDialog method.
			 */
			List<String> listing = Manager.getHostList();
			String[] ident = listing.toArray(new String[0]);
			
			/*
			 * Now we can begin the generation of the actual dialog box which
			 * entails generating a new icon for use as well.  We do not make
			 * use of RapidDialog as this one should be unique to the program.
			 */
			
			// Generate Icon
			ImageIcon icon = new ImageIcon(
					Lyra.getIconByMap("Prompt-Question"));

			// Create Instance
			String s = (String)JOptionPane.showInputDialog(
					myOrg,
			        Language.get("Host.Select.Dialog.Message"),
			        Language.get("Host.Select.Dialog.Title"),
			        JOptionPane.PLAIN_MESSAGE,
			        icon,
			        ident,
			        ident[0]
			  );

			// User must have something selected
			if ((s != null) 
			 && (s.length() > 0)) {
				
				// Now that we have a name get the details
				details = Manager.getHostDetails(s);
				
				// Log Attempt
				Logger.Info(
					CLASS,
					"hostInitialise",
					Language.get("Host.Select").replaceAll("MHOST", s)
				  );
			}

			else {
				// User did not want to cooperate so pass on nothing
				details = new HashMap<String,String>();
			}
		}
		
		/*
		 * The easiest element to work with is a single instance since it will
		 * just be used directly.
		 */
		else {
			// Extract the appropriate elements
			details = Manager.getHostDetails();
		}
		
		
		/*
		 * Now that we have information on the host we wish to connect to
		 * all that remains is connecting to the host, attaching the database
		 * and dealing with the song listing.
		 */
		if (details.size() > 0) {
			
			// Record for safety
			activeHost = details.get("Identifier");
			
			// Create instance
			Host = new Terminus(Logger,
					details.get("Host"), 
					details.get("Port"), 
					details.get("Pass")
				);
			
			// Verify connection
			if (Host.Connect()) {
				
				// Log Attempt
				Logger.Info(
					CLASS,
					"hostInitialise",
					Language.get("Database.Attach.Host")
				  );
				
				/*
				 * We can now attach the host specific database.  We wait for
				 * connection as it would be a waste of a step without a good
				 * solid connection.
				 */				
				if (Db.attachHost(new File(rootUser, HOST + details.get("Filename")))) {
					
					/*
					 * Lyra can support multiple hosts, which means that in 
					 * general we want to make sure that the host we are
					 * attaching is up to date.
					 */
					Patch.hostPatch(Logger, Db, Manager.getHostVersion());
					
					/*
					 * The Watcher thread is a critical component to making this
					 * process work.  We need the ability to detect changes and
					 * act upon them.
					 */
					Watcher = new Updater(Logger, details, Language);
					
					/*
					 * Extract components of the host daemon itself, this will
					 * will provide us with additional elements necessary to
					 * establish the front-end.
					 */
					Information = Watcher.startUpdate();
						
					/*
					 * If the Watcher instance is not established.  Then we will
					 * exit.
					 */
					if (Information == null) return false;

					
					/*
					 * The timer capability will enable us to create an update
					 * element for the play interface(s).
					 */
					Clock = new Pendulum(Logger, Information, Language);
					
					// Add ourselves for updates
					Watcher.addCoreListener(Clock);
					

					/*
					 * The audit record can be established here in order to
					 * track user actions.  These actions can in turn be used
					 * to form smart playlists.
					 */
					Recorder = new Audit(Manager, Information, Logger, Language);
					
					// Add ourselves for updates
					Watcher.addCoreListener(Recorder);
					
					
					/*
					 * We have the ability to send changes to the currently
					 * playing songs to the Last.fm service.  But this is only
					 * required if the service is established.
					 */
					if (!Global.get("LastDotFm.Session").equals("0")) {
						
						// Create Instance
						Snoop = new Snooper(
								Information, 
								Logger, 
								Global, 
								Language
							);
					
						// Add ourselves for updates
						Watcher.addCoreListener(Snoop);
					}
					
					
					/*
					 * With statistics, state and playlist information in place
					 * it is now time to delve into the database and synch up
					 * the server with our local database instance.
					 */
					
					// Create a blocking interface
					AbstractBlock block = new InfiniteProgressImpl(myPane);
					
					// Create songs interface
					Songs = new Library(
							Logger, 
							Manager, 
							Host, 
							block, 
							Information, 
							Language
						);

					// Add listener to track changes
					Watcher.addUpdateListener(Songs);
					
					
					/*
					 * The Organiser has the ability to manage play lists so we
					 * need to create that interface.  We also create the 
					 * playback control instance.
					 */
					Lists   = new Playlist(Logger, Manager, Host, Language);
					Control = new Playback(Logger, Host, Language);
					Modes   = new Options(Logger, Host, Language);
					
					/*
					 * We have now gotten all the other components ready and 
					 * into place.  So now all we need to do is associate these
					 * elements for the user.
					 */
					myOrg.initUpdate(
							Logger, 
							Manager, 
							Watcher, 
							Clock, 
							Information, 
							Lists, 
							Control, 
							Modes
						);
					
					// Return true
					return true;
				}
				
				// Notify the user that the connection has failed
				else {
					Logger.Info(
						CLASS,
						"hostInitialise",
						Language.get("Database.Attach.Fail")
					  );
				}
			}
			
			// We should log the fact the Host did not respond
			else {
				Logger.Info(
					CLASS,
					"hostInitialise",
					Language.get("Host.Fail")
				  );
			}
		}
	   
		// All else failed
		return false;
	}
	// End of hostInitialise Method
    
    
	/** 
	 * This method aims to sort out the various complexities related to drawing
	 * parameters from many sources.   It will first check the command line and
	 * then load in the appropriate files.
	 * <p>
	 * @param args Command Line driven arguments;
	 * @return boolean Simple True or False event
	 */
	private boolean progInitialise(String[] args) {
		
		// Regular expression handling
		Pattern regExp = Pattern.compile("^-(\\w)$");
		Matcher regMatch;
				
		// String key empty for starters
		String key = "";
		
		// Regular Expression Match
		char argMatch;
		
		
		/*
		 * Scan through any and all arguments and determine their validity and
		 * structure.  Failure in the arguments results in failure in the 
		 * execution of the code.
		 */
		for (int argCount=0; argCount < args.length; argCount++) {
			
			// Arguments always have to be a key first
			if (key.isEmpty()) {
				// Look for a match
				regMatch = regExp.matcher(args[argCount]);
				
				/*
				 * Here we are looking for the actual switch to an argument
				 * and a match here will trigger finding the associated value
				 * after the match.
				 */
				if (regMatch.find()) {
					
					// What was that character
					argMatch = regMatch.group(1).charAt(0);	
					
					// Switch and determine proper attribute to replace
					switch(argMatch){
					    // Debugging level:
						case 'd': key = "Debug.Level"; break;
						
						// Language file location:
						case 'l': key = "Program.Language"; break;
						
						// Language file location:
						case 'p': key = "Root.Program"; break;
						
						// Language file location:
						case 'u': key = "Root.User"; break;
						
						// Invalid switch
						default:
							// Return false to bypass future processing
							return false;
					}
					
					/*
					 * Now that we have found a match we assume the pair will
					 * be found in the next found argument.  If this is not the
					 * case we withdraw.
					 */
					if (argCount++ < args.length) {
						// Look for a match
						regMatch = regExp.matcher(args[argCount]);
						
						// Parameters should never follow one another
						if (!regMatch.find()) {						
							// Create or replace the hash element
							Global.put(key, args[argCount]);
							
							// Reset for next cycle
							key = "";
						}
						
						// Orphaned switch
						else {
							// Failed, notify the caller
							return false;
						}
					}
				}
				
				// Mismatched parameter
				else {
					// Cannot have parameters without a switch
					return false;
				}
			}
		}
		
		/*
		 * We have a need to confirm the existence of the provided root
		 * directories.   No matter the choice, we will need to confirm
		 * that everything works as expected.
		 * 
		 * The program root will contain the defaults as well as all of
		 * the language files.
		 */
		rootProgram = (Global.containsKey("Root.Program"))
		 ? new File(Global.get("Root.Program"))
		 : new File("./");
		
		if (!rootProgram.exists()) return false;
		if (!rootProgram.canWrite()) return false;

		
		/*
		 * The user path will contain information that is pertinent to the
		 * user specifically.  Since it may not exist at the time it is
		 * necessary to establish the structure.
		 */
		rootUser = (Global.containsKey("Root.User"))
		 ? new File(Global.get("Root.User"))
		 : new File(System.getProperty("user.home") + PROG);
		
		 
		/*
		 * Structure is missing so create the basis of it. 
		 */
		if (!rootUser.exists()) {
			
			// Create parent directory
			rootUser.mkdir();
			
			// Create sub-directories
			new File(rootUser, CONF).mkdir();
			new File(rootUser, HOST).mkdir();
			new File(rootUser, LOGS).mkdir();
			
			
			// Transfer over the database file
			Operations.fileCopy(
				Logger,
				new File(rootProgram, CONF + DATABASE), 
				new File(rootUser, CONF + DATABASE)
			 );
		}
		
		/*
		 * Cache is a directory structure that appeared in the 1.0.1 release.
		 * So we cannot confirm the existence if the User structure was built
		 * before version 1.0.1.
		 */
		if (!new File(rootUser, CACHE).exists()) {
			
			// Create Cache
			new File(rootUser, CACHE).mkdir();
			
			// Transfer over the blank art cover
			Operations.fileCopy(
				Logger,
				new File(rootProgram, CACHE + BLANK), 
				new File(rootUser, CACHE + BLANK)
			 );
		}
		
		
		/*
		 * Now that we have established that the roots are in fact acceptable
		 * we can now configure additional elements that are more specific.
		 */
		if (Db.Connect(new File(rootUser, CONF + DATABASE))) {
			
			// Create instance of the Database Comptroller
			Manager = new Comptroller(Db);
			
			
			/*
			 * We need to update the Main database as soon as possible.  This
			 * will cause the Global map to contain the new information as 
			 * soon as updated.
			 */
			Patch.mainPatch(Logger, Db, Manager.getSystemVersion());
			
			
			
			/*
			 * Global properties are important as they provide the application
			 * with core configurations and provide a launch point for database
			 * and host-based configuration.
			 */
			Manager.fetchGlobal(Global);
			
			// Update Ledger to reflect our settings
			Logger.Adjust(Global);
			
			
			/*
			 * Language elements establish the language used by the application
			 * at all levels of utilisation including debugging level.  So now 
			 * that we have user options in place we can establish that portion
			 * of the process.
			 */
			String language = Manager.getProgLanguage(
								Global.get("Program.Language"));
			
			// Attach the new database
			if (Db.attachLang(new File(rootProgram, LANG + language))) {
			
				// Extract language elements
				Language = Manager.fetchLanguage();
			
			
				/*
				 * Now that the bulk of the configuration has been reached we will
				 * display debugging information for the world to see.
				 */
			
				// Display database settings
				Db.debugDatabase();
			
				// Display the global hash settings
				Logger.debugHash(Global);
			
				// Display the language element
				Logger.debugHash(Language);
			
				
				/*
				 * It may seem like an unusual space for changing Look and
				 * Feel but this will permit any interface generated to have
				 * the look we want.
				 */
				/*
				 * Look and Feel Handler.  We want the Nimbus Look and Feel so this
				 * will require explicit selection.
				 */
				try {
				    for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
				        if (Global.get("Setting.Interface.Theme").equals(info.getName())) {
				            UIManager.setLookAndFeel(info.getClassName());
				            break;
				        }
				    }
				} 
				
				// No action necessary will make use of Metal instead
				catch (Exception e) {}
				
			
				/*
				 * Assuming we made it this far, we can allow the rest of the
				 * program execution to continue.
				 */
				return true;
			}
		}
		
		// A process has failed
		return false;
	}
	// End progInitialise Method


	/**
	 * Execution of the program will normal be ended through this process
	 * as it will ensure that various handles are closed and cleaned up
	 * prior to the completion. 
	 */
	private void completeExecution() {
		
		/*
		 * An updater connection needs to be terminated along with all of
		 * of the other elements.  This will ensure that the updater and 
		 * associated hosts gets done.
		 */
		if (Watcher != null) {
		
			// Disconnect watcher from host
			Watcher.stopUpdate();
		}
		
		/*
		 * The pendulum may have spawned an additional thread.  In order to
		 * make leaving this application easier we simply stop the thread
		 * prior to ending the process.
		 */
		if (Clock != null) {
			
			// End the pendulum instance
			Clock.stopRun();
		}
		
		/*
		 * The host connection will be closed here, making sure that the
		 * host knows that we have disconnected from the server process.	
		 */
		if (Host != null) {
			
			// Log Attempt
			Logger.Info(
				CLASS,
				"completeExecution",
				Language.get("Host.Disconnect")
			  );
		
			// Disconnect from the database
			Host.Disconnect();
		}
		
		/*
		 *  Be a good citizen and close out the database.  SQLite seems to
		 *  be able to handle sudden disconnections but no real need to
		 *  do so.
		 */
		if (Db != null) {
		
			// Log Attempt
			Logger.Info(
				CLASS,
				"completeExecution",
				Language.get("Database.Disconnect")
			  );
		
			// Disconnect from the database
			Db.Disconnect();
		}
		
		// Final Termination
		System.exit(0);
	}
	// End of completeExecution Method
	
	
	/**
	 * Once the user has reached a point where a user-interface is present it
	 * will typically be shunted though this method.   This will permit the
	 * user to select if he/she wants to end termination.
	 * <p>
	 * @param source Source window.
	 */
	private void completeExecution(Window source) {
			
		/*
		 * User does not care to be notified about this event.  So we just
		 * terminate execution neatly.
		 */
		if (!Boolean.parseBoolean(Global.get("State.Close.Prompt")))
			completeExecution();

		
		/*
		 * The user has a choice in the matter if they so choose to make use
		 * of the prompt.
		 */
		if (CloseApplicationDialog.getVerdict(Logger, Manager, Language, source)) {
		
			// Pass through completion
			completeExecution();
		}
	}
	// End of completeExecution Method
	
	
	/**
	 * A static pass-through to enable termination of application from
	 * anywhere without requiring the passage of this class to each and
	 * every element that may eventually need it.
	 * <p>
	 * @param source Source window.
	 */
	static public void closeApplication(Window source) {
		
		/*
		 * A static pass-through to enable termination of application from
		 * anywhere without requiring the passage of this class to each and
		 * every element that may eventually need it.
		 */
		if (Self != null)
			Self.completeExecution(source);
	}
	// End of closeApplication Method
	
	
	/**
	 * A static pass-through to enable termination of application from
	 * anywhere that will display a generic error message.  This is normally
	 * produced if the host suddenly drops.
	 */
	static public void closeApplication() {
		
		/*
		 * A static pass-through to enable termination of application from
		 * anywhere without requiring the passage of this class to each and
		 * every element that may eventually need it.
		 */
		if (Self != null) {
		
			// Display Error Message
			RapidDialog rd = new RapidDialog(Self.Language);
			rd.noticeFail(null, Self.Language.get("Error.Generic"));
			
			// Complete Execution
			Self.completeExecution();
		}	
	}
	// End of closeApplication Method
	
	
	/**
	 * Returns the path holding the designated icons for use.   The path can
	 * change at startup based on colour selection for the theme.
	 * <p>
	 * @param ident String which contains key for the map.
	 * @return String containing path.
	 */
	static public String getIconByMap(String ident) {
	
		// Pass through and return
		return Lyra.getIconByFile(
				Constants.fileIcons.get(ident));
	}
	// End of getIconByMap
	
	
	/**
	 * Returns the path holding the designated icons for use.   The path can
	 * change at startup based on colour selection for the theme.  Makes use of
	 * a direct filename entry instead of an abstract.
	 * <p>
	 * @param filename String which contains filename.
	 * @return String containing path.
	 */
	static public String getIconByFile(String filename) {
	
		/*
		 * We have already done the generation of the path so we simply
		 * return this element back to us.
		 */
		if (pathIcon != null)
			return Lyra.pathIcon + filename;
		
		// Generate the new path
		Lyra.pathIcon = Lyra.rootProgram.getAbsolutePath() 
		              + Lyra.ICON + "/"
		              + Lyra.Global.get("Setting.Interface.Icons") + "/";
		
		// Returns generated path
		return Lyra.pathIcon + filename;
	}
	// End of getIconByPath
	
	
	/**
	 * Returns the path holding the designated help file for use.   These
	 * vary based on language elements passed at startup.
	 * <p>
	 * @param ident String which contains key for the map.
	 * @return String containing path.
	 */
	static public String getDocsByMap(String ident) {
	
		// Pass through and return
		return Lyra.getDocsByFile(
				Constants.fileHelp.get(ident));
	}
	// End of getDocspByMap
	
	
	/**
	 * Returns the path holding the designated icons for use.   The path can
	 * change at startup based on colour selection for the theme.  Makes use of
	 * a direct filename entry instead of an abstract.
	 * <p>
	 * @param filename String which contains filename.
	 * @return String containing path.
	 */
	static public String getDocsByFile(String filename) {
	
		/*
		 * We have already done the generation of the path so we simply
		 * return this element back to us.
		 */
		if (pathDocs != null)
			return Lyra.pathDocs + filename;
		
		// Generate the new path
		Lyra.pathDocs = Lyra.rootProgram.getAbsolutePath() 
		              + Lyra.DOCS + "/"
		              + Lyra.Global.get("Program.Language") + "/";
		
		// Returns generated path
		return Lyra.pathDocs + filename;
	}
	// End of getDocsByFile
	
	
	/**
	 * Returns the path with filename to extract a cached image used for the
	 * album art display.
	 * <p>
	 * @param filename String which contains filename.
	 * @return String containing path.
	 */
	static public String getCacheByFile(String filename) {
	
		/*
		 * We have already done the generation of the path so we simply
		 * return this element back to us.
		 */
		if (pathCache != null)
			return Lyra.pathCache + filename;
		
		// Generate the new path
		Lyra.pathCache = Lyra.rootUser.getAbsolutePath() 
		              + Lyra.CACHE + "/";
		
		// Returns generated path
		return Lyra.pathCache + filename;
	}
	// End of getCacheByFile
}
