package ij;
import ij.util.Tools;
import ij.text.TextWindow;
import ij.plugin.MacroInstaller;
import ij.plugin.frame.Recorder;
import ij.plugin.frame.Editor;
import ij.io.OpenDialog;
import java.io.*;
import java.util.*;
import java.awt.event.KeyEvent;
import java.awt.Menu;


/** Runs ImageJ menu commands in a separate thread.*/
public class Executer implements Runnable {

	private static String previousCommand;
	private static CommandListener listener;
	private static Vector listeners = new Vector();
	
	private String command;
	private Thread thread;
	
	/** Create an Executer to run the specified menu command
		in this thread using the active image. */
	public Executer(String cmd) {
		command = cmd;
	}

	/** Create an Executer that runs the specified menu 
		command in a separate thread using the specified image,
		or using the active image if 'imp' is null. */
	public Executer(String cmd, ImagePlus imp) {
		if (cmd.startsWith("Repeat")) {
			command = previousCommand;
			IJ.setKeyUp(KeyEvent.VK_SHIFT);		
		} else {
			command = cmd;
			if (!(cmd.equals("Undo")||cmd.equals("Close")))
				previousCommand = cmd;
		}
		IJ.resetEscape();
		thread = new Thread(this, cmd);
		thread.setPriority(Math.max(thread.getPriority()-2, Thread.MIN_PRIORITY));
		if (imp!=null)
			WindowManager.setTempCurrentImage(thread, imp);
		thread.start();
	}

	public void run() {
		if (command==null)
			return;
		if (listeners.size()>0) synchronized (listeners) {
			for (int i=0; i<listeners.size(); i++) {
				CommandListener listener = (CommandListener)listeners.elementAt(i);
				command = listener.commandExecuting(command);
				if (command==null) return;
			}
		}
		try {
			if (Recorder.record) {
				Recorder.setCommand(command);
				runCommand(command);
				Recorder.saveCommand();
			} else
				runCommand(command);
			int len = command.length();
			if (len>0 && command.charAt(len-1)!=']')
				IJ.setKeyUp(IJ.ALL_KEYS);  // set keys up except for "<", ">", "+" and "-" shortcuts
		} catch(Throwable e) {
			IJ.showStatus("");
			IJ.showProgress(1, 1);
			ImagePlus imp = WindowManager.getCurrentImage();
			if (imp!=null) imp.unlock();
			String msg = e.getMessage();
			if (e instanceof OutOfMemoryError)
				IJ.outOfMemory(command);
			else if (e instanceof RuntimeException && msg!=null && msg.equals(Macro.MACRO_CANCELED))
				; //do nothing
			else {
				CharArrayWriter caw = new CharArrayWriter();
				PrintWriter pw = new PrintWriter(caw);
				e.printStackTrace(pw);
				String s = caw.toString();
				if (IJ.isMacintosh()) {
					if (s.indexOf("ThreadDeath")>0)
						return;
					s = Tools.fixNewLines(s);
				}
				int w=500, h=340;
				if (s.indexOf("UnsupportedClassVersionError")!=-1) {
					if (s.indexOf("version 49.0")!=-1) {
						s = e + "\n \nThis plugin requires Java 1.5 or later.";
						w=700; h=150;
					}
					if (s.indexOf("version 50.0")!=-1) {
						s = e + "\n \nThis plugin requires Java 1.6 or later.";
						w=700; h=150;
					}
					if (s.indexOf("version 51.0")!=-1) {
						s = e + "\n \nThis plugin requires Java 1.7 or later.";
						w=700; h=150;
					}
					if (s.indexOf("version 52.0")!=-1) {
						s = e + "\n \nThis plugin requires Java 1.8 or later.";
						w=700; h=150;
					}
				}
				if (IJ.getInstance()!=null) {
					s = IJ.getInstance().getInfo()+"\n \n"+s;
					new TextWindow("Exception", s, w, h);
				} else
					IJ.log(s);
			}
		} finally {
			if (thread!=null)
				WindowManager.setTempCurrentImage(null);
		}
	}
	    
