package ij.plugin.filter;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.util.Tools;
import ij.io.FileOpener;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import ij.measure.Calibration;
import ij.plugin.frame.Recorder;

public class ImageProperties implements PlugInFilter, TextListener {
	private final String SAME = "-";
	ImagePlus imp;
	static final int NANOMETER=0, MICROMETER=1, MILLIMETER=2, CENTIMETER=3,
		 METER=4, KILOMETER=5, INCH=6, FOOT=7, MILE=8, PIXEL=9, OTHER_UNIT=10;
	int oldUnitIndex;
	double oldUnitsPerCm;
	Vector nfields, sfields;
	boolean duplicatePixelWidth = true;
	String calUnit;
	double calPixelWidth, calPixelHeight, calPixelDepth;
	TextField pixelWidthField, pixelHeightField, pixelDepthField;
	int textChangedCount;

	public int setup(String arg, ImagePlus imp) {
		this.imp = imp;
		return DOES_ALL+NO_CHANGES;
	}

	public void run(ImageProcessor ip) {
		showDialog(imp);
	}
	
	void showDialog(ImagePlus imp) {
		String options = Macro.getOptions();
		boolean legacyMacro = false;
		if (options!=null ) {
			String options2 = options.replaceAll(" depth=", " slices=");
			options2 = options2.replaceAll(" interval=", " frame=");
			Macro.setOptions(options2);
			if (options.contains("unit="))
				legacyMacro = true;
		}
		Calibration cal = imp.getCalibration();
		Calibration calOrig = cal.copy();
		oldUnitIndex = getUnitIndex(cal.getUnit());
		oldUnitsPerCm = getUnitsPerCm(oldUnitIndex);
		int stackSize = imp.getImageStackSize();
		int channels = imp.getNChannels();
		int slices = imp.getNSlices();
		int frames = imp.getNFrames();
		boolean global1 = imp.getGlobalCalibration()!=null;
		boolean global2;
		int digits = cal.pixelWidth<1.0||cal.pixelHeight<1.0||cal.pixelDepth<1.0?7:4;
		String xunit = cal.getXUnit();
		String yunit = cal.getYUnit();
		String zunit = cal.getZUnit();
		GenericDialog gd = new GenericDialog(imp.getTitle());
		gd.addNumericField("Channels (c):", channels, 0);
		gd.addNumericField("Slices (z):", slices, 0);
		gd.addNumericField("Frames (t):", frames, 0);
		gd.setInsets(0, 5, 0);
		gd.addMessage("Note: c*z*t must equal "+stackSize, null, Color.darkGray);
		gd.setInsets(15, 0, 0);
		if (legacyMacro)
			gd.addStringField("Unit of length:", cal.getUnit());
		int fieldWidth = 9;
		gd.addNumericField("Pixel_width:", cal.pixelWidth, digits, fieldWidth, null);		
		gd.addToSameRow();
		gd.addStringField("_", xunit, 5);				
		gd.addNumericField("Pixel_height:", cal.pixelHeight, digits, fieldWidth, null);	
		gd.addToSameRow();
		gd.addStringField("_", yunit.equals(xunit)?SAME:yunit, 5);			
		gd.addNumericField("Voxel_depth:", cal.pixelDepth, digits, fieldWidth, null);
		gd.addToSameRow();
		gd.addStringField("_", zunit.equals(xunit)?SAME:zunit, 5);			
		gd.setInsets(10, 0, 5);
		double interval = cal.frameInterval;
		String intervalStr = IJ.d2s(interval, (int)interval==interval?0:2) + " " + cal.getTimeUnit();
		gd.addStringField("Frame interval:", intervalStr);
		String xo = cal.xOrigin==(int)cal.xOrigin?IJ.d2s(cal.xOrigin,0):IJ.d2s(cal.xOrigin,2);
		String yo = cal.yOrigin==(int)cal.yOrigin?IJ.d2s(cal.yOrigin,0):IJ.d2s(cal.yOrigin,2);
		String zo = "";
		if (imp.getNSlices()>1) {
			zo = cal.zOrigin==(int)cal.zOrigin?IJ.d2s(cal.zOrigin,0):IJ.d2s(cal.zOrigin,2);
			zo = "," + zo;
		}
		gd.addStringField("Origin (pixels):", xo+","+yo+zo);
		gd.setInsets(5, 20, 0);
		gd.addCheckbox("Global", global1);
		nfields = gd.getNumericFields();
		if (nfields!=null) {
			pixelWidthField  = (TextField)nfields.elementAt(3);
			pixelHeightField  = (TextField)nfields.elementAt(4);
			pixelDepthField  = (TextField)nfields.elementAt(5);
			for (int i=0; i<nfields.size(); i++)
				((TextField)nfields.elementAt(i)).addTextListener(this);
        }
        sfields = gd.getStringFields();
        if (sfields!=null) {
        	for (int i=0; i<sfields.size(); i++)
            	((TextField)sfields.elementAt(i)).addTextListener(this);
        }
		calUnit = cal.getUnit();
		calPixelWidth = cal.pixelWidth;
		calPixelHeight = cal.pixelHeight;
		calPixelDepth = cal.pixelDepth;
		gd.showDialog();
		if (gd.wasCanceled())
			return;
 		channels = (int)gd.getNextNumber();
 		if (channels<1) channels = 1;
 		slices = (int)gd.getNextNumber();
 		if (slices<1) slices = 1;
 		frames = (int)gd.getNextNumber();
 		if (frames<1) frames = 1;
 		if (channels*slices*frames==stackSize)
 			imp.setDimensions(channels, slices, frames);
 		else
 			IJ.error("Properties", "The product of channels ("+channels+"), slices ("+slices
 				+")\n and frames ("+frames+") must equal the stack size ("+stackSize+").");
		String unit = "";
		if (legacyMacro)
			unit = gd.getNextString();   
 		double pixelWidth = gd.getNextNumber();
 		String xunit2 = gd.getNextString();
 		double pixelHeight = gd.getNextNumber();
 		String yunit2 = gd.getNextString();
 		double pixelDepth = gd.getNextNumber();
 		String zunit2 = gd.getNextString();
 		boolean reset = false;
 		boolean xUnitChanged=false,yUnitChanged=false,zUnitChanged=false;
 		if (legacyMacro) {
			if (!unit.equals(cal.getUnit())) {
				cal.setYUnit(null);
				cal.setZUnit(null);
			}
			cal.setUnit(unit);
		} else {
			xUnitChanged = !xunit2.equals(xunit);
			if (xUnitChanged)
				cal.setXUnit(xunit2);
			yUnitChanged = !yunit2.equals(yunit) && !yunit2.equals(SAME);
			if (yUnitChanged)
				cal.setYUnit(yunit2);
			zUnitChanged = !zunit2.equals(zunit) && !zunit2.equals(SAME);
			if (zUnitChanged)
				cal.setZUnit(zunit2);
			unit = xunit2;			
		}
		if (unit.equals("") || unit.equalsIgnoreCase("none") || pixelWidth==0.0) {
			// reset
			cal.setUnit(null);
			cal.pixelWidth = 1.0;
			cal.pixelHeight = 1.0;
			cal.pixelDepth = 1.0;
		} else {
			cal.pixelWidth = pixelWidth;
			cal.pixelHeight = pixelHeight;
			cal.pixelDepth = pixelDepth;
		}
		
		gd.setSmartRecording(interval==0);
		String frameInterval = validateInterval(gd.getNextString());
		String[] intAndUnit = Tools.split(frameInterval, " -");
		interval = Tools.parseDouble(intAndUnit[0]);
		cal.frameInterval = Double.isNaN(interval)?0.0:interval;
		String timeUnit = intAndUnit.length>=2?intAndUnit[1]:"sec";
		if (timeUnit.equals("sec")&&cal.frameInterval<=2.0&&cal.frameInterval>=1.0/30.0)
			cal.fps = 1.0/cal.frameInterval;
		if (timeUnit.equals("usec"))
			timeUnit = IJ.micronSymbol + "sec";
		cal.setTimeUnit(timeUnit);

		gd.setSmartRecording(cal.xOrigin==0&&cal.yOrigin==0&&cal.zOrigin==0);
        String[] origin = Tools.split(gd.getNextString(), " ,");
		gd.setSmartRecording(false);
		double x = origin.length>=1?Tools.parseDouble(origin[0]):Double.NaN;
		double y = origin.length>=2?Tools.parseDouble(origin[1]):Double.NaN;
		double z = origin.length>=3?Tools.parseDouble(origin[2]):Double.NaN;
		cal.xOrigin= Double.isNaN(x)?0.0:x;
		cal.yOrigin= Double.isNaN(y)?cal.xOrigin:y;
		cal.zOrigin= Double.isNaN(z)?0.0:z;
 		global2 = gd.getNextBoolean();
		if (!cal.equals(calOrig))
			imp.setCalibration(cal);
 		imp.setGlobalCalibration(global2?cal:null);
		if (global2 || global2!=global1)
			WindowManager.repaintImageWindows();
		else
			imp.repaintWindow();
		if (global2 && global2!=global1)
			FileOpener.setShowConflictMessage(true);
			
		if (Recorder.record) {
			if (Recorder.scriptMode()) {
				if (xUnitChanged)
					Recorder.recordCall("imp.getCalibration().setXUnit(\""+xunit2+"\");", true);
				if (yUnitChanged)
					Recorder.recordCall("imp.getCalibration().setYUnit(\""+yunit2+"\");", true);
				if (zUnitChanged)
					Recorder.recordCall("imp.getCalibration().setZUnit(\""+zunit2+"\");", true);
			} else {
				if (xUnitChanged)
					Recorder.record("Stack.setXUnit", xunit2);
				if (yUnitChanged)
					Recorder.record("Stack.setYUnit", yunit2);
				if (zUnitChanged)
					Recorder.record("Stack.setZUnit", zunit2);
			}
		}

	}
	
