/* 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.logisim.std.gates;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.HashMap;

import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.util.GraphicsUtil;

public class PainterShaped {	
	private static final GeneralPath PATH_NARROW;
	private static final GeneralPath PATH_MEDIUM;
	private static final GeneralPath PATH_WIDE;

	private static final GeneralPath SHIELD_NARROW;
	private static final GeneralPath SHIELD_MEDIUM;
	private static final GeneralPath SHIELD_WIDE;
	
	static {
		PATH_NARROW = new GeneralPath();
		PATH_NARROW.moveTo(0, 0);
		PATH_NARROW.quadTo(-10, -15, -30, -15);
		PATH_NARROW.quadTo(-22,   0, -30,  15);
		PATH_NARROW.quadTo(-10,  15,   0,   0);
		PATH_NARROW.closePath();
		
		PATH_MEDIUM = new GeneralPath();
		PATH_MEDIUM.moveTo(0, 0);
		PATH_MEDIUM.quadTo(-20, -25, -50, -25);
		PATH_MEDIUM.quadTo(-37,   0, -50,  25);
		PATH_MEDIUM.quadTo(-20,  25,   0,   0);
		PATH_MEDIUM.closePath();
		
		PATH_WIDE = new GeneralPath();
		PATH_WIDE.moveTo(0, 0);
		PATH_WIDE.quadTo(-25, -35, -70, -35);
		PATH_WIDE.quadTo(-50,   0, -70,  35);
		PATH_WIDE.quadTo(-25,  35,   0,   0);
		PATH_WIDE.closePath();
		
		SHIELD_NARROW = new GeneralPath();
		SHIELD_NARROW.moveTo(-30, -15);
		SHIELD_NARROW.quadTo(-22,   0, -30,  15);
		
		SHIELD_MEDIUM = new GeneralPath();
		SHIELD_MEDIUM.moveTo(-50, -25);
		SHIELD_MEDIUM.quadTo(-37,   0, -50,  25);
		
		SHIELD_WIDE = new GeneralPath();
		SHIELD_WIDE.moveTo(-70, -35);
		SHIELD_WIDE.quadTo(-50,   0, -70,  35);
	}

	private PainterShaped() { }
	
	private static HashMap<Integer,int[]> INPUT_LENGTHS = new HashMap<Integer,int[]>();
	
	static void paintAnd(InstancePainter painter, int width, int height) {
		Graphics g = painter.getGraphics();
		GraphicsUtil.switchToWidth(g, 2);
		int[] xp = new int[] { -width / 2, -width + 1, -width + 1, -width / 2 }; 
		int[] yp = new int[] { -width / 2, -width / 2, width / 2, width / 2 };
		GraphicsUtil.drawCenteredArc(g, -width / 2, 0, width / 2, -90, 180);

		g.drawPolyline(xp, yp, 4);
		if (height > width) {
			g.drawLine(-width + 1, -height / 2, -width + 1, height / 2);
		}
	}
	
	static void paintOr(InstancePainter painter, int width, int height) {
		Graphics g = painter.getGraphics();
		GraphicsUtil.switchToWidth(g, 2);
		/* The following, used previous to version 2.5.1, didn't use GeneralPath
		g.setColor(Color.LIGHT_GRAY);
		if (width < 40) {
			GraphicsUtil.drawCenteredArc(g, -30, -21, 36, -90, 53);
			GraphicsUtil.drawCenteredArc(g, -30,  21, 36, 90, -53);
		} else if (width < 60) {
			GraphicsUtil.drawCenteredArc(g, -50, -37, 62, -90, 53);
			GraphicsUtil.drawCenteredArc(g, -50,  37, 62, 90, -53);
		} else {
			GraphicsUtil.drawCenteredArc(g, -70, -50, 85, -90, 53);
			GraphicsUtil.drawCenteredArc(g, -70,  50, 85, 90, -53);
		}
		paintShield(g, -width, 0, width, height);
		*/
		
		GeneralPath path;
		if (width < 40) {
			path = PATH_NARROW;
		} else if (width < 60) {
			path = PATH_MEDIUM;
		} else {
			path = PATH_WIDE;
		}
		((Graphics2D) g).draw(path);
		if (height > width) {
			paintShield(g, 0, width, height);
		}
	}
	
	static void paintNot(InstancePainter painter) {
		Graphics g = painter.getGraphics();
		GraphicsUtil.switchToWidth(g, 2);
		if (painter.getAttributeValue(NotGate.ATTR_SIZE) == NotGate.SIZE_NARROW) {
			GraphicsUtil.switchToWidth(g, 2);
			int[] xp = new int[4];
			int[] yp = new int[4];
			xp[0] =  -6; yp[0] =  0;
			xp[1] = -19; yp[1] = -6;
			xp[2] = -19; yp[2] =  6;
			xp[3] =  -6; yp[3] =  0;
			g.drawPolyline(xp, yp, 4);
			g.drawOval(-6, -3, 6, 6);
		} else {
			int[] xp = new int[4];
			int[] yp = new int[4];
			xp[0] = -10; yp[0] = 0;
			xp[1] = -29; yp[1] = -7;
			xp[2] = -29; yp[2] = 7;
			xp[3] = -10; yp[3] = 0;
			g.drawPolyline(xp, yp, 4);
			g.drawOval(-9, -4, 9, 9);
		}
	}
	
	static void paintXor(InstancePainter painter, int width, int height) {
		Graphics g = painter.getGraphics();
		paintOr(painter, width - 10, width - 10);
		paintShield(g, -10, width - 10, height);
	}

	private static void paintShield(Graphics g, int xlate,
			int width, int height) {
		GraphicsUtil.switchToWidth(g, 2);
		g.translate(xlate, 0);
		((Graphics2D) g).draw(computeShield(width, height));
		g.translate(-xlate, 0);

		/* The following, used previous to version 2.5.1, didn't use GeneralPath
		if (width < 40) {
			GraphicsUtil.drawCenteredArc(g, x - 26, y, 30, -30, 60);
		} else if (width < 60) {
			GraphicsUtil.drawCenteredArc(g, x - 43, y, 50, -30, 60);
		} else {
			GraphicsUtil.drawCenteredArc(g, x - 60, y, 70, -30, 60);
		}
		if (height > width) { // we need to draw the shield
			GraphicsUtil.drawCenteredArc(g,
				x - dx, y - (width + extra) / 2,
				extra, -30, 60);
			GraphicsUtil.drawCenteredArc(g,
				x - dx, y + (width + extra) / 2,
				extra, -30, 60);
		}
		*/
	}
	
	private static GeneralPath computeShield(int width, int height) {
		GeneralPath base;
		if (width < 40) {
			base = SHIELD_NARROW;
		} else if (width < 60) {
			base = SHIELD_MEDIUM;
		} else {
			base = SHIELD_WIDE;
		}

		if (height <= width) { // no wings
			return base;
		} else { // we need to add wings
			int wingHeight = (height - width) / 2;
			int dx = Math.min(20, wingHeight / 4);
			
			GeneralPath path = new GeneralPath();
			path.moveTo(-width, -height / 2);
			path.quadTo(-width + dx, -(width + height) / 4, -width, -width / 2);
			path.append(base, true);
			path.quadTo(-width + dx, (width + height) / 4, -width, height / 2);
			return path;
		}
	}

	static void paintInputLines(InstancePainter painter, AbstractGate factory) {
		Location loc = painter.getLocation();
		boolean printView = painter.isPrintView();
		GateAttributes attrs = (GateAttributes) painter.getAttributeSet();
		Direction facing = attrs.facing;
		int inputs = attrs.inputs;
		int negated = attrs.negated;
		
		int[] lengths = getInputLineLengths(attrs, factory);
		if (painter.getInstance() == null) { // drawing ghost - negation bubbles only
			for (int i = 0; i < inputs; i++) {
				boolean iNegated = ((negated >> i) & 1) == 1;
				if (iNegated) {
					Location offs = factory.getInputOffset(attrs, i);
					Location loci = loc.translate(offs.getX(), offs.getY());
					Location cent = loci.translate(facing, lengths[i] + 5);
					painter.drawDongle(cent.getX(), cent.getY());
				}
			}
		} else {
			Graphics g = painter.getGraphics();
			Color baseColor = g.getColor();
			GraphicsUtil.switchToWidth(g, 3);
			for (int i = 0; i < inputs; i++) {
				Location offs = factory.getInputOffset(attrs, i);
				Location src = loc.translate(offs.getX(), offs.getY());
				int len = lengths[i];
				if (len != 0 && (!printView || painter.isPortConnected(i + 1))) {
					if (painter.getShowState()) {
						Value val = painter.getPort(i + 1);
						g.setColor(val.getColor());
					} else {
						g.setColor(baseColor);
					}
					Location dst = src.translate(facing, len);
					g.drawLine(src.getX(), src.getY(), dst.getX(), dst.getY());
				}
				if (((negated >> i) & 1) == 1) {
					Location cent = src.translate(facing, lengths[i] + 5);
					g.setColor(baseColor);
					painter.drawDongle(cent.getX(), cent.getY());
					GraphicsUtil.switchToWidth(g, 3);
				}
			}
		}
	}
	
	private static int[] getInputLineLengths(GateAttributes attrs, AbstractGate factory) {
		int inputs = attrs.inputs;
		int mainHeight = ((Integer) attrs.size.getValue()).intValue();
		Integer key = Integer.valueOf(inputs * 31 + mainHeight);
		Object ret = INPUT_LENGTHS.get(key);
		if (ret != null) {
			return (int[]) ret;
		}
		
		Direction facing = attrs.facing;
		if (facing != Direction.EAST) {
			attrs = (GateAttributes) attrs.clone();
			attrs.facing = Direction.EAST;
		}

		int[] lengths = new int[inputs];
		INPUT_LENGTHS.put(key, lengths);
		int width = mainHeight;
		Location loc0 = OrGate.FACTORY.getInputOffset(attrs, 0);
		Location locn = OrGate.FACTORY.getInputOffset(attrs, inputs - 1);
		int totalHeight = 10 + loc0.manhattanDistanceTo(locn);
		if (totalHeight < width) totalHeight = width;
		
		GeneralPath path = computeShield(width, totalHeight);
		for (int i = 0; i < inputs; i++) {
			Location loci = OrGate.FACTORY.getInputOffset(attrs, i);
			Point2D p = new Point2D.Float(loci.getX() + 1, loci.getY());
			int iters = 0;
			while (path.contains(p) && iters < 15) {
				iters++;
				p.setLocation(p.getX() + 1, p.getY());
			}
			if (iters >= 15) iters = 0;
			lengths[i] = iters;
		}
		
		/* used prior to 2.5.1, when moved to GeneralPath
		int wingHeight = (totalHeight - mainHeight) / 2;
		double wingCenterX = wingHeight * Math.sqrt(3) / 2;
		double mainCenterX = mainHeight * Math.sqrt(3) / 2;
		
		for (int i = 0; i < inputs; i++) {
			Location loci = factory.getInputOffset(attrs, i);
			int disti = 5 + loc0.manhattanDistanceTo(loci);
			if (disti > totalHeight - disti) { // ensure on top half
				disti = totalHeight - disti;
			}
			double dx;
			if (disti < wingHeight) { // point is on wing
				int dy = wingHeight / 2 - disti;
				dx = Math.sqrt(wingHeight * wingHeight - dy * dy) - wingCenterX;
			} else { // point is on main shield
				int dy = totalHeight / 2 - disti;
				dx = Math.sqrt(mainHeight * mainHeight - dy * dy) - mainCenterX;
			}
			lengths[i] = (int) (dx - 0.5);
		}
		*/
		return lengths;
	}
}
