package com.limegroup.gnutella.util;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.StringTokenizer;

import com.limegroup.gnutella.MediaType;
import com.limegroup.gnutella.settings.URLHandlerSettings;


/**
 * This class launches files in their associated applications and opens 
 * urls in the default browser for different operating systems.  This
 * really only works meaningfully for the Mac and Windows.<p>
 *
 * Acknowledgement goes to Eric Albert for demonstrating the general 
 * technique for loading the MRJ classes in his frequently-used
 * "BrowserLauncher" code.
 * <p>
 * This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu) 
 * and may be redistributed or modified in any form without restrictions as
 * long as the portion of this comment from this paragraph through the end of  
 * the comment is not removed.  The author requests that he be notified of any 
 * application, applet, or other binary that makes use of this code, but that's 
 * more out of curiosity than anything and is not required.  This software
 * includes no warranty.  The author is not repsonsible for any loss of data 
 * or functionality or any adverse or unexpected effects of using this software.
 * <p>
 * Credits:
 * <br>Steven Spencer, JavaWorld magazine 
 * (<a href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java Tip 66</a>)
 * <br>Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, 
 * Andrea Cantatore, Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron 
 * Rabakukk
 *
 * @author Eric Albert 
 *  (<a href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
 * @version 1.4b1 (Released June 20, 2001)
 */
 //2345678|012345678|012345678|012345678|012345678|012345678|012345678|012345678|
public final class Launcher {

	/**
	 * <tt>boolean</tt> specifying whether or not the necessary Mac
	 * classes were loaded successfully.
	 */
	private static boolean _macClassesLoadedSuccessfully = true;

	/**
	 * The openURL method of com.apple.mrj.MRJFileUtils.
	 */
	private static Method _openURL;

	/** 
	 * Loads the necessary Mac classes if running on Mac.
	 */
	static {
	    if(CommonUtils.isMacOSX()) {
			try {
				loadMacClasses();		
			} catch(IOException ioe) {
				_macClassesLoadedSuccessfully = false;
			}
		}
	}

	/** 
	 * This class should be never be instantiated; this just ensures so. 
	 */
	private Launcher() {}
	
	/**
	 * Opens the specified url in a browser. 
	 *
	 * <p>A browser will only be opened if the underlying operating system 
	 * recognizes the url as one that should be opened in a browser, 
	 * namely a url that ends in .htm or .html.
	 *
	 * @param url  The url to open
	 *
	 * @return  An int indicating the success of the browser launch
	 *
	 * @throws IOException if the url cannot be loaded do to an IO problem
	 */
	public static int openURL(String url) throws IOException {	   
		if(CommonUtils.isWindows()) {
			return openURLWindows(url);
		}	   
		else if(CommonUtils.isMacOSX()) {
			openURLMac(url);
		}
		else {
		    // Other OS
			launchFileOther(url);
		}
		return -1;
	}

	/**
	 * Opens the default web browser on windows, passing it the specified
	 * url.
	 *
	 * @param url the url to open in the browser
	 * @return the error code of the native call, -1 if the call failed
	 *  for any reason
	 */
	private static int openURLWindows(String url) throws IOException {
		return new WindowsLauncher().openURL(url);
	}
	
	/**
	 * Opens the specified url in the default browser on the Mac.
	 * This makes use of the dynamically-loaded MRJ classes.
	 *
	 * @param url the url to load
	 *
	 * @throws <tt>IOException</tt> if the necessary mac classes were not
	 *         loaded successfully or if another exception was
	 *         throws -- it wraps these exceptions in an <tt>IOException</tt>
	 */
	private static void openURLMac(String url) throws IOException {
		if(!_macClassesLoadedSuccessfully) throw new IOException();
		try {
			Object[] params = new Object[] {url};
			_openURL.invoke(null, params);
		} 
		catch (NoSuchMethodError err) {
			throw new IOException();
			// this can occur when earlier versions of MRJ are used which
			// do not support the openURL method.
		} catch (NoClassDefFoundError err) {
			throw new IOException();
			// this can occur under runtime environments other than MRJ.
		} catch (IllegalAccessException iae) {
			throw new IOException();
		} catch (InvocationTargetException ite) {
			throw new IOException();
		}
	}

