package edu.cmu.hcii.whyline;

import java.io.*;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import edu.cmu.hcii.whyline.ui.UI;
import edu.cmu.hcii.whyline.ui.WhylineUI;
import edu.cmu.hcii.whyline.ui.launcher.MacLauncherUI;

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ANYTHING IMPORTED AND LOADED BY THIS FILE CANNOT BE INSTRUMENTED. 
// IT WILL BE LOADED BEFORE THE APP LOADS!
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Contains the main() that invokes the Whyline as well as a method to invoke the user's program with the tracing agent.
 * 
 * @author Andrew J. Ko
 *
 */
public final class Whyline {
	
	public static final int WHYLINE_FAILURE_EXIT_CODE = 711;

	private static final String JDK_SOURCE_PATH = "JDK_SOURCE_PATH";
	
	private static final String WHYLINE_HOME_PATH_KEY = "whylineHomePath";
	private static String WHYLINE_HOME;
	
	private static File WHYLINE_FOLDER;
	
	// NAMES
	public static final String IDS_NAME = "ids";
	public static final String CALLS_NAME = "calls";
	public static final String VALUES_NAME = "values";
	public static final String CLASSIDS_NAME = "classids";

	public static final String TRACE_FILE_SUFFIX = ".history";

	// TOP-LEVEL TRACE NAMES
	public static final String STATIC_FOLDER_PATH = "static";
	public static final String DYNAMIC_FOLDER_PATH = "dynamic";
	public static final String META_PATH = "meta";
	public static final String USAGE_PATH = "usage.log";

	// DYNAMIC INFO TRACE NAMES
	public static final String IMMUTABLES_PATH = DYNAMIC_FOLDER_PATH + File.separatorChar + "immutables";
	public static final String OBJECT_TYPES_PATH = DYNAMIC_FOLDER_PATH + File.separatorChar + "objects";
	public static final String HISTORY_PATH = DYNAMIC_FOLDER_PATH + File.separatorChar + "history";

	// HISTORY NAMES
	public static final String RANDOM_PATH = HISTORY_PATH + File.separatorChar + "random";
	public static final String SERIAL_PATH = HISTORY_PATH + File.separatorChar + "serial";

	// RANDOM ACCESS NAMES
	public static final String IDS_PATH = RANDOM_PATH + File.separatorChar + IDS_NAME;
	public static final String CALLS_PATH = RANDOM_PATH + File.separatorChar + CALLS_NAME;
	public static final String VALUES_PATH = RANDOM_PATH + File.separatorChar + VALUES_NAME;
	public static final String RANGES_PATH = RANDOM_PATH + File.separatorChar + "ranges";
	public static final String IO_PATH = RANDOM_PATH + File.separatorChar + "io";
	public static final String EXCEPTIONS_PATH = RANDOM_PATH + File.separatorChar + "exceptions";
	public static final String STATIC_ASSIGNMENTS_PATH = RANDOM_PATH + File.separatorChar + "globals";
	public static final String FIELD_ASSIGNMENTS_PATH = RANDOM_PATH + File.separatorChar + "fields";
	public static final String ARRAY_ASSIGNMENTS_PATH = RANDOM_PATH + File.separatorChar + "arrays";
	public static final String INSTANTIATIONS_PATH = RANDOM_PATH + File.separatorChar + "instantiations";
	public static final String INITIALIZATIONS_PATH = RANDOM_PATH + File.separatorChar + "initializations";
	public static final String RUNS_PATH = RANDOM_PATH + File.separatorChar + "runs";
	public static final String INVOCATIONS_PATH = RANDOM_PATH + File.separatorChar + "invocations";
	public static final String ARGUMENTS_PATH = RANDOM_PATH + File.separatorChar + "arguments";
	public static final String IMAGE_PATH = ARGUMENTS_PATH + File.separatorChar + "image";
	public static final String KEY_PATH = ARGUMENTS_PATH + File.separatorChar + "key";
	public static final String MOUSE_PATH = ARGUMENTS_PATH + File.separatorChar + "mouse";
	public static final String REPAINT_PATH = ARGUMENTS_PATH + File.separatorChar + "repaint";
	public static final String CREATE_PATH = ARGUMENTS_PATH + File.separatorChar + "create";

	// STATIC INFO TRACE NAMES
	public static final String SOURCE_PATH = STATIC_FOLDER_PATH + File.separatorChar + "source";
	public static final String CLASSNAMES_PATH = STATIC_FOLDER_PATH + File.separatorChar + "classnames";
	public static final String CLASSES_PATH = STATIC_FOLDER_PATH + File.separatorChar + "classes";
	public static final String CLASSIDS_PATH = STATIC_FOLDER_PATH + File.separatorChar + CLASSIDS_NAME;
	public static final String CALL_GRAPH_PATH = STATIC_FOLDER_PATH + File.separatorChar + "callgraph";
	public static final String OUTPUT_PATH = STATIC_FOLDER_PATH + File.separatorChar + "output";

