package ij.macro;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.measure.*;
import ij.plugin.*;
import ij.plugin.filter.*;
import ij.plugin.frame.*;
import ij.text.*;
import ij.io.*;
import ij.util.*;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.io.*;
import java.awt.event.KeyEvent;
import java.lang.reflect.*;
import java.net.URL;
import java.awt.datatransfer.*;

/** This class implements the built-in macro functions. */
public class Functions implements MacroConstants, Measurements {
	Interpreter interp;
	Program pgm;
    boolean updateNeeded;
    boolean autoUpdate = true;
    ImagePlus defaultImp;
    ImageProcessor defaultIP;
    int imageType;
    boolean colorSet, fontSet;
    Color defaultColor;
    double defaultValue = Double.NaN;
    Plot plot;
    static int plotID;
    int justification = ImageProcessor.LEFT_JUSTIFY;
    Font font;
    GenericDialog gd;
    PrintWriter writer;
    boolean altKeyDown, shiftKeyDown;
    boolean antialiasedText;
    StringBuffer buffer;
    RoiManager roiManager;
    
    boolean saveSettingsCalled;
	boolean usePointerCursor, hideProcessStackDialog;
	float divideByZeroValue;
    int jpegQuality;
    int lineWidth;
    boolean doScaling;
    boolean weightedColor;
    double[] weights;
	boolean interpolateScaledImages, open100Percent, blackCanvas;
	boolean useJFileChooser,debugMode;
	Color foregroundColor, backgroundColor, roiColor;
	boolean pointAutoMeasure, requireControlKey, useInvertingLut;
	boolean doubleBuffer, disablePopup;
	int measurements;
	int decimalPlaces;
	boolean blackBackground;
	static Dialog waitForUserDialog;

	Functions(Interpreter interp, Program pgm) {
		this.interp = interp;
		this.pgm = pgm;
	}
 
	void doFunction(int type) {
		switch (type) {
			case RUN: doRun(); break;
			case SELECT: IJ.selectWindow(getStringArg()); resetImage(); break;
			case WAIT: IJ.wait((int)getArg()); break;
			case BEEP: interp.getParens(); IJ.beep(); break;
			case RESET_MIN_MAX: interp.getParens(); IJ.resetMinAndMax(); resetImage(); break;
			case RESET_THRESHOLD: interp.getParens(); IJ.resetThreshold(); resetImage(); break;
			case PRINT: print(); break;
			case WRITE: IJ.write(getStringArg()); break;
			case DO_WAND: IJ.doWand((int)getFirstArg(), (int)getLastArg()); resetImage(); break;
			case SET_MIN_MAX: IJ.setMinAndMax(getFirstArg(), getLastArg()); resetImage(); break;
			case SET_THRESHOLD: setThreshold(); break;
			case SET_TOOL: setTool(); break;
			case SET_FOREGROUND: setForegroundColor(); break;
			case SET_BACKGROUND: setBackgroundColor(); break;
			case SET_COLOR: setColor(); break;
			case MAKE_LINE: makeLine(); break;
			case MAKE_OVAL: makeOval(); break;
			case MAKE_RECTANGLE: makeRectangle(); break;
			case DUMP: interp.dump(); break;
			case LINE_TO: lineTo(); break;
			case MOVE_TO: moveTo(); break;
			case DRAW_LINE: drawLine(); break;
			case REQUIRES: requires(); break;
			case AUTO_UPDATE: autoUpdate = getBooleanArg(); break;
			case UPDATE_DISPLAY: interp.getParens(); updateDisplay(); break;
			case DRAW_STRING: drawString(); break;
			case SET_PASTE_MODE: IJ.setPasteMode(getStringArg()); break;
			case DO_COMMAND: doCommand(); break;
			case SHOW_STATUS: IJ.showStatus(getStringArg()); interp.statusUpdated=true; break;
			case SHOW_PROGRESS: showProgress(); break;
			case SHOW_MESSAGE: showMessage(false); break;
			case SHOW_MESSAGE_WITH_CANCEL: showMessage(true); break;
			case SET_PIXEL: case PUT_PIXEL: setPixel(); break;
			case SNAPSHOT: case RESET: case FILL: doIPMethod(type); break;
			case SET_LINE_WIDTH: getProcessor().setLineWidth((int)getArg()); break;
			case CHANGE_VALUES: changeValues(); break;
			case SELECT_IMAGE: selectImage(); break;
			case EXIT: exit(); break;
			case SET_LOCATION: setLocation(); break;
			case GET_CURSOR_LOC: getCursorLoc(); break;
			case GET_LINE: getLine(); break;
			case GET_VOXEL_SIZE: getVoxelSize(); break;
			case GET_HISTOGRAM: getHistogram(); break;
			case GET_BOUNDING_RECT: case GET_BOUNDS: getBounds(); break;
			case GET_LUT: getLut(); break;
			case SET_LUT: setLut(); break;
			case GET_COORDINATES: getCoordinates(); break;
			case MAKE_SELECTION: makeSelection(); break;
			case SET_RESULT: setResult(); break;
			case UPDATE_RESULTS: updateResults(); break;
			case SET_BATCH_MODE: setBatchMode(); break;
			case PLOT: doPlot(); break;
			case SET_JUSTIFICATION: setJustification(); break;
			case SET_Z_COORDINATE: setZCoordinate(); break;
			case GET_THRESHOLD: getThreshold(); break;
			case GET_PIXEL_SIZE: getPixelSize(); break;
			case SETUP_UNDO: interp.getParens(); Undo.setup(Undo.TRANSFORM, getImage()); break;
			case SAVE_SETTINGS: saveSettings(); break;
			case RESTORE_SETTINGS: restoreSettings(); break;
			case SET_KEY_DOWN: setKeyDown(); break;
			case OPEN: open(); break;
			case SET_FONT: setFont(); break;
			case GET_MIN_AND_MAX: getMinAndMax(); break;
			case CLOSE: close(); break;
			case SET_SLICE: setSlice(); break;
			case NEW_IMAGE: newImage(); break;
			case SAVE: IJ.save(getStringArg()); break;
			case SAVE_AS: saveAs(); break;
			case SET_AUTO_THRESHOLD: setAutoThreshold(); break;
			case RENAME: resetImage(); getImage().setTitle(getStringArg()); break;
			case GET_STATISTICS: getStatistics(true); break;
			case GET_RAW_STATISTICS: getStatistics(false); break;
			case FLOOD_FILL: floodFill(); break;
			case RESTORE_PREVIOUS_TOOL: restorePreviousTool(); break;
			case SET_VOXEL_SIZE: setVoxelSize(); break;
			case GET_LOCATION_AND_SIZE: getLocationAndSize(); break;
			case GET_DATE_AND_TIME: getDateAndTime(); break;
			case SET_METADATA: setMetadata(); break;
			case CALCULATOR: imageCalculator(); break;
			case SET_RGB_WEIGHTS: setRGBWeights(); break;
			case MAKE_POLYGON: makePolygon(); break;
			case SET_SELECTION_NAME: setSelectionName(); break;
			case DRAW_RECT: case FILL_RECT: case DRAW_OVAL: case FILL_OVAL: drawOrFill(type); break;
			case SET_OPTION: setOption(); break;
			case SHOW_TEXT: showText(); break;
			case SET_SELECTION_LOC: setSelectionLocation(); break;
			case GET_DIMENSIONS: getDimensions(); break;
			case WAIT_FOR_USER: waitForUser(); break;
		}
	}
	
	final double getFunctionValue(int type) {
		double value = 0.0;
		switch (type) {
			case GET_PIXEL: value = getPixel(); break;
			case ABS: case COS: case EXP: case FLOOR: case LOG: case ROUND: 
			case SIN: case SQRT: case TAN: case ATAN: case ASIN: case ACOS:
				value = math(type);
				break;
			case MAX_OF: case MIN_OF: case POW: case ATAN2: value=math2(type); break;
			case GET_TIME: interp.getParens(); value=System.currentTimeMillis(); break;
			case GET_WIDTH: interp.getParens(); value=getImage().getWidth(); break;
			case GET_HEIGHT: interp.getParens(); value=getImage().getHeight(); break;
			case RANDOM: value=random(); break;
			case GET_COUNT: case NRESULTS: value=getResultsCount(); break;
			case GET_RESULT: value=getResult(); break;
			case GET_NUMBER: value=getNumber(); break;
			case NIMAGES: value=getImageCount(); break;
			case NSLICES: value=getStackSize(); break;
			case LENGTH_OF: value=lengthOf(); break;
			case GET_ID: interp.getParens(); resetImage(); value=getImage().getID(); break;
			case BIT_DEPTH: interp.getParens(); value = getImage().getBitDepth(); break;
			case SELECTION_TYPE: value=getSelectionType(); break;
			case IS_OPEN: value=isOpen(); break;
			case IS_ACTIVE: value=isActive(); break;
			case INDEX_OF: value=indexOf(); break;
			case LAST_INDEX_OF: value=getFirstString().lastIndexOf(getLastString()); break;
			case CHAR_CODE_AT: value=getFirstString().charAt((int)getLastArg()); break;
			case GET_BOOLEAN: value=getBoolean(); break;
			case STARTS_WITH: case ENDS_WITH: value = startsWithEndsWith(type); break;
			case IS_NAN: value = Double.isNaN(getArg())?1:0; break;
			case GET_ZOOM: value = getZoom(); break;
			case PARSE_FLOAT: value = parseDouble(getStringArg()); break;
			case PARSE_INT: value = parseInt(); break;
			case IS_KEY_DOWN: value=isKeyDown(); break;
			case GET_SLICE_NUMBER: interp.getParens(); value=getImage().getCurrentSlice(); break;
			case SCREEN_WIDTH: case SCREEN_HEIGHT: value = getScreenDimension(type); break;
			case CALIBRATE: value = getImage().getCalibration().getCValue(getArg()); break;
			case ROI_MANAGER: value = roiManager(); break;
			case TOOL_ID: interp.getParens(); value = Toolbar.getToolId(); break;
			case IS: value = is(); break;
			case GET_VALUE: value = getValue(); break;
			case STACK: value = doStack(); break;
			case MATCHES: value = matches(); break;
			default:
				interp.error("Numeric function expected");
		}
		return value;
	}

	String getStringFunction(int type) {
		String str;
		switch (type) {
			case D2S: str = d2s(); break;
			case TO_HEX: str = toString(16); break;
			case TO_BINARY: str = toString(2); break;
			case GET_TITLE: interp.getParens(); resetImage(); str=getImage().getTitle(); break;
			case GET_STRING: str = getStringDialog(); break;
			case SUBSTRING: str = substring(); break;
			case FROM_CHAR_CODE: str = fromCharCode(); break;
			case GET_INFO: str = getInfo(); break;			
			case GET_IMAGE_INFO: interp.getParens(); str = getImageInfo(); break;			
			case GET_DIRECTORY: str = getDirectory(); break;
			case GET_ARGUMENT: interp.getParens(); str=interp.argument!=null?interp.argument:""; break;
			case TO_LOWER_CASE: str = getStringArg().toLowerCase(Locale.US); break;
			case TO_UPPER_CASE: str = getStringArg().toUpperCase(Locale.US); break;
			case RUN_MACRO: str = runMacro(false); break;
			case EVAL: str = runMacro(true); break;
			case TO_STRING: str = doToString(); break;
			case REPLACE: str = replace(); break;
			case DIALOG: str = doDialog(); break;
			case GET_METADATA: str = getMetadata(); break;
			case FILE: str = doFile(); break;
			case SELECTION_NAME: str = selectionName(); break;
			case GET_VERSION: interp.getParens();  str = IJ.getVersion(); break;
			case GET_RESULT_LABEL: str = getResultLabel(); break;
			case CALL: str = call(); break;
			case STRING: str = doString(); break;
			case EXT: str = doExt(); break;
			case EXEC: str = exec(); break;
			default:
				str="";
				interp.error("String function expected");
		}
		return str;
	}

	Variable[] getArrayFunction(int type) {
		Variable[] array;
		switch (type) {
			case GET_PROFILE: array=getProfile(); break;
			case NEW_ARRAY: array = newArray(); break;
			case SPLIT: array = split(); break;
			case GET_FILE_LIST: array = getFileList(); break;
			case GET_FONT_LIST: array = getFontList(); break;
			case NEW_MENU: array = newMenu(); break;
			case GET_LIST: array = getList(); break;
			default:
				array = null;
				interp.error("Array function expected");
		}
		return array;
	}

	final double math(int type) {
		double arg = getArg();
		switch (type) {
			case ABS: return Math.abs(arg);
			case COS: return Math.cos(arg);
			case EXP: return Math.exp(arg);
			case FLOOR: return Math.floor(arg);
			case LOG: return Math.log(arg);
			case ROUND: return Math.round(arg);
			case SIN: return Math.sin(arg);
			case SQRT: return Math.sqrt(arg);
			case TAN: return Math.tan(arg);
			case ATAN: return Math.atan(arg);
			case ASIN: return Math.asin(arg);
			case ACOS: return Math.acos(arg);
			default: return 0.0;
		}
	}

	final double math2(int type) {
		double a1 = getFirstArg();
		double a2 = getLastArg();
		switch (type) {
			case MIN_OF: return Math.min(a1, a2);
			case MAX_OF: return Math.max(a1, a2);
			case POW: return Math.pow(a1, a2);
			case ATAN2: return Math.atan2(a1, a2);
			default: return 0.0;
		}
	}

	final String getString() {
		String str = interp.getStringTerm();
		while (true) {
			interp.getToken();
			if (interp.token=='+')
				str += interp.getStringTerm();
			else {
				interp.putTokenBack();
				break;
			}
		};
		return str;
	}

	final boolean isStringFunction() {
		Symbol symbol = pgm.table[interp.tokenAddress];
		return symbol.type==D2S;
	}

	final double getArg() {
		interp.getLeftParen();
		double arg = interp.getExpression();
		interp.getRightParen();
		return arg;
	}

	final double getFirstArg() {
		interp.getLeftParen();
		return interp.getExpression();
	}

	final double getNextArg() {
		interp.getComma();
		return interp.getExpression();
	}

	final double getLastArg() {
		interp.getComma();
		double arg = interp.getExpression();
		interp.getRightParen();
		return arg;
	}

	String getStringArg() {
		interp.getLeftParen();
		String arg = getString();
		interp.getRightParen();
		return arg;
	}

	final String getFirstString() {
		interp.getLeftParen();
		return getString();
	}

	final String getNextString() {
		interp.getComma();
		return getString();
	}

	final String getLastString() {
		interp.getComma();
		String arg = getString();
		interp.getRightParen();
		return arg;
	}

