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

import java.awt.Color;
import java.awt.Graphics;
import java.io.File;
import java.io.IOException;
import java.util.WeakHashMap;

import com.cburch.hex.HexModel;
import com.cburch.hex.HexModelListener;
import com.cburch.logisim.circuit.CircuitState;
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.gui.hex.HexFile;
import com.cburch.logisim.gui.hex.HexFrame;
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.proj.Project;
import com.cburch.logisim.tools.MenuExtender;
import com.cburch.logisim.tools.key.BitWidthConfigurator;
import com.cburch.logisim.tools.key.JoinedConfigurator;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.StringGetter;
import com.cburch.logisim.util.StringUtil;

abstract class Mem extends InstanceFactory {
	// Note: The code is meant to be able to handle up to 32-bit addresses, but it
	// hasn't been debugged thoroughly. There are two definite changes I would
	// make if I were to extend the address bits: First, there would need to be some
	// modification to the memory's graphical representation, because there isn't
	// room in the box to include such long memory addresses with the current font
	// size. And second, I'd alter the MemContents class's PAGE_SIZE_BITS constant
	// to 14 so that its "page table" isn't quite so big.
	public static final Attribute<BitWidth> ADDR_ATTR = Attributes.forBitWidth(
			"addrWidth", Strings.getter("ramAddrWidthAttr"), 2, 24);
	public static final Attribute<BitWidth> DATA_ATTR = Attributes.forBitWidth(
			"dataWidth", Strings.getter("ramDataWidthAttr"));
	
	// port-related constants
	static final int DATA = 0;
	static final int ADDR = 1;
	static final int CS = 2;
	static final int MEM_INPUTS = 3;

	// other constants
	static final int DELAY = 10;

	private WeakHashMap<Instance,File> currentInstanceFiles;

	Mem(String name, StringGetter desc, int extraPorts) {
		super(name, desc);
		currentInstanceFiles = new WeakHashMap<Instance,File>();
		setInstancePoker(MemPoker.class);
		setKeyConfigurator(JoinedConfigurator.create(
				new BitWidthConfigurator(ADDR_ATTR, 2, 24, 0),
				new BitWidthConfigurator(DATA_ATTR)));

		setOffsetBounds(Bounds.create(-140, -40, 140, 80));
	}
	
	abstract void configurePorts(Instance instance);
	@Override
	public abstract AttributeSet createAttributeSet();
	abstract MemState getState(InstanceState state);
	abstract MemState getState(Instance instance, CircuitState state);
	abstract HexFrame getHexFrame(Project proj, Instance instance, CircuitState state);
	@Override
	public abstract void propagate(InstanceState state);

	@Override
	protected void configureNewInstance(Instance instance) {
		configurePorts(instance);
	}
	
	void configureStandardPorts(Instance instance, Port[] ps) {
		ps[DATA] = new Port(   0,  0, Port.INOUT, DATA_ATTR);
		ps[ADDR] = new Port(-140,  0, Port.INPUT, ADDR_ATTR);
		ps[CS]   = new Port( -90, 40, Port.INPUT, 1);
		ps[DATA].setToolTip(Strings.getter("memDataTip"));
		ps[ADDR].setToolTip(Strings.getter("memAddrTip"));
		ps[CS].setToolTip(Strings.getter("memCSTip"));
	}

	@Override
	public void paintInstance(InstancePainter painter) {
		Graphics g = painter.getGraphics();
		Bounds bds = painter.getBounds();

		// draw boundary
		painter.drawBounds();

		// draw contents
		if (painter.getShowState()) {
			MemState state = getState(painter);
			state.paint(painter.getGraphics(), bds.getX(), bds.getY());
		} else {
			BitWidth addr = painter.getAttributeValue(ADDR_ATTR);
			int addrBits = addr.getWidth();
			int bytes = 1 << addrBits;
			String label;
			if (this instanceof Rom) {
				if (addrBits >= 30) {
					label = StringUtil.format(Strings.get("romGigabyteLabel"), ""
							+ (bytes >>> 30));
				} else if (addrBits >= 20) {
					label = StringUtil.format(Strings.get("romMegabyteLabel"), ""
							+ (bytes >> 20));
				} else if (addrBits >= 10) {
					label = StringUtil.format(Strings.get("romKilobyteLabel"), ""
							+ (bytes >> 10));
				} else {
					label = StringUtil.format(Strings.get("romByteLabel"), ""
							+ bytes);
				}
			} else {
				if (addrBits >= 30) {
					label = StringUtil.format(Strings.get("ramGigabyteLabel"), ""
							+ (bytes >>> 30));
				} else if (addrBits >= 20) {
					label = StringUtil.format(Strings.get("ramMegabyteLabel"), ""
							+ (bytes >> 20));
				} else if (addrBits >= 10) {
					label = StringUtil.format(Strings.get("ramKilobyteLabel"), ""
							+ (bytes >> 10));
				} else {
					label = StringUtil.format(Strings.get("ramByteLabel"), ""
							+ bytes);
				}
			}
			GraphicsUtil.drawCenteredText(g, label, bds.getX() + bds.getWidth()
					/ 2, bds.getY() + bds.getHeight() / 2);
		}

		// draw input and output ports
		painter.drawPort(DATA, Strings.get("ramDataLabel"), Direction.WEST);
		painter.drawPort(ADDR, Strings.get("ramAddrLabel"), Direction.EAST);
		g.setColor(Color.GRAY);
		painter.drawPort(CS, Strings.get("ramCSLabel"), Direction.SOUTH);
	}
	
	File getCurrentImage(Instance instance) {
		return currentInstanceFiles.get(instance);
	}
	
	void setCurrentImage(Instance instance, File value) {
		currentInstanceFiles.put(instance, value);
	}
	
	public void loadImage(InstanceState instanceState, File imageFile)
			throws IOException { 
		MemState s = this.getState(instanceState);
		HexFile.open(s.getContents(), imageFile);
		this.setCurrentImage(instanceState.getInstance(), imageFile);
	}

	@Override
	protected Object getInstanceFeature(Instance instance, Object key) {
		if (key == MenuExtender.class) return new MemMenu(this, instance);
		return super.getInstanceFeature(instance, key);
	}
	
	static class MemListener implements HexModelListener {
		Instance instance;
		
		MemListener(Instance instance) { this.instance = instance; }
		
		public void metainfoChanged(HexModel source) { }

		public void bytesChanged(HexModel source, long start,
				long numBytes, int[] values) {
			instance.fireInvalidated();
		}
	}
}