	// GLOBAL FILE AND FOLDER NAMES
	public static final String CLASS_CACHE_PATH = "classes";
	public static final String ANALYZED_CLASS_CACHE_FOLDER_NAME = "uninstrumented";
	public static final String INSTRUMENTED_CLASS_CACHE_FOLDER_NAME = "instrumented";
	public static final String EXECUTIONS_FILE_NAME = "configurations.xml";
	public static final String SAVED_TRACES_FOLDER_NAME = "saved";
	public static final String WORKING_TRACE_FOLDER_NAME = "recent";

	private static File WORKING_TRACE_FOLDER;
	private static File WORKING_CLASSIDS_FILE;

	private static File WORKING_IMMUTABLES_FILE;
	private static File WORKING_CLASSNAMES_FILE;
	private static File WORKING_META_FILE;
	private static File WORKING_OBJECT_TYPES_FILE;

	private static File WORKING_SERIAL_HISTORY_FOLDER;
	private static File WORKING_SOURCE_FOLDER;

	private static File CLASS_CACHE_FOLDER;	
	private static File UNINSTRUMENTED_CLASS_CACHE_FOLDER;	
	private static File INSTRUMENTED_CLASS_CACHE_FOLDER;	
	private static File SAVED_TRACES_FOLDER;
	
	public static File WHYLINE_JAR_PATH = null;

	// These must absolutely execute at this position in this file, after all of the fields above are initialized.
	static {
		
		try {
		
			initializeSystemProperties();
			String home = loadPreferences();
			setHome(new File(home));
			
		}
		catch(Exception e) {
			e.printStackTrace();
		}

	}
	
	public static final void main(String[] args) throws Exception {
				
		if(args.length  == 0) {

			WHYLINE_JAR_PATH = new File(System.getProperty("user.dir"), "whyline.jar");
			if(!WHYLINE_JAR_PATH.exists()) {
				
				javax.swing.JOptionPane.showMessageDialog(null, "<html>Couldn't find \"whyline.jar\" at \n\n" + WHYLINE_JAR_PATH, "Problem", javax.swing.JOptionPane.ERROR_MESSAGE);
				System.exit(0);
				
			}
			
			if(System.getProperty("os.name").startsWith("Mac")) {
				new MacLauncherUI();
			}
			else {
				new edu.cmu.hcii.whyline.ui.launcher.LauncherUI();
			}
			
		}
		else {

			if(args.length > 1)
				Whyline.debug("Ignoring extra arguments...");

			File trace = new File(args[0]);
			if(!trace.exists()) {

				javax.swing.JOptionPane.showMessageDialog(null, "<html>Couldn't find a trace at "+ trace, "Problem", javax.swing.JOptionPane.ERROR_MESSAGE);
				System.exit(0);

			}

			debug("Loading trace at \"" + trace.getPath());

			new WhylineUI(null, trace, WhylineUI.Mode.WHYLINE);	
			
		}
		
	}
	
	private static void initializeSystemProperties() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
		
		System.setProperty("swing.defaultlaf", "javax.swing.plaf.metal.MetalLookAndFeel");
	    System.setProperty("swing.aatext", "true");
	    System.setProperty("com.apple.mrj.application.growbox.intrudes", "false");
	    System.setProperty("awt.useSystemAAFontSettings", "true");