	boolean getBooleanArg() {
		interp.getLeftParen();
		double arg = interp.getBooleanExpression();
		interp.checkBoolean(arg);
		interp.getRightParen();
		return arg==0?false:true;
	}

	final Variable getVariableArg() {
		interp.getLeftParen();
		Variable v = getVariable();
		interp.getRightParen();
		return v;
	}

	final Variable getFirstVariable() {
		interp.getLeftParen();
		return getVariable();
	}

	final Variable getNextVariable() {
		interp.getComma();
		return getVariable();
	}

	final Variable getLastVariable() {
		interp.getComma();
		Variable v = getVariable();
		interp.getRightParen();
		return v;
	}

	final Variable getVariable() {
		interp.getToken();
		if (interp.token!=WORD)
			interp.error("Variable expected");
		Variable v = interp.lookupLocalVariable(interp.tokenAddress);
		if (v==null)
				v = interp.push(interp.tokenAddress, 0.0, null, interp);
		Variable[] array = v.getArray();
		if (array!=null) {
			int index = interp.getIndex();
			checkIndex(index, 0, array.length-1);
			v = array[index]; 
		}
		return v;
	}

	final Variable getFirstArrayVariable() {
		interp.getLeftParen();
		return getArrayVariable();
	}

	final Variable getNextArrayVariable() {
		interp.getComma();
		return getArrayVariable();
	}

	final Variable getLastArrayVariable() {
		interp.getComma();
		Variable v = getArrayVariable();
		interp.getRightParen();
		return v;
	}

	final Variable getArrayVariable() {
		interp.getToken();
		if (interp.token!=WORD)
			interp.error("Variable expected");
		Variable v = interp.lookupLocalVariable(interp.tokenAddress);
		if (v==null)
				v = interp.push(interp.tokenAddress, 0.0, null, interp);
		return v;
	}

	final double[] getFirstArray() {
		interp.getLeftParen();
		return getNumericArray();
	}

	final double[] getNextArray() {
		interp.getComma();
		return getNumericArray();
	}

	final double[] getLastArray() {
		interp.getComma();
		double[] a = getNumericArray();
		interp.getRightParen();
		return a;
	}

	double[] getNumericArray() {
		Variable[] a1 = getArray();
		double[] a2 = new double[a1.length];
		for (int i=0; i<a1.length; i++)
			a2[i] = a1[i].getValue();
		return a2;
	}

	String[] getStringArray() {
		Variable[] a1 = getArray();
		String[] a2 = new String[a1.length];
		for (int i=0; i<a1.length; i++) {
			String s = a1[i].getString();
			if (s==null) s = "" + a1[i].getValue();
			a2[i] = s;
		}
		return a2;
	}

	Variable[] getArray() {
		interp.getToken();
		boolean newArray = interp.token==ARRAY_FUNCTION && pgm.table[interp.tokenAddress].type==NEW_ARRAY;
		if (!(interp.token==WORD||newArray))
			interp.error("Array expected");
		Variable[] a;
		if (newArray)
			a = getArrayFunction(NEW_ARRAY);
		else {
			Variable v = interp.lookupVariable();
			a= v.getArray();
		}
		if (a==null)
			interp.error("Array expected");
		return a;
	}
		
	Color getColor() {
		String color = getString();
		color = color.toLowerCase(Locale.US);
		if (color.equals("black"))
			return Color.black;
		else if (color.equals("white"))
			return Color.white;
		else if (color.equals("red"))
			return Color.red;
		else if (color.equals("green"))
			return Color.green;
		else if (color.equals("blue"))
			return Color.blue;
		else if (color.equals("cyan"))
			return Color.cyan;
		else if (color.equals("darkgray"))
			return Color.darkGray;
		else if (color.equals("gray"))
			return Color.gray;
		else if (color.equals("lightgray"))
			return Color.lightGray;
		else if (color.equals("magenta"))
			return Color.magenta;
		else if (color.equals("orange"))
			return Color.orange;
		else if (color.equals("yellow"))
			return Color.yellow;
		else if (color.equals("pink"))
			return Color.pink;
		else
			interp.error("'red', 'green', etc. expected");
		return null;
	}

	void checkIndex(int index, int lower, int upper) {
		if (index<lower || index>upper)
			interp.error("Index ("+index+") is outside of the "+lower+"-"+upper+" range");
	}

	void doRun() {
		interp.getLeftParen();
		String arg1 = getString();
		interp.getToken();
		if (!(interp.token==')' || interp.token==','))
			interp.error("',' or ')'  expected");
		String arg2 = null;
		if (interp.token==',') {
			arg2 = getString();
			interp.getRightParen();
		}
		if (arg2!=null)
			IJ.run(arg1, arg2);
		else
			IJ.run(arg1);
		resetImage();
		IJ.setKeyUp(IJ.ALL_KEYS);
		shiftKeyDown = altKeyDown = false;
	}

	void setForegroundColor() {
		IJ.setForegroundColor((int)getFirstArg(), (int)getNextArg(), (int)getLastArg());
		resetImage(); 
		defaultColor = null;
		defaultValue = Double.NaN;
	}

	void setBackgroundColor() {
		IJ.setBackgroundColor((int)getFirstArg(), (int)getNextArg(), (int)getLastArg());
		resetImage(); 
	}

	void setColor() {
		colorSet = true;
        interp.getLeftParen();
		if (isStringArg()) {
			defaultColor = getColor();
			getProcessor().setColor(defaultColor);
			defaultValue = Double.NaN;
        	interp.getRightParen();
        	return;
		}
		double arg1 = (int)interp.getExpression();
		if (interp.nextToken()==')')
			{interp.getRightParen(); setColor(arg1); return;}
		int red=(int)arg1, green=(int)getNextArg(), blue=(int)getLastArg();
	    if (red<0) red=0; if (green<0) green=0; if (blue<0) blue=0; 
	    if (red>255) red=255; if (green>255) green=255; if (blue>255) blue=255;  
		defaultColor = new Color(red, green, blue);
		getProcessor().setColor(defaultColor);
		defaultValue = Double.NaN;
	}
	
	void setColor(double value) {
		ImageProcessor ip = getProcessor();
		ImagePlus imp = getImage();
		switch (imp.getBitDepth()) {
			case 8:
				if (value<0 || value>255)
					interp.error("Argument out of 8-bit range (0-255)");
				ip.setValue(value);
				break;
			case 16:
				if (imp.getLocalCalibration().isSigned16Bit())
					value += 32768;
				if (value<0 || value>65535)
					interp.error("Argument out of 16-bit range (0-65535)");
				ip.setValue(value);
				break;
			default:
				ip.setValue(value);
				break;
		}
		defaultValue = value;
		defaultColor = null;
	}

	void makeLine() {
		double x1d = getFirstArg();
		double y1d = getNextArg();
		double x2d = getNextArg();
		interp.getComma();
		double y2d = interp.getExpression();
		interp.getToken();
		if (interp.token==')')
			IJ.makeLine(x1d, y1d, x2d, y2d);
		else {
			int x1 = (int)Math.round(x1d);
			int y1 = (int)Math.round(y1d);
			int x2 = (int)Math.round(x2d);
			int y2 = (int)Math.round(y2d);
			int max = 200;
			int[] x = new int[max];
			int[] y = new int[max];
			x[0]=x1; y[0]=y1; x[1]=x2; y[1]=y2;
			int n = 2;
			while (interp.token==',' && n<max) {
				x[n] = (int)Math.round(interp.getExpression());
				if (n==2 && interp.nextToken()==')') {
					interp.getRightParen();
					Line.setWidth((int)x[n]);
					IJ.makeLine(x1, y1, x2, y2);
					return;
				}
				interp.getComma();
				y[n] = (int)Math.round(interp.getExpression());
				interp.getToken();
				n++;
			}
			if (n==max && interp.token!=')')
				interp.error("More than "+max+" points");
			getImage().setRoi(new PolygonRoi(x, y, n, Roi.POLYLINE));
		}
		resetImage(); 
	}

	void makeOval() {
		Roi previousRoi = getImage().getRoi();
		if (shiftKeyDown||altKeyDown) getImage().saveRoi();
		IJ.makeOval((int)getFirstArg(), (int)getNextArg(), (int)getNextArg(), (int)getLastArg());
		Roi roi = getImage().getRoi();
		if (previousRoi!=null && roi!=null)
			updateRoi(roi);
		resetImage();
	}
	
	void makeRectangle() {
		Roi previousRoi = getImage().getRoi();
		if (shiftKeyDown||altKeyDown) getImage().saveRoi();
		IJ.makeRectangle((int)getFirstArg(), (int)getNextArg(), (int)getNextArg(), (int)getLastArg());
		Roi roi = getImage().getRoi();
		if (previousRoi!=null && roi!=null)
			updateRoi(roi);
		resetImage();
	}
	
	ImagePlus getImage() {
		if (defaultImp==null)
			defaultImp = IJ.getImage();
		if (defaultImp==null)
			{interp.error("No image"); return null;}	
		if (defaultImp.getWindow()==null && IJ.getInstance()!=null && !interp.isBatchMode() && WindowManager.getTempCurrentImage()==null)
			throw new RuntimeException(Macro.MACRO_CANCELED);
		return defaultImp;
	}
	
	void resetImage() {
		defaultImp = null;
		defaultIP = null;
		colorSet = fontSet = false;
	}

	ImageProcessor getProcessor() {
		if (defaultIP==null) {
			defaultImp = getImage();
			defaultIP = defaultImp.getProcessor();
		}
		return defaultIP;
	}

	int getType() {
		if (defaultImp==null)
			defaultImp = IJ.getImage();
		imageType = defaultImp.getType();
		return imageType;
	}

	void setPixel() {
		interp.getLeftParen();
		int a1 = (int)interp.getExpression();
		interp.getComma();
		double a2 = interp.getExpression();
		interp.getToken();
		if (interp.token==',') {
			double a3 = interp.getExpression();
			interp.getRightParen();
			if (getType()==ImagePlus.GRAY32)
				getProcessor().putPixelValue(a1, (int)a2, a3);
			else
				getProcessor().putPixel(a1, (int)a2, (int)a3);
		} else {
			if (interp.token!=')') interp.error("')' expected");
			getProcessor().setf(a1, (float)a2);
		}
		updateNeeded = true;
	}

	double getPixel() {
		interp.getLeftParen();
		int a1 = (int)interp.getExpression();
		ImageProcessor ip = getProcessor();
		double value = 0.0;
		interp.getToken();
		if (interp.token==',') {
			int a2 = (int)interp.getExpression();
			interp.getRightParen();
			if (getType()==ImagePlus.GRAY32)
				value = ip.getPixelValue(a1, a2);
			else
				value = ip.getPixel(a1, a2);
		} else {
			if (interp.token!=')') interp.error("')' expected");
			value = ip.getf(a1);
		}
		return value;
	}
	
	void setZCoordinate() {
		int z = (int)getArg();
		ImagePlus imp = getImage();
		ImageStack stack = imp.getStack();
		int size = stack.getSize();
		if (z<0 || z>=size)
			interp.error("Z coordinate ("+z+") is out of 0-"+(size-1)+ " range");
		this.defaultIP = stack.getProcessor(z+1);		
	}
	
	void moveTo() {
		interp.getLeftParen();
		int a1 = (int)(interp.getExpression()+0.5);
		interp.getComma();
		int a2 = (int)(interp.getExpression()+0.5);
		interp.getRightParen();
		getProcessor().moveTo(a1, a2);
	}
	
	void lineTo() {
		interp.getLeftParen();
		int a1 = (int)(interp.getExpression()+0.5);
		interp.getComma();
		int a2 = (int)(interp.getExpression()+0.5);
		interp.getRightParen();
		ImageProcessor ip = getProcessor();
		if (!colorSet) setForegroundColor(ip);
		ip.lineTo(a1, a2);
		updateAndDraw(defaultImp);
	}

	void drawLine() {
		interp.getLeftParen();
		int x1 = (int)(interp.getExpression()+0.5);
		interp.getComma();
		int y1 = (int)(interp.getExpression()+0.5);
		interp.getComma();
		int x2 = (int)(interp.getExpression()+0.5);
		interp.getComma();
		int y2 = (int)(interp.getExpression()+0.5);
		interp.getRightParen();
		ImageProcessor ip = getProcessor();
		if (!colorSet) setForegroundColor(ip);
		ip.drawLine(x1, y1, x2, y2);
		updateAndDraw(defaultImp);
	}
	
	void setForegroundColor(ImageProcessor ip) {
		if (defaultColor!=null)
			ip.setColor(defaultColor);
		else if (!Double.isNaN(defaultValue))
			ip.setValue(defaultValue);
		else
			ip.setColor(Toolbar.getForegroundColor());
		colorSet = true;
	}

	void doIPMethod(int type) {
        interp.getParens(); 
		ImageProcessor ip = getProcessor();
		switch (type) {
			case SNAPSHOT: ip.snapshot(); break;
			case RESET:
				ip.reset();
				updateNeeded = true;
				break;
			case FILL: 
				ImagePlus imp = getImage();
				Roi roi = imp.getRoi();
				if (!colorSet) setForegroundColor(ip);
				if (roi==null) {
					ip.resetRoi();
					ip.fill();
				} else {
					ip.setRoi(roi);
					ip.fill(ip.getMask());
				}
				updateAndDraw(imp);
				break;
		}
	}

	void updateAndDraw(ImagePlus imp) {
		if (autoUpdate)
			imp.updateChannelAndDraw();
		else
			updateNeeded = true;
	}
	
	void updateDisplay() {
		if (updateNeeded && WindowManager.getImageCount()>0) {
			ImagePlus imp = getImage();
			imp.updateAndDraw();
			updateNeeded = false;
		}
	}

	void drawString() {
		interp.getLeftParen();
		String str = getString();
		interp.getComma();
		int x = (int)(interp.getExpression()+0.5);
		interp.getComma();
		int y = (int)(interp.getExpression()+0.5);
		interp.getRightParen();
		ImageProcessor ip = getProcessor();
		if (!colorSet)
			setForegroundColor(ip);
		setFont(ip);
		ip.setJustification(justification);
		ip.setAntialiasedText(antialiasedText);
		ip.drawString(str, x, y);
		updateAndDraw(defaultImp);
	}
	
	void setFont(ImageProcessor ip) {
		if (font!=null && !fontSet)
			ip.setFont(font);
		fontSet = true;
	}

	void setJustification() {
		String str = getStringArg().toLowerCase(Locale.US);
		int just = ImageProcessor.LEFT_JUSTIFY;
		if (str.equals("center"))
			just = ImageProcessor.CENTER_JUSTIFY;
		else if (str.equals("right"))
			just = ImageProcessor.RIGHT_JUSTIFY;
		justification = just;
	}

