/* 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.Attributes;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Location;
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.tools.key.JoinedConfigurator;
import com.cburch.logisim.util.GraphicsUtil;

public class BitSelector extends InstanceFactory {
	public static final Attribute<BitWidth> GROUP_ATTR
		= Attributes.forBitWidth("group", Strings.getter("bitSelectorGroupAttr"));

	public BitSelector() {
		super("BitSelector", Strings.getter("bitSelectorComponent"));
		setAttributes(new Attribute[] {
				StdAttr.FACING, StdAttr.WIDTH, GROUP_ATTR
			}, new Object[] {
				Direction.EAST, BitWidth.create(8), BitWidth.ONE
			});
		setKeyConfigurator(JoinedConfigurator.create(
				new BitWidthConfigurator(GROUP_ATTR, 1, Value.MAX_WIDTH, 0),
				new BitWidthConfigurator(StdAttr.WIDTH)));

		setIconName("bitSelector.gif");
		setFacingAttribute(StdAttr.FACING);
	}
	
	@Override
	public Bounds getOffsetBounds(AttributeSet attrs) {
		Direction facing = attrs.getValue(StdAttr.FACING);
		Bounds base = Bounds.create(-30, -15, 30, 30);
		return base.rotate(Direction.EAST, facing, 0, 0);
	}
	
	@Override
	protected void configureNewInstance(Instance instance) {
		instance.addAttributeListener();
		updatePorts(instance);
	}
	
	@Override
	protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
		if (attr == StdAttr.FACING) {
			instance.recomputeBounds();
			updatePorts(instance);
		} else if (attr == StdAttr.WIDTH || attr == GROUP_ATTR) {
			updatePorts(instance);
		}
	}

	private void updatePorts(Instance instance) {
		Direction facing = instance.getAttributeValue(StdAttr.FACING);
		BitWidth data = instance.getAttributeValue(StdAttr.WIDTH);
		BitWidth group = instance.getAttributeValue(GROUP_ATTR);
		int groups = (data.getWidth() + group.getWidth() - 1) / group.getWidth() - 1;
		int selectBits = 1;
		if (groups > 0) {
			while (groups != 1) { groups >>= 1; selectBits++; }
		}
		BitWidth select = BitWidth.create(selectBits);

		Location inPt;
		Location selPt;
		if (facing == Direction.WEST) {
			inPt  = Location.create(30, 0);
			selPt = Location.create(10, 10);
		} else if (facing == Direction.NORTH) {
			inPt  = Location.create(  0, 30);
			selPt = Location.create(-10, 10);
		} else if (facing == Direction.SOUTH) {
			inPt  = Location.create(  0, -30);
			selPt = Location.create(-10, -10);
		} else {
			inPt  = Location.create(-30, 0);
			selPt = Location.create(-10, 10);
		}
		
		Port[] ps = new Port[3];
		ps[0] = new Port(0, 0, Port.OUTPUT, group.getWidth());
		ps[1] = new Port(inPt.getX(), inPt.getY(), Port.INPUT, data.getWidth());
		ps[2] = new Port(selPt.getX(), selPt.getY(), Port.INPUT, select.getWidth());
		ps[0].setToolTip(Strings.getter("bitSelectorOutputTip"));
		ps[1].setToolTip(Strings.getter("bitSelectorDataTip"));
		ps[2].setToolTip(Strings.getter("bitSelectorSelectTip"));
		instance.setPorts(ps);
	}

	@Override
	public void propagate(InstanceState state) {
		Value data = state.getPort(1);
		Value select = state.getPort(2);
		BitWidth groupBits = state.getAttributeValue(GROUP_ATTR);
		Value group;
		if (!select.isFullyDefined()) {
			group = Value.createUnknown(groupBits);
		} else {
			int shift = select.toIntValue() * groupBits.getWidth();
			if (shift >= data.getWidth()) {
				group = Value.createKnown(groupBits, 0);
			} else if (groupBits.getWidth() == 1) {
				group = data.get(shift);
			} else {
				Value[] bits = new Value[groupBits.getWidth()];
				for (int i = 0; i < bits.length; i++) {
					if (shift + i >= data.getWidth()) {
						bits[i] = Value.FALSE;
					} else {
						bits[i] = data.get(shift + i);
					}
				}
				group = Value.create(bits);
			}
		}
		state.setPort(0, group, Plexers.DELAY);
	}

	@Override
	public void paintGhost(InstancePainter painter) {
		Plexers.drawTrapezoid(painter.getGraphics(), painter.getBounds(),
				painter.getAttributeValue(StdAttr.FACING), 9);
	}

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

		Plexers.drawTrapezoid(g, painter.getBounds(), facing, 9);
		Bounds bds = painter.getBounds();
		g.setColor(Color.BLACK);
		GraphicsUtil.drawCenteredText(g, "Sel",
				bds.getX() + bds.getWidth() / 2,
				bds.getY() + bds.getHeight() / 2);
		painter.drawPorts();
	}
}
