package ij.plugin.filter;
import ij.*;
import ij.gui.*;
import ij.process.*;
import java.awt.*;
import java.awt.geom.*;


/** This plugin implements the Image/Rotate/Arbitrarily command. */
public class Rotator implements ExtendedPlugInFilter, DialogListener {
	public static final String GRID = "|GRID|";
	private int flags = DOES_ALL|SUPPORTS_MASKING|PARALLELIZE_STACKS;
	private static double angle = 15.0;
	private static boolean fillWithBackground;
	private static boolean enlarge;
	private static int gridLines = 1;
	private ImagePlus imp;
	private int bitDepth;
	private boolean canEnlarge;
	private boolean isEnlarged;
	private GenericDialog gd;
	private PlugInFilterRunner pfr;
	private String[] methods = ImageProcessor.getInterpolationMethods();
	private static int interpolationMethod = ImageProcessor.BILINEAR;
	private Overlay overlay;
	private boolean done;

	public int setup(String arg, ImagePlus imp) {
		this.imp = imp;
		if (imp!=null) {
			bitDepth = imp.getBitDepth();
			Roi roi = imp.getRoi();
			if (roi!=null && roi.isLine())
				roi = null;
			Rectangle r = roi!=null?roi.getBounds():null;
			canEnlarge = r==null || (r.x==0&&r.y==0&&r.width==imp.getWidth()&&r.height==imp.getHeight());
			if (imp.getDisplayMode()==IJ.COMPOSITE) { // setup Undo for composite color stacks
				Undo.setup(Undo.TRANSFORM, imp);
				flags = flags | NO_UNDO_RESET;
			}
			overlay = imp.getOverlay();
			Undo.saveOverlay(imp);
			if (overlay==null)
				overlay = new Overlay();
		}
		return flags;
	}

	public void run(ImageProcessor ip) {
		if (enlarge && gd.wasOKed()) synchronized(this) {
			if (!isEnlarged) {
				enlargeCanvas();
				isEnlarged=true;
			}
		}
		if (isEnlarged) {	//enlarging may have made the ImageProcessor invalid, also for the parallel threads
			int slice = pfr.getSliceNumber();
			if (imp.getStackSize()==1)
				ip = imp.getProcessor();
			else
				ip = imp.getStack().getProcessor(slice);
		}
		ip.setInterpolationMethod(interpolationMethod);
		if (fillWithBackground) {
			Color bgc = Toolbar.getBackgroundColor();
			if (bitDepth==8)
				ip.setBackgroundValue(ip.getBestIndex(bgc));
			else if (bitDepth==24)
				ip.setBackgroundValue(bgc.getRGB());
		} else
			ip.setBackgroundValue(0);
		ip.rotate(angle);
		if (!gd.wasOKed())
			drawGridLines(gridLines);
		if (overlay!=null && !imp.getHideOverlay()) {
			Overlay overlay2 = overlay.rotate(angle, ip.getWidth()/2, ip.getHeight()/2);
			if (overlay2!=null && overlay2.size()>0)
				imp.setOverlay(overlay2);
		}
		if (isEnlarged && imp.getStackSize()==1) {
			imp.changes = true;
			imp.updateAndDraw();
			Undo.setup(Undo.COMPOUND_FILTER_DONE, imp);
		}
		if (done) { // remove grid
			Overlay ovly = imp.getOverlay();
			if (ovly!=null) {
				ovly.remove(GRID);
				if (ovly.size()==0) imp.setOverlay(null);			
			}
		}
	}

	void enlargeCanvas() {
		imp.unlock();
		IJ.run(imp, "Select All", "");
		IJ.run(imp, "Rotate...", "angle="+angle);
		Roi roi = imp.getRoi();
		Rectangle r = roi.getBounds();
		if (r.width<imp.getWidth()) r.width = imp.getWidth();
		if (r.height<imp.getHeight()) r.height = imp.getHeight();
		IJ.showStatus("Rotate: Enlarging...");
		if (imp.getStackSize()==1)
			Undo.setup(Undo.COMPOUND_FILTER, imp);
		IJ.run(imp, "Canvas Size...", "width="+r.width+" height="+r.height+" position=Center "+(fillWithBackground?"":"zero"));
		IJ.showStatus("Rotating...");
	}

	void drawGridLines(int lines) {
		if (overlay==null)
			return;
		overlay.remove(GRID);
		if (lines==0)
			return;
		GeneralPath path = new GeneralPath();
		float width = imp.getWidth();
		float height = imp.getHeight();
		float xinc = width/lines;
		float yinc = height/lines;
		float xstart = xinc/2f;
		float ystart = yinc/2f;
		for (int i=0; i<lines; i++) {
			path.moveTo(xstart+xinc*i, 0f);
			path.lineTo(xstart+xinc*i, height);
			path.moveTo(0f, ystart+yinc*i);
			path.lineTo(width, ystart+yinc*i);
		}
		Roi roi = new ShapeRoi(path);
		roi.setName(GRID);
		roi.setStrokeWidth(0);
		overlay.add(roi);
	}

	public int showDialog(ImagePlus imp, String command, PlugInFilterRunner pfr) {
		this.pfr = pfr;
		String macroOptions = Macro.getOptions();
		if (macroOptions!=null) {
			if (macroOptions.indexOf(" interpolate")!=-1)
				macroOptions.replaceAll(" interpolate", " interpolation=Bilinear");
			else if (macroOptions.indexOf(" interpolation=")==-1)
				macroOptions = macroOptions+" interpolation=None";
			Macro.setOptions(macroOptions);
		}
		gd = new GenericDialog("Rotate");
		gd.addSlider("Angle:", -90, 90, angle, 0.1);
		gd.addNumericField("Grid lines:", gridLines, 0);
		gd.addChoice("Interpolation:", methods, methods[interpolationMethod]);
		if (bitDepth==8 || bitDepth==24)
			gd.addCheckbox("Fill with background color", fillWithBackground);
		if (canEnlarge)
			gd.addCheckbox("Enlarge image", enlarge);
		else
			enlarge = false;
		gd.addPreviewCheckbox(pfr);
		gd.addDialogListener(this);
		gd.showDialog();
		if (gd.wasCanceled()) {
			if (overlay.size()>0) {
				overlay.remove(GRID);
				imp.setOverlay(overlay);
			}
			return DONE;
		}
		Overlay ovly = imp.getOverlay();
		if (ovly!=null) {
			ovly.remove(GRID);
			if (ovly.size()==0) imp.setOverlay(null);		
		}
		if (enlarge)
			flags |= NO_CHANGES;			// undoable as a "compound filter"
		else if (imp.getStackSize()==1)			
			flags |= KEEP_PREVIEW;		// standard filter without enlarge
		done = true;
		return IJ.setupDialog(imp, flags);
	}

	public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
		angle = gd.getNextNumber();
		//only check for invalid input to "angle", don't care about gridLines
		if (gd.invalidNumber()) {
			if (gd.wasOKed()) IJ.error("Angle is invalid.");
			return false;
		}
		gridLines = (int)gd.getNextNumber();
		interpolationMethod = gd.getNextChoiceIndex();
		if (bitDepth==8 || bitDepth==24)
			fillWithBackground = gd.getNextBoolean();
		if (canEnlarge)
			enlarge = gd.getNextBoolean();
		return true;
	}

	/** Returns the current angle. */
	public static double getAngle() {
		return angle;
	}

	public void setNPasses(int nPasses) {
	}

}