	void changeValues() {
		double darg1 = getFirstArg();
		double darg2 = getNextArg();
		double darg3 = getLastArg();
		ImagePlus imp = getImage();
		ImageProcessor ip = getProcessor();
		Roi roi = imp.getRoi();
		ImageProcessor mask = null;
		if (roi==null || !roi.isArea()) {
			ip.resetRoi();
			roi = null;
		} else {
			ip.setRoi(roi);
			mask = ip.getMask();
			if (mask!=null) ip.snapshot();
		}
		int xmin=0, ymin=0, xmax=imp.getWidth(), ymax=imp.getHeight();
		if (roi!=null) {
			Rectangle r = roi.getBounds();
			xmin=r.x; ymin=r.y; xmax=r.x+r.width; ymax=r.y+r.height;
		}
		boolean isFloat = getType()==ImagePlus.GRAY32;
		if (imp.getBitDepth()==24) {
			darg1 = (int)darg1&0xffffff;
			darg2 = (int)darg2&0xffffff;
		}
		double v;
		for (int y=ymin; y<ymax; y++) {
			for (int x=xmin; x<xmax; x++) {
				v = isFloat?ip.getPixelValue(x,y):ip.getPixel(x,y)&0xffffff;
				if (v>=darg1 && v<=darg2) {
					if (isFloat)
						ip.putPixelValue(x, y, darg3);
					else
						ip.putPixel(x, y, (int)darg3);
				}
			}
		}
		if (mask!=null) ip.reset(mask);
		if (imp.getType()==ImagePlus.GRAY16 || imp.getType()==ImagePlus.GRAY32)
			ip.resetMinAndMax();
		imp.updateAndDraw();
		updateNeeded = false;
	}

	void requires() {
		if (IJ.versionLessThan(getStringArg()))
			interp.done = true;
	}

	Random ran;	
	double random() {
		interp.getParens();
 		if (ran==null)
			ran = new Random();
		return ran.nextDouble();
	}
	
	//void setSeed() {
	//	long seed = (long)getArg();
	//	if (ran==null)
	//		ran = new Random(seed);
	//	else
	//		ran.setSeed(seed);
	//}

	double getResult() {
		interp.getLeftParen();
		String column = getString();
		int row = -1;
		if (interp.nextNonEolToken()==',') {
			interp.getComma();
			row = (int)interp.getExpression();
		}
		interp.getRightParen();
		ResultsTable rt = Analyzer.getResultsTable();
		int counter = rt.getCounter();
		if (counter==0)
			interp.error("\"Results\" table empty");
		if (row==-1) row = counter-1;
		if (row<0 || row>=counter)
			interp.error("Row ("+row+") out of range");
		int col = rt.getColumnIndex(column);
		if (!rt.columnExists(col))
			return Double.NaN;
		else
   			return rt.getValueAsDouble(col, row);
	}

	String getResultLabel() {
		int row = (int)getArg();
		ResultsTable rt = Analyzer.getResultsTable();
		int counter = rt.getCounter();
		if (counter==0)
			interp.error("\"Results\" table empty");
		if (row<0 || row>=counter)
			interp.error("Row ("+row+") out of range");
		String label = rt.getLabel(row);
   		return label!=null?label:"";
	}

	void setResult() {
		interp.getLeftParen();
		String column = getString();
		interp.getComma();
		int row = (int)interp.getExpression();
		interp.getComma();
		double value = 0.0;
		String label = null;
		if (column.equals("Label"))
			label = getString();
		else
			value = interp.getExpression();		
		interp.getRightParen();
		ResultsTable rt = Analyzer.getResultsTable();
		if (row<0 || row>rt.getCounter())
			interp.error("Row ("+row+") out of range");
		if (row==rt.getCounter())
			rt.incrementCounter();
		try {
			if (label!=null)
				rt.setLabel(label, row);
			else
				rt.setValue(column, row, value);
		} catch (Exception e) {
			interp.error(""+e.getMessage());
		}
	}
	
	void updateResults() {
		interp.getParens();
		ResultsTable rt = Analyzer.getResultsTable();
		rt.show("Results");
	}

	double getNumber() {
		String prompt = getFirstString();
		double defaultValue = getLastArg();
		String title = interp.macroName!=null?interp.macroName:"";
		if (title.endsWith(" Options"))
			title = title.substring(0, title.length()-8);
		GenericDialog gd = new GenericDialog(title);
		int decimalPlaces = (int)defaultValue==defaultValue?0:2;
		gd.addNumericField(prompt, defaultValue, decimalPlaces);
		gd.showDialog();
		if (gd.wasCanceled()) {
			interp.done = true;
			return defaultValue;
		}
		double v = gd.getNextNumber();
		if (gd.invalidNumber())
			return defaultValue;
		else
			return v;
	}

	double getBoolean() {
		String prompt = getStringArg();
		String title = interp.macroName!=null?interp.macroName:"";
		if (title.endsWith(" Options"))
			title = title.substring(0, title.length()-8);
		YesNoCancelDialog d = new YesNoCancelDialog(IJ.getInstance(), title, prompt);
		if (d.cancelPressed()) {
			interp.done = true;
			return 0.0;
		} else if (d.yesPressed())
			return 1.0;
		else
			return 0.0;
	}

	double getBoolean2() {
		String prompt = getFirstString();
		interp.getComma();
		double defaultValue = interp.getBooleanExpression();
		interp.checkBoolean(defaultValue);
		interp.getRightParen();
		String title = interp.macroName!=null?interp.macroName:"";
		if (title.endsWith(" Options"))
			title = title.substring(0, title.length()-8);
		GenericDialog gd = new GenericDialog(title);
		gd.addCheckbox(prompt, defaultValue==1.0?true:false);
		gd.showDialog();
		if (gd.wasCanceled()) {
			interp.done = true;
			return 0.0;
		}
		return gd.getNextBoolean()?1.0:0.0;
	}
	
	String getStringDialog() {
		interp.getLeftParen();
		String prompt = getString();
		interp.getComma();
		String defaultStr = getString();
		interp.getRightParen();
		
		String title = interp.macroName!=null?interp.macroName:"";
		if (title.endsWith(" Options"))
			title = title.substring(0, title.length()-8);
		GenericDialog gd = new GenericDialog(title);
		gd.addStringField(prompt, defaultStr, 20);
		gd.showDialog();
		String str = "";
		if (gd.wasCanceled())
			interp.done = true;
		else
			str = gd.getNextString();
   		return str;
	}

	String d2s() {
		return IJ.d2s(getFirstArg(), (int)getLastArg());
	}

	String toString(int base) {
		int arg = (int)getArg();
		if (base==2)
			return Integer.toBinaryString(arg);
		else
			return Integer.toHexString(arg);
	}
	
	double getStackSize() {
		interp.getParens();
		return getImage().getStackSize();
	}
	
	double getImageCount() {
		interp.getParens();
		return WindowManager.getImageCount();
	}
	
	double getResultsCount() {
		interp.getParens();
		return Analyzer.getResultsTable().getCounter();
	}

	void getCoordinates() {
		Variable xCoordinates = getFirstArrayVariable();
		Variable yCoordinates = getLastArrayVariable();
		resetImage();
		ImagePlus imp = getImage();
		Roi roi = imp.getRoi();
		if (roi==null)
			interp.error("Selection required");
		Polygon p = roi.getPolygon();
    	Variable[] xa = new Variable[p.npoints];
    	for (int i=0; i<p.npoints; i++)
    		xa[i] = new Variable(p.xpoints[i]);
		xCoordinates.setArray(xa);
    	Variable[] ya = new Variable[p.npoints];
    	for (int i=0; i<p.npoints; i++)
    		ya[i] = new Variable(p.ypoints[i]);
		yCoordinates.setArray(ya);
	}
	
	Variable[] getProfile() {
		interp.getParens();
		ImagePlus imp = getImage();
		if (imp.getRoi()==null)
			interp.error("Selection required");
		ProfilePlot pp = new ProfilePlot(imp, IJ.altKeyDown());
		double[] array = pp.getProfile();
		if (array==null)
			{interp.done=true; return null;}
		else
			return new Variable(array).getArray();
	}

	Variable[] newArray() {
		interp.getLeftParen();
		int next = interp.nextNonEolToken();
		if (next==STRING_CONSTANT || interp.nextNextNonEolToken()==','
		|| next=='-' || next==PI)
			return initNewArray();
		int size = (int)interp.getExpression();
		interp.getRightParen();
    	Variable[] array = new Variable[size];
    	for (int i=0; i<size; i++)
    		array[i] = new Variable();
    	return array;
	}
	
	Variable[] split() {
		String s1 = getFirstString();
		String s2 = null;
		if (interp.nextToken()==')')
			interp.getRightParen();
		else
			s2 = getLastString();
		if (s1==null) return null;
		String[] strings = (s2==null||s2.equals(""))?Tools.split(s1):Tools.split(s1, s2);
    	Variable[] array = new Variable[strings.length];
    	for (int i=0; i<strings.length; i++)
    		array[i] = new Variable(0, 0.0, strings[i]);
    	return array;
	}

	Variable[] getFileList() {
		String dir = getStringArg();
		File f = new File(dir);
		if (!f.exists() || !f.isDirectory())
			return new Variable[0];
		String[] list = f.list();
		if (list==null)
			return new Variable[0];
		if (System.getProperty("os.name").indexOf("Linux")!=-1)
			ij.util.StringSorter.sort(list);
    	File f2;
    	int hidden = 0;
    	for (int i=0; i<list.length; i++) {
    		if (list[i].startsWith(".")) {
    			list[i] = null;
    			hidden++;
    		} else {
    			f2 = new File(dir, list[i]);
    			if (f2.isDirectory())
    				list[i] = list[i] + "/";
    		}
    	}
    	int n = list.length-hidden;
		if (n<=0)
			return new Variable[0];
    	if (hidden>0) {
			String[] list2 = new String[n];
			int j = 0;
			for (int i=0; i<list.length; i++) {
				if (list[i]!=null)
					list2[j++] = list[i];
			}
			list = list2;
		}
    	Variable[] array = new Variable[n];
    	for (int i=0; i<n; i++)
    		array[i] = new Variable(0, 0.0, list[i]);
    	return array;
	}
	
	Variable[] initNewArray() {
		Vector vector = new Vector();
		int size = 0;
		do {
		    Variable v = new Variable();
			if (interp.nextNonEolToken()==STRING_CONSTANT)
				v.setString(getString());
			else
				v.setValue(interp.getExpression());
			vector.addElement(v);
			size++;
			interp.getToken();				
		} while (interp.token==',');
		if (interp.token!=')')
			interp.error("';' expected");
    	Variable[] array = new Variable[size];
		vector.copyInto((Variable[])array);
    	return array;
	}

	String fromCharCode() {
		char[] chars = new char[100];
		int count = 0;
		interp.getLeftParen();
		while(interp.nextToken()!=')') {
			int value = (int)interp.getExpression();
			if (value<0 || value>65535)
				interp.error("Value (" + value + ") out of 0-65535 range");
			chars[count++] = (char)value;
			if (interp.nextToken()==',')
				interp.getToken();
		}
		interp.getRightParen();		
    	return new String(chars, 0, count);
	}

	String getInfo() {
		if (interp.nextNextNonEolToken()==STRING_CONSTANT
		|| (interp.nextNonEolToken()=='('&&interp.nextNextNonEolToken()!=')'))
			return getInfo(getStringArg());
		else {
			interp.getParens();
			return getWindowContents();
		}
	}
	
	String getInfo(String key) {
			if (key.equals("image.subtitle")) {
				ImagePlus imp = getImage();
				ImageWindow win = imp.getWindow();
				return win!=null?win.createSubtitle():"";
			} else if (key.equals("slice.label")) {
				ImagePlus imp = getImage();
				if (imp.getStackSize()==1) return "";
				String label = imp.getStack().getShortSliceLabel(imp.getCurrentSlice());
				return label!=null?label:"";
			} else if (key.equals("window.contents")) {
				return getWindowContents();
			} else if (key.equals("image.description")) {
				String description = "";
				FileInfo fi = getImage().getOriginalFileInfo();
				if (fi!=null) description = fi.description;
				if  (description==null) description = "";
				return description;
			} else {
				String value = "";
				try {value = System.getProperty(key);}
				catch (Exception e) {};
				return value!=null?value:"";
			}
	}
	
	String getWindowContents() {
		Frame frame = WindowManager.getFrontWindow();
		if (frame!=null && frame instanceof TextWindow) {
			TextPanel tp = ((TextWindow)frame).getTextPanel();
			return tp.getText();			
		} else if (frame!=null && frame instanceof Editor) {
			return ((Editor)frame).getText();			
		} else if (frame!=null && frame instanceof Recorder) {
			return ((Recorder)frame).getText();			
		} else
			return getImageInfo();
	}
	
	String getImageInfo() {		
		ImagePlus imp = getImage();
		Info infoPlugin = new Info();
		return infoPlugin.getImageInfo(imp, getProcessor());
	}

	public String getDirectory() {
		String dir = IJ.getDirectory(getStringArg());
		if (dir==null) dir = "";
		return dir;
	}

	double getSelectionType() {
		interp.getParens();
		double type = -1;
		ImagePlus imp = getImage();
		Roi roi = imp.getRoi();
		if (roi!=null)
			type = roi.getType();
		return type;
	}

	void showMessage(boolean withCancel) {
		String message;
		interp.getLeftParen();
		String title = getString();
		if (interp.nextToken()==',') {
			interp.getComma();
			message = getString();
		} else {
			message = title;
			title = "";
		}
		interp.getRightParen();
		if (withCancel)
			IJ.showMessageWithCancel(title, message);
		else
			IJ.showMessage(title, message);
	}
	
	double lengthOf() {
		int length = 0;
		interp.getLeftParen();
		switch (interp.nextToken()) {
			case STRING_CONSTANT:
			case STRING_FUNCTION:
			case USER_FUNCTION:
				length = getString().length();
				break; 
			case WORD:
				if (pgm.code[interp.pc+2]=='[') {
					length = getString().length();
					break;
				}
				interp.getToken();
				Variable v = interp.lookupVariable();
				if (v==null) return 0.0;
				String s = v.getString();
				if (s!=null)
					length = s.length();
				else {
					Variable[] array = v.getArray();
					if (array!=null)
						length = array.length;
					else
						interp.error("String or array expected");
				}					
				break;
			default:
				interp.error("String or array expected");
		}
		interp.getRightParen();
		return length;
	}
	
	void getCursorLoc() {
		Variable x = getFirstVariable();
		Variable y = getNextVariable();
		Variable z = getNextVariable();
		Variable flags = getLastVariable();
		ImagePlus imp = getImage();
		ImageCanvas ic = imp.getCanvas();
		if (ic==null) return;
		Point p = ic.getCursorLoc();
		x.setValue(p.x);
		y.setValue(p.y);
		z.setValue(imp.getCurrentSlice()-1);
		flags.setValue(ic.getModifiers());
	}
	