	/**
	 * Launches the file whose abstract path is specified in the 
	 * <tt>File</tt> parameter.  This method will not launch any file
	 * with .exe, .vbs, .lnk, .bat, .sys, or .com extensions, diplaying 
	 * an error if one of the file is of one of these types.
	 *
	 * @param path  The path of the file to launch
	 *
	 * @return  An int indicating the success of the browser launch
	 *
	 * @throws IOException if the file cannot be launched do to an IO problem
	 */
	public static int launchFile(File file) throws IOException, SecurityException {
		String path = file.getCanonicalPath();
		String extCheckString = path.toLowerCase();

        // Expand pmf files before display
        if ( extCheckString.endsWith(".pmf") ) {
            file = PackagedMediaFileUtils.preparePMFFile(file.toString());
            // Don't launch an invalid file
            if ( file == null )
                return -1; 
            path           = file.getCanonicalPath();
            extCheckString = path.toLowerCase();
        }

		if(!extCheckString.endsWith(".exe") &&
		   !extCheckString.endsWith(".vbs") &&
		   !extCheckString.endsWith(".lnk") &&
		   !extCheckString.endsWith(".bat") &&
		   !extCheckString.endsWith(".sys") &&
		   !extCheckString.endsWith(".com")) {
			if(CommonUtils.isWindows()) {
				return launchFileWindows(path);
			}
			else if(CommonUtils.isMacOSX()) {
				launchFileMacOSX(path);
			}
			else {
			    // Other OS, use helper apps
				launchFileOther(path);
			}
		}
		else {
			throw new SecurityException();
		}
		return -1;		
	}

    /**
     * Windows: Launches the Explorer and highlights the file.
     * Mac OS X: Launches the Finder and opens the file if it is
     *  a directory or its parent if it is a file
     * 
     * @param file
     * @return true on success
     * @throws IOException
     */
    public static boolean launchExplorer(File file) throws IOException, SecurityException {
        if (CommonUtils.isWindows()) {
            String explorePath = file.getPath(); 
            try { 
                explorePath = file.getCanonicalPath(); 
            } catch(IOException ioe) { } 
            
            //launches explorer and highlights the file
            Runtime.getRuntime().exec(new String[] {"explorer","/select,", explorePath });
            return true;
        } else if (CommonUtils.isMacOSX()) {
            if(file.isFile()) {
                launchFile(file.getParentFile());
            } else {
                launchFile(file);
            }
            return true;
        }
        return false;
    }
    
	/**
	 * Launches the given file on Windows.
	 *
	 * @param path the path of the file to launch
	 *
	 * @return an int for the exit code of the native method
	 */
	private static int launchFileWindows(String path) throws IOException {		
		return new WindowsLauncher().launchFile(path);
	}

	/**
	 * Launches a file on OSX, appending the full path of the file to the
	 * "open" command that opens files in their associated applications
	 * on OSX.
	 *
	 * @param file the <tt>File</tt> instance denoting the abstract pathname
	 *  of the file to launch
	 * @throws IOException if an I/O error occurs in making the runtime.exec()
	 *  call or in getting the canonical path of the file
	 */
	private static void launchFileMacOSX(final String file) throws IOException {
	    Runtime.getRuntime().exec(new String[]{"open", file});
	}

	/** 
	 * Loads specialized classes for the Mac needed to launch files.
	 *
	 * @return <tt>true</tt>  if initialization succeeded,
	 *	   	   <tt>false</tt> if initialization failed
	 *
	 * @throws <tt>IOException</tt> if an exception occurs loading the
	 *         necessary classes
	 */
	private static void loadMacClasses() throws IOException {
		try {
			Class mrjAdapter = Class.forName("net.roydesign.mac.MRJAdapter");
			_openURL = mrjAdapter.getDeclaredMethod("openURL", new Class[]{String.class});
		} catch (ClassNotFoundException cnfe) {
			throw new IOException();
		} catch (NoSuchMethodException nsme) {
			throw new IOException();
		} catch (SecurityException se) {
			throw new IOException();
		} 
	}
    
    
	/**
	 * Attempts to launch the given file.
	 * NOTE: WE COULD DO THIS ONE BETTER!!
	 *
	 * @throws IOException  if the call to Runtime.exec throws an IOException
	 *                      or if the Process created by the Runtime.exec call
	 *                      throws an InterruptedException
	 */
	private static void launchFileOther(String path) throws IOException {
	    String handler;
	    if (MediaType.getAudioMediaType().matches(path)) {
	    	handler = URLHandlerSettings.AUDIO_PLAYER.getValue();
	    } else if (MediaType.getVideoMediaType().matches(path)) {
	    	handler = URLHandlerSettings.VIDEO_PLAYER.getValue();
	    } else if (MediaType.getImageMediaType().matches(path)) {
	    	handler = URLHandlerSettings.IMAGE_VIEWER.getValue();
	    } else {
	    	handler = URLHandlerSettings.BROWSER.getValue();
	    }

		
        if (handler.indexOf("$URL$") != -1) {
			System.out.println("starting " + handler);
			StringTokenizer tok = new StringTokenizer (handler);
			String[] strs = new String[tok.countTokens()];
			for (int i = 0; tok.hasMoreTokens(); i++) {
				strs[i] = StringUtils.replace(tok.nextToken(), "$URL$", path);
				
				System.out.print(" "+strs[i]);
			}
			try {
				Runtime.getRuntime().exec(strs);
			} catch(IOException e) {
				e.printStackTrace();
			}
		} else {
			System.out.println("starting " + handler);
            String[] strs = {handler, path};
            Runtime.getRuntime().exec(strs);
        }
    }
}