	String validateInterval(String interval) {
		if (interval.indexOf(" ")!=-1)
			return interval;
		int firstLetter = -1;
		for (int i=0; i<interval.length(); i++) {
			char c = interval.charAt(i);
			if (Character.isLetter(c)) {
				firstLetter = i;
				break;
			}
		}
		if (firstLetter>0 && firstLetter<interval.length()-1)
			interval = interval.substring(0,firstLetter)+" "+interval.substring(firstLetter, interval.length());
		return interval;
	}
	
	double getNewScale(String newUnit, double oldScale) {
		//IJ.log("getNewScale: "+newUnit);
		if (oldUnitsPerCm==0.0) return 0.0;
		double newScale = 0.0;
		int newUnitIndex = getUnitIndex(newUnit);
		if (newUnitIndex!=oldUnitIndex) {
			double newUnitsPerCm = getUnitsPerCm(newUnitIndex);
			if (oldUnitsPerCm!=0.0 && newUnitsPerCm!=0.0) {
				newScale = oldScale * (oldUnitsPerCm/newUnitsPerCm);
			}
		}
		return newScale;
	}
	
	static int getUnitIndex(String unit) {
		unit = unit.toLowerCase(Locale.US);
		if (unit.equals("cm")||unit.startsWith("cent"))
			return CENTIMETER;
		else if (unit.equals("mm")||unit.startsWith("milli"))
			return MILLIMETER;
		else if (unit.startsWith("inch"))
			return INCH;
		else if (unit.startsWith(""+IJ.micronSymbol)||unit.startsWith("u")||unit.startsWith("micro"))
			return MICROMETER;
		else if (unit.equals("nm")||unit.startsWith("nano"))
			return NANOMETER;
		else if (unit.equals("m") || unit.startsWith("meter"))
			return METER;
		else if (unit.equals("km")||unit.startsWith("kilo"))
			return KILOMETER;
		else if (unit.equals("ft")||unit.equals("foot")||unit.equals("feet"))
			return FOOT;
		else if (unit.equals("mi")||unit.startsWith("mile"))
			return MILE;
		else
			return OTHER_UNIT;
	}
	