	void getLine() {
		Variable vx1 = getFirstVariable();
		Variable vy1 = getNextVariable();
		Variable vx2 = getNextVariable();
		Variable vy2 = getNextVariable();
		Variable lineWidth = getLastVariable();
		resetImage();
		ImagePlus imp = getImage();
		double x1=-1, y1=-1, x2=-1, y2=-1;
		Roi roi = imp.getRoi();
		if (roi!=null && roi.getType()==Roi.LINE) {
			Line line = (Line)roi;
			x1=line.x1d; y1=line.y1d; x2=line.x2d; y2=line.y2d;
		}
		vx1.setValue(x1);
		vy1.setValue(y1);
		vx2.setValue(x2);
		vy2.setValue(y2);				
		lineWidth.setValue(Line.getWidth());				
	}
	
	void getVoxelSize() {
		Variable width = getFirstVariable();
		Variable height = getNextVariable();
		Variable depth = getNextVariable();
		Variable unit = getLastVariable();
		resetImage();
		ImagePlus imp = getImage();
		Calibration cal = imp.getCalibration();
		width.setValue(cal.pixelWidth);
		height.setValue(cal.pixelHeight);
		depth.setValue(cal.pixelDepth);
		unit.setString(cal.getUnits());
	}
	
	void getHistogram() {
		interp.getLeftParen();
		Variable values = null;
		if (interp.nextToken()==NUMBER)
			interp.getExpression();
		else
			values = getArrayVariable();
		Variable counts = getNextArrayVariable();
		interp.getComma();
		int nBins = (int)interp.getExpression();
		ImagePlus imp = getImage();
		double histMin=0.0, histMax=0.0;
		boolean setMinMax = false;
		int bitDepth = imp.getBitDepth();
		if (interp.nextToken()==',') {
			histMin = getNextArg();
			histMax = getLastArg();
			if (bitDepth==8 || bitDepth==24)
				interp.error("16 or 32-bit image required to set histMin and histMax");
			setMinMax = true;
		} else 
			interp.getRightParen();
		if ((bitDepth==8||bitDepth==24) && nBins!=256)
			interp.error("Bin count ("+nBins+") must be 256 for 8-bit and RGB images");
		if (nBins==65536 && bitDepth==16) {
			Variable[] array = counts.getArray();
			int[] hist = getProcessor().getHistogram();
			if (array!=null && array.length==nBins) {
				for (int i=0; i<nBins; i++)
					array[i].setValue(hist[i]);
			} else
				counts.setArray(new Variable(hist).getArray());
			return;
		}
		ImageStatistics stats;
		if (setMinMax)
			stats = imp.getStatistics(AREA+MEAN+MODE+MIN_MAX, nBins, histMin, histMax);
		else
			stats = imp.getStatistics(AREA+MEAN+MODE+MIN_MAX, nBins);
		if (values!=null) {
			Calibration cal = imp.getCalibration();
			double[] array = new double[nBins];
			double value = cal.getCValue(stats.histMin);
			double inc = 1.0;
			if (bitDepth==16 || bitDepth==32 || cal.calibrated())
				inc = (cal.getCValue(stats.histMax) - cal.getCValue(stats.histMin))/stats.nBins;
			for (int i=0; i<nBins; i++) {
				array[i] = value;
				value += inc;
			}
			values.setArray(new Variable(array).getArray());
		}
		Variable[] array = counts.getArray();
		if (array!=null && array.length==nBins) {
			for (int i=0; i<nBins; i++)
				array[i].setValue(stats.histogram[i]);
		} else
			counts.setArray(new Variable(stats.histogram).getArray());
	}
	
	void getLut() {
		Variable reds = getFirstArrayVariable();
		Variable greens = getNextArrayVariable();
		Variable blues = getLastArrayVariable();
		resetImage();
		ImagePlus imp = getImage();
		IndexColorModel cm = null;
		if (imp.isComposite())
			cm = ((CompositeImage)imp).getChannelLut();
		else {
			ImageProcessor ip = imp.getProcessor();
			if (ip instanceof ColorProcessor)
				interp.error("Non-RGB image expected");
			cm = (IndexColorModel)ip.getColorModel();
		}
		int mapSize = cm.getMapSize();
		byte[] rLUT = new byte[mapSize];
		byte[] gLUT = new byte[mapSize];
		byte[] bLUT = new byte[mapSize];
		cm.getReds(rLUT); 
		cm.getGreens(gLUT); 
		cm.getBlues(bLUT);
		reds.setArray(new Variable(rLUT).getArray());
		greens.setArray(new Variable(gLUT).getArray());
		blues.setArray(new Variable(bLUT).getArray());
	}

	void setLut() {
		double[] reds = getFirstArray();
		double[] greens = getNextArray();
		double[] blues = getLastArray();
		int length = reds.length;		
		if (greens.length!=length || blues.length!=length)
			interp.error("Arrays are not the same length");
		resetImage();
		ImagePlus imp = getImage();
		if (imp.getBitDepth()==24)
			interp.error("Non-RGB image expected");
		ImageProcessor ip = getProcessor();
		byte[] r = new byte[length];
		byte[] g = new byte[length];
		byte[] b = new byte[length];
		for (int i=0; i<length; i++) {
			r[i] = (byte)reds[i];
			g[i] = (byte)greens[i];
			b[i] = (byte)blues[i];
		}
		LUT lut = new LUT(8, length, r, g, b);
		if (imp.isComposite())
			((CompositeImage)imp).setChannelLut(lut);
		else
			ip.setColorModel(lut);
		imp.updateAndDraw();
		updateNeeded = false;
	}

	void getThreshold() {
		Variable lower = getFirstVariable();
		Variable upper = getLastVariable();
		ImagePlus imp = getImage();
		ImageProcessor ip = getProcessor();
		double t1 = ip.getMinThreshold();
		double t2 = ip.getMaxThreshold();
		if (t1==ImageProcessor.NO_THRESHOLD) {
			t1 = -1;
			t2 = -1;
		} else if (imp.getBitDepth()==16) {
			Calibration cal = imp.getCalibration();
			t1 = cal.getCValue(t1); 
			t2 = cal.getCValue(t2); 
		}

		lower.setValue(t1);
		upper.setValue(t2);
	}

	void getPixelSize() {
		Variable unit = getFirstVariable();
		Variable width = getNextVariable();
		Variable height = getNextVariable();
		Variable depth = null;
		if (interp.nextToken()==',')
			depth = getNextVariable();
		interp.getRightParen();
		Calibration cal = getImage().getCalibration();
		unit.setString(cal.getUnits());
		width.setValue(cal.pixelWidth);
		height.setValue(cal.pixelHeight);
		if (depth!=null)
		depth.setValue(cal.pixelDepth);
	}

	void makeSelection() {
        String type = null;
        int roiType = -1;
        interp.getLeftParen();
		if (isStringArg()) {
			type = getString().toLowerCase();
			roiType = Roi.POLYGON;
			if (type.indexOf("free")!=-1)
				roiType = Roi.FREEROI;
			if (type.indexOf("traced")!=-1)
				roiType = Roi.TRACED_ROI;
			if (type.indexOf("line")!=-1) {
				if (type.indexOf("free")!=-1)
					roiType = Roi.FREELINE;
				else
					roiType = Roi.POLYLINE;
			}
			if (type.indexOf("angle")!=-1)
				roiType = Roi.ANGLE;
			if (type.indexOf("point")!=-1)
				roiType = Roi.POINT;
		} else {
			roiType = (int)interp.getExpression();
			if (roiType<0 || roiType==Roi.COMPOSITE)
				interp.error("Invalid selection type ("+roiType+")");
			if (roiType==Roi.RECTANGLE) roiType = Roi.POLYGON;		
			if (roiType==Roi.OVAL) roiType = Roi.FREEROI;		
		}
		double[] x = getNextArray();
		double[] y = getLastArray();
		int n = x.length;		
		if (y.length!=n)
			interp.error("Arrays are not the same length");
		resetImage();
		ImagePlus imp = getImage();
		int[] xcoord = new int[n];
		int[] ycoord = new int[n];
		int height = imp.getHeight();
		for (int i=0; i<n; i++) {
			xcoord[i] = (int)Math.round(x[i]);
			ycoord[i] = (int)Math.round(y[i]);
		}
		Roi roi = null;
		if (roiType==Roi.LINE) {
			if (xcoord.length!=2)
				interp.error("2 element arrays expected");
			roi = new Line(xcoord[0], ycoord[0], xcoord[1], ycoord[1]);
		} else if (roiType==Roi.POINT)
			roi = new PointRoi(xcoord, ycoord, n);
		else
			roi = new PolygonRoi(xcoord, ycoord, n, roiType);
		Roi previousRoi = imp.getRoi();
		if (shiftKeyDown||altKeyDown) imp.saveRoi();
		imp.setRoi(roi);
		if (roiType==Roi.POLYGON || roiType==Roi.FREEROI) {
			roi = imp.getRoi();
			if (previousRoi!=null && roi!=null)
				updateRoi(roi); 
		}
		updateNeeded = false;
	}

	void doPlot() {
		interp.getToken();
		if (interp.token!='.')
			interp.error("'.' expected");
		interp.getToken();
		if (!(interp.token==WORD || interp.token==PREDEFINED_FUNCTION))
			interp.error("Function name expected: ");
		String name = interp.tokenString;
		if (name.equals("create")) {
			newPlot();
			return;
		}
		if (plot==null)
			interp.error("No plot defined");
		if (name.equals("show")) {
		    showPlot();
		    return;
		} else if (name.equals("update")) {
		    updatePlot(); 
		    return;
		} else if (name.equals("setLimits")) {
			plot.setLimits(getFirstArg(), getNextArg(), getNextArg(), getLastArg());
		    return;
		} else if (name.equals("addText") || name.equals("drawLabel")) {
		    addPlotText(); 
		    return;
		} else if (name.equals("setColor")) {
		    setPlotColor(); 
		    return;
		} else if (name.equals("add")) {
			String arg = getFirstString();
			arg = arg.toLowerCase(Locale.US);
			int what = Plot.CIRCLE;
			if (arg.indexOf("curve")!=-1 || arg.indexOf("line")!=-1)
				what = Plot.LINE;
			else if (arg.indexOf("box")!=-1)
				what = Plot.BOX;
			else if (arg.indexOf("triangle")!=-1)
				what = Plot.TRIANGLE;
			else if (arg.indexOf("cross")!=-1)
				what = Plot.CROSS;		
			else if (arg.indexOf("dot")!=-1)
				what = Plot.DOT;		
			else if (arg.indexOf("x")!=-1)
				what = Plot.X;
			else if (arg.indexOf("error")!=-1)
				what = -1;
		    addToPlot(what); 
		    return;
		} else if (name.startsWith("setLineWidth")) {
		    plot.setLineWidth((int)getArg()); 
		    return;
		} else if (name.startsWith("setJustification")) {
		    doFunction(SET_JUSTIFICATION); 
		    return;
		} else
			interp.error("Unrecognized plot function");
	}

	void newPlot() {
		String title = getFirstString();
		String xLabel = getNextString();
		String yLabel = getNextString();
		double[] x, y;
		if (interp.nextToken()==')')
			x = y = null;
		else {
			x = getNextArray();
			if (interp.nextToken()==')') {
				y = x;
				x = new double[y.length];
				for (int i=0; i<y.length; i++)
					x[i] = i;
			} else
				y = getNextArray();
		}
		interp.getRightParen();
		plot = new Plot(title, xLabel, yLabel, x, y);							
	}
	
	void showPlot() {
		if (plot!=null) {
			PlotWindow plotWindow = plot.show();
			if (plotWindow!=null)
				plotID = plotWindow.getImagePlus().getID();
		}
		plot = null;
		interp.getParens();			
	}

	void updatePlot() {
		if (plot!=null) {
			ImagePlus plotImage = WindowManager.getImage(plotID);
			ImageWindow win = plotImage!=null?plotImage.getWindow():null;
			if (win!=null)
				((PlotWindow)win).drawPlot(plot);
			else {
				PlotWindow plotWindow = plot.show();
				plotID = plotWindow.getImagePlus().getID();
			}
		}
		plot = null;
		interp.getParens();			
	}
	
	void addPlotText() {
		String str = getFirstString();
		double x = getNextArg();
		double y = getLastArg();
		plot.setJustification(justification);
		plot.addLabel(x, y, str);
	}

	void setPlotColor() {
		interp.getLeftParen();
		plot.setColor(getColor());
		interp.getRightParen();
	}

	void addToPlot(int what) {
		double[] x = getNextArray();
		double[] y;
		if (interp.nextToken()==')') {
			y = x;
			x = new double[y.length];
			for (int i=0; i<y.length; i++)
				x[i] = i;
		} else {
			interp.getComma();
			y = getNumericArray();
		}
		interp.getRightParen();
		if (what==-1)
			plot.addErrorBars(y);
		else			
			plot.addPoints(x, y, what);		
	}
	
	void getBounds() {
		Variable x = getFirstVariable();
		Variable y = getNextVariable();
		Variable width = getNextVariable();
		Variable height = getLastVariable();
		resetImage();
		ImagePlus imp = getImage();
		Roi roi = imp.getRoi();
		if (roi!=null) {
			Rectangle r = roi.getBounds();
			x.setValue(r.x);
			y.setValue(r.y);
			width.setValue(r.width);
			height.setValue(r.height);
		} else {
			x.setValue(0);
			y.setValue(0);
			width.setValue(imp.getWidth());
			height.setValue(imp.getHeight());
		}
	}

	String substring() {
		String s = getFirstString();
		int index1 = (int)getNextArg();
		int index2 = (int)getLastArg();
		if (index1>index2)
			interp.error("beginIndex>endIndex");
		checkIndex(index1, 0, s.length());
		checkIndex(index2, 0, s.length());
		return s.substring(index1, index2);
	}

	int indexOf() {
		String s1 = getFirstString();
		String s2 = getNextString();
		int fromIndex = 0;
		if (interp.nextToken()==',') {
			fromIndex = (int)getLastArg();
			checkIndex(fromIndex, 0, s1.length()-1);
		} else
			interp.getRightParen();			
		if (fromIndex==0)
			return s1.indexOf(s2);
		else
			return s1.indexOf(s2, fromIndex);
	}

	int startsWithEndsWith(int type) {
		String s1 = getFirstString();
		String s2 = getLastString();
		if (type==STARTS_WITH)
			return s1.startsWith(s2)?1:0;
		else
			return s1.endsWith(s2)?1:0;
	}

