package sim.lib.memory;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Scanner;

import gjt.BulletinLayout;

import sim.*;
import sim.engine.*;
import sim.lib.memory.hex.*;
import sim.util.SimSeparator;
import sim.util.DialogButton;
import sim.lib.wires.Junction;
import sim.util.*;

public class Rom extends MemoryStructure {
	/* ==================================================================
	 Creation Part
	 ================================================================= */

	private static Image ICON = GuiFileLink.getImage("sim/lib/memory/RomIcon.gif");

	public Image getIcon() {
		return Rom.ICON;
	}

	public String getBubbleHelp() {
		return "ROM";
	}

	/* ==================================================================
	 GUI part
	 ================================================================= */
	public Rom() {
		super();
		this.componentName = "ROM";
	}

	protected Wrapper getCopy() {
		Rom result = new Rom();

		result.setBusSize(this.busSize);
		result.setAddressSpace(this.addressSpace);
		result.initializeMemory();

		return result;
	}

	public void paint(Graphics g) {
		// draw if visible
		if (this.isVisible()) {
			int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
			int increment = gridGap / 4;

			int x = gridGap;
			int y = gridGap;
			int width = 8 * gridGap;
			int height = 5 * gridGap;
			int center = x + width / 2;

			Shape old = g.getClip();
			FontMetrics fm;

			//outer frame
			g.setColor(this.brush);
			g.drawRect(x, y, width, height);

			// Component name
			width = 4 * gridGap;
			x = center - width / 2;
			g.setClip(x, y, width, height);

			g.setFont(new Font(Wrapper.FONT_NAME, Font.BOLD, 3 * increment));
			fm = g.getFontMetrics(g.getFont());
			x = center - fm.stringWidth(this.componentName) / 2;
			y = 2 * gridGap;
			g.drawString(this.componentName, x, y);

			g.setClip(old);

			//address window
			width = 3 * gridGap + 2 * increment;
			height = gridGap + 2 * increment;
			x = gridGap;
			y = 3 * gridGap - increment;
			g.setFont(new Font(Wrapper.FONT_NAME, Font.PLAIN, 3 * increment));

			g.setColor(WrapperPainted.BACKGROUND);
			g.clearRect(x, y, width, height);
			g.fillRect(x, y, width, height);
			g.setColor(this.brush);
			g.drawRect(x, y, width, height);

			g.setClip(x, y, width, height);
			if (this.addressToDraw != null) {
				g.setClip(x, y, width, height);
				x += (width - fm.stringWidth(this.addressToDraw)) / 2;
				g.drawString(this.addressToDraw, x, y + gridGap);
			}
			g.setClip(old);

			//data windows
			x = 9 * gridGap - width;

			g.setColor(WrapperPainted.BACKGROUND);
			g.clearRect(x, y, width, height);
			g.fillRect(x, y, width, height);
			g.setColor(this.brush);
			g.drawRect(x, y, width, height);

			g.setClip(x, y, width, height);
			if (this.dataToDraw != null) {
				g.setClip(x, y, width, height);
				x += (width - fm.stringWidth(this.dataToDraw)) / 2;
				g.drawString(this.dataToDraw, x, y + gridGap);
			}
			g.setClip(old);

			//control wires
			g.setColor(this.brush);
			g.drawLine(2 * gridGap, 6 * gridGap, 2 * gridGap, 7 * gridGap);
			g.drawLine(8 * gridGap, 6 * gridGap, 8 * gridGap, 7 * gridGap);

			//IO bus
			g.fillRect(0, 2 * gridGap - 1, gridGap, 3);
			g.fillRect(9 * gridGap, 2 * gridGap - 1, gridGap, 3);

			//label pins
			y = 2 * gridGap + increment;
			g.drawString("A" + (this.requiredAddress - 1) + "-0", gridGap + increment, y);
			String toDraw = "D" + (this.busSize - 1) + "-0";
			g.drawString(toDraw, 9 * gridGap - fm.stringWidth(toDraw), y);

			g.drawString("___", 2 * gridGap - fm.stringWidth("___") / 2, 23 * increment - fm.getHeight());
			g.drawString("CE", 2 * gridGap - fm.stringWidth("CE") / 2, 23 * increment);
			g.drawString("___", 8 * gridGap - fm.stringWidth("___") / 2, 23 * increment - fm.getHeight());
			g.drawString("OE", 8 * gridGap - fm.stringWidth("OE") / 2, 23 * increment);


		}
	}

