package psplot;

import java.awt.Color;
import real.*;

public class Arrow implements mouseActive {
	RealPoint v[];
	double tw, tl, offset, hw, hl;
	double ell;
	public RealPoint head, tail;
	Real2DVector axis;
	PlotCanvas canvas;
	public Color fillColor = Color.black;
	
	//                 4
	//  6 ------------- 5   
	//  |                     3
	//  0 ------------- 1
	//                 2
	
	//   new design:   
	//                   6
	//    9 ----- 8 ------ 7   
	//  0                     5
	//    1 ----- 2 ------ 3
	//                   4
	
	// w = width of stem, 2*w-2.5*w = head length, 3*w = head width
	public Arrow(RealPoint t, RealPoint h, double w, PlotCanvas c) {
		canvas = c;
		tw = w*0.5;
		hl = 2*w;
		offset = 0.5*w;
		hw = w;
		
		v = new RealPoint[10];
		axis = new Real2DVector(0, 0);
		locate(h, t);
	}
	
	public Arrow(RealPoint t, Real2DVector v, double w, PlotCanvas c) {
		this(t, new RealPoint(t.x + v.x, t.y + v.y), w, c);
	}
	
	public void setFillColor(Color c) {
		fillColor = c;
	}
	
	public void locate(RealPoint h, RealPoint t) {
		double r = t.distanceTo(h);
		locate(h, t, r);
	}
	
	// In addition, build the convex polygons for tail etc.
	// in local coordinates and use those 
	
	public void locate(RealPoint h, RealPoint t, double r) {
		// System.out.println("h, t, r = " + h + ", " + t + ", " + r);
		if (r > 0.0) {
			ell = r;
			head = h; tail = t;
			tl = r - hl;
			double x = (h.x - t.x)/r; double y = (h.y - t.y)/r;
			axis.x = x; axis.y = y;
			double nx = -y, ny = x;
			// now [ x, y] = unit along the arrow
			// and [-y, x] = this rotated 90 degrees
			v[0] = new RealPoint(t.x, t.y);
			v[1] = new RealPoint(t.x - tw*nx + tw*x, t.y - tw*ny + tw*y);
			v[3] = new RealPoint(h.x - (hl*x + tw*nx), h.y - (hl*y + tw*ny));
			v[2] = RealPoint.interpolate(0.5, v[1], v[3]);
			v[4] = new RealPoint(h.x - ((offset+hl)*x + (hw+tw)*nx), 
				h.y - ((offset+hl)*y + (hw+tw)*ny));
			v[5] = new RealPoint(h.x, h.y);
			v[7] = new RealPoint(h.x - (hl*x - tw*nx), h.y - (hl*y - tw*ny));
			v[9] = new RealPoint(t.x + tw*nx + tw*x, t.y + tw*ny + tw*y);
			v[8] = RealPoint.interpolate(0.5, v[7], v[9]);
			v[6] = new RealPoint(h.x - ((offset+hl)*x - (hw+tw)*nx), 
			h.y - ((offset+hl)*y - (hw+tw)*ny));
		}
	}
	
	// -------------------------------------------------------------
	
	public Real2DVector vector() {
		return(new Real2DVector(head.x - tail.x, head.y - tail.y));
	}
	
	// -------------------------------------------------------------
	
	public final static int HEAD = 1, MIDDLE = 2, TAIL = 4;
	public int active = -1;
	// rp = coordinates relative to axis
	RealPoint rp;
	RealPolygon boundary, vb;
	ConvexPolygon cp;
	Real2DVector a;
	
	public boolean mouseDown(RealPoint P, int flag) {
		if (mouseDown(P) && (active & flag) != 0) {
			return(true);
		} else return(false);
	}
	