	double isActive() {
		int id = (int)getArg();
		ImagePlus imp = WindowManager.getCurrentImage();
		if (imp==null || imp.getID()!=id)
			return 0.0; //false
		else
			return 1.0; //true
	}
	
	double isOpen() {
        interp.getLeftParen();
		if (isStringArg()) {
			String title = getString();
			interp.getRightParen();
			boolean open = WindowManager.getFrame(title)!=null;
			if (open)
				return 1.0;
			else if (Interpreter.isBatchMode() && Interpreter.imageTable!=null) {
				for (Enumeration en=Interpreter.imageTable.elements(); en.hasMoreElements();) {
					ImagePlus imp = (ImagePlus)en.nextElement();
					if (imp!=null && imp.getTitle().equals(title))
						return 1.0;
				}
			}
			return 0.0;
		} else {
			int id = (int)interp.getExpression();
			interp.getRightParen();
			return WindowManager.getImage(id)==null?0.0:1.0;
		}
	}

	boolean isStringArg() {
		int nextToken = pgm.code[interp.pc+1];
		int tok = nextToken&0xff;
		if (tok==STRING_CONSTANT||tok==STRING_FUNCTION) return true;
		if (tok!=WORD) return false;
		Variable v = interp.lookupVariable(nextToken>>TOK_SHIFT);
		if (v==null) return false;
		int type = v.getType();
		if (type!=Variable.ARRAY)
			return v.getType()==Variable.STRING;
		Variable[] array = v.getArray();
		if (array.length==0) return false;
		return array[0].getType()==Variable.STRING;
	}

	void exit() {
		String msg = null;
		if (interp.nextToken()=='(') {
			interp.getLeftParen();
			if (isStringArg())
				msg = getString();
			interp.getRightParen();
		}
		interp.finishUp();
		if (msg!=null)
			IJ.showMessage("Macro", msg);
		throw new RuntimeException(Macro.MACRO_CANCELED);
	}
	
	void showProgress() {
		ImageJ ij = IJ.getInstance();
		ij.gui.ProgressBar progressBar = ij!=null?ij.getProgressBar():null;
		interp.getLeftParen();
		double arg1 = interp.getExpression();
		if (interp.nextToken()==',') {
			interp.getComma();
			double arg2 = interp.getExpression();
			if (progressBar!=null) progressBar.show((arg1+1.0)/arg2, true);						
		} else 
			if (progressBar!=null) progressBar.show(arg1, true);
		interp.getRightParen();
		interp.showingProgress = true; 	
	}
	
	void saveSettings() {
		interp.getParens();
		usePointerCursor = Prefs.usePointerCursor;
		hideProcessStackDialog = IJ.hideProcessStackDialog;
		divideByZeroValue = FloatBlitter.divideByZeroValue;
		jpegQuality = JpegWriter.getQuality();
		lineWidth = Line.getWidth();
		doScaling = ImageConverter.getDoScaling();
		weightedColor = Prefs.weightedColor;
		weights = ColorProcessor.getWeightingFactors();
		interpolateScaledImages = Prefs.interpolateScaledImages;
		open100Percent = Prefs.open100Percent;
		blackCanvas = Prefs.blackCanvas;
		useJFileChooser = Prefs.useJFileChooser;
		debugMode = IJ.debugMode;
		foregroundColor =Toolbar.getForegroundColor();
		backgroundColor =Toolbar.getBackgroundColor();
		roiColor = Roi.getColor();
		pointAutoMeasure = Prefs.pointAutoMeasure;
		requireControlKey = Prefs.requireControlKey;
		useInvertingLut = Prefs.useInvertingLut;
		doubleBuffer = Prefs.doubleBuffer;
		saveSettingsCalled = true;
		measurements = Analyzer.getMeasurements();
		decimalPlaces = Analyzer.getPrecision();
		blackBackground = Prefs.blackBackground;
	}
	
	void restoreSettings() {
		interp.getParens();
		if (!saveSettingsCalled)
			interp.error("saveSettings() not called");
		Prefs.usePointerCursor = usePointerCursor;
		IJ.hideProcessStackDialog = hideProcessStackDialog;
		FloatBlitter.divideByZeroValue = divideByZeroValue;
		JpegWriter.setQuality(jpegQuality);
		Line.setWidth(lineWidth);
		ImageConverter.setDoScaling(doScaling);
		if (weightedColor!=Prefs.weightedColor) {
			ColorProcessor.setWeightingFactors(weights[0], weights[1], weights[2]);
			Prefs.weightedColor = !(weights[0]==1d/3d && weights[1]==1d/3d && weights[2]==1d/3d);
		}
		Prefs.interpolateScaledImages = interpolateScaledImages;
		Prefs.open100Percent = open100Percent;
		Prefs.blackCanvas = blackCanvas;
		Prefs.useJFileChooser = useJFileChooser;
		Prefs.useInvertingLut = useInvertingLut;
		Prefs.doubleBuffer = doubleBuffer;
		IJ.debugMode = debugMode;
		Toolbar.setForegroundColor(foregroundColor);
		Toolbar.setBackgroundColor(backgroundColor);
		Roi.setColor(roiColor);
		Analyzer.setMeasurements(measurements);
		Analyzer.setPrecision(decimalPlaces);
		ColorProcessor.setWeightingFactors(weights[0], weights[1], weights[2]);
		Prefs.blackBackground = blackBackground;
	}
	
	void setKeyDown() {
		String keys = getStringArg();
		keys = keys.toLowerCase(Locale.US);
		altKeyDown = keys.indexOf("alt")!=-1;
		if (altKeyDown)
			IJ.setKeyDown(KeyEvent.VK_ALT);
		else
			IJ.setKeyUp(KeyEvent.VK_ALT);
		shiftKeyDown = keys.indexOf("shift")!=-1;
		if (shiftKeyDown)
			IJ.setKeyDown(KeyEvent.VK_SHIFT);
		else
			IJ.setKeyUp(KeyEvent.VK_SHIFT);		
		if (keys.equals("space"))
			IJ.setKeyDown(KeyEvent.VK_SPACE);
		else
			IJ.setKeyUp(KeyEvent.VK_SPACE);
		if (keys.indexOf("esc")!=-1)
			abortPluginOrMacro();
		else
			interp.keysSet = true;
	}
	
	void abortPluginOrMacro() {
		Interpreter.abortPrevious();
		IJ.setKeyDown(KeyEvent.VK_ESCAPE);
		ImagePlus imp = WindowManager.getCurrentImage();
		if (imp!=null) {
			ImageWindow win = imp.getWindow();
			if (win!=null) {
				win.running = false;
				win.running2 = false;
			}
		}
		//Macro.abort();
	}
	
	void open() {
		interp.getLeftParen();
		if (interp.nextToken()==')') {
			interp.getRightParen();
			IJ.open();
		} else {
			String path = getString();
			interp.getRightParen();
			IJ.open(path);
		}
		resetImage();
	}
	
	double roiManager() {
		String cmd = getFirstString();
		cmd = cmd.toLowerCase();
		String path = null;
		int index=0;
		double countOrIndex=Double.NaN;
		boolean twoArgCommand = cmd.equals("open")||cmd.equals("save")||cmd.equals("rename");
		boolean select = cmd.equals("select");
		if (twoArgCommand)
			path = getLastString();
		else if (select)
			index = (int)getLastArg();
		else
			interp.getRightParen();
		if (RoiManager.getInstance()==null&&roiManager==null) {
			if (Interpreter.isBatchMode())
				roiManager = new RoiManager(true);
			else
				IJ.run("ROI Manager...");
		}
		RoiManager rm = roiManager!=null?roiManager:RoiManager.getInstance();
		if (rm==null)
			interp.error("ROI Manager not found");
		if (twoArgCommand)
			rm.runCommand(cmd, path);
		else if (select) {
			int n = rm.getList().getItemCount();
			checkIndex(index, 0, n-1);
			if (shiftKeyDown || altKeyDown) {
				rm.select(index, shiftKeyDown, altKeyDown);
				shiftKeyDown = altKeyDown = false;
			} else
				roiManagerSelect(rm, index);
		} else if (cmd.equals("count"))
			countOrIndex = rm.getList().getItemCount();
		else if (cmd.equals("index"))
			countOrIndex = rm.getList().getSelectedIndex();
		else {
			if (!rm.runCommand(cmd))
				interp.error("Invalid ROI Manager command");
		}
		return countOrIndex;			
	}
	
	void roiManagerSelect(RoiManager rm, int index) {
		int delay = 1;
		long start = System.currentTimeMillis();
		while (true) {
			rm.select(index);
			if (delay>1) IJ.wait(delay);
			if (rm.getList().isIndexSelected(index))
				break;
			//IJ.log(index+" "+delay);
			rm.select(-1); // deselect all
			IJ.wait(delay);
			delay *= 2; if (delay>32) delay=32;
			if ((System.currentTimeMillis()-start)>1000L)
				interp.error("Failed to select");
		}
	}
	
	void setFont() {
		String name = getFirstString();
		int size = (int)getNextArg();
		int style = 0;
		antialiasedText = false;
		if (interp.nextToken()==',') {
			String styles = getLastString().toLowerCase();
			if (styles.indexOf("bold")!=-1) style += Font.BOLD;
			if (styles.indexOf("italic")!=-1) style += Font.ITALIC;
			if (styles.indexOf("anti")!=-1) antialiasedText = true;
		} else
			interp.getRightParen();
		font = new Font(name, style, size);
		fontSet = false;
	}

	void getMinAndMax() {
		Variable min = getFirstVariable();
		Variable max = getLastVariable();
		ImagePlus imp = getImage();
		double v1 = imp.getDisplayRangeMin();
		double v2 = imp.getDisplayRangeMax();
		if (imp.getBitDepth()==16) {
			Calibration cal = imp.getCalibration();
			v1 = cal.getCValue(v1); 
			v2 = cal.getCValue(v2); 
		}
		min.setValue(v1);
		max.setValue(v2);
	}
	
	void selectImage() {
        interp.getLeftParen();
		if (isStringArg()) {
			selectImage(getString());
			interp.getRightParen();
		} else {
			int id = (int)interp.getExpression();
			IJ.selectWindow(id);
			interp.getRightParen();
		}
		resetImage();
	}
	
	void selectImage(String title) {
		if (Interpreter.isBatchMode()) {
			if (Interpreter.imageTable!=null) {
				for (Enumeration en=Interpreter.imageTable.elements(); en.hasMoreElements();) {
					ImagePlus imp = (ImagePlus)en.nextElement();
					if (imp!=null) {
						if (imp.getTitle().equals(title)) {
							ImagePlus imp2 = WindowManager.getCurrentImage();
							if (imp2!=null && imp2!=imp) imp2.saveRoi();
							WindowManager.setTempCurrentImage(imp);
							return;
						}
					}
				}
			}
			selectWindowManagerImage(title);
		} else
			selectWindowManagerImage(title);
	}
	
	void notFound(String title) {
		interp.error(title+" not found");
	}

	void selectWindowManagerImage(String title) {
		long start = System.currentTimeMillis();
		while (System.currentTimeMillis()-start<4000) { // 4 sec timeout
			int[] wList = WindowManager.getIDList();
			int len = wList!=null?wList.length:0;
			for (int i=0; i<len; i++) {
				ImagePlus imp = WindowManager.getImage(wList[i]);
				if (imp!=null) {
					if (imp.getTitle().equals(title)) {
						IJ.selectWindow(imp.getID());
						return;
					}
				}
			}
			IJ.wait(10);
		}
		notFound(title);
	}

	void close() {
		interp.getParens();
		ImagePlus imp = getImage();
		ImageWindow win = imp.getWindow();
		if (win!=null) {
			imp.changes = false;
			win.close();
		} else {
			imp.saveRoi();
			WindowManager.setTempCurrentImage(null);
			interp.removeBatchModeImage(imp);
		}
		resetImage();
	}
	
	void setBatchMode() {
		boolean enterBatchMode = false;
		String sarg = null;
		interp.getLeftParen();
		if (isStringArg())
			sarg = getString();
		else {
			double arg = interp.getBooleanExpression();
			interp.checkBoolean(arg);
			enterBatchMode = arg==1.0;
		}
		interp.getRightParen();
		if (!interp.isBatchMode())
			interp.calledMacro = false;
		resetImage();
		if (enterBatchMode)  { // true
			if (interp.isBatchMode()) return;
			interp.setBatchMode(true);
			ImagePlus tmp = WindowManager.getTempCurrentImage();
			if (tmp!=null)
				Interpreter.addBatchModeImage(tmp);
			return;
		}
		IJ.showProgress(0, 0);
		ImagePlus imp2 = WindowManager.getCurrentImage();
		WindowManager.setTempCurrentImage(null);
		roiManager = null;
		if (sarg==null) {  //false
			interp.setBatchMode(false);
			displayBatchModeImage(imp2);
		} else {
			Vector v = Interpreter.imageTable;
			if (v==null) return;
			interp.setBatchMode(false);
			for (int i=0; i<v.size(); i++) {
				imp2 = (ImagePlus)v.elementAt(i);
				if (imp2!=null) 
					displayBatchModeImage(imp2);
			}
		}
	}
	
	void displayBatchModeImage(ImagePlus imp2) {
		if (imp2!=null) {
			ImageWindow win = imp2.getWindow();
			if (win==null)
				imp2.show();
			else {
				if (!win.isVisible()) win.show(); 
				imp2.updateAndDraw();
			}
			Roi roi = imp2.getRoi();
			if (roi!=null) imp2.setRoi(roi);
		}
	}
	
	void setLocation() {
		int x = (int)getFirstArg();
		int y = (int)getNextArg();
		int width=0, height=0;
		if (interp.nextToken()==',') {
			width = (int)getNextArg();
			height = (int)getNextArg();
		}
		interp.getRightParen();
		if (width==0&&height==0) {
			Frame frame = WindowManager.getFrontWindow();
			if (frame!=null)
				frame.setLocation(x, y);
		} else {
			ImagePlus imp = getImage();
			ImageWindow win = imp.getWindow();
			if (win!=null)
				win.setLocationAndSize(x, y, width, height);
		}
	}
	
	void setSlice() {
		int n = (int)getArg();
		ImagePlus imp = getImage();
		int nSlices = imp.getStackSize();
		if (n==1 && nSlices==1)
			return;
		else if (n<1 || n>nSlices)
			interp.error("Argument must be >=1 and <="+nSlices);
		else {
			if (imp.isHyperStack())
				imp.setPosition(n);
			else
				imp.setSlice(n);
		}
		resetImage();
	}
	
	void newImage() {
		String title = getFirstString();
		String type = getNextString();
		int width = (int)getNextArg();
		int height = (int)getNextArg();
		int depth = (int)getLastArg();
		IJ.newImage(title, type, width, height, depth);
		resetImage();
	}

