package sim.lib.memory;

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

import sim.*;
import sim.engine.*;

import sim.lib.wires.Junction;

public class Register extends RegisterStructure {
	/* ==================================================================
	 Creation Part
	 ================================================================= */

	private static Image ICON = GuiFileLink.getImage("sim/lib/memory/RegisterIcon.gif");
	//public boolean modDisp = false;

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

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

	/* ==================================================================
	 GUI part
	 ================================================================= */
	public Register() {
		super();
		this.name = "Register";
	}

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

		result.changeDelay(this.delay);
		result.setBusSize(this.busSize);

		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 wireLen = gridGap;
			int center = x + width / 2;
			FontMetrics fm;

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

			//name
			Shape old = g.getClip();
			width = 6 * 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.name) / 2;
			y = 2 * gridGap + 2 * increment;
			g.drawString(this.name, x, y);

			g.setClip(old);

			//input and output bus
			int offset = 0;
			int thickness = 1;
			if (this.busSize != 1) {
				offset = -1;
				thickness = 3;
			}

			x = center + offset;
			y = 0;
			g.fillRect(x, y, thickness, wireLen);
			y = wireLen + height;
			g.fillRect(x, y, thickness, wireLen);

			//enable line
			g.setFont(new Font(Wrapper.FONT_NAME, Font.PLAIN, 3 * increment));
			fm = g.getFontMetrics(g.getFont());

			x = 9 * gridGap;
			y = 3 * gridGap;
			g.drawLine(x, y, x + wireLen, y);
			g.drawString("ld", x - fm.stringWidth("ld") - increment, y + increment);

			//output window
			width = 3 * gridGap;
			height = 2 * gridGap;
			x = center - width / 2;
			y = 6 * gridGap - height;
			g.setColor(WrapperPainted.BACKGROUND);
			g.fillRect(x, y, width, height);
			g.setColor(this.brush);
			g.drawRect(x, y, width, height);

			//output value
			g.setClip(x, y, width, height);
			x = center - fm.stringWidth(this.value) / 2;
			y = 6 * gridGap - height / 2 + increment;
			g.drawString(this.value, x, y);
			g.setClip(old);


		}
	}

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

		return result;
	}

	public void droped() {
		this.clock = Wrapper.setPinAt(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 3, 1);
		this.input = Wrapper.setPinAt(this.gridLocation.x + this.gridSize.width / 2, this.gridLocation.y, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x + this.gridSize.width / 2, this.gridLocation.y + 7, this.busSize);

		this.changeColor(Color.black);
		this.oldValue = null;
		this.oldStore = null;
	}

	public void selected() {
		this.input.removePin();
		this.output.removePin();
		this.clock.removePin();

		this.changeColor(Color.green);
	}

	public void checkAfterSelected() {
		Wrapper.checkPin(this.input);
		Wrapper.checkPin(this.output);
		Wrapper.checkPin(this.clock);
	}

	public String getValue() {
		return this.value;
	}

	public int getHex() {
		return this.hex;
	}
	/* ==================================================================
	 Simulation part
	 ================================================================= */
	public void evaluateOutput(double currentTime, Data[] currentInputs, EnginePeer peer) {
		double time = this.delay + currentTime;
		boolean foundUndefined = false;
		int loop, index, hex = 0, base = 1;

		// check clock
		if (!currentInputs[this.busSize].isUndefined()) {
			if ((!this.pastClock) && currentInputs[this.busSize].getValue()) {
				this.clkGoesHigh = currentTime;
			}

			this.pastClock = currentInputs[this.busSize].getValue();
		} else {
			this.pastClock = true;
		}

		// drive content and display hex value
		boolean undef = false;
		if (this.clkGoesHigh == currentTime) {
			for (loop = 0; loop < (this.busSize / 4 + 1); loop++) {
				for (index = loop * 4; (index < 4 * (loop + 1)) && (index < this.busSize); index++) {
					if (currentInputs[index].isUndefined()) {
						foundUndefined = true;
						undef = true;
						peer.setOutputPinUndefined(index, time);
						this.store[index] = 2;
					} else if (currentInputs[index].getValue()) {
						hex = hex + base;
						peer.setOutputPinValue(index, true, time);
						this.store[index] = 1;
					} else {
						peer.setOutputPinValue(index, false, time);
						this.store[index] = 0;
					}

					base = 2 * base;
				}

				if (loop == 0) {
					if (foundUndefined) {
						this.value = "-";
						this.hex = 0;
					} else {
						this.value = Integer.toHexString(hex);
						this.hex = hex;
					}
				} else if (foundUndefined) {
					this.value = "-" + this.value;
				} else if (this.value.length() < this.valueLenght) {
					this.value = Integer.toHexString(hex) + this.value;
				}

				hex = 0;
				base = 1;
				foundUndefined = false;
			}
			this.value = this.value.toUpperCase();
			//if(undef == false){
				//System.out.println("VAL:"+this.value);
				//this.hex = Integer.decode("0x" + new StringBuilder(this.value).reverse().toString());
				//System.out.println("HEX:"+this.hex);
			//}else
				//this.hex = 0;
			CentralPanel.ACTIVE_GRID.paintComponent(this);
		}
	}

	public void createEnginePeer(EnginePeerList epl) {
		this.pastClock = false;
		this.clkGoesHigh = -1;
		EnginePeer ep = new EnginePeer(this.busSize + 1, this.busSize, this);

		for (int loop = 0; loop < this.busSize; loop++) {
			ep.setInputPin(loop, this.input.getNodes().getItemAt(loop));
			ep.setOutputPin(loop, this.output.getNodes().getItemAt(loop));
		}

		ep.setInputPin(this.busSize, this.clock.getNodes().getItemAt(0));

		for (int loop = 0; loop < this.busSize; loop++) {
			if (this.store[loop] == 2) {
				ep.setOutputPinUndefined(loop, 0);
			} else {
				ep.setOutputPinValue(loop, this.store[loop] == 1, 0);
			}
		}

		epl.insertItem(ep);
	}
	
	@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("\nentity " + this.getVHDLName() + " is\n");
				writer.write("port( \n");
				for(int i = 0; i < this.busSize; i++){
					if(i != 0)
						writer.write(",");
					writer.write("x" + i);
				}
				writer.write(" : in std_logic;\n");
				writer.write("clk : in std_logic;\n");
				for(int i = 0; i < this.busSize; i++){
					if(i != 0)
						writer.write(",");
					writer.write("f" + 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("begin\n");
				writer.write("process (clk) is\n");
				writer.write("begin\n");
				writer.write("if(rising_edge(clk)) then\n");
				for(int i = 0; i < this.busSize; i++){
					writer.write("f"+i+" <= " + "x" + i+";\n");
				}
				writer.write("end if;\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 "REGISTER_"+this.busSize;
	}
}
