package sim.lib.memory;

import java.awt.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.Writer;
import java.util.Scanner;

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

import sim.lib.wires.Junction;

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

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

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

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

	/* ==================================================================
	 GUI part
	 ================================================================= */
	public ShiftRegister() {
		super();

		this.name = "Shift Register";
	}

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

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

			// control lines
			String str;
			g.setFont(new Font(Wrapper.FONT_NAME, Font.PLAIN, 3 * increment));
			fm = g.getFontMetrics(g.getFont());

			// shift left <<
			str = "<<";
			x = gridGap;
			y = 3 * gridGap;
			g.drawLine(x - wireLen, y, x, y);
			g.drawString(str, x + increment, y + increment);

			// iL
			str = "iL";
			y += gridGap;
			g.drawLine(x - wireLen, y, x, y);
			g.drawString(str, x + increment, y + increment);

			//oL
			str = "oL";
			y += gridGap;
			g.drawLine(x - wireLen, y, x, y);
			g.drawString(str, x + increment, y + increment);

			//enable line
			str = "ld";
			x = 9 * gridGap;
			y = 2 * gridGap;
			g.drawLine(x, y, x + wireLen, y);
			g.drawString(str, x - fm.stringWidth(str) - increment, y + increment);

			// shift right >>
			str = ">>";
			y += gridGap;
			g.drawLine(x, y, x + wireLen, y);
			g.drawString(str, x - fm.stringWidth(str) - increment, y + increment);

			// iR
			str = "iR";
			y += gridGap;
			g.drawLine(x, y, x + wireLen, y);
			g.drawString(str, x - fm.stringWidth(str) - increment, y + increment);

			// oR
			str = "oR";
			y += gridGap;
			g.drawLine(x, y, x + wireLen, y);
			g.drawString(str, x - fm.stringWidth(str) - 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);
		}
	}
	
	public String getValue() {
		return this.value;
	}
	
	public int getHex() {
		return this.hex;
	}
	
	/* ==================================================================
	 Maintanance Part
	 ================================================================= */
	private Junction RE = null;
	private Junction LE = null;
	private Junction iR = null;
	private Junction oR = null;
	private Junction iL = null;
	private Junction oL = null;

	public boolean canDrop() {
		boolean result = Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 3, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 4, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 5, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 2, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 3, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 4, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 5, 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.LE = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 3, 1);
		this.iL = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 4, 1);
		this.oL = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 5, 1);

		this.clock = Wrapper.setPinAt(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 2, 1);

		this.RE = Wrapper.setPinAt(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 3, 1);
		this.iR = Wrapper.setPinAt(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 4, 1);
		this.oR = Wrapper.setPinAt(this.gridLocation.x + this.gridSize.width, this.gridLocation.y + 5, 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.RE.removePin();
		this.LE.removePin();
		this.iL.removePin();
		this.oL.removePin();
		this.iR.removePin();
		this.oR.removePin();

		this.changeColor(Color.green);
	}

	public void checkAfterSelected() {
		Wrapper.checkPin(this.input);
		Wrapper.checkPin(this.output);
		Wrapper.checkPin(this.clock);
		Wrapper.checkPin(this.RE);
		Wrapper.checkPin(this.LE);
		Wrapper.checkPin(this.iL);
		Wrapper.checkPin(this.oL);
		Wrapper.checkPin(this.iR);
		Wrapper.checkPin(this.oR);
	}
	/* ==================================================================
	 Simulation part
	 ================================================================= */
	protected boolean pastLE;
	protected boolean pastRE;
	private double leGoesHigh;
	private double reGoesHigh;
	private boolean handleShift;

	public void evaluateOutput(double currentTime, Data[] currentInputs, EnginePeer peer) {
		double time = this.delay + currentTime;
		boolean foundUndefined = false;
		boolean update = 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;
		}

		// check RE
		if (!currentInputs[this.busSize + 1].isUndefined()) {
			if ((!this.pastRE) && currentInputs[this.busSize + 1].getValue()) {
				this.reGoesHigh = currentTime;
				this.handleShift = false;
			}

			this.pastRE = currentInputs[this.busSize + 1].getValue();
		} else {
			this.pastRE = true;
		}

		// check LE
		if (!currentInputs[this.busSize + 3].isUndefined()) {
			if ((!this.pastLE) && currentInputs[this.busSize + 3].getValue()) {
				this.leGoesHigh = currentTime;
				this.handleShift = false;
			}

			this.pastLE = currentInputs[this.busSize + 3].getValue();
		} else {
			this.pastLE = true;
		}


		// load data
		if (this.clkGoesHigh == currentTime) {
			for (loop = 0; loop < this.busSize; loop++) {
				if (currentInputs[loop].isUndefined()) {
					this.store[loop] = 2;
				} else if (currentInputs[loop].getValue()) {
					this.store[loop] = 1;
				} else {
					this.store[loop] = 0;
				}
			}

			this.handleShift = false;
			update = true;
		}

		// need to shift?
		boolean shiftRight = (this.reGoesHigh == currentTime);
		boolean shiftLeft = (this.leGoesHigh == currentTime);

		// shift in one direction only
		if ((shiftRight ^ shiftLeft) && (!this.handleShift)) {
			this.handleShift = true;

			if (shiftRight) {
				if (this.store[0] == 2) {
					peer.setOutputPinUndefined(this.busSize, time);
				} else {
					peer.setOutputPinValue(this.busSize, this.store[0] == 1, time);
				}

				for (loop = 0; loop < (this.busSize - 1); loop++) {
					this.store[loop] = this.store[loop + 1];
				}

				peer.setOutputPinUndefined(this.busSize + 1, time);

				if (currentInputs[this.busSize + 2].isUndefined()) {
					this.store[this.busSize - 1] = 2;
				} else if (currentInputs[this.busSize + 2].getValue()) {
					this.store[this.busSize - 1] = 1;
				} else {
					this.store[this.busSize - 1] = 0;
				}
			} else {
				if (this.store[this.busSize - 1] == 2) {
					peer.setOutputPinUndefined(this.busSize + 1, time);
				} else {
					peer.setOutputPinValue(this.busSize + 1, this.store[this.busSize - 1] == 1, time);
				}

				peer.setOutputPinUndefined(this.busSize, time);

				for (loop = this.busSize - 1; loop > 0; loop--) {
					this.store[loop] = this.store[loop - 1];
				}

				if (currentInputs[this.busSize + 4].isUndefined()) {
					this.store[0] = 2;
				} else if (currentInputs[this.busSize + 4].getValue()) {
					this.store[0] = 1;
				} else {
					this.store[0] = 0;
				}
			}

			update = true;
		} else if (update) {
			peer.setOutputPinUndefined(this.busSize + 1, time);
			peer.setOutputPinUndefined(this.busSize, time);
		}

		// drive content and display hex value
		if (update) {
			hex = 0;
			base = 1;

			for (loop = 0; loop < (this.busSize / 4 + 1); loop++) {
				for (index = loop * 4; (index < 4 * (loop + 1)) && (index < this.busSize); index++) {
					if (this.store[index] == 2) {
						foundUndefined = true;
						peer.setOutputPinUndefined(index, time);
					} else if (this.store[index] == 1) {
						hex = hex + base;
						peer.setOutputPinValue(index, true, time);
					} else {
						peer.setOutputPinValue(index, false, time);
					}

					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();
			CentralPanel.ACTIVE_GRID.paintComponent(this);
		}
	}

	public void createEnginePeer(EnginePeerList epl) {
		this.pastClock = false;
		this.clkGoesHigh = -1;
		this.pastLE = false;
		this.leGoesHigh = -1;
		this.pastRE = false;
		this.reGoesHigh = -1;

		this.handleShift = true;

		int loop;
		EnginePeer ep = new EnginePeer(this.busSize + 5, this.busSize + 2, this);

		for (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));
		ep.setInputPin(this.busSize + 1, this.RE.getNodes().getItemAt(0));
		ep.setInputPin(this.busSize + 2, this.iR.getNodes().getItemAt(0));
		ep.setInputPin(this.busSize + 3, this.LE.getNodes().getItemAt(0));
		ep.setInputPin(this.busSize + 4, this.iL.getNodes().getItemAt(0));

		ep.setOutputPin(this.busSize, this.oR.getNodes().getItemAt(0));
		ep.setOutputPin(this.busSize + 1, this.oL.getNodes().getItemAt(0));

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

		ep.setOutputPinUndefined(this.busSize + 1, 0);
		ep.setOutputPinUndefined(this.busSize, 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("use ieee.numeric_std.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(" ,clk, ren, inr, len, inl : in std_logic;\n"); //clock, right shift enable, in right, left shift enable, in left, out right, out left
				//writer.write(" : in std_logic);\n");
				for(int i = 0; i < this.busSize; i++){
					if(i != 0)
						writer.write(",");
					writer.write("f" + i);
				}
				writer.write(" : inout std_logic;\n");
				writer.write("outr, outl");
				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,ren,len) 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("elsif(rising_edge(ren)) then\n"); //right shift
				writer.write("outr <= f0;\n");
				for(int i = 0; i < this.busSize-1; i++){
					writer.write("f"+i+" <= " + "f" + (i+1)+";\n");
				}
				writer.write("f"+(this.busSize-1)+" <= inr;\n");
				
				writer.write("elsif(rising_edge(len)) then\n");  //left shift
				writer.write("outl <= f"+(this.busSize-1)+";\n");
				for(int i = 0; i < (this.busSize-1); i++){
					writer.write("f"+(i+1)+" <= " + "f" +i+";\n");
				}
				writer.write("f0 <= inl;\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 "SHIFTREGISTER_"+this.busSize;
	}
}
