package ij.plugin;
import ij.*;
import ij.gui.*;
import ij.process.*;
import ij.measure.*;
import ij.util.Tools;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

/** This plugin implements the Edit/Scale command. */
public class Scaler implements PlugIn, TextListener, FocusListener {
    private ImagePlus imp;
    private static String xstr = "0.5";
    private static String ystr = "0.5";
    private static int newWidth, newHeight;
    private static boolean newWindow = true;
    private static boolean interpolate = true;
    private static boolean fillWithBackground;
    private static boolean processStack = true;
    private double xscale;
    private double yscale;
    private String title = "Untitled";
    private Vector fields;
    private double bgValue;
    private boolean constainAspectRatio = true;
    private TextField xField, yField, widthField, heightField;
    private Rectangle r;
    private Object fieldWithFocus;

	public void run(String arg) {
		imp = IJ.getImage();
		Roi roi = imp.getRoi();
		if (roi!=null && !roi.isArea())
			imp.killRoi(); // ignore any line selection
		ImageProcessor ip = imp.getProcessor();
		if (!showDialog(ip))
			return;
		if (ip.getWidth()>1 && ip.getHeight()>1)
			ip.setInterpolate(interpolate);
		else
			ip.setInterpolate(false);
		ip.setBackgroundValue(bgValue);
		imp.startTiming();
		try {
			if (newWindow && imp.getStackSize()>1 && processStack)
				createNewStack(imp, ip);
			else
				scale(ip);
		}
		catch(OutOfMemoryError o) {
			IJ.outOfMemory("Scale");
		}
		IJ.showProgress(1.0);
	}
	
	void createNewStack(ImagePlus imp, ImageProcessor ip) {
		Rectangle r = ip.getRoi();
		boolean crop = r.width!=imp.getWidth() || r.height!=imp.getHeight();
		int nSlices = imp.getStackSize();
	    ImageStack stack1 = imp.getStack();
	    ImageStack stack2 = new ImageStack(newWidth, newHeight);
 		ImageProcessor ip1, ip2;
 		boolean interp = interpolate;
 		if (imp.getWidth()==1 || imp.getHeight()==1)
 			interp = false;
		for (int i=1; i<=nSlices; i++) {
			IJ.showStatus("Scale: " + i + "/" + nSlices);
			ip1 = stack1.getProcessor(i);
			String label = stack1.getSliceLabel(i);
			if (crop) {
				ip1.setRoi(r);
				ip1 = ip1.crop();
			}
			ip1.setInterpolate(interp);
			ip2 = ip1.resize(newWidth, newHeight);
			if (ip2!=null)
				stack2.addSlice(label, ip2);
			IJ.showProgress(i, nSlices);
		}
		ImagePlus imp2 = imp.createImagePlus();
		imp2.setStack(title, stack2);
		Calibration cal = imp2.getCalibration();
		if (cal.scaled()) {
			cal.pixelWidth *= 1.0/xscale;
			cal.pixelHeight *= 1.0/yscale;
		}
		int[] dim = imp.getDimensions();
		imp2.setDimensions(dim[2], dim[3], dim[4]);
		IJ.showProgress(1.0);
		if (imp.isHyperStack())
			imp2.setOpenAsHyperStack(true);
		if (imp.isComposite())
			imp2 = new CompositeImage(imp2, ((CompositeImage)imp).getMode());
		imp2.show();
		imp2.changes = true;
	}

	void scale(ImageProcessor ip) {
		if (newWindow) {
			Rectangle r = ip.getRoi();
			ImagePlus imp2 = imp.createImagePlus();
			imp2.setProcessor(title, ip.resize(newWidth, newHeight));
			Calibration cal = imp2.getCalibration();
			if (cal.scaled()) {
				cal.pixelWidth *= 1.0/xscale;
				cal.pixelHeight *= 1.0/yscale;
			}
			imp2.show();
			imp.trimProcessor();
			imp2.trimProcessor();
			imp2.changes = true;
		} else {
			if (processStack && imp.getStackSize()>1) {
				Undo.reset();
				StackProcessor sp = new StackProcessor(imp.getStack(), ip);
				sp.scale(xscale, yscale, bgValue);
			} else {
				ip.snapshot();
				Undo.setup(Undo.FILTER, imp);
				ip.setSnapshotCopyMode(true);
				ip.scale(xscale, yscale);
				ip.setSnapshotCopyMode(false);
			}
			imp.killRoi();
			imp.updateAndDraw();
			imp.changes = true;
		}
	}
	
