/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.draw.tools;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.List;

import javax.swing.Icon;

import com.cburch.draw.actions.ModelAddAction;
import com.cburch.draw.canvas.Canvas;
import com.cburch.draw.model.CanvasModel;
import com.cburch.draw.shapes.Curve;
import com.cburch.draw.shapes.CurveUtil;
import com.cburch.draw.shapes.DrawAttr;
import com.cburch.draw.shapes.LineUtil;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.util.Icons;

public class CurveTool extends AbstractTool {
	private static final int BEFORE_CREATION = 0;
	private static final int ENDPOINT_DRAG = 1;
	private static final int CONTROL_DRAG = 2;
	
	private DrawingAttributeSet attrs;
	private int state;
	private Location end0;
	private Location end1;
	private Curve curCurve;
	private boolean mouseDown;
	private int lastMouseX;
	private int lastMouseY;
	
	public CurveTool(DrawingAttributeSet attrs) {
		this.attrs = attrs;
		state = BEFORE_CREATION;
		mouseDown = false;
	}
	
	@Override
	public Icon getIcon() {
		return Icons.getIcon("drawcurv.gif");
	}

	@Override
	public Cursor getCursor(Canvas canvas) {
		return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
	}
	
	@Override
	public void toolDeselected(Canvas canvas) {
		state = BEFORE_CREATION;
		repaintArea(canvas);
	}
	
	@Override
	public void mousePressed(Canvas canvas, MouseEvent e) {
		int mx = e.getX();
		int my = e.getY();
		lastMouseX = mx;
		lastMouseY = my;
		mouseDown = true;
		int mods = e.getModifiersEx();
		if ((mods & InputEvent.CTRL_DOWN_MASK) != 0) {
			mx = canvas.snapX(mx);
			my = canvas.snapY(my);
		}
		
		switch (state) {
		case BEFORE_CREATION:
		case CONTROL_DRAG:
			end0 = Location.create(mx, my);
			end1 = end0;
			state = ENDPOINT_DRAG;
			break;
		case ENDPOINT_DRAG:
			curCurve = new Curve(end0, end1, Location.create(mx, my));
			state = CONTROL_DRAG;
			break;
		}
		repaintArea(canvas);
	}
	
	@Override
	public void mouseDragged(Canvas canvas, MouseEvent e) {
		updateMouse(canvas, e.getX(), e.getY(), e.getModifiersEx());
		repaintArea(canvas);
	}
	
	@Override
	public void mouseReleased(Canvas canvas, MouseEvent e) {
		Curve c = updateMouse(canvas, e.getX(), e.getY(), e.getModifiersEx());
		mouseDown = false;
		if (state == CONTROL_DRAG) {
			if (c != null) {
				attrs.applyTo(c);
				CanvasModel model = canvas.getModel();
				canvas.doAction(new ModelAddAction(model, c));
				canvas.toolGestureComplete(this, c);
			}
			state = BEFORE_CREATION;
		}
		repaintArea(canvas);
	}
	
	@Override
	public void keyPressed(Canvas canvas, KeyEvent e) {
		int code = e.getKeyCode();
		if (mouseDown && (code == KeyEvent.VK_SHIFT
				|| code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_ALT)) {
			updateMouse(canvas, lastMouseX, lastMouseY, e.getModifiersEx());
			repaintArea(canvas);
		}
	}
	
	@Override
	public void keyReleased(Canvas canvas, KeyEvent e) {
		keyPressed(canvas, e);
	}

	@Override
	public void keyTyped(Canvas canvas, KeyEvent e) {
		char ch = e.getKeyChar();
		if (ch == '\u001b') { // escape key
			state = BEFORE_CREATION;
			repaintArea(canvas);
			canvas.toolGestureComplete(this, null);
		}
	}
	
	private Curve updateMouse(Canvas canvas, int mx, int my, int mods) {
		lastMouseX = mx;
		lastMouseY = my;
		
		boolean shiftDown = (mods & MouseEvent.SHIFT_DOWN_MASK) != 0;
		boolean ctrlDown = (mods & MouseEvent.CTRL_DOWN_MASK) != 0;
		boolean altDown = (mods & MouseEvent.ALT_DOWN_MASK) != 0;
		Curve ret = null;
		switch (state) {
		case ENDPOINT_DRAG:
			if (mouseDown) {
				if (shiftDown) {
					Location p = LineUtil.snapTo8Cardinals(end0, mx, my);
					mx = p.getX();
					my = p.getY();
				}
				if (ctrlDown) {
					mx = canvas.snapX(mx);
					my = canvas.snapY(my);
				}
				end1 = Location.create(mx, my);
			}
			break;
		case CONTROL_DRAG:
			if (mouseDown) {
				int cx = mx;
				int cy = my;
				if (ctrlDown) {
					cx = canvas.snapX(cx);
					cy = canvas.snapY(cy);
				}
				if (shiftDown) {
					double x0 = end0.getX();
					double y0 = end0.getY();
					double x1 = end1.getX();
					double y1 = end1.getY();
					double midx = (x0 + x1) / 2;
					double midy = (y0 + y1) / 2;
					double dx = x1 - x0;
					double dy = y1 - y0;
					double[] p = LineUtil.nearestPointInfinite(cx, cy,
							midx, midy, midx - dy, midy + dx);
					cx = (int) Math.round(p[0]);
					cy = (int) Math.round(p[1]);
				}
				if (altDown) {
					double[] e0 = { end0.getX(), end0.getY() };
					double[] e1 = { end1.getX(), end1.getY() };
					double[] mid = { cx, cy };
					double[] ct = CurveUtil.interpolate(e0, e1, mid);
					cx = (int) Math.round(ct[0]);
					cy = (int) Math.round(ct[1]);
				}
				ret = new Curve(end0, end1, Location.create(cx, cy));
				curCurve = ret;
			}
			break;
		}
		return ret;
	}

	private void repaintArea(Canvas canvas) {
		canvas.repaint();
	}
	
	@Override
	public List<Attribute<?>> getAttributes() {
		return DrawAttr.getFillAttributes(attrs.getValue(DrawAttr.PAINT_TYPE));
	}

	@Override
	public void draw(Canvas canvas, Graphics g) {
		g.setColor(Color.GRAY);
		switch (state) {
		case ENDPOINT_DRAG:
			g.drawLine(end0.getX(), end0.getY(), end1.getX(), end1.getY());
			break;
		case CONTROL_DRAG:
			((Graphics2D) g).draw(curCurve.getCurve2D());
			break;
		}
	}
}