	void saveAs() {
		String format = getFirstString();
		String path =  null;
		if (interp.nextNonEolToken()==',')
			path = getLastString();
		else
			interp.getRightParen();
		IJ.saveAs(format, path);
	}

	double getZoom() {
		interp.getParens();
		ImagePlus imp = getImage();
		ImageCanvas ic = imp.getCanvas();
		if (ic==null)
			{interp.error("Image not displayed"); return 0.0;}
		else
			return ic.getMagnification();
	}
	
	void setAutoThreshold() {
		interp.getParens();
		ImagePlus imp = getImage();
		ImageProcessor ip = getProcessor();
		double min=0.0, max=0.0;
		boolean notByteData = !(ip instanceof ByteProcessor);
		if (notByteData) {
			if (ip instanceof ColorProcessor)
				interp.error("Non-RGB image expected");
			ip.resetMinAndMax();
			min = ip.getMin(); max = ip.getMax();
			ip = new ByteProcessor(ip.createImage());
		}
		ip.setRoi(imp.getRoi());
		ImageStatistics stats = ImageStatistics.getStatistics(ip, AREA+MIN_MAX+MODE, null);
		int threshold = ip.getAutoThreshold(stats.histogram);
		int count1=0, count2=0;
		for (int i=0; i<256; i++) {
			if (i<threshold)
				count1 += stats.histogram[i];
			else
				count2 += stats.histogram[i];
		}
		boolean unbalanced = (double)count1/count2>1.25 || (double)count2/count1>1.25;
		//IJ.log(unbalanced+"  "+count1+"  "+count2);
		double lower, upper;
		if (unbalanced) {
			if ((stats.max-stats.dmode)>(stats.dmode-stats.min))
				{lower=threshold; upper=255.0;}
			else
				{lower=0.0; upper=threshold;}
		} else {
			if (ip.isInvertedLut())
				{lower=threshold; upper=255.0;}
			else
				{lower=0.0; upper=threshold;}
		}
		if (notByteData) {
			if (max>min) {
				lower = min + (lower/255.0)*(max-min);
				upper = min + (upper/255.0)*(max-min);
			} else
				lower = upper = min;
		}
		if (imp.getBitDepth()==16) {
			Calibration cal = imp.getCalibration();
			lower = cal.getCValue(lower); 
			upper = cal.getCValue(upper); 
		}
		IJ.setThreshold(lower, upper);
		resetImage();
	}
	
	double parseDouble(String s) {
			if (s==null) return 0.0;
			s = s.trim();
			if (s.indexOf(' ')!=-1) s = s.substring(0, s.indexOf(' '));
			return Tools.parseDouble(s);
	}
	
	double parseInt() {
		String s = getFirstString();
		int radix = 10;
		if (interp.nextToken()==',') {
			interp.getComma();
			radix = (int)interp.getExpression();
			if (radix<2||radix>36) radix = 10;
		}
		interp.getRightParen();
		double n;
		try {
			if (radix==10) {
				n = parseDouble(s);
				if (!Double.isNaN(n)) n = Math.round(n);
			} else
				n = Integer.parseInt(s, radix);
		} catch (NumberFormatException e) {
			n = NaN;
		}
		return n;			
	}

	void print() {
		interp.inPrint = true;
		String s = getFirstString();
		if (interp.nextNonEolToken()==',') {
			if (s.startsWith("[") && s.endsWith("]")) {
				printToWindow(s);
				return;
			} else if (s.equals("~0~")) {
				if (writer==null)
					interp.error("File not open");
                String s2 = getLastString();
                if (s2.endsWith("\n"))
                    writer.print(s2);
                else
                    writer.println(s2);
				interp.inPrint = false;
				return;
			}
			StringBuffer sb = new StringBuffer(s);
			do {
				sb.append(" ");
				sb.append(getNextString());
			} while (interp.nextNonEolToken()==',');
			s = sb.toString();
		}
		interp.getRightParen();
		IJ.log(s);
		interp.inPrint = false;
	}
	
	void printToWindow(String s) {
		String title = s.substring(1, s.length()-1);
		String s2 = getLastString();
		boolean isCommand = s2.startsWith("\\");
		Frame frame = WindowManager.getFrame(title);
		if (frame==null) {
			if (isCommand) {
				interp.done = true;
				return;
			} else
				interp.error("Window not found");
		}
		boolean isEditor = frame instanceof Editor;
		if (!(isEditor || frame instanceof TextWindow))
			interp.error("Window is not text window");
		if (isEditor) {
			Editor ed = (Editor)frame;
			ed.setIsMacroWindow(true);
			if (isCommand)
				handleEditorCommand(ed, s2);
			else
				ed.append(s2);
		} else {
			TextWindow tw = (TextWindow)frame;
			if (isCommand)
				handleTextWindowCommand(tw, s2);
			else
				tw.append(s2);
		}
	}
	
	void handleEditorCommand(Editor ed, String s) {
		if (s.startsWith("\\Update:")) {
			TextArea ta = ed.getTextArea();
			ta.setText(s.substring(8, s.length()));
			ta.setEditable(false);
		} else if (s.equals("\\Close"))
			ed.close();
		else
			ed.append(s);
	}

	void handleTextWindowCommand(TextWindow tw, String s) {
		TextPanel tp = tw.getTextPanel();
		if (s.startsWith("\\Update:")) {
			int n = tp.getLineCount();
			String s2 = s.substring(8, s.length());
			if (n==0)
				tp.append(s2);
			else
				tp.setLine(n-1, s2);
		} else if (s.startsWith("\\Update")) {
			int cindex = s.indexOf(":");
			if (cindex==-1)
				{tp.append(s); return;}
			String nstr = s.substring(7, cindex);
			int line = (int)Tools.parseDouble(nstr, -1);
			if (line<0) interp.error("Row index<0 or NaN");
			int count = tp.getLineCount();
			while (line>=count) {
				tp.append("");
				count++;
			}
			String s2 = s.substring(cindex+1, s.length());
			tp.setLine(line, s2);
		} else if (s.equals("\\Clear"))
			tp.clear();
		else if (s.equals("\\Close"))
			tw.close();
		else if (s.startsWith("\\Headings:"))
			tp.setColumnHeadings(s.substring(10));
		else
			tp.append(s);
	}

	
	double isKeyDown() {
		double value = 0.0;
		String key = getStringArg().toLowerCase(Locale.US);
		if (key.indexOf("alt")!=-1) value = IJ.altKeyDown()==true?1.0:0.0;
		else if (key.indexOf("shift")!=-1) value = IJ.shiftKeyDown()==true?1.0:0.0;
		else if (key.indexOf("space")!=-1) value = IJ.spaceBarDown()==true?1.0:0.0;
		else interp.error("Invalid key");
		return value;
	}

	String runMacro(boolean eval) {
		interp.getLeftParen();
		String name = getString();
		String arg = null;
		if (interp.nextNonEolToken()==',') {
			interp.getComma();
			arg = getString();
		}
		interp.getRightParen();
		if (eval)
			return IJ.runMacro(name, arg);
		else
			return IJ.runMacroFile(name, arg);
	}

	void setThreshold() {
		double lower = getFirstArg();
		double upper = getNextArg();
		String mode = null;
		if (interp.nextNonEolToken()==',') {
			interp.getComma();
			mode = getString();
		}
		interp.getRightParen();
		IJ.setThreshold(lower, upper, mode);
		resetImage();
	}

	void drawOrFill(int type) {
		int x = (int)getFirstArg();
		int y = (int)getNextArg();
		int width = (int)getNextArg();
		int height = (int)getLastArg();
		ImageProcessor ip = getProcessor();
		if (!colorSet) setForegroundColor(ip);
		switch (type) {
			case DRAW_RECT: ip.drawRect(x, y, width, height); break;
			case FILL_RECT: ip.setRoi(x, y, width, height); ip.fill(); break;
			case DRAW_OVAL: ip.drawOval(x, y, width, height); break;
			case FILL_OVAL: ip.fillOval(x, y, width, height); break;
		}
		updateAndDraw(defaultImp);
	}

	double getScreenDimension(int type) {
		interp.getParens();
		Dimension screen = IJ.getScreenSize();
		if (type==SCREEN_WIDTH)
			return screen.width;
		else
			return screen.height;
	}

	void getStatistics(boolean calibrated) {
		Variable count = getFirstVariable();
		Variable mean=null, min=null, max=null, std=null, hist=null;
		int params = AREA+MEAN+MIN_MAX;
		interp.getToken();
		int arg = 1;
		while (interp.token==',') {
			arg++;
			switch (arg) {
				case 2: mean = getVariable(); break;
				case 3: min = getVariable(); break;
				case 4: max = getVariable(); break;
				case 5: std = getVariable(); params += STD_DEV; break;
				case 6: hist = getArrayVariable(); break;
				default: interp.error("')' expected");
			}
			interp.getToken();
		}
		if (interp.token!=')') interp.error("')' expected");
		resetImage();
		ImagePlus imp = getImage();
		Calibration cal = calibrated?imp.getCalibration():null;
		ImageProcessor ip = getProcessor();
		ip.setRoi(imp.getRoi());
		ImageStatistics stats = ImageStatistics.getStatistics(ip, params, cal);
		if (calibrated)
			count.setValue(stats.area);
		else
			count.setValue(stats.pixelCount);
		if (mean!=null) mean.setValue(stats.mean);
		if (min!=null) min.setValue(stats.min);
		if (max!=null) max.setValue(stats.max);
		if (std!=null) std.setValue(stats.stdDev);
		if (hist!=null) {
			boolean is16bit = !calibrated && ip instanceof ShortProcessor;
			int[] histogram = is16bit?stats.histogram16:stats.histogram;
		    int bins = is16bit?(int)(stats.max+1):histogram.length;
			Variable[] array = new Variable[bins];
			int hmax = is16bit?(int)stats.max:255;
			for (int i=0; i<=hmax; i++)
				array[i] = new Variable(histogram[i]);
			hist.setArray(array);
		}
	}
	
	String replace() {
		String s1 = getFirstString();
		String s2 = getNextString();
		String s3 = getLastString();
		if (s2.length()==1 && s3.length()==1)
			return s1.replace(s2.charAt(0), s3.charAt(0));
		else
			return s1.replaceAll(s2, s3);
	}
	
	void floodFill() {
		int x = (int)getFirstArg();
		int y = (int)getNextArg();
		boolean fourConnected = true;
		if (interp.nextNonEolToken()==',') {
			String s = getLastString();
			if (s.indexOf("8")!=-1)
				fourConnected = false;
		} else
			interp.getRightParen();
		ImageProcessor ip = getProcessor();
		if (!colorSet) setForegroundColor(ip);
		FloodFiller ff = new FloodFiller(ip);
		if (fourConnected)
			ff.fill(x, y);
		else
			ff.fill8(x, y);
		updateAndDraw(defaultImp);
		if (Recorder.record && pgm.hasVars)
			Recorder.record("floodFill", x, y);
	}
	
	void restorePreviousTool() {
		interp.getParens();
		Toolbar tb = Toolbar.getInstance();
		if (tb!=null) tb.restorePreviousTool();
	}
	
	void setVoxelSize() {
		double width = getFirstArg();
		double height = getNextArg();
		double depth = getNextArg();
		String unit = getLastString();
		resetImage();
		ImagePlus imp = getImage();
		Calibration cal = imp.getCalibration();
		cal.pixelWidth = width;
		cal.pixelHeight = height;
		cal.pixelDepth = depth;
		cal.setUnit(unit);
		imp.repaintWindow();
	}

	void getLocationAndSize() {
		Variable v1 = getFirstVariable();
		Variable v2 = getNextVariable();
		Variable v3 = getNextVariable();
		Variable v4 = getLastVariable();
		ImagePlus imp = getImage();
		int x=0, y=0, w=0, h=0;
		ImageWindow win = imp.getWindow();
		if (win!=null) {
			Point loc = win.getLocation();
			Dimension size = win.getSize();
			x=loc.x; y=loc.y; w=size.width; h=size.height;
		}
		v1.setValue(x);
		v2.setValue(y);
		v3.setValue(w);
		v4.setValue(h);
	}
	