	boolean showDialog(ImageProcessor ip) {
		int bitDepth = imp.getBitDepth();
		boolean isStack = imp.getStackSize()>1;
        r = ip.getRoi();
       	int width = newWidth;
		if (width==0) width = r.width;
        int height = (int)((double)width*r.height/r.width);
        xscale = Tools.parseDouble(xstr, 0.0);
        yscale = Tools.parseDouble(ystr, 0.0);
        if (xscale!=0.0 && yscale!=0.0) {
       		width = (int)(r.width*xscale);
        	height = (int)(r.height*yscale);
        } else {
        	xstr = "-";
        	ystr = "-";
        }
		GenericDialog gd = new GenericDialog("Scale");
		gd.addStringField("X Scale (0.05-25):", xstr);
		gd.addStringField("Y Scale (0.05-25):", ystr);
		gd.setInsets(5, 0, 5);
		gd.addStringField("Width (pixels):", ""+width);
		gd.addStringField("Height (pixels):", ""+height);
		fields = gd.getStringFields();
		for (int i=0; i<3; i++) {
			((TextField)fields.elementAt(i)).addTextListener(this);
            ((TextField)fields.elementAt(i)).addFocusListener(this);
        }
		xField = (TextField)fields.elementAt(0);
		yField = (TextField)fields.elementAt(1);
		widthField = (TextField)fields.elementAt(2);
		heightField = (TextField)fields.elementAt(3);
        fieldWithFocus = xField;
		gd.addCheckbox("Interpolate", interpolate);
		if (bitDepth==8 || bitDepth==24)
			gd.addCheckbox("Fill with Background Color", fillWithBackground);
		if (isStack)
			gd.addCheckbox("Process Entire Stack", processStack);
		gd.addCheckbox("Create New Window", newWindow);
		title = WindowManager.getUniqueName(imp.getTitle());
		gd.setInsets(10, 0, 0);
		gd.addStringField("Title:", title, 12);
		gd.showDialog();
		if (gd.wasCanceled())
			return false;
		xstr = gd.getNextString();
		ystr = gd.getNextString();
        xscale = Tools.parseDouble(xstr, 0.0);
        yscale = Tools.parseDouble(ystr, 0.0);
        String wstr = gd.getNextString();
		newWidth = (int)Tools.parseDouble(wstr, 0);
		newHeight = (int)Tools.parseDouble(gd.getNextString(), 0);
		if (newHeight!=0 && (wstr.equals("-") || wstr.equals("0")))
                newWidth= (int)(newHeight*(double)r.width/r.height);
		if (newWidth==0 || newHeight==0) {
			IJ.error("Invalid width or height entered");
			return false;
		}
		if (xscale>25.0) xscale = 25.0;
		if (yscale>25.0) yscale = 25.0;
		if (xscale>0.0 && yscale>0.0) {
			newWidth = (int)(r.width*xscale);
			newHeight = (int)(r.height*yscale);
		}
		interpolate = gd.getNextBoolean();
		if (bitDepth==8 || bitDepth==24)
			fillWithBackground = gd.getNextBoolean();
		if (isStack)
			processStack = gd.getNextBoolean();
		newWindow = gd.getNextBoolean();
		if (!newWindow && xscale==0.0) {
			xscale = (double)newWidth/r.width;
			yscale = (double)newHeight/r.height;
		}
		title = gd.getNextString();

		if (fillWithBackground) {
			Color bgc = Toolbar.getBackgroundColor();
			if (bitDepth==8)
				bgValue = ip.getBestIndex(bgc);
			else if (bitDepth==24)
				bgValue = bgc.getRGB();
		} else {
			if (bitDepth==8)
				bgValue = ip.isInvertedLut()?0.0:255.0; // white
			else if (bitDepth==24)
				bgValue = 0xffffffff; // white
		}
		return true;
	}

	public void textValueChanged(TextEvent e) {
        Object source = e.getSource();
        double newXScale = xscale;
        double newYScale = yscale;
        if (source==xField && fieldWithFocus==xField) {
            String newXText = xField.getText();
            newXScale = Tools.parseDouble(newXText,0);
            if (newXScale==0) return;
            if (newXScale!=xscale) {
                int newWidth = (int)(newXScale*r.width);
                widthField.setText(""+newWidth);
                if (constainAspectRatio) {
                    yField.setText(newXText);
                    int newHeight = (int)(newXScale*r.height);
                    heightField.setText(""+newHeight);
                }
            }
        } else if (source==yField && fieldWithFocus==yField) {
            String newYText = yField.getText();
            newYScale = Tools.parseDouble(newYText,0);
            if (newYScale==0) return;
            if (newYScale!=yscale) {
                int newHeight = (int)(newYScale*r.height);
                heightField.setText(""+newHeight);
            }
        } else if (source==widthField && fieldWithFocus==widthField) {
            int newWidth = (int)Tools.parseDouble(widthField.getText(), 0.0);
            if (newWidth!=0) {
                int newHeight = (int)(newWidth*(double)r.height/r.width);
                heightField.setText(""+newHeight);
                xField.setText("-");
                yField.setText("-");
                newXScale = 0.0;
                newYScale = 0.0;
            }
        }
		xscale = newXScale;
		yscale = newYScale;
	}

	public void focusGained(FocusEvent e) {
        fieldWithFocus = e.getSource();
		if (fieldWithFocus==widthField)
            constainAspectRatio = true;
        else if (fieldWithFocus==yField)
            constainAspectRatio = false;
	}

	public void focusLost(FocusEvent e) {}

}
