package ij.gui;
import java.awt.*;
import java.awt.image.*;
import ij.*;
import ij.plugin.frame.Recorder;
import ij.process.FloatPolygon;
import ij.measure.Calibration;

/** Elliptical region of interest. */
public class EllipseRoi extends PolygonRoi {
	private static final int vertices = 72;
	private static double defaultRatio = 0.6;
	private double xstart, ystart;
	private double aspectRatio = defaultRatio;
	private int[] handle = {0, vertices/4, vertices/2, vertices/2+vertices/4};

	public EllipseRoi(double x1, double y1, double x2, double y2, double aspectRatio) {
		super(new float[vertices], new float[vertices], vertices, FREEROI);
		if (aspectRatio<0.0) aspectRatio = 0.0;
		if (aspectRatio>1.0) aspectRatio = 1.0;
		this.aspectRatio = aspectRatio;
		makeEllipse(x1, y1, x2, y2);
		state = NORMAL;
	}

	public EllipseRoi(int sx, int sy, ImagePlus imp) {
		super(sx, sy, imp);
		type = FREEROI;
		xstart = ic.offScreenXD(sx);
		ystart = ic.offScreenYD(sy);
	}

	public void draw(Graphics g) {
		super.draw(g);
		int size2 = HANDLE_SIZE/2;
		if (!overlay) {
			for (int i=0; i<handle.length; i++)
				drawHandle(g, xp2[handle[i]]-size2, yp2[handle[i]]-size2);
		}
	}

	protected void grow(int sx, int sy) {
		double x1 = xstart;
		double y1 = ystart;
		double x2 = ic.offScreenXD(sx);
		double y2 = ic.offScreenYD(sy);
		makeEllipse(x1, y1, x2, y2);
		imp.draw();
	}
		
	void makeEllipse(double x1, double y1, double x2, double y2) {
		double centerX = (x1 + x2)/2.0;
		double centerY = (y1 + y2)/2.0;
		double dx = x2 - x1;
		double dy = y2 - y1;
		double major = Math.sqrt(dx*dx + dy*dy);
		double minor = major*aspectRatio;
		double phiB = Math.atan2(dy, dx);         
		double alpha = phiB*180.0/Math.PI;
		nPoints = 0;
		for (int i=0; i<vertices; i++) {
			double degrees = i*360.0/vertices;
			double beta1 = degrees/180.0*Math.PI;
			dx = Math.cos(beta1)*major/2.0;
			dy = Math.sin(beta1)*minor/2.0;
			double beta2 = Math.atan2(dy, dx);
			double rad = Math.sqrt(dx*dx + dy*dy);
			double beta3 = beta2+ alpha/180.0*Math.PI;
			double dx2 = Math.cos(beta3)*rad;
			double dy2 = Math.sin(beta3)*rad;
			xpf[nPoints] = (float)(centerX+dx2);
			ypf[nPoints] = (float)(centerY+dy2);
			nPoints++;
		}
		makePolygonRelative();
		cachedMask = null;
	}

	void makePolygonRelative() {
		FloatPolygon poly = new FloatPolygon(xpf, ypf, nPoints);
		Rectangle r = poly.getBounds();
		x = r.x;
		y = r.y;
		width = r.width;
		height = r.height;
		bounds = poly.getFloatBounds();
		float xbase = (float)bounds.getX();
		float ybase = (float)bounds.getY();
		for (int i=0; i<nPoints; i++) {
			xpf[i] = xpf[i]-xbase;
			ypf[i] = ypf[i]-ybase;
		}
	}
	
	protected void handleMouseUp(int screenX, int screenY) {
		if (state==CONSTRUCTING) {
            addOffset();
			finishPolygon();
			if (Recorder.record) {
				double x1 = xpf[handle[2]]+x;
				double y1 = ypf[handle[2]]+y;
				double x2 = xpf[handle[0]]+x;
				double y2 = ypf[handle[0]]+y;
 				if (Recorder.scriptMode())
					Recorder.recordCall("imp.setRoi(new EllipseRoi("+x1+", "+y1+", "+x2+", "+y2+", "+IJ.d2s(aspectRatio,2)+"));");
				else
					Recorder.record("makeEllipse", (int)x1, (int)y1, (int)x2, (int)y2, aspectRatio);
			}
        }
		state = NORMAL;
	}
	