	// rp = null, active = -1 on entry
	public boolean mouseDown(RealPoint P) {
		ConvexPolygon poly;
		if ((poly = convexTail()).contains(P)) {
			active = TAIL;
			rp = relativeLocation(P, tail);
			boundary = canvas.vertexBoundary(2, 4, 4, 2);
			Real2DVector v = vector().times(-1);
			vb = boundary.translatedBy(v);
			cp = new ConvexPolygon(vb);
			vb = boundary.clippedTo(cp);
			return(true);
		} else if ((poly = convexMiddle()).contains(P)) {
			active = MIDDLE;
			boundary = canvas.vertexBoundary(2, 4, 4, 2);
			cp = new ConvexPolygon(boundary);
			rp = relativeLocation(P, head);
			return(true);
		} else if ((poly = convexHead()).contains(P)) {
			active = HEAD;
			// save the axis
			a = new Real2DVector(axis.x, axis.y);
			boundary = canvas.vertexBoundary(0, 2, 2, 0);
			cp = new ConvexPolygon(boundary);
			rp = relativeLocation(P, head);
			return(true);
		}
		return(false);
	}
	
	public boolean mouseDrag(RealPoint P) {
		switch(active) {
			case HEAD:
				// stretch
				stretch(P, cp);
				break;
			case MIDDLE:
				// rotate
				rotateTo(P, cp);
				break;
			case TAIL:
				// translate
				translate(P, vb);
				break;
		}
		if (active > -1) {
			return(true);
		}
		return(false);
	}
	
	public boolean mouseUp(RealPoint P) {
		if (active > -1) {
			switch(active) {
				case HEAD:
					break;
				case MIDDLE:
					break;
				case TAIL:
					break;
				default:
					break;
			}
			active = -1;
			rp = null;
			boundary = null;
			return(true);
		}
		return(false);
	}
	
	// changes h to its projection of onto the axis
	// a = unit vector
	double project(Real2DVector h, Real2DVector a) {
		double d = a.x*h.x + a.y*h.y;
		h.x = d*a.x; h.y = d*a.y;
		return(d);
	}
	
	boolean blocked = false;
	
	// returns first point of the arc from P to C+a
	// where it crosses ell, or the end point of the arc if none do
	// C, P known inside half plane of m
	RealPoint firstCrossing(RealLine m, RealPoint C, RealPoint P, Real2DVector a) {
		RealPoint Q = new RealPoint(C.x + a.x, C.y + a.y);
		double c = m.A*C.x + m.B*C.y;
		double d = (m.C - c);
		// System.out.println("m = " + m);
		if (d >= ell) {
			// the line does not meet the circle
			blocked = false;
			return(Q);
		} else {
			// d < ell
			Real2DVector u = new Real2DVector(m.A, m.B);
			Real2DVector v = new Real2DVector(P.x - C.x, P.y - C.y);
			double p = Math.acos(d/ell);
			double s = v.angleBetween(u);
			// System.out.println("mouse = " + a);
			// System.out.println("angle to [A, B] = " + s);
			double t = v.angleBetween(a);
			if (s > 0) {
				s -= p;
				if (t <= s) {
					blocked = false;
					return(Q);
				}
			} else {
				s += p;
				if (t >= s) {
					blocked = false;
					return(Q);
				}
			}
			if (Math.abs(t) > p) { 
				blocked = false;
				return(Q);
			}
			blocked = true;
			v = v.rotatedBy(s);
			// System.out.println("moving");
			return(new RealPoint(C.x + v.x, C.y + v.y));
		} 
	}
	
	void rotateTo(RealPoint h, ConvexPolygon cp) {
		Real2DVector a = new Real2DVector(h.x - tail.x, h.y - tail.y);
		double r = tail.distanceTo(h);
		if (r > 0.001) {
			a.x *= (ell/r); a.y *= (ell/r);
			RealLine m[] = cp.ell;
			RealPoint H = new RealPoint(head.x, head.y);
			int i = 0;
			do {
				H = firstCrossing(m[i++], tail, H, a);
			} while (!blocked && i < 4);
			locate(H, tail);
		}
	}
	