	    // Initialize the UI settings.
		UI.class.getName();

	}

	private static Preferences getPreferences() { 
		
		return Preferences.userNodeForPackage(Whyline.class); 
		
	}

	private static String loadPreferences() {

		UI.class.getName();
		
		// Load the preferences, if there are any.
		Preferences userPrefs = getPreferences();

		// If no preference is set, use the current working directory.
		String WHYLINE_HOME = userPrefs.get(WHYLINE_HOME_PATH_KEY, System.getProperty("user.dir") + File.separatorChar + "whyline" + File.separatorChar);

		// Now store the preference, in case it wasn't stored before
		userPrefs.put(WHYLINE_HOME_PATH_KEY, WHYLINE_HOME);
		try {
			userPrefs.flush();
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}

		return WHYLINE_HOME;
		
	}
	
	public static String getJDKJavaDocPath() {
		
		return getPreferences().get("JDK_JAVADOC_PATH", "http://java.sun.com/j2se/1.5.0/docs/api/");
		
	}
	
	public static void setJDKSourcePath(String sourcePath) {

		Preferences userPrefs = getPreferences();
		userPrefs.put(JDK_SOURCE_PATH, sourcePath);
		try { userPrefs.flush(); } catch(BackingStoreException e) { e.printStackTrace(); }

	}
	
	public static String getJDKSourcePath() {

		Preferences userPrefs = getPreferences();

		String sourcePath = userPrefs.get(JDK_SOURCE_PATH, null);

		if(sourcePath == null) {
			
			String OSXPathToSource = "/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home/src.jar";
			String WindowsPathToSource = "C:/jdk1.5.0/src.zip";

			String osName = System.getProperty("os.name");
			if(osName.contains("OS X")) sourcePath = OSXPathToSource;
			else if(osName.contains("Windows")) sourcePath = WindowsPathToSource;
			
			userPrefs.put(JDK_SOURCE_PATH, sourcePath);
			try { userPrefs.flush(); } catch(BackingStoreException e) { e.printStackTrace(); }
			
		}
			
		return sourcePath;
		
	}
	
	public static void setHome(File home) {

		Preferences userPrefs = getPreferences();
		userPrefs.put(WHYLINE_HOME_PATH_KEY, home.getAbsolutePath());
		try {
			userPrefs.flush();
		} catch (BackingStoreException e) {
			e.printStackTrace();
		}

		WHYLINE_FOLDER = home;
		WHYLINE_FOLDER.mkdir();	

		WORKING_TRACE_FOLDER = new File(WHYLINE_FOLDER, WORKING_TRACE_FOLDER_NAME);
		WORKING_CLASSIDS_FILE = new File(Whyline.WHYLINE_FOLDER, CLASSIDS_NAME);
		WORKING_IMMUTABLES_FILE = new File(WORKING_TRACE_FOLDER, IMMUTABLES_PATH);
		WORKING_META_FILE = new File(WORKING_TRACE_FOLDER, META_PATH);
		WORKING_OBJECT_TYPES_FILE = new File(Whyline.WORKING_TRACE_FOLDER, OBJECT_TYPES_PATH);
		WORKING_SERIAL_HISTORY_FOLDER = new File(Whyline.WORKING_TRACE_FOLDER, SERIAL_PATH);
		WORKING_SOURCE_FOLDER  = new File(WORKING_TRACE_FOLDER, SOURCE_PATH);
		WORKING_CLASSNAMES_FILE = new File(WORKING_TRACE_FOLDER, CLASSNAMES_PATH);

		CLASS_CACHE_FOLDER = new File(WHYLINE_FOLDER, CLASS_CACHE_PATH);	
		UNINSTRUMENTED_CLASS_CACHE_FOLDER = new File(getClassCacheFolder(), ANALYZED_CLASS_CACHE_FOLDER_NAME);	
		INSTRUMENTED_CLASS_CACHE_FOLDER = new File(getClassCacheFolder(), INSTRUMENTED_CLASS_CACHE_FOLDER_NAME);	
		SAVED_TRACES_FOLDER = new File(WHYLINE_FOLDER, SAVED_TRACES_FOLDER_NAME);
		
	}
	
	public static File getHome() { return WHYLINE_FOLDER; }

	public static File getWorkingTraceFolder() { return WORKING_TRACE_FOLDER; }
	public static File getWorkingClassIDsFile() { return WORKING_CLASSIDS_FILE; }
	public static File getWorkingImmutablesFile() { return WORKING_IMMUTABLES_FILE; }
	public static File getWorkingClassnamesFile() { return WORKING_CLASSNAMES_FILE; }
	public static File getWorkingMetaFile() { return WORKING_META_FILE; }
	public static File getWorkingObjectTypesFile() { return WORKING_OBJECT_TYPES_FILE; }
	public static File getWorkingSerialHistoryFolder() { return WORKING_SERIAL_HISTORY_FOLDER; }
	public static File getWorkingSourceFolder() { return WORKING_SOURCE_FOLDER; }
	public static File getClassCacheFolder() { return CLASS_CACHE_FOLDER; }
	public static File getUninstrumentedClassCacheFolder() { return UNINSTRUMENTED_CLASS_CACHE_FOLDER; }
	public static File getInstrumentedClassCacheFolder() { return INSTRUMENTED_CLASS_CACHE_FOLDER; }
	public static File getSavedTracesFolder() { return SAVED_TRACES_FOLDER; }

	public static final void debug(String message) {
		
		System.out.println("whyline >\t" + message);
		
	}
	
	public static final void debugBreak() {
		
		debug("");
		System.out.println("whyline >\t>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
		debug("");
		
	}
			
}