	protected void moveHandle(int sx, int sy) {
		double ox = ic.offScreenXD(sx); 
		double oy = ic.offScreenYD(sy);
		double xbase=x, ybase=y;
		if (bounds!=null) {
			xbase = bounds.x;
			ybase = bounds.y;
		}
		double x1 = xpf[handle[2]]+xbase;
		double y1 = ypf[handle[2]]+ybase;
		double x2 = xpf[handle[0]]+xbase;
		double y2 = ypf[handle[0]]+ybase;
		switch(activeHandle) {
			case 0: 
				x2 = ox;
				y2 = oy;
				break;
			case 1: 
				double dx = (xpf[handle[3]]+xbase) - ox;
				double dy = (ypf[handle[3]]+ybase) - oy;
				updateRatio(Math.sqrt(dx*dx+dy*dy), x1, y1, x2, y2);
				break;
			case 2: 
				x1 = ox;
				y1 = oy;
				break;
			case 3: 
				dx = (xpf[handle[1]]+xbase) - ox;
				dy = (ypf[handle[1]]+ybase) - oy;
				updateRatio(Math.sqrt(dx*dx+dy*dy), x1, y1, x2, y2);
				break;
		}
		makeEllipse(x1, y1, x2, y2);
		imp.draw();
	}
	
	void updateRatio(double minor, double x1, double y1, double x2, double y2) {
		double dx = x2 - x1;
		double dy = y2 - y1;
		double major = Math.sqrt(dx*dx+dy*dy);
		aspectRatio = minor/major;
		if (aspectRatio>1.0) aspectRatio = 1.0;
		defaultRatio = aspectRatio;
	}
	
	public int isHandle(int sx, int sy) {
		int size = HANDLE_SIZE+5;
		int halfSize = size/2;
		int index = -1;
		for (int i=0; i<handle.length; i++) {
			int sx2 = xp2[handle[i]]-halfSize, sy2=yp2[handle[i]]-halfSize;
			if (sx>=sx2 && sx<=sx2+size && sy>=sy2 && sy<=sy2+size) {
				index = i;
				break;
			}
		}
		return index;
	}
	
	/** Returns the perimeter of this ellipse. */
	public double getLength() {
		double length = 0.0;
		double dx, dy;
		double w2=1.0, h2=1.0;
		if (imp!=null) {
			Calibration cal = imp.getCalibration();
			w2 = cal.pixelWidth*cal.pixelWidth;
			h2 = cal.pixelHeight*cal.pixelHeight;
		}
		for (int i=0; i<(nPoints-1); i++) {
			dx = xpf[i+1]-xpf[i];
			dy = ypf[i+1]-ypf[i];
			length += Math.sqrt(dx*dx*w2+dy*dy*h2);
		}
		dx = xpf[0]-xpf[nPoints-1];
		dy = ypf[0]-ypf[nPoints-1];
		length += Math.sqrt(dx*dx*w2+dy*dy*h2);
		return length;
	}

	/** Returns x1, y1, x2, y2 and aspectRatio as a 5 element array. */
	public double[] getParams() {
		double xbase=x, ybase=y;
		if (bounds!=null) {
			xbase = bounds.x;
			ybase = bounds.y;
		}
		double[] params = new double[5];
		params[0] = xpf[handle[2]]+xbase;
		params[1]  = ypf[handle[2]]+ybase;
		params[2]  = xpf[handle[0]]+xbase;
		params[3]  = ypf[handle[0]]+ybase;
		params[4]  = aspectRatio;
		return params;
	}

	public double[] getFeretValues() {
		double a[] = super.getFeretValues();
		double pw=1.0, ph=1.0;
		if (imp!=null) {
			Calibration cal = imp.getCalibration();
			pw = cal.pixelWidth;
			ph = cal.pixelHeight;
		}
		double[] p = getParams();
		double dx = (p[2] - p[0])*pw;
		double dy = (p[3] - p[1])*ph;
		double major = Math.sqrt(dx*dx+dy*dy);
		double minor = major*p[4];
		a[0] = major;
		a[2] = (pw==ph)?minor:a[2];
		return a;
	}
	
	/** Always returns true. */
	public boolean subPixelResolution() {
		return true;
	}

}