	void runCommand(String cmd) {
		Hashtable table = Menus.getCommands();
		String className = (String)table.get(cmd);
		if (className!=null) {
			String arg = "";
			if (className.endsWith("\")")) {
				// extract string argument (e.g. className("arg"))
				int argStart = className.lastIndexOf("(\"");
				if (argStart>0) {
					arg = className.substring(argStart+2, className.length()-2);
					className = className.substring(0, argStart);
				}
			}
			if (IJ.shiftKeyDown() && className.startsWith("ij.plugin.Macro_Runner") && !Menus.getShortcuts().contains("*"+cmd))
    			IJ.open(IJ.getDirectory("plugins")+arg);
    		else
				IJ.runPlugIn(cmd, className, arg);
		} else { // command is not a plugin
			// is command in the Plugins>Macros menu?
			if (MacroInstaller.runMacroCommand(cmd))
				return;
			// is it in the Image>Lookup Tables menu?
			if (loadLut(cmd))
				return;
			// is it in the File>Open Recent menu?
			if (openRecent(cmd))
				return;
			// is it an example in Help>Examples menu?
			if (openExample(cmd))
				return;
			if ("Auto Threshold".equals(cmd)&&(String)table.get("Auto Threshold...")!=null)
				runCommand("Auto Threshold...");
			else if ("Enhance Local Contrast (CLAHE)".equals(cmd)&&(String)table.get("CLAHE ")!=null)
				runCommand("CLAHE ");
			else
				IJ.error("Unrecognized command: \"" + cmd+"\"");
	 	}
    }
    
	private boolean openExample(String name) {
		boolean isMacro = name.endsWith(".ijm");
		boolean isJava = name.endsWith(".java");
		boolean isJavaScript = name.endsWith(".js");
		boolean isBeanShell = name.endsWith(".bsh");
		boolean isPython = name.endsWith(".py");
		if (!(isMacro||isJava||isJavaScript||isBeanShell||isPython))
			return false;
		boolean run = !isJava && (Prefs.autoRunExamples||IJ.shiftKeyDown()||IJ.controlKeyDown()||IJ.altKeyDown());
		int rows = 24;
		int columns = 70;
		int options = Editor.MENU_BAR;
		String text = null;
		Editor ed = new Editor(rows, columns, 0, options);
		String dir = "Macro/";
		if (isJava)
			dir = "Java/";
		else if (isJavaScript)
			dir = "JavaScript/";
		else if (isBeanShell)
			dir = "BeanShell/";
		else if (isPython)
			dir = "Python/";
		String url = "http://wsr.imagej.net/download/Examples/"+dir+name;
		text = IJ.openUrlAsString(url);
		if (text.startsWith("<Error: ")) {
			IJ.error("Open Example", text);
			return true;
		}
		ed.create(name, text);
		if (run) {
			if (isJavaScript)
				ed.evaluateScript(".js");
			else if (isBeanShell)
				ed.evaluateScript(".bsh");
			else if (isPython)
				ed.evaluateScript(".py");
			else
				IJ.runMacro(text);
		}
		return true;
	}

    /** Opens a .lut file from the ImageJ/luts directory and returns 'true' if successful. */
    public static boolean loadLut(String name) {
		String path = IJ.getDirectory("luts")+name.replace(" ","_")+".lut";
		File f = new File(path);
		if (!f.exists()) {
			path = IJ.getDirectory("luts")+name+".lut";
			f = new File(path);
		}
		if (!f.exists()) {
			path = IJ.getDirectory("luts")+name.toLowerCase().replace(" ","_")+".lut";
			f = new File(path);
		}
		if (f.exists()) {
			String dir = OpenDialog.getLastDirectory();
			IJ.open(path);
			OpenDialog.setLastDirectory(dir);
			return true;
		}
		return false;
    }

    /** Opens a file from the File/Open Recent menu 
 	      and returns 'true' if successful. */
    boolean openRecent(String cmd) {
		Menu menu = Menus.getOpenRecentMenu();
		if (menu==null) return false;
		for (int i=0; i<menu.getItemCount(); i++) {
			if (menu.getItem(i).getLabel().equals(cmd)) {
				IJ.open(cmd);
				return true;
			}
		}
		return false;
    }

	/** Returns the last command executed. Returns null
		if no command has been executed. */
	public static String getCommand() {
		return previousCommand;
	}
	
	/** Adds the specified command listener. */
	public static void addCommandListener(CommandListener listener) {
		listeners.addElement(listener);
	}
	
	/** Removes the specified command listener. */
	public static void removeCommandListener(CommandListener listener) {
		listeners.removeElement(listener);
	}
	
	public static int getListenerCount() {
		return listeners.size();
	}

}


