package plot;

import java.awt.*;
import java.util.Stack;

public class DrawCanvas extends Canvas {
	int W, H, w, h;
	Stack pathStack;
	RealGraphics gfx;
	Color colour = Color.black;
	
	PathConstructor cp = null;
	ActivePath activePath = null;

	public DrawCanvas(int w, int h) {
		this.w = w; this.h = h;
		W = w; H = h;
		resize(w, h);
		setBackground(Color.white);
		pathStack = new Stack();
	}

	/** Reverses the normal ordering of paint() and 
		update() for flicker-free performance. */
	public void paint(Graphics g) {
		update(g);
	}

	PlotPath gridLines;
	Image img;
	/** Usually overridden. */
	public void update(Graphics g) {
		if (gfx == null) {
			img = createImage(W, H);
			Graphics G = img.getGraphics();
			gfx = new RealGraphics(G, w, h);
			gridLines = new PlotPath(gfx);
			try {
				for (int i=0;i < w;i += 10) {
					gridLines.moveto(i, 0);
					gridLines.lineto(i, h);
				}
				for (int i=0;i < h;i += 10) {
					gridLines.moveto(0, i);
					gridLines.lineto(w, i);
				}
			} catch (NoCurrentPointException e) { ; }
		}
		gfx.clear();
		if (displayGrid) {
			gridLines.stroke(new Color(4*51, 4*51, 255));
		}
		for (int i=0;i<pathStack.size();i++) {
			DrawPath path = (DrawPath) pathStack.elementAt(i);
			path.draw(gfx);
		}
		if (cp != null) {
			cp.draw(gfx);
		}
		
		g.drawImage(img, 0, 0, null);
	}
	
	// - button events from the frame -------------------------
	
	public void setColor(Color c) {
		colour = c;
	}
	
	boolean displayGrid = true;
	public void grid() {
		displayGrid = !displayGrid;
		repaint();
	}
	
	public void delete() {
		// delete segment by segment if cp != null
		// else delete the active path
		if (activePath == cp && cp != null) {
			if (cp.size() > 0) {
				cp.pop();
			} else {
				cp = null;
				if (pathStack.size() > 0) {
					activePath = (ActivePath) pathStack.peek();
					activePath.setActive(true);
				}
			} 
		} else if (activePath != null) {
			pathStack.removeElement(activePath);
			if (pathStack.size()> 0) {
				activePath = (ActivePath) pathStack.peek();
				activePath.setActive(true);
			} else {
				activePath = null;
			}
		}
		adjusting = false;
		repaint();
	}
	
	public void newpath() {
		if (cp != null) {
			DrawPath dp = new DrawPath(cp);
			pathStack.push(dp);
		}
		if (activePath != null) {
			activePath.setActive(false);
		}
		cp = new PathConstructor(gfx);
		activePath = cp;
		activePath.setActive(true);
		move();
	}
	
	public void move() {
		if (cp != null) {
			cp.setState(PathConstructor.Move);
		}
		adjusting = false;
	}
	
	public void stroke() {
		if (activePath == cp && cp != null) {
			DrawPath dp = new DrawPath(cp, DrawPath.Stroke, colour);
			pathStack.push(dp);
			activePath = dp;
			cp = null;
		} else if (activePath != null) {
			DrawPath dp = (DrawPath) activePath;
			dp.stroke(colour);
		}
		adjusting = false;
		repaint();
	}
	
	public void fill() {
		if (activePath == cp && cp != null) {
			DrawPath dp = new DrawPath(cp, DrawPath.Fill, colour);
			pathStack.push(dp);
			activePath = dp;
			cp = null;
		} else if (activePath != null) {
			DrawPath dp = (DrawPath) activePath;
			dp.fill(colour);
		}
		adjusting = false;
		repaint();
	}
	
	public void line() {
		if (cp != null) {
			cp.setState(PathConstructor.Line);
		}  
		adjusting = false;
	}
	
	int[] box() {
		double b[] = null;
		for (int i=0;i<pathStack.size();i++) {
			DrawPath p = (DrawPath) pathStack.elementAt(i);
			b = DrawPath.envelope(b, p.box());
		}
		if (b != null) {
			int c[] = new int[4];
			c[0] = (int) Math.floor(b[0]);
			c[1] = (int) Math.floor(b[1]);
			c[2] = (int) Math.ceil(b[2]);
			c[3] = (int) Math.ceil(b[3]);
			return(c);
		} else return(null);
	}
	
	public String toPS() {
		int b[] = box();
		if (b != null) {
			StringBuffer sb = new StringBuffer();
			sb.append("%!\n");
			sb.append('\n');
			sb.append("%%BoundingBox: " 
				+ b[0] + " " 
				+ b[1] + " " 
				+ b[2] + " " 
				+ b[3] + "\n"); 
			sb.append('\n');		
			for (int i=0;i<pathStack.size();i++) {
				DrawPath path = (DrawPath) pathStack.elementAt(i);
				sb.append(path.toPS());	
				sb.append('\n');		
			}
			adjusting = false;
			String s = new String(sb);
			return(s);
		} else return("");
	}
	
	public void curve() {
		if (cp != null) {
			cp.setState(PathConstructor.Curve);
		}
		adjusting = false;
	}
	
	public void close() {
		if (cp != null && cp.last() != null) {
			try {
				cp.addSegment(cp.last());
			} catch(NoCurrentPointException exc) { ; }
			cp.close();
			repaint();
		}
		adjusting = false;
	}
		
	boolean adjusting = false;
	public void adjust() {
		adjusting = true;
	}
	
	// - mouse events ------------------------------------
	
	PixelBox activeBox = null;
	public boolean mouseDown(Event e, int x, int y) {
		// mark the active node, set the active path
		double P[] = gfx.toRealPoint(x, y);
		if (adjusting) {
			PixelBox a = null;
			DrawPath q = null;
			PixelBox p;
			DrawPath path = null;
			for (int i=0;i<pathStack.size();i++) {
				path = (DrawPath) pathStack.elementAt(i);
				if ((p = (PixelBox) path.mouseDown(P)) != null) { 
					a = p;
					q = path;
				}
			}
			if (cp != null) {
				if ((p = (PixelBox) cp.mouseDown(P)) != null) {
					a = p;
					q = path;
				}
			}
			// System.out.println("a = " + a);
			if (a != null) {
				if (q != null & q != activePath) {
					if (activePath != null) activePath.setActive(false);
					q.setActive(true);
					activePath = q;
				}
				a.setColor(Color.red);
				activeBox = a;
				return(true);
			} 
		} else if (cp != null) {
			try {
				cp.addSegment(P);
			} catch(NoCurrentPointException exc) { ; }
			repaint();
		}
		return(true);
	}
	
	public boolean mouseDrag(Event e, int x, int y) {
		double P[] = gfx.toRealPoint(x, y);
		if (activeBox != null) {
			activeBox.mouseDrag(P);
			repaint();
		}
		return(true);
	}
	
	public boolean mouseUp(Event e, int x, int y) {
		double P[] = gfx.toRealPoint(x, y);
		if (activeBox != null) {
			activeBox.setColor(Color.cyan);
			activeBox = null;
			repaint();
		}
		return(true);
	}
	
}
	
