/* 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.wiring;

import java.awt.Color;
import java.awt.Graphics;

import com.cburch.logisim.circuit.RadixOption;
import com.cburch.logisim.comp.TextField;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceData;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstanceLogger;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.util.GraphicsUtil;

public class Probe extends InstanceFactory {
	public static final Probe FACTORY = new Probe();

	private static class StateData implements InstanceData, Cloneable {
		Value curValue = Value.NIL;
		
		@Override
		public Object clone() {
			try {
				return super.clone();
			} catch (CloneNotSupportedException e) {
				return null;
			}
		}
	}

	public static class ProbeLogger extends InstanceLogger {
		public ProbeLogger() { }
		
		@Override
		public String getLogName(InstanceState state, Object option) {
			String ret = state.getAttributeValue(StdAttr.LABEL);
			return ret != null && !ret.equals("") ? ret : null;
		}

		@Override
		public Value getLogValue(InstanceState state, Object option) {
			return getValue(state);
		}
	}

	public Probe() {
		super("Probe", Strings.getter("probeComponent"));
		setIconName("probe.gif");
		setFacingAttribute(StdAttr.FACING);
		setInstanceLogger(ProbeLogger.class);
	}

	@Override
	public AttributeSet createAttributeSet() {
		return new ProbeAttributes();
	}

	@Override
	public Bounds getOffsetBounds(AttributeSet attrsBase) {
		ProbeAttributes attrs = (ProbeAttributes) attrsBase;
		return getOffsetBounds(attrs.facing, attrs.width, attrs.radix);
	}
	
	//
	// graphics methods
	//
	@Override
	public void paintGhost(InstancePainter painter) {
		Graphics g = painter.getGraphics();
		Bounds bds = painter.getOffsetBounds();
		g.drawOval(bds.getX() + 1, bds.getY() + 1,
			bds.getWidth() - 1, bds.getHeight() - 1);
	}

	@Override
	public void paintInstance(InstancePainter painter) {
		Value value = getValue(painter);
		
		Graphics g = painter.getGraphics();
		Bounds bds = painter.getBounds(); // intentionally with no graphics object - we don't want label included
		int x = bds.getX();
		int y = bds.getY();
		g.setColor(Color.WHITE);
		g.fillRect(x + 5, y + 5, bds.getWidth() - 10, bds.getHeight() - 10);
		g.setColor(Color.GRAY);
		if (value.getWidth() <= 1) {
			g.drawOval(x + 1, y + 1,
				bds.getWidth() - 2, bds.getHeight() - 2);
		} else {
			g.drawRoundRect(x + 1, y + 1,
				bds.getWidth() - 2, bds.getHeight() - 2, 6, 6);
		}

		g.setColor(Color.BLACK);
		painter.drawLabel();

		if (!painter.getShowState()) {
			if (value.getWidth() > 0) {
				GraphicsUtil.drawCenteredText(g, "x" + value.getWidth(),
					bds.getX() + bds.getWidth() / 2, bds.getY() + bds.getHeight() / 2);
			}
		} else {
			paintValue(painter, value);
		}

		painter.drawPorts();
	}
	
	static void paintValue(InstancePainter painter, Value value) {
		Graphics g = painter.getGraphics();
		Bounds bds = painter.getBounds(); // intentionally with no graphics object - we don't want label included

		RadixOption radix = painter.getAttributeValue(RadixOption.ATTRIBUTE);
		if (radix == null || radix == RadixOption.RADIX_2) {
			int x = bds.getX();
			int y = bds.getY();
			int wid = value.getWidth();
			if (wid == 0) {
				x += bds.getWidth() / 2;
				y += bds.getHeight() / 2;
				GraphicsUtil.switchToWidth(g, 2);
				g.drawLine(x - 4, y, x + 4, y);
				return;
			}
			int x0 = bds.getX() + bds.getWidth() - 5;
			int compWidth = wid * 10;
			if (compWidth < bds.getWidth() - 3) {
				x0 = bds.getX() + (bds.getWidth() + compWidth) / 2 - 5;
			}
			int cx = x0;
			int cy = bds.getY() + bds.getHeight() - 12;
			int cur = 0;
			for (int k = 0; k < wid; k++) {
				GraphicsUtil.drawCenteredText(g,
					value.get(k).toDisplayString(), cx, cy);
				++cur;
				if (cur == 8) {
					cur = 0;
					cx = x0;
					cy -= 20;
				} else {
					cx -= 10;
				}
			}
		} else {
			String text = radix.toString(value);
			GraphicsUtil.drawCenteredText(g, text,
					bds.getX() + bds.getWidth() / 2,
					bds.getY() + bds.getHeight() / 2);
		}
	}
	
	//
	// methods for instances
	//
	@Override
	protected void configureNewInstance(Instance instance) {
		instance.setPorts(new Port[] { new Port(0, 0, Port.INPUT, BitWidth.UNKNOWN) });
		instance.addAttributeListener();
		configureLabel(instance);
	}

	@Override
	protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
		if (attr == Pin.ATTR_LABEL_LOC) {
			configureLabel(instance);
		} else if (attr == StdAttr.FACING || attr == RadixOption.ATTRIBUTE) {
			instance.recomputeBounds();
			configureLabel(instance);
		}
	}

	@Override
	public void propagate(InstanceState state) {
		StateData oldData = (StateData) state.getData();
		Value oldValue = oldData == null ? Value.NIL : oldData.curValue;
		Value newValue = state.getPort(0);
		boolean same = oldValue == null ? newValue == null : oldValue.equals(newValue);
		if (!same) {
			if (oldData == null) {
				oldData = new StateData();
				oldData.curValue = newValue;
				state.setData(oldData);
			} else {
				oldData.curValue = newValue;
			}
			int oldWidth = oldValue == null ? 1 : oldValue.getBitWidth().getWidth();
			int newWidth = newValue.getBitWidth().getWidth();
			if (oldWidth != newWidth) {
				ProbeAttributes attrs = (ProbeAttributes) state.getAttributeSet();
				attrs.width = newValue.getBitWidth();
				state.getInstance().recomputeBounds();
				configureLabel(state.getInstance());
			}
			state.fireInvalidated();
		}
	}

	private static Value getValue(InstanceState state) {
		StateData data = (StateData) state.getData();
		return data == null ? Value.NIL : data.curValue;
	}
	
	void configureLabel(Instance instance) {
		ProbeAttributes attrs = (ProbeAttributes) instance.getAttributeSet();
		Probe.configureLabel(instance, attrs.labelloc, attrs.facing);
	}

	//
	// static methods
	//
	static Bounds getOffsetBounds(Direction dir, BitWidth width,
			RadixOption radix) {
		Bounds ret = null;
		int len = radix == null || radix == RadixOption.RADIX_2 ? width.getWidth() : radix.getMaxLength(width);
		if (dir == Direction.EAST) {
			switch (len) {
			case 0:
			case 1: ret = Bounds.create(-20, -10, 20, 20); break;
			case 2: ret = Bounds.create(-20, -10, 20, 20); break;
			case 3: ret = Bounds.create(-30, -10, 30, 20); break;
			case 4: ret = Bounds.create(-40, -10, 40, 20); break;
			case 5: ret = Bounds.create(-50, -10, 50, 20); break;
			case 6: ret = Bounds.create(-60, -10, 60, 20); break;
			case 7: ret = Bounds.create(-70, -10, 70, 20); break;
			case 8: ret = Bounds.create(-80, -10, 80, 20); break;
			case  9: case 10: case 11: case 12:
			case 13: case 14: case 15: case 16:
				ret = Bounds.create(-80, -20, 80, 40); break;
			case 17: case 18: case 19: case 20:
			case 21: case 22: case 23: case 24:
				ret = Bounds.create(-80, -30, 80, 60); break;
			case 25: case 26: case 27: case 28:
			case 29: case 30: case 31: case 32:
				ret = Bounds.create(-80, -40, 80, 80); break;
			}
		} else if (dir == Direction.WEST) {
			switch (len) {
			case 0:
			case 1: ret = Bounds.create(0, -10, 20, 20); break;
			case 2: ret = Bounds.create(0, -10, 20, 20); break;
			case 3: ret = Bounds.create(0, -10, 30, 20); break;
			case 4: ret = Bounds.create(0, -10, 40, 20); break;
			case 5: ret = Bounds.create(0, -10, 50, 20); break;
			case 6: ret = Bounds.create(0, -10, 60, 20); break;
			case 7: ret = Bounds.create(0, -10, 70, 20); break;
			case 8: ret = Bounds.create(0, -10, 80, 20); break;
			case  9: case 10: case 11: case 12:
			case 13: case 14: case 15: case 16:
				ret = Bounds.create(0, -20, 80, 40); break;
			case 17: case 18: case 19: case 20:
			case 21: case 22: case 23: case 24:
				ret = Bounds.create(0, -30, 80, 60); break;
			case 25: case 26: case 27: case 28:
			case 29: case 30: case 31: case 32:
				ret = Bounds.create(0, -40, 80, 80); break;
			}
		} else if (dir == Direction.SOUTH) {
			switch (len) {
			case 0:
			case 1: ret = Bounds.create(-10, -20, 20, 20); break;
			case 2: ret = Bounds.create(-10, -20, 20, 20); break;
			case 3: ret = Bounds.create(-15, -20, 30, 20); break;
			case 4: ret = Bounds.create(-20, -20, 40, 20); break;
			case 5: ret = Bounds.create(-25, -20, 50, 20); break;
			case 6: ret = Bounds.create(-30, -20, 60, 20); break;
			case 7: ret = Bounds.create(-35, -20, 70, 20); break;
			case 8: ret = Bounds.create(-40, -20, 80, 20); break;
			case  9: case 10: case 11: case 12:
			case 13: case 14: case 15: case 16:
				ret = Bounds.create(-40, -40, 80, 40); break;
			case 17: case 18: case 19: case 20:
			case 21: case 22: case 23: case 24:
				ret = Bounds.create(-40, -60, 80, 60); break;
			case 25: case 26: case 27: case 28:
			case 29: case 30: case 31: case 32:
				ret = Bounds.create(-40, -80, 80, 80); break;
			}
		} else if (dir == Direction.NORTH) {
			switch (len) {
			case 0:
			case 1: ret = Bounds.create(-10, 0, 20, 20); break;
			case 2: ret = Bounds.create(-10, 0, 20, 20); break;
			case 3: ret = Bounds.create(-15, 0, 30, 20); break;
			case 4: ret = Bounds.create(-20, 0, 40, 20); break;
			case 5: ret = Bounds.create(-25, 0, 50, 20); break;
			case 6: ret = Bounds.create(-30, 0, 60, 20); break;
			case 7: ret = Bounds.create(-35, 0, 70, 20); break;
			case 8: ret = Bounds.create(-40, 0, 80, 20); break;
			case  9: case 10: case 11: case 12:
			case 13: case 14: case 15: case 16:
				ret = Bounds.create(-40, 0, 80, 40); break;
			case 17: case 18: case 19: case 20:
			case 21: case 22: case 23: case 24:
				ret = Bounds.create(-40, 0, 80, 60); break;
			case 25: case 26: case 27: case 28:
			case 29: case 30: case 31: case 32:
				ret = Bounds.create(-40, 0, 80, 80); break;
			}
		}
		if (ret == null) {
			ret = Bounds.create(0, -10, 20, 20); // should never happen
		}
		return ret;
	}

	static void configureLabel(Instance instance, Direction labelLoc,
			Direction facing) {
		Bounds bds = instance.getBounds();
		int x;
		int y;
		int halign;
		int valign;
		if (labelLoc == Direction.NORTH) {
			halign = TextField.H_CENTER;
			valign = TextField.V_BOTTOM;
			x = bds.getX() + bds.getWidth() / 2;
			y = bds.getY() - 2;
			if (facing == labelLoc) {
				halign = TextField.H_LEFT;
				x += 2;
			}
		} else if (labelLoc == Direction.SOUTH) {
			halign = TextField.H_CENTER;
			valign = TextField.V_TOP;
			x = bds.getX() + bds.getWidth() / 2;
			y = bds.getY() + bds.getHeight() + 2;
			if (facing == labelLoc) {
				halign = TextField.H_LEFT;
				x += 2;
			}
		} else if (labelLoc == Direction.EAST) {
			halign = TextField.H_LEFT;
			valign = TextField.V_CENTER;
			x = bds.getX() + bds.getWidth() + 2;
			y = bds.getY() + bds.getHeight() / 2;
			if (facing == labelLoc) {
				valign = TextField.V_BOTTOM;
				y -= 2;
			}
		} else { // WEST
			halign = TextField.H_RIGHT;
			valign = TextField.V_CENTER;
			x = bds.getX() - 2;
			y = bds.getY() + bds.getHeight() / 2;
			if (facing == labelLoc) {
				valign = TextField.V_BOTTOM;
				y -= 2;
			}
		}

		instance.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT,
				x, y, halign, valign);
	}
}