	// constrain to inside R, n = location relative to tail
	void stretch(RealPoint h, ConvexPolygon cp) {
		Real2DVector e = new Real2DVector(h.x - tail.x, h.y - tail.y);
		double d = project(e, a);
		if (d > 0) {
			e.x -= rp.x*a.x;
			e.y -= rp.x*a.y;
		} else {
			e.x += rp.x*a.x;
			e.y += rp.x*a.y;
		}
		h.x = tail.x + e.x;
		h.y = tail.y + e.y;
		// project h onto the boundary of a parallelogram
		// cp has [A,B] bottom, top; [C,D] left, right
		h = cp.clipEnd(tail, h);
		locate(h, tail);
	}
	
	// translate; n = relative location; must be clipped to R
	public void translate(RealPoint t, RealPolygon R) {
		t.x -= (rp.x*axis.x - rp.y*axis.y);
		t.y -= (rp.x*axis.y + rp.y*axis.x);
		// project h onto the boundary of a parallelogram
		// R has [A,B] bottom, top; [C,D] left, right
		t = R.projection(t);
		RealPoint h = new RealPoint(t.x + (head.x - tail.x), t.y + (head.y - tail.y));
		locate(h, t, ell);
	}
	
	// returns coordinates relative to the axis of the arrow
	public RealPoint relativeLocation(RealPoint P, RealPoint Q) {
		double x = P.x - Q.x, y = P.y - Q.y;
		double X = x*axis.x + y*axis.y;
		double Y = -y*axis.x + x*axis.y;
		return(new RealPoint(X, Y));
	}
	
	// --------------------------------------------------------------
	
	public PlotPath path() {
		PlotPath p = new PlotPath(canvas);
		p.moveto(v[9]);
		p.lineto(v[0]);
		p.lineto(v[1]);
		p.lineto(v[3]);
		p.lineto(v[4]);
		p.lineto(v[5]);
		p.lineto(v[6]);
		p.lineto(v[7]);
		p.lineto(v[9]);
		p.close();
		return(p);
	}
	
	public PlotPath tailPath() {
		PlotPath p = new PlotPath(canvas);
		p.lineto(v[0]);
		p.lineto(v[1]);
		p.lineto(v[2]);
		p.lineto(v[8]);
		p.lineto(v[9]);
		p.moveto(v[0]);
		p.close();
		return(p);
	}
	
	public PlotPath middlePath() {
		PlotPath p = new PlotPath(canvas);
		p.moveto(v[2]);
		p.lineto(v[3]);
		p.lineto(v[7]);
		p.lineto(v[8]);
		p.lineto(v[2]);
		p.close();
		return(p);
	}
	
	public PlotPath headPath() {
		PlotPath p = new PlotPath(canvas);
		p.moveto(v[3]);
		p.lineto(v[4]);
		p.lineto(v[5]);
		p.lineto(v[6]);
		p.lineto(v[7]);
		p.lineto(v[3]);
		p.close();
		return(p);
	}
	
	ConvexPolygon convexTail() {
		RealPoint P[] = new RealPoint[5];
		P[0] = v[0];
		P[1] = v[1];
		P[2] = v[2];
		P[3] = v[8];
		P[4] = v[9];
		return(new ConvexPolygon(P));
	}
	
	ConvexPolygon convexMiddle() {
		RealPoint P[] = new RealPoint[4];
		P[0] = v[2];
		P[1] = v[3];
		P[2] = v[7];
		P[3] = v[8];
		return(new ConvexPolygon(P));
	}
	
	ConvexPolygon convexHead() {
		RealPoint P[] = new RealPoint[3];
		P[0] = v[5];
		P[1] = v[6];
		P[2] = v[4];
		return(new ConvexPolygon(P));
	}
	
	public Arrow transformed(AffineTransform2D a) {
		RealPoint h = a.transform(head);
		RealPoint t = a.transform(tail);
		return(new Arrow(t, h, 2*tw, canvas));
	}
	
	public String toString() {
		String s = new String("");
		for (int i=0;i<v.length;i++) {
			if (v[i] != null) {
				s = s + ':' + v[i].toString();
			} else {
				s = s + ": * ";
			}
		}
		return(s);
	}
} 