	/* ==================================================================
	 Maintanance Part
	 ================================================================= */
	public boolean canDrop() {
		boolean result = Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 2, this.requiredAddress);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 10, this.gridLocation.y + 2, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 2, this.gridLocation.y + 7, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 8, this.gridLocation.y + 7, 1);

		return result;
	}

	public void droped() {
		this.address = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 2, this.requiredAddress);
		this.data = Wrapper.setPinAt(this.gridLocation.x + 10, this.gridLocation.y + 2, this.busSize);
		this.notCE = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y + 7, 1);
		this.notOE = Wrapper.setPinAt(this.gridLocation.x + 8, this.gridLocation.y + 7, 1);

		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}

	public void selected() {
		this.address.removePin();
		this.data.removePin();
		this.notCE.removePin();
		this.notOE.removePin();

		this.changeColor(Color.green);
	}

	public void checkAfterSelected() {
		Wrapper.checkPin(this.address);
		Wrapper.checkPin(this.data);
		Wrapper.checkPin(this.notCE);
		Wrapper.checkPin(this.notOE);
	}

	/* ==================================================================
	 Simulation part
	 ================================================================= */
	public void evaluateOutput(double currentTime, Data[] currentInputs, EnginePeer peer) {
		// check address
		boolean foundUndefined = false;
		int base = 1;
		int newValue = 0;

		for (int loop = 0; (loop < this.requiredAddress) && (!foundUndefined); loop++) {
			if (currentInputs[2 + loop].isUndefined()) {
				foundUndefined = true;
				newValue = -1;
			} else if (currentInputs[2 + loop].getValue()) {
				newValue = newValue + base;
			}

			base = 2 * base;
		}

		if (newValue != this.oldAddress) {
			this.addressChange = currentTime;
			this.oldAddress = newValue;
			this.addressHandled = false;

			if (this.oldAddress == -1) {
				this.addressToDraw = "-";
				this.dataToDraw = "-";

				for (newValue = 1; newValue < this.addressLenght; newValue++) {
					this.addressToDraw = this.addressToDraw + "-";
				}

				for (newValue = 1; newValue < this.wordLenght; newValue++) {
					this.dataToDraw = this.dataToDraw + "-";
				}
			} else {
				this.addressToDraw = Integer.toHexString(this.oldAddress);

				for (newValue = this.addressToDraw.length(); newValue < this.addressLenght; newValue++) {
					this.addressToDraw = "0" + this.addressToDraw;
				}

				this.dataToDraw = (new Character(this.memoryContent[this.oldAddress][this.wordLenght - 1])).toString();

				for (newValue = 1; newValue < this.wordLenght; newValue++) {
					this.dataToDraw = this.dataToDraw + this.memoryContent[this.oldAddress][this.wordLenght - newValue - 1];
				}

				this.addressToDraw = this.addressToDraw.toUpperCase();
				this.dataToDraw = this.dataToDraw.toUpperCase();
			}

			CentralPanel.ACTIVE_GRID.paintComponent(this);
		}

		// check notCE
		if (currentInputs[0].isUndefined() || currentInputs[0].getValue()) {
			if (!this.pastNotCE) {
				this.notCE_goesHigh = currentTime;
			}

			this.pastNotCE = true;
		} else {
			if (this.pastNotCE) {
				this.notCE_goesLow = currentTime;
			}

			this.pastNotCE = false;
		}

		// check notOE
		if (currentInputs[1].isUndefined() || currentInputs[1].getValue()) {
			if (!this.pastNotOE) {
				this.notOE_goesHigh = currentTime;
			}

			this.pastNotOE = true;
		} else {
			if (this.pastNotOE) {
				this.notOE_goesLow = currentTime;
			}

			this.pastNotOE = false;
		}

		// simulate
		if (!this.pastNotCE && !this.pastNotOE) {
			// handle start of reading
			if ((this.notOE_goesLow == currentTime) && (this.notCE_goesLow == currentTime)) {
				this.startPutting = currentTime + Math.max(this.oe, this.acs);
				this.addressHandled = false;
				this.undefineOutputs(currentTime + Math.min(this.olz, this.clz), peer);
				this.isReading = true;
			} else if (this.notOE_goesLow == currentTime) {
				this.startPutting = currentTime + this.oe;
				this.addressHandled = false;
				this.undefineOutputs(currentTime + this.olz, peer);
				this.isReading = true;
			} else if (this.notCE_goesLow == currentTime) {
				this.startPutting = currentTime + this.acs;
				this.addressHandled = false;
				this.undefineOutputs(currentTime + this.clz, peer);
				this.isReading = true;
			}

			// handle reading
			if (!this.addressHandled) {
				if (this.startPutting >= this.addressChange + this.oh) // handle start of reading
				{
					this.setSignalsAt(this.oldAddress, this.startPutting, peer);
				} else {
					this.undefineOutputs(this.addressChange + this.oh, peer);
					this.setSignalsAt(this.oldAddress, this.addressChange + this.aa, peer);
				}

				this.addressHandled = true;
			}
		} else {
			// handle stop of reading
			if (this.isReading) {
				if ((this.notOE_goesHigh == currentTime) && (this.notCE_goesHigh == currentTime)) {
					this.startFloating = currentTime + Math.min(this.ohz, this.chz);
					this.floatOutputs(this.startFloating, peer);
				} else if (this.notOE_goesHigh == currentTime) {
					this.startFloating = currentTime + this.ohz;
					this.floatOutputs(this.startFloating, peer);
				} else if (this.notCE_goesHigh == currentTime) {
					this.startFloating = currentTime + this.chz;
					this.floatOutputs(this.startFloating, peer);
				}

				this.isReading = false;
			}

			// handle address change during stop
			if (!this.addressHandled && (this.startFloating > this.addressChange + this.oh)) {
				if (this.startPutting >= this.addressChange + this.oh) // handle start of reading
				{
					this.setSignalsAt(this.oldAddress, this.startPutting, peer);
				} else {
					this.undefineOutputs(this.addressChange + this.oh, peer);
					this.setSignalsAt(this.oldAddress, this.addressChange + this.aa, peer);
				}

				this.floatOutputs(this.startFloating, peer);
				this.addressHandled = true;
			}
		}
	}

	public void createEnginePeer(EnginePeerList epl) {
		int loop;
		EnginePeer ep = new EnginePeer(2 + this.requiredAddress, this.busSize, this);

		ep.setInputPin(0, this.notCE.getNodes().getItemAt(0));
		ep.setInputPin(1, this.notOE.getNodes().getItemAt(0));

		for (loop = 0; loop < this.busSize; loop++) {
			ep.setOutputPin(loop, this.data.getNodes().getItemAt(loop));
		}

		for (loop = 0; loop < this.requiredAddress; loop++) {
			ep.setInputPin(loop + 2, this.address.getNodes().getItemAt(loop));
		}

		this.notCE_goesLow = 0;
		this.notCE_goesHigh = 0;
		this.notOE_goesLow = 0;
		this.notOE_goesHigh = 0;

		this.pastNotCE = true;
		this.pastNotOE = true;

		this.oldAddress = -1;
		this.addressChange = 0;
		this.addressHandled = false;

		this.startPutting = 0;
		this.startFloating = 0;
		this.isReading = false;

		epl.insertItem(ep);

		if (this.editor != null) {
			this.editor.prepareForSimulation();
		}
	}

	/* ==================================================================
	 Storage Part
	 ================================================================= */
	public String getSpecificParameters() {
		String result = this.targetFile + Wrapper.SEPARATOR + this.busSize + Wrapper.SEPARATOR + this.addressSpace + Wrapper.SEPARATOR;

		result = result + this.aa + Wrapper.SEPARATOR + this.acs + Wrapper.SEPARATOR + this.clz + Wrapper.SEPARATOR + this.oe + Wrapper.SEPARATOR;
		result = result + this.olz + Wrapper.SEPARATOR + this.chz + Wrapper.SEPARATOR + this.ohz + Wrapper.SEPARATOR + this.oh + Wrapper.SEPARATOR;

		return result;
	}

	public void loadWrapper(String[] specificParameters) throws SimException {
		if (specificParameters.length == this.getNumberOfSpecificParameters()) {
			try {
				this.targetFile = specificParameters[0];
				this.setBusSize(Integer.valueOf(specificParameters[1]).intValue());
				this.setAddressSpace(Integer.valueOf(specificParameters[2]).intValue());

				this.aa = Double.valueOf(specificParameters[3]).doubleValue();
				this.acs = Double.valueOf(specificParameters[4]).doubleValue();
				this.clz = Double.valueOf(specificParameters[5]).doubleValue();
				this.oe = Double.valueOf(specificParameters[6]).doubleValue();
				this.olz = Double.valueOf(specificParameters[7]).doubleValue();
				this.chz = Double.valueOf(specificParameters[8]).doubleValue();
				this.ohz = Double.valueOf(specificParameters[9]).doubleValue();
				this.oh = Double.valueOf(specificParameters[10]).doubleValue();

				this.initializeMemory();
			} catch (NumberFormatException e) {
				throw (new SimException("incorrect parameter type"));
			}
		} else {
			throw (new SimException("incorrect number of parameters"));
		}
	}

	public int getNumberOfSpecificParameters() {
		return 11;
	}

	/* ==================================================================
	 Popup Part
	 ================================================================= */
	private int oldAddressSpace;
	private int oldBusSize = 0;

	public boolean hasProperties() {
		return true;
	}

	public Component getPropertyWindow() {
		return (new RomProperties(this.targetFile, this.componentName, this.busSize, this.requiredAddress, this.aa, this.acs, this.clz, this.oe, this.olz, this.chz, this.ohz, this.oh));
	}

	public void respondToChanges(Component property) {
		try {
			CentralPanel.ACTIVE_GRID.eraseComponent(this, false);

			RomProperties x = (RomProperties) property;

			this.aa = x.getAA();
			this.acs = x.getACS();
			this.clz = x.getCLZ();
			this.oe = x.getOE();
			this.olz = x.getOLZ();
			this.chz = x.getCHZ();
			this.ohz = x.getOHZ();
			this.oh = x.getOH();

			if (this.oldBusSize == 0) {
				this.oldBusSize = this.busSize;
				this.oldAddressSpace = this.requiredAddress;
			}

			this.setBusSize(x.getBusSize());
			this.setAddressSpace((int) Math.pow(2, x.getAddressSize()));

			this.targetFile = x.getFileName();
			this.componentName = x.getComponentName();
			this.initializeMemory();

			CentralPanel.ACTIVE_GRID.paintComponent(this);
		} catch (Throwable err) {
			this.setBusSize(this.oldBusSize);
			this.setAddressSpace((int) Math.pow(2, this.oldAddressSpace));
			this.oldBusSize = 0;
			this.initializeMemory();

			CentralPanel.ACTIVE_GRID.paintComponent(this);

			MainWindow.OK_WINDOW.setDescription("Can not increase memory size");
			MainWindow.OK_WINDOW.setMessage("fail to allocate more memory");
			MainWindow.OK_WINDOW.setVisible(true);
		}

		if (this.editor != null) {
			this.editor.changeParameters(this.memoryContent, this.targetFile, this.busSize, this.addressSpace);
		}
	}

	public void restoreOriginalProperties() {
		if (this.oldBusSize != 0) {
			this.setBusSize(this.oldBusSize);
			this.setAddressSpace((int) Math.pow(2, this.oldAddressSpace));

			this.initializeMemory();
			this.oldBusSize = 0;

			if (this.editor != null) {
				this.editor.changeParameters(this.memoryContent, this.targetFile, this.busSize, this.addressSpace);
			}
		}
	}
	
	@Override
	public void writeVHDLEntity(String path) {
		String fileName = "gates.vhd";
		File file = new File(path+"\\"+fileName);
		boolean written = false;
		try {
			Scanner scanner = new Scanner(file);
			while(scanner.hasNextLine()){
				String line = scanner.nextLine();
				if(line.contains(this.getVHDLName())){
					written = true;
				}
			}
			scanner.close();
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		}
		
		if(!written){
			Writer writer = null;
			try {
				writer = new FileWriter(path+"\\"+fileName,true);
				writer.write("\nlibrary IEEE;\n");
				writer.write("use IEEE.STD_LOGIC_1164.ALL;\n");
				writer.write("use ieee.numeric_std.all;\n");
				
				writer.write("\nentity " + this.getVHDLName() + " is\n");
				writer.write("port( notce,notwe,notoe : in std_logic;\n");
				int addr = this.addressSpace;
				int addr_bits = 0;
				while(addr != 1){
					addr_bits++;
					addr = addr/2;
				}
				for(int i = 0; i < addr_bits; i++){
					if(i != 0)
						writer.write(",");
					writer.write("a" + i);
				}
				writer.write(" : in std_logic;\n");
				for(int i = 0; i < this.busSize; i++){
					if(i != 0)
						writer.write(",");
					writer.write("d" + i);
				}
				writer.write(" : out std_logic);\n");
				writer.write("end "+ this.getVHDLName() + ";\n\n");
				
				writer.write("architecture behav of "+ this.getVHDLName() + " is\n");
				writer.write("signal address: std_logic_vector("+(addr_bits-1)+" downto 0);\n");
				writer.write("signal data: std_logic_vector("+(this.busSize-1)+" downto 0);\n");
				writer.write("type ram_t is array (0 to "+(this.addressSpace-1)+") of std_logic_vector("+(this.busSize-1)+" downto 0);\n");
				writer.write("signal ram : ram_t := (others => (others => '0'));\n");
				writer.write("begin\n");
				for(int x = 0; x < addr_bits; x++){
					writer.write("address(" + x + ") <= a" + x + ";\n");
				}
				for(int x = 0; x < this.busSize; x++){
					writer.write("d" + x + " <= data(" + x + ") when notoe='0' else '0';\n");
				}
				writer.write("process (address) is\n");
				writer.write("begin\n");
				writer.write("data <= ram(to_integer(unsigned(address)));\n");
				writer.write("end process;\n");
				writer.write("end behav;\n");
				writer.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		
	}

	@Override
	public String getVHDLName() {
		// TODO Auto-generated method stub
		return "ROM_ADDR"+this.addressSpace+"_DATA"+this.busSize;
	}
}