	String doDialog() {
		interp.getToken();
		if (interp.token!='.')
			interp.error("'.' expected");
		interp.getToken();
		if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION))
			interp.error("Function name expected: ");
		String name = interp.tokenString;
		try {
			if (name.equals("create")) {
				gd = new GenericDialog(getStringArg());
				return null;
			}
			if (gd==null) {
				interp.error("No dialog created with Dialog.create()"); 
				return null;
			}
			if (name.equals("addString")) {
				String label = getFirstString();
				String defaultStr = getNextString();
				int columns = 8;
				if (interp.nextNonEolToken()==',')
					columns = (int)getNextArg();
				interp.getRightParen();
				gd.addStringField(label, defaultStr, columns);
			} else if (name.equals("addNumber")) {
				int columns = 6;
				String units = null;
				String prompt = getFirstString();
				double defaultNumber = getNextArg();
				int decimalPlaces = (int)defaultNumber==defaultNumber?0:3;
				if (interp.nextNonEolToken()==',') {
					decimalPlaces = (int)getNextArg();
					columns = (int)getNextArg();
					units = getLastString();
				} else
					interp.getRightParen();
				gd.addNumericField(prompt, defaultNumber, decimalPlaces, columns, units);
			} else if (name.equals("addCheckbox")) {
				gd.addCheckbox(getFirstString(), getLastArg()==1?true:false);
			} else if (name.equals("addMessage")) {
				gd.addMessage(getStringArg());
			} else if (name.equals("addChoice")) {
				String prompt = getFirstString();
				interp.getComma();
				String[] choices = getStringArray();
				String defaultChoice = null;
				if (interp.nextNonEolToken()==',') {
					interp.getComma();
					defaultChoice = getString();
				} else
					defaultChoice = choices[0];
				interp.getRightParen();
				gd.addChoice(prompt, choices, defaultChoice);
			} else if (name.equals("show")) {
				interp.getParens();
				gd.showDialog();
				if (gd.wasCanceled()) {
					interp.finishUp();
					throw new RuntimeException(Macro.MACRO_CANCELED);
				}
			} else if (name.equals("getString")) {
				interp.getParens();
				return gd.getNextString();
			} else if (name.equals("getNumber")) {
				interp.getParens();
				return ""+gd.getNextNumber();
			} else if (name.equals("getCheckbox")) {
				interp.getParens();
				return gd.getNextBoolean()==true?"1":"0";
			} else if (name.equals("getChoice")) {
				interp.getParens();
				return gd.getNextChoice();
			} else
				interp.error("Unrecognized Dialog function "+name);
		} catch (IndexOutOfBoundsException e) {
			interp.error("Dialog error");
		}
		return null;
	}
	
	void getDateAndTime() {
		Variable year = getFirstVariable();
		Variable month = getNextVariable();
		Variable dayOfWeek = getNextVariable();
		Variable dayOfMonth = getNextVariable();
		Variable hour = getNextVariable();
		Variable minute = getNextVariable();
		Variable second = getNextVariable();
		Variable millisecond = getLastVariable();
		Calendar date = Calendar.getInstance();
		year.setValue(date.get(Calendar.YEAR));
		month.setValue(date.get(Calendar.MONTH));
		dayOfWeek.setValue(date.get(Calendar.DAY_OF_WEEK)-1);
		dayOfMonth.setValue(date.get(Calendar.DAY_OF_MONTH));
		hour.setValue(date.get(Calendar.HOUR_OF_DAY));
		minute.setValue(date.get(Calendar.MINUTE));
		second.setValue(date.get(Calendar.SECOND));
		millisecond.setValue(date.get(Calendar.MILLISECOND));
	}
	
	void setMetadata() {
		String metadata = null;
		String arg1 = getFirstString();
		boolean oneArg = false;
		if (interp.nextNonEolToken()==',')
			metadata = getLastString();
		else
			interp.getRightParen();
		boolean isInfo = false;
		if (metadata==null) {
			metadata = arg1;
			oneArg = true;
			if (metadata.startsWith("Info:")) {
				metadata = metadata.substring(5);
				isInfo = true;
			}
		} else
			isInfo = arg1.startsWith("info") || arg1.startsWith("Info");
		ImagePlus imp = getImage();
		if (isInfo)
			imp.setProperty("Info", metadata);
		else {
			if (imp.getStackSize()==1) {
				if (oneArg)
					imp.setProperty("Info", metadata);
				else {
					imp.setProperty("Label", metadata);
					if (!Interpreter.isBatchMode()) imp.repaintWindow();
				}
			} else {
				imp.getStack().setSliceLabel(metadata, imp.getCurrentSlice());
				if (!Interpreter.isBatchMode()) imp.repaintWindow();
			}
		}
	}

	String getMetadata() {
		String type = "label";
		boolean noArg = true;
		if (interp.nextNonEolToken()=='(' && interp.nextNextNonEolToken()!=')') {
			type = getStringArg().toLowerCase(Locale.US);
			noArg = false;
		} else
			interp.getParens();
		ImagePlus imp = getImage();
		String metadata = null;
		if (type.indexOf("label")!=-1) {
			if (imp.getStackSize()==1) {
				metadata = (String)imp.getProperty("Label");
				if (metadata==null && noArg)
					metadata = (String)imp.getProperty("Info");
			} else 
				metadata = imp.getStack().getSliceLabel(imp.getCurrentSlice());
		} else
			metadata = (String)imp.getProperty("Info");
		if (metadata==null) metadata = "";
		return metadata;
	}

	ImagePlus getImageArg() {
		ImagePlus img = null;
		if (isStringArg()) {
			String title = getString();
			img = WindowManager.getImage(title);
		} else {
			int id = (int)interp.getExpression();
			img = WindowManager.getImage(id);
		}
		if (img==null) interp.error("Image not found");
		return img;
	}

	void imageCalculator() {
		String operator = getFirstString();
		interp.getComma();
		ImagePlus img1 = getImageArg();
		interp.getComma();
		ImagePlus img2 = getImageArg();
		interp.getRightParen();
		ImageCalculator ic = new ImageCalculator();
		ic.calculate(operator, img1, img2);
	}

	void setRGBWeights() {
		double r = getFirstArg();
		double g = getNextArg();
		double b = getLastArg();
		if (interp.rgbWeights==null)
			interp.rgbWeights = ColorProcessor.getWeightingFactors();
		ColorProcessor.setWeightingFactors(r, g, b);
	}

	void makePolygon() {
		int max = 200;
		int[] x = new int[max];
		int[] y = new int[max];
		x[0] = (int)Math.round(getFirstArg());
		y[0] = (int)Math.round(getNextArg());
		interp.getToken();
		int n = 1;
		while (interp.token==',' && n<max) {
			x[n] = (int)Math.round(interp.getExpression());
			interp.getComma();
			y[n] = (int)Math.round(interp.getExpression());
			interp.getToken();
			n++;
		}
		if (n<3)
			interp.error("Fewer than 3 points");
		if (n==max && interp.token!=')')
			interp.error("More than "+max+" points");
		ImagePlus imp = getImage();
		Roi previousRoi = imp.getRoi();
		if (shiftKeyDown||altKeyDown) imp.saveRoi();
		imp.setRoi(new PolygonRoi(x, y, n, Roi.POLYGON));
		Roi roi = imp.getRoi();
		if (previousRoi!=null && roi!=null)
			updateRoi(roi); 
		resetImage(); 
	}
	
	void updateRoi(Roi roi) {
		if (shiftKeyDown || altKeyDown)
			roi.update(shiftKeyDown, altKeyDown);
		shiftKeyDown = altKeyDown = false;
	}
	
	String doFile() {
		interp.getToken();
		if (interp.token!='.')
			interp.error("'.' expected");
		interp.getToken();
		if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION || interp.token==PREDEFINED_FUNCTION))
			interp.error("Function name expected: ");
		String name = interp.tokenString;
		if (name.equals("open"))
			return openFile();
		else if (name.equals("openAsString"))
			return openAsString(false);
		else if (name.equals("openAsRawString"))
			return openAsString(true);
		else if (name.equals("openUrlAsString"))
			return openUrlAsString();
		else if (name.equals("openDialog"))
			return openDialog();
		else if (name.equals("close"))
			return closeFile();
		else if (name.equals("separator")) {
			interp.getParens();
			return File.separator;
		} else if (name.equals("directory")) {
			interp.getParens();
			String lastDir = OpenDialog.getLastDirectory();
			return lastDir!=null?lastDir:"";
		} else if (name.equals("name")) {
			interp.getParens();
			String lastName = OpenDialog.getLastName();
			return lastName!=null?lastName:"";
		} else if (name.equals("rename")) {
			File f1 = new File(getFirstString());
			File f2 = new File(getLastString());
			if (checkPath(f1) && checkPath(f2)) 
				return f1.renameTo(f2)?"1":"0";
			else
				return "0";
		}
		File f = new File(getStringArg());
		if (name.equals("getLength")||name.equals("length"))
			return ""+f.length();
		else if (name.equals("getName"))
			return f.getName();
		else if (name.equals("getAbsolutePath"))
			return f.getAbsolutePath();
		else if (name.equals("getParent"))
			return f.getParent();
		else if (name.equals("exists"))
			return f.exists()?"1":"0";
		else if (name.equals("isDirectory"))
			return f.isDirectory()?"1":"0";
		else if (name.equals("makeDirectory")||name.equals("mkdir")) {
			f.mkdir(); return null;
		} else if (name.equals("lastModified"))
			return ""+f.lastModified();
		else if (name.equals("dateLastModified"))
			return (new Date(f.lastModified())).toString();
		else if (name.equals("delete")) {
			if (isValid(f)) 
				f.delete();
			return null;
		} else
			interp.error("Unrecognized File function "+name);
		return null;
	}
	
	boolean isValid(File f) {
		String path = f.getPath();
		if (path.equals("0") || path.equals("NaN") )
				interp.error("Invalid path");
		path = f.getAbsolutePath();
		if (!(path.indexOf("ImageJ")!=-1
		||path.startsWith(System.getProperty("java.io.tmpdir"))
		||path.startsWith(System.getProperty("user.home")))) {
			interp.error("File must be in ImageJ, home or tmp directory");
			return false;
		} else
			return true;
	}
	
	boolean checkPath(File f) {
		String path = f.getPath();
		if (path.equals("0") || path.equals("NaN")) {
				interp.error("Invalid path");
				return false;
		} else
			return true;
	}

	String openDialog() {
		String title = getStringArg();
		OpenDialog od = new OpenDialog(title, null);
		String directory = od.getDirectory();
		String name = od.getFileName();
		if (name==null)
			return "";
		else
			return directory+name;
	}

	void setSelectionName() {
		Roi roi = getImage().getRoi();
		if (roi==null)
			interp.error("No selection");
		else
			roi.setName(getStringArg());
	}

	String selectionName() {
		Roi roi = getImage().getRoi();
		String name = null;
		if (roi==null)
			interp.error("No selection");
		else
			name = roi.getName();
		return name!=null?name:"";
	}
	
	String openFile() {
		if (writer!=null) {
			interp.error("Currently, only one file can be open at a time");
			return"";
		}
		String path = getFirstString();
		String defaultName = null;
		if (interp.nextToken()==')')
			interp.getRightParen();
		else
			defaultName = getLastString();
		if (path.equals("") || defaultName!=null) {
			String title = defaultName!=null?path:"openFile";
			defaultName = defaultName!=null?defaultName:"log.txt";
			SaveDialog sd = new SaveDialog(title, defaultName, ".txt");
			if(sd.getFileName()==null) return "";
			path = sd.getDirectory()+sd.getFileName();
		} else {
			File file = new File(path);
			if (file.exists() && !(path.endsWith(".txt")||path.endsWith(".java")||path.endsWith(".xls")||path.endsWith(".ijm")))
				interp.error("File exists and suffix is not '.txt', '.java', etc.");
		}
		try {
			FileOutputStream fos = new FileOutputStream(path);
			BufferedOutputStream bos = new BufferedOutputStream(fos);
			writer = new PrintWriter(bos);
		}
		catch (IOException e) {
			interp.error("File open error \n\""+e.getMessage()+"\"\n");
			return "";
		}
		return "~0~";
	}

	String openAsString(boolean raw) {
		int max = 100000;
		String path = getFirstString();
		if (raw && interp.nextToken()==',')
			max = (int)getNextArg();
		interp.getRightParen();			
		if (path.equals("")) {
			OpenDialog od = new OpenDialog("OpenAsString...", "");
			String directory = od.getDirectory();
			String name = od.getFileName();
			if (name==null) return "";
			path = directory + name;
		}
		String str = "";
		File file = new File(path);
		if (!file.exists())
			interp.error("File not found");
		try {
			StringBuffer sb = new StringBuffer(5000);
			BufferedReader r = new BufferedReader(new FileReader(file));
			if (raw) {
				while (sb.length()<max) {
					int c=r.read();
					if (c==-1)
						break;
					else
						sb.append((char)c);
				}
			} else {
				while (true) {
					String s=r.readLine();
					if (s==null)
						break;
					else
						sb.append(s+"\n");
				}
			}
			r.close();
			str = new String(sb);
		}
		catch (Exception e) {
			interp.error("File open error \n\""+e.getMessage()+"\"\n");
		}
		return str;
	}
	
	String closeFile() {
		String f = getStringArg();
		if (!f.equals("~0~"))
			interp.error("Invalid file variable");
		if (writer!=null) {
			writer.close();
			writer = null;
		}
		return null;
	}
	
	// Calls a public static method with an arbitrary number
	// of String parameters, returning a String.
	// Contributed by Johannes Schindelin
	String call() {
		// get class and method name
		String fullName = getFirstString();
		int dot = fullName.lastIndexOf('.');
		if(dot<0)
			interp.error("Expected 'classname.methodname'");
		String className = fullName.substring(0,dot);
		String methodName = fullName.substring(dot+1);

		// get optional string arguments
		Object[] args = null;
		if (interp.nextNonEolToken()==',') {
			Vector vargs = new Vector();
			do
				vargs.add(getNextString());
			while (interp.nextNonEolToken()==',');
			args = vargs.toArray();
		}
		interp.getRightParen();
		if (args==null) args = new Object[0];

		// get the class
		Class c;
		try {
			c = IJ.getClassLoader().loadClass(className);
		} catch(Exception ex) {
			interp.error("Could not load class "+className);
			return null;
		}

		// get method
		Method m;
		try {
			Class[] argClasses = null;
			if(args.length>0) {
				argClasses = new Class[args.length];
				for(int i=0;i<args.length;i++)
					argClasses[i] = args[i].getClass();
			}
			m = c.getMethod(methodName,argClasses);
		} catch(Exception ex) {
			interp.error("Could not find the method "+methodName+" with "+
				     args.length+" parameter(s) in class "+className);
			return null;
		}

		try {
			Object obj = m.invoke(null, args);
			return obj!=null?obj.toString():null;
		} catch(InvocationTargetException e) {
			CharArrayWriter caw = new CharArrayWriter();
			PrintWriter pw = new PrintWriter(caw);
			e.getCause().printStackTrace(pw);
			String s = caw.toString();
			if (IJ.getInstance()!=null)
				new TextWindow("Exception", s, 400, 400);
			else
				IJ.log(s);
			return null;
		} catch(Exception e) {
			IJ.log("Call error ("+e+")");
			return null;
		} 
			
 	}
 	
 	Variable[] getFontList() {
		interp.getParens();
		String fonts[] = null;
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		fonts = ge.getAvailableFontFamilyNames();
		if (fonts==null) return null;
    	Variable[] array = new Variable[fonts.length];
    	for (int i=0; i<fonts.length; i++)
    		array[i] = new Variable(0, 0.0, fonts[i]);
    	return array;
	}
	
	String openUrlAsString() {
		String urlString = getStringArg();
		StringBuffer sb = null;
		try {
			URL url = new URL(urlString);
			InputStream in = url.openStream();
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
			sb = new StringBuffer() ;
			String line;
			while ((line=br.readLine()) != null)
				sb.append (line + "\n");
			in.close ();
		} catch (IOException e) {
			String msg = ""+e;
			if (msg.indexOf("UnknownHost")!=-1 || msg.indexOf("FileNotFound")!=-1)
				return "";
			else
				interp.error(msg);
			sb = null;
		}
		if (sb!=null)
			return new String(sb);
		else
			return null;
	}
	
	void setOption() {
		String arg1 = getFirstString();
		interp.getComma();
		double arg2 = interp.getBooleanExpression();
		interp.checkBoolean(arg2);
		interp.getRightParen();
		boolean state = arg2==0?false:true;
		arg1 = arg1.toLowerCase(Locale.US);
		if (arg1.equals("disablepopupmenu")) {
			ImageCanvas ic = getImage().getCanvas();
			if (ic!=null) ic.disablePopupMenu(state);
		} else if (arg1.equals("show all")) {
			ImagePlus img = getImage();
			ImageCanvas ic = img.getCanvas();
			if (ic!=null) {
				boolean previousState = ic.getShowAllROIs();
				ic.setShowAllROIs(state);
				if (state!=previousState) img.draw();
			}
		} else if (arg1.equals("changes"))
			getImage().changes = state;
		else if (arg1.equals("debugmode"))
			IJ.debugMode = state;
		else if (arg1.equals("openusingplugins"))
			Opener.setOpenUsingPlugins(state);
		else if (arg1.equals("queuemacros"))
			pgm.queueCommands = state;
		else if (arg1.equals("disableundo"))
			Prefs.disableUndo = state;
		else if (arg1.startsWith("openashyper"))
			getImage().setOpenAsHyperStack(true);
		else
			interp.error("Invalid option");
	}
	
	void showText() {
		String title = getFirstString();
		String text = getLastString();
		Editor ed = new Editor();
		ed.setSize(350, 300);
		ed.create(title, text);
	}
	
	Variable[] newMenu() {
        String name = getFirstString();
        interp.getComma();
        String[] commands = getStringArray();
        interp.getRightParen();
        if (pgm.menus==null)
            pgm.menus = new Hashtable();
        pgm.menus.put(name, commands);
    	Variable[] commands2 = new Variable[commands.length];
    	for (int i=0; i<commands.length; i++)
    		commands2[i] = new Variable(0, 0.0, commands[i]);
    	return commands2;
	}
	
	void setSelectionLocation() {
		int x = (int)Math.round(getFirstArg());
		int y = (int)Math.round(getLastArg());
		resetImage();
		ImagePlus imp = getImage();
		Roi roi = imp.getRoi();
		if (roi==null)
			interp.error("Selection required");
		roi.setLocation(x, y);
		imp.draw();
	}
	
	double is() {
		boolean state = false;
		String arg = getStringArg();
		arg = arg.toLowerCase(Locale.US);
		if (arg.equals("locked"))
			state = getImage().isLocked();
		else if (arg.indexOf("invert")!=-1)
			state = getImage().isInvertedLut();
		else if (arg.indexOf("hyper")!=-1)
			state = getImage().isHyperStack();
		else if (arg.indexOf("batch")!=-1)
			state = Interpreter.isBatchMode();
		else if (arg.indexOf("applet")!=-1)
			state = IJ.getApplet()!=null;
		else if (arg.indexOf("virtual")!=-1)
			state = getImage().getStack().isVirtual();
		else if (arg.indexOf("composite")!=-1)
			state = getImage().isComposite();
		else
			interp.error("Argument must be 'locked', 'Inverted LUT' or 'HyperStack'");
		return state?1.0:0.0;
	}

	Variable[] getList() {
		String key = getStringArg();
		if (key.equals("java.properties")) {
			Properties props = System.getProperties();
			Vector v = new Vector();
			for (Enumeration en=props.keys(); en.hasMoreElements();)
				v.addElement((String)en.nextElement());
			Variable[] array = new Variable[v.size()];
			for (int i=0; i<array.length; i++)
				array[i] = new Variable(0, 0.0, (String)v.elementAt(i));
			return array;
		} else if (key.equals("window.titles")) {
			Frame[] list = WindowManager.getNonImageWindows();
			Variable[] array = new Variable[list.length];
			for (int i=0; i<list.length; i++) {
				Frame frame = list[i];
				array[i] = new Variable(0, 0.0, frame.getTitle());
			}
			return array;
		} else {
			interp.error("Unvalid key");
			return null;
		}
	}
	
	String doString() {
		interp.getToken();
		if (interp.token!='.')
			interp.error("'.' expected");
		interp.getToken();
		if (interp.token!=WORD)
			interp.error("Function name expected: ");
		String name = interp.tokenString;
		if (name.equals("append"))
			return appendToBuffer();
		else if (name.equals("copy"))
			return copyStringToClipboard();
		else if (name.equals("copyResults"))
			return copyResults();
		else if (name.equals("paste"))
			return getClipboardContents();
		else if (name.equals("resetBuffer"))
			return resetBuffer();
		else if (name.equals("buffer"))
			return getBuffer();
		else
			interp.error("Unrecognized String function");
		return null;
	}
	
	String appendToBuffer() {
		String text = getStringArg();
		if (buffer==null)
			buffer = new StringBuffer(256);
		buffer.append(text);
		return null;
	}

	String copyStringToClipboard() {
		String text = getStringArg();
		StringSelection ss = new StringSelection(text);
		java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		clipboard.setContents(ss, null);
		return null;
	}
		  
	String getClipboardContents() {
		interp.getParens();
		java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		Transferable data = clipboard.getContents(null);
		String s = null;
		try {s = (String)data.getTransferData(DataFlavor.stringFlavor);} 
		catch (Exception e) {s = data.toString();}
		return s;
	}
    	
	String copyResults() {
		interp.getParens();
		if (!IJ.isResultsWindow())
			interp.error("No results");
		TextPanel tp = IJ.getTextPanel();
		StringSelection ss = new StringSelection(tp.getText());
		java.awt.datatransfer.Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
		clipboard.setContents(ss, null);
		return null;
	}

	String resetBuffer() {
		interp.getParens();
		buffer = new StringBuffer(256);
		return null;
	}

	String getBuffer() {
		interp.getParens();
		if (buffer==null)
			buffer = new StringBuffer(256);
		return buffer.toString();
	}
	
	void doCommand() {
		String arg = getStringArg();
		if (arg.equals("Start Animation"))
			arg = "Start Animation [\\]";
		IJ.doCommand(arg);
	}
	
	void getDimensions() {
		Variable width = getFirstVariable();
		Variable height = getNextVariable();
		Variable channels = getNextVariable();
		Variable slices = getNextVariable();
		Variable frames = getLastVariable();
		ImagePlus imp = getImage();
		int[] dim = imp.getDimensions();
		width.setValue(dim[0]);
		height.setValue(dim[1]);
		channels.setValue(dim[2]);
		slices.setValue(dim[3]);
		frames.setValue(dim[4]);
	}

	public static void registerExtensions(MacroExtension extensions) {
		Interpreter interp = Interpreter.getInstance();
		if (interp==null) {
			IJ.error("Macro must be running to install macro extensions");
			return;
		}
		interp.pgm.extensionRegistry = new Hashtable();
		ExtensionDescriptor[] descriptors = extensions.getExtensionFunctions();
		for (int i=0; i<descriptors.length; ++i)
			interp.pgm.extensionRegistry.put(descriptors[i].name, descriptors[i]);
	}
	
	String doExt() {
		interp.getToken();
		if (interp.token!='.')
			interp.error("'.' expected");
		interp.getToken();
		if (!(interp.token==WORD || interp.token==STRING_FUNCTION || interp.token==NUMERIC_FUNCTION || interp.token==PREDEFINED_FUNCTION))
			interp.error("Function name expected: ");
		String name = interp.tokenString;
		if (name.equals("install")) {
			Object plugin = IJ.runPlugIn(getStringArg(), "");
			if (plugin==null) interp.error("Plugin not found");
			return null;
		}
		ExtensionDescriptor desc = null;
		if (pgm.extensionRegistry!=null)
			desc = (ExtensionDescriptor) pgm.extensionRegistry.get(name);
		if (desc == null) {
			interp.error("Unrecognized Ext function");
			return null;
		}
		return desc.dispatch(this);
	}
	
	String exec() {
		String[] cmd;
		StringBuffer sb = new StringBuffer(256);
		String arg1 = getFirstString();
		if (interp.nextNonEolToken()==',') {
			Vector v = new Vector();
			v.add(arg1);
			do
				v.add(getNextString());
			while (interp.nextNonEolToken()==',');
			cmd = new String[v.size()];
			v.copyInto((String[])cmd);
		} else
			cmd = Tools.split(arg1);
		interp.getRightParen();
		boolean openingDoc = cmd.length==2&&cmd[0].equals("open") || cmd.length==5&&cmd[3].equals("excel.exe");
		if (openingDoc&&IJ.isWindows()) {
			String path = cmd[1];
			if (path.startsWith("http://")||path.startsWith("HTTP://")) {
				cmd = new String[4];
				cmd[2] = "start";
				cmd[3] = path;
			} else {
				cmd = new String[3];
				cmd[2] = path;
			}
			cmd[0] = "cmd";
			cmd[1] = "/c";
		}
		BufferedReader reader = null;
		try {                                                                                             
			Process p = Runtime.getRuntime().exec(cmd);   
			if (openingDoc) return null;
			reader = new BufferedReader(new InputStreamReader(p.getInputStream()));  
			String line; int count=1;                                                                                
			while ((line=reader.readLine())!=null)  {                                              
        		sb.append(line+"\n");
        		if (count++==1&&line.startsWith("Microsoft Windows"))
        			break; // user probably ran 'cmd' without /c option
        	}                                                             
		} catch (Exception e) {                                                                         
    		sb.append(e.getMessage()+"\n");                                                                          
		} finally {
			if (reader!=null) try {reader.close();} catch (IOException e) {}
		}
		return sb.toString(); 
	}

	double getValue() {
		String key = getStringArg();
		if (key.indexOf("foreground")!=-1)
			return Toolbar.getForegroundColor().getRGB()&0xffffff;
		else if (key.indexOf("background")!=-1)
			return Toolbar.getBackgroundColor().getRGB()&0xffffff;
		else {
			interp.error("Invalid key");
			return 0.0;
		}
	}

	double doStack() {
		interp.getToken();
		if (interp.token!='.')
			interp.error("'.' expected");
		interp.getToken();
		if (interp.token!=WORD && interp.token!=PREDEFINED_FUNCTION)
			interp.error("Function name expected: ");
		String name = interp.tokenString;
		if (name.equals("isHyperStack"))
			return getImage().isHyperStack()?1.0:0.0;
		else if (name.equals("getDimensions"))
			{getDimensions(); return Double.NaN;}
		ImagePlus imp = getImage();
		if (name.equals("setPosition"))
			{setPosition(imp); return Double.NaN;}
		if (name.equals("getPosition"))
			{getPosition(imp); return Double.NaN;}
		if (name.equals("getFrameRate"))
			{interp.getParens(); return imp.getCalibration().fps;}
		if (name.equals("setFrameRate"))
			{imp.getCalibration().fps=getArg(); return Double.NaN;}
		if (imp.getStackSize()==1)
			interp.error("Stack required");
		if (name.equals("setDimensions"))
			setDimensions(imp);
		else if (name.equals("setChannel"))
			imp.setPosition((int)getArg(), imp.getSlice(), imp.getFrame());
		else if (name.equals("setSlice"))
			imp.setPosition(imp.getChannel(), (int)getArg(), imp.getFrame());
		else if (name.equals("setFrame"))
			imp.setPosition(imp.getChannel(), imp.getSlice(), (int)getArg());
		else if (name.equals("setDisplayMode"))
			setDisplayMode(imp, getStringArg());
		else if (name.equals("getDisplayMode"))
			getDisplayMode(imp);
		else if (name.equals("swap"))
			swapStackImages(imp);
		else
			interp.error("Unrecognized Stack function");
		return Double.NaN;
	}
	
	void setDisplayMode(ImagePlus imp, String mode) {
		mode = mode.toLowerCase(Locale.US);
		if (!imp.isComposite())
			interp.error("Composite image required");
		int m = -1;
		if (mode.equals("composite"))
			m = CompositeImage.COMPOSITE;
		else if (mode.equals("color"))
			m = CompositeImage.COLOR;
		else if (mode.startsWith("gray"))
			m = CompositeImage.GRAYSCALE;
		if (m==-1) 
			interp.error("Invalid mode");
		((CompositeImage)imp).setMode(m);
		imp.updateAndDraw();
	}
	
	void swapStackImages(ImagePlus imp) {
		int n1 = (int)getFirstArg();
		int n2 = (int)getLastArg();
		ImageStack stack = imp.getStack();
		int size = stack.getSize();
		if (n1<1||n1>size||n2<1||n2>size)
			interp.error("Argument out of range");
		Object pixels = stack.getPixels(n1);
		String label = stack.getSliceLabel(n1);
		stack.setPixels(stack.getPixels(n2), n1);
		stack.setSliceLabel(stack.getSliceLabel(n2), n1);
		stack.setPixels(pixels, n2);
		stack.setSliceLabel(label, n2);
		int current = imp.getCurrentSlice();
		if (imp.isComposite()) {
			CompositeImage ci = (CompositeImage)imp;
			if (ci.getMode()==CompositeImage.COMPOSITE) {
				ci.reset();
				imp.updateAndDraw();
				imp.repaintWindow();
				return;
			}
		}
		if (n1==current || n2==current)
			imp.setStack(null, stack);
	}

	void getDisplayMode(ImagePlus imp) {
		Variable v = getVariableArg();
		String mode = "";
		if (imp.isComposite())
			mode = ((CompositeImage)imp).getModeAsString();
		v.setString(mode);
	}

	void getPosition(ImagePlus imp) {
		Variable channel = getFirstVariable();
		Variable slice = getNextVariable();
		Variable frame = getLastVariable();
		int c = imp.getChannel();
		int z = imp.getSlice();
		int t = imp.getFrame();
		if (c*z*t>imp.getStackSize())
			{c=1; z=imp.getCurrentSlice(); t=1;}
		channel.setValue(c);
		slice.setValue(z);
		frame.setValue(t);
	}

	void setPosition(ImagePlus img) {
		int channel = (int)getFirstArg();
		int slice = (int)getNextArg();
		int frame = (int)getLastArg();
		img.setPosition(channel, slice, frame);
	}

	void setDimensions(ImagePlus img) {
		int c = (int)getFirstArg();
		int z = (int)getNextArg();
		int t = (int)getLastArg();
		img.setDimensions(c, z, t);
		if (img.getWindow()==null) img.setOpenAsHyperStack(true);
	}
	
	void setTool() {
        interp.getLeftParen();
		if (isStringArg()) {
			boolean ok = IJ.setTool(getString());
			if (!ok) interp.error("Unrecognized tool name");
		} else
			IJ.setTool((int)interp.getExpression());
		interp.getRightParen();
	}

	String doToString() {
		String s = getFirstString();
		interp.getToken();
		if (interp.token==',') {
			double value = Tools.parseDouble(s);
			s = IJ.d2s(value, (int)interp.getExpression());
			interp.getToken();
		}
		if (interp.token!=')') interp.error("')' expected");
		return s;
	}
	
	double matches() {
		String str = getFirstString();
		String regex = getLastString();
		boolean matches = str.matches(regex);
		return matches?1.0:0.0;
	}
	
	void waitForUser() {
		if (waitForUserDialog!=null && waitForUserDialog.isVisible())
			interp.error("Duplicate call");
		String title = getFirstString();
		String text;
		if (interp.nextToken()==',')
			text = getLastString();
		else {
			text = title;
			title = "Action Required";
			interp.getRightParen();
		}
		waitForUserDialog = new WaitForUserDialog(title, text);
		waitForUserDialog.show();
	}

} // class Functions

