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

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

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.InstanceFactory;
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.tools.key.BitWidthConfigurator;
import com.cburch.logisim.util.GraphicsUtil;

public class PriorityEncoder extends InstanceFactory {
	private static final int OUT = 0;
	private static final int EN_IN = 1;
	private static final int EN_OUT = 2;
	private static final int GS = 3;
	
	public PriorityEncoder() {
		super("Priority Encoder", Strings.getter("priorityEncoderComponent"));
		setAttributes(new Attribute[] {
				StdAttr.FACING, Plexers.ATTR_SELECT, Plexers.ATTR_DISABLED
			}, new Object[] {
				Direction.EAST, BitWidth.create(3), Plexers.DISABLED_FLOATING
			});
		setKeyConfigurator(new BitWidthConfigurator(Plexers.ATTR_SELECT, 1, 5, 0));
		setIconName("priencod.gif");
		setFacingAttribute(StdAttr.FACING);
	}
	
	@Override
	public Bounds getOffsetBounds(AttributeSet attrs) {
		Direction dir = attrs.getValue(StdAttr.FACING);
		BitWidth select = attrs.getValue(Plexers.ATTR_SELECT);
		int inputs = 1 << select.getWidth();
		int offs = -5 * inputs;
		int len = 10 * inputs + 10;
		if (dir == Direction.NORTH) {
			return Bounds.create(offs,   0, len, 40);
		} else if (dir == Direction.SOUTH) {
			return Bounds.create(offs, -40, len, 40);
		} else if (dir == Direction.WEST) {
			return Bounds.create(  0, offs, 40, len);
		} else { // dir == Direction.EAST
			return Bounds.create(-40, offs, 40, len);
		}
	}

	@Override
	protected void configureNewInstance(Instance instance) {
		instance.addAttributeListener();
		updatePorts(instance);
	}
	
	@Override
	protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
		if (attr == StdAttr.FACING || attr == Plexers.ATTR_SELECT) {
			instance.recomputeBounds();
			updatePorts(instance);
		} else if (attr == Plexers.ATTR_SELECT) {
			updatePorts(instance);
		} else if (attr == Plexers.ATTR_DISABLED) {
			instance.fireInvalidated();
		}
	}

	private void updatePorts(Instance instance) {
		Object dir = instance.getAttributeValue(StdAttr.FACING);
		BitWidth select = instance.getAttributeValue(Plexers.ATTR_SELECT);
		int n = 1 << select.getWidth();
		Port[] ps = new Port[n + 4];
		if (dir == Direction.NORTH || dir == Direction.SOUTH) {
			int x = -5 * n + 10;
			int y = dir == Direction.NORTH ? 40 : -40; 
			for (int i = 0; i < n; i++) {
				ps[i] = new Port(x + 10 * i, y, Port.INPUT, 1);
			}
			ps[n + OUT] = new Port(0, 0, Port.OUTPUT, select.getWidth());
			ps[n + EN_IN] = new Port(x + 10 * n, y / 2, Port.INPUT, 1);
			ps[n + EN_OUT] = new Port(x - 10, y / 2, Port.OUTPUT, 1);
			ps[n + GS] = new Port(10, 0, Port.OUTPUT, 1);
		} else {
			int x = dir == Direction.EAST ? -40 : 40; 
			int y = -5 * n + 10;
			for (int i = 0; i < n; i++) {
				ps[i] = new Port(x, y + 10 * i, Port.INPUT, 1);
			}
			ps[n + OUT] = new Port(0, 0, Port.OUTPUT, select.getWidth());
			ps[n + EN_IN] = new Port(x / 2, y + 10 * n, Port.INPUT, 1);
			ps[n + EN_OUT] = new Port(x / 2, y - 10, Port.OUTPUT, 1);
			ps[n + GS] = new Port(0, 10, Port.OUTPUT, 1);
		}

		for (int i = 0; i < n; i++) {
			ps[i].setToolTip(Strings.getter("priorityEncoderInTip", "" + i));
		}
		ps[n + OUT].setToolTip(Strings.getter("priorityEncoderOutTip"));
		ps[n + EN_IN].setToolTip(Strings.getter("priorityEncoderEnableInTip"));
		ps[n + EN_OUT].setToolTip(Strings.getter("priorityEncoderEnableOutTip"));
		ps[n + GS].setToolTip(Strings.getter("priorityEncoderGroupSignalTip"));

		instance.setPorts(ps);
	}

	@Override
	public void propagate(InstanceState state) {
		BitWidth select = state.getAttributeValue(Plexers.ATTR_SELECT);
		int n = 1 << select.getWidth();
		boolean enabled = state.getPort(n + EN_IN) != Value.FALSE;
		
		int out = -1;
		Value outDefault;
		if (enabled) {
			outDefault = Value.createUnknown(select);
			for (int i = n - 1; i >= 0; i--) {
				if (state.getPort(i) == Value.TRUE) {
					out = i;
					break;
				}
			}
		} else {
			Object opt = state.getAttributeValue(Plexers.ATTR_DISABLED);
			Value base = opt == Plexers.DISABLED_ZERO ? Value.FALSE : Value.UNKNOWN;
			outDefault = Value.repeat(base, select.getWidth());
		}
		if (out < 0) {
			state.setPort(n + OUT, outDefault, Plexers.DELAY);
			state.setPort(n + EN_OUT, enabled ? Value.TRUE : Value.FALSE, Plexers.DELAY);
			state.setPort(n + GS, Value.FALSE, Plexers.DELAY);
		} else {
			state.setPort(n + OUT, Value.createKnown(select, out), Plexers.DELAY);
			state.setPort(n + EN_OUT, Value.FALSE, Plexers.DELAY);
			state.setPort(n + GS, Value.TRUE, Plexers.DELAY);
		}
	}

	@Override
	public void paintInstance(InstancePainter painter) {
		Graphics g = painter.getGraphics();
		Direction facing = painter.getAttributeValue(StdAttr.FACING);

		painter.drawBounds();
		Bounds bds = painter.getBounds();
		g.setColor(Color.GRAY);
		int x0;
		int y0;
		int halign;
		if (facing == Direction.WEST) {
			x0 = bds.getX() + bds.getWidth() - 3;
			y0 = bds.getY() + 15;
			halign = GraphicsUtil.H_RIGHT;
		} else if (facing == Direction.NORTH) {
			x0 = bds.getX() + 10;
			y0 = bds.getY() + bds.getHeight() - 2;
			halign = GraphicsUtil.H_CENTER;
		} else if (facing == Direction.SOUTH) {
			x0 = bds.getX() + 10;
			y0 = bds.getY() + 12;
			halign = GraphicsUtil.H_CENTER;
		} else {
			x0 = bds.getX() + 3;
			y0 = bds.getY() + 15;
			halign = GraphicsUtil.H_LEFT;
		}
		GraphicsUtil.drawText(g, "0", x0, y0, halign, GraphicsUtil.V_BASELINE);
		g.setColor(Color.BLACK);
		GraphicsUtil.drawCenteredText(g, "Pri",
				bds.getX() + bds.getWidth() / 2,
				bds.getY() + bds.getHeight() / 2);
		painter.drawPorts();
	}
}

