package ij;
import ij.util.Tools;
import ij.text.TextWindow;
import ij.plugin.MacroInstaller;
import ij.plugin.Duplicator;
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;
import java.awt.GraphicsEnvironment;


/** 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;
	private boolean repeatingCommand;

	/** 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);
			repeatingCommand = true;
		} 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 (IJ.recording()) {
				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 (Prefs.nonBlockingFilterDialogs) {
				// we have the plugin class name, let us see whether it is allowed to run it
				ImagePlus imp = WindowManager.getCurrentImage();
				boolean imageLocked = imp!=null && imp.isLockedByAnotherThread();
				if (imageLocked && !allowedWithLockedImage(className)) {
					IJ.beep();
					IJ.showStatus("\""+cmd + "\" blocked because \"" + imp.getTitle() + "\" is locked");
					return;
				}
			}
			// run the plugin
			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 (IJ.getInstance()!=null && !GraphicsEnvironment.isHeadless()) {
				if (Editor.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 {
				if ("Table...".equals(cmd))
					IJ.runPlugIn("ij.plugin.NewPlugin", "table");
				else {
					if (repeatingCommand)
						IJ.runMacro(previousCommand);
					else {
						if (!extraCommand(cmd))
							IJ.error("Unrecognized command: \"" + cmd+"\"");
					}
				}
			}
	 	}
    }
    
	private boolean extraCommand(String cmd) {
		if (cmd!=null && cmd.equals("Duplicate Image...")) {
			ImagePlus imp = WindowManager.getCurrentImage();
			if (imp!=null) {
				Duplicator.ignoreNextSelection();
				IJ.run(imp, "Duplicate...", "");
			} else
				IJ.noImage();
			return true;
		} else
			return false;
	}

	/** If the foreground image is locked during a filter operation with NonBlockingGenericDialog,
	 *  the following plugins are allowed */
	boolean allowedWithLockedImage(String className) {
		return className.equals("ij.plugin.Zoom") ||
				className.equals("ij.plugin.frame.ContrastAdjuster") ||
				className.equals("ij.plugin.SimpleCommands") ||  //includes Plugins>Utiltites>Reset (needed to reset a locked image)
				className.equals("ij.plugin.WindowOrganizer") ||
				className.equals("ij.plugin.URLOpener");
	}

    /** 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() && Character.isLowerCase(name.charAt(0))) {
			String name2 = name.substring(0,1).toUpperCase()+name.substring(1);
			path = IJ.getDirectory("luts")+name2+".lut";
			f = new File(path);
		}
		if (!f.exists() && name.toLowerCase().equals("viridis")) {
			path = IJ.getDirectory("luts")+"mpl-viridis.lut";  //Fiji version
			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;
	}

	public static void setAsRepeatCommand(String cmd) {
		previousCommand = cmd;
	}

	/** 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();
	}

}