	static double getUnitsPerCm(int unitIndex) {
		switch (unitIndex) {
			case NANOMETER: return  10000000.0;
			case MICROMETER: return    10000.0;
			case MILLIMETER: return       10.0;
			case CENTIMETER: return        1.0;
			case METER: return             0.01;
			case KILOMETER: return         0.00001;
			case INCH: return              0.3937;
			case FOOT: return              0.0328083;
			case MILE: return              0.000006213;
			default: return                0.0;
		}
	}

   	public void textValueChanged(TextEvent e) {
   		textChangedCount++;
		Object source = e.getSource();
        
        int channels = (int)Tools.parseDouble(((TextField)nfields.elementAt(2)).getText(),-99);
        int depth = (int)Tools.parseDouble(((TextField)nfields.elementAt(3)).getText(),-99);
        int frames = (int)Tools.parseDouble(((TextField)nfields.elementAt(4)).getText(),-99);
        
		double newPixelWidth = calPixelWidth;
		String newWidthText = pixelWidthField.getText();
		if (source==pixelWidthField)
			newPixelWidth = Tools.parseDouble(newWidthText,-99);
		double newPixelHeight = calPixelHeight;
		if (source==pixelHeightField) {
			String newHeightText = pixelHeightField.getText();
			newPixelHeight = Tools.parseDouble(newHeightText,-99);
			if (!newHeightText.equals(newWidthText))
				duplicatePixelWidth = false;
		}
		double newPixelDepth = calPixelDepth;
		if (source==pixelDepthField) {
			String newDepthText = pixelDepthField.getText();
			newPixelDepth = Tools.parseDouble(newDepthText,-99);
			if (!newDepthText.equals(newWidthText))
				duplicatePixelWidth = false;
		}
		if (textChangedCount==1 && (calPixelHeight!=1.0||calPixelDepth!=1.0))
			duplicatePixelWidth = false;
		if (source==pixelWidthField && newPixelWidth!=-99 && duplicatePixelWidth) {
			pixelHeightField.setText(newWidthText);
			pixelDepthField.setText(newWidthText);
			calPixelHeight = calPixelWidth;
			calPixelDepth = calPixelWidth;
		}
		calPixelWidth = newPixelWidth;
		calPixelHeight = newPixelHeight;
 		calPixelDepth = newPixelDepth;
 		TextField unitField = (TextField)sfields.elementAt(0);
 		String newUnit = unitField.getText();
 		if (!newUnit.equals(calUnit)) {
			double oldXScale = newPixelWidth!=0?1.0/newPixelWidth:0;
			double oldYScale = newPixelHeight!=0?1.0/newPixelHeight:0;
			double oldZScale = newPixelDepth!=0?1.0/newPixelDepth:0;
			double newXScale = getNewScale(newUnit, oldXScale);
			double newYScale = getNewScale(newUnit, oldYScale);
			double newZScale = getNewScale(newUnit, oldZScale);
			if (newXScale!=0.0) {
				double w = 1.0/newXScale;
				double h = 1.0/newYScale;
				double d = 1.0/newZScale;
				int digits = w<1.0||h<1.0||d<1.0?7:4;
				pixelWidthField.setText(IJ.d2s(1/newXScale,digits));
				pixelHeightField.setText(IJ.d2s(1/newYScale,digits));
				pixelDepthField.setText(IJ.d2s(1/newZScale,digits));
				calPixelWidth = 1.0/newXScale;
				calPixelHeight = 1.0/newYScale;
				calPixelDepth = 1.0/newZScale;
				oldUnitIndex = getUnitIndex(newUnit);
				oldUnitsPerCm = getUnitsPerCm(oldUnitIndex);
			}
			calUnit = newUnit;
		}
 	}

}
