package sim.lib.others;

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;
import sim.lib.wires.JunctionList;

public class PulseGenerator extends WrapperPainted implements EngineModule
{
/* ==================================================================
	Creation Part
	================================================================= */
	private static Image ICON = GuiFileLink.getImage("sim/lib/others/PulseGeneratorIcon.gif");
	
	public Image getIcon()
	{
		return PulseGenerator.ICON;
	}
	
	public Wrapper createWrapper()
	{
		return this.getCopy();
	}
	
	public Wrapper createWrapper(Point gridPosition)
	{
		PulseGenerator result = this.getCopy();
		result.setGridLocation(gridPosition);
		return result;
	}
	
	public String getBubbleHelp()
	{
		return "Pulse generator";
	}
	
/* ==================================================================
	GUI part
	================================================================= */
	public PulseGenerator()
	{
		super();
		
		this.setParameters(4, 4);
	}
	
	private PulseGenerator getCopy()
	{
		PulseGenerator result = new PulseGenerator();
		
		result.setParameters(this.outputSize, this.periodInCycles);
		result.changeDelay(this.delay);
		
		return result;
	}
	
	public void initializeGridSize()
	{
		this.setGridSize(4, 4);
	}
	
	public void paint(Graphics g)
	{
		// draw if visible
		if(this.isVisible())
		{
			int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
			int increment = gridGap / 4;
			int middle = this.outputSize / 2;
			
			g.setColor(this.brush);
			g.setFont(new Font(Wrapper.FONT_NAME, Font.PLAIN, 3 * increment));
			
			int up = gridGap;
			int height = gridGap * this.getGridSize().height;
			int tripple = height / 2;
			int textOffset = 2 * increment;
			
			g.drawRect(gridGap, textOffset, 2 * gridGap, height - 4 * increment);
			g.drawLine(0, tripple, gridGap, tripple);
			g.drawLine(gridGap, tripple + textOffset, gridGap + textOffset, tripple);
			g.drawLine(gridGap, tripple - textOffset, gridGap + textOffset, tripple);
			
			tripple = 3 * gridGap;
			textOffset = tripple - increment;
			FontMetrics fm = this.getFontMetrics(this.getFont());
			
			for(height = 0; height < middle; height++)
			{
				g.drawString(Integer.toString(height), textOffset - fm.stringWidth(Integer.toString(height)), up + increment);
				g.drawLine(tripple, up, tripple + gridGap, up);
				up = up + gridGap;
			}
			
			if((this.outputSize % 2 ) != 0)
			{
				g.drawString(Integer.toString(height), textOffset - fm.stringWidth(Integer.toString(height)), up + increment);
				g.drawLine(tripple, up, tripple + gridGap, up);
				up = up + gridGap;
				
				for(height = middle + 1; height < this.outputSize; height++)
				{
					g.drawString(Integer.toString(height), textOffset - fm.stringWidth(Integer.toString(height)), up + increment);
					g.drawLine(tripple, up, tripple + gridGap, up);
					up = up + gridGap;
				}
			}
			else
			{
				up = up + gridGap;
				
				for(height = middle; height < this.outputSize; height++)
				{
					g.drawString(Integer.toString(height), textOffset - fm.stringWidth(Integer.toString(height)), up + increment);
					g.drawLine(tripple, up, tripple + gridGap, up);
					up = up + gridGap;
				}
			}
		}
	}
	
/* ==================================================================
	Maintanance Part
	================================================================= */
	private JunctionList output = new JunctionList();
	private Junction input = null;
	private int outputSize;
	private int periodInCycles;
	private boolean[][] pulses;
	
	public void setParameters(int size, int period)
	{
		this.outputSize = size;
		this.periodInCycles = period;
		this.pulses = new boolean[this.outputSize][this.periodInCycles];
		
		for(size = 0; size < this.outputSize; size++)
			for(period = 0; period < this.periodInCycles; period++)
				this.pulses[size][period] = false;
		
		this.setGridSize(4, 2 * (size / 2) + 2);
	}
	
	public boolean canDrop()
	{
		boolean result = true;
		int middle = this.outputSize / 2;
		int end = this.gridLocation.x + this.gridSize.width;
		int loop;
		
		// check outputs
		for(loop = 0; (loop < middle) && result; loop++)
			result = Wrapper.canDropJuncion(end, this.gridLocation.y + 1 + loop, 1);
		
		if(result)
		{
			if((this.outputSize % 2) != 0)
			{
				result = Wrapper.canDropJuncion(end, this.gridLocation.y + 1 + middle, 1);
				for(loop = middle + 1; (loop < this.outputSize) && result; loop++)
					result = Wrapper.canDropJuncion(end, this.gridLocation.y + 1 + loop, 1);
			}
			else
			{
				for(loop = middle; (loop < this.outputSize) && result; loop++)
					result = Wrapper.canDropJuncion(end, this.gridLocation.y + 2 + loop, 1);
			}
		}
		
		// check imputs
		if(result)
			result = Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 1 + middle, 1);
		
		return result;
	}
		
	public void droped()
	{
		int loop;
		int middle = this.outputSize / 2;
		int end = this.gridLocation.x + this.gridSize.width;
		
		this.output.setSize(this.outputSize);
		
		for(loop = 0; loop < middle; loop++)
			this.output.changeItem(loop, Wrapper.setPinAt(end, this.gridLocation.y + 1 + loop, 1));
		
		if((this.outputSize % 2) != 0)
		{
			this.output.changeItem(middle, Wrapper.setPinAt(end, this.gridLocation.y + 1 + middle, 1));
			for(loop = middle + 1; loop < this.outputSize; loop++)
				this.output.changeItem(loop, Wrapper.setPinAt(end, this.gridLocation.y + 1 + loop, 1));
		}
		else
		{
			for(loop = middle; loop < this.outputSize; loop++)
				this.output.changeItem(loop, Wrapper.setPinAt(end, this.gridLocation.y + 2 + loop, 1));
		}
		
		this.input = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 1 + middle, 1);
		
		this.changeColor(Color.black);
		this.oldPulses = null;
	}
	
	public void selected()
	{
		for(int loop = 0; loop < this.outputSize; loop++)
			this.output.getItemAt(loop).removePin();
		
		this.input.removePin();
		
		this.changeColor(Color.green);
	}
	
	public void checkAfterSelected()
	{
		for(int loop = 0; loop < this.outputSize; loop++)
			Wrapper.checkPin(this.output.getItemAt(loop));
		
		Wrapper.checkPin(this.input);
	}
	
/* ==================================================================
	Simulation part
	================================================================= */
	protected double delay = 1;
	protected int currentCycle;
	protected boolean pastInput;
	
	public void evaluateOutput(double currentTime, Data[] currentInputs, EnginePeer peer)
	{
		if(this.pastInput ^ currentInputs[0].getValue())
		{
			double time = currentTime + this.delay;
			
			for(int index = 0; index < this.outputSize; index++)
				peer.setOutputPinValue(index, this.pulses[index][this.currentCycle], time);
			
			this.currentCycle = (this.currentCycle + 1) % this.periodInCycles;
		}
			
		
		this.pastInput = currentInputs[0].getValue();
	}
	
	public void createEnginePeer(EnginePeerList epl)
	{
		EnginePeer ep = new EnginePeer(1, this.outputSize, this);
		
		ep.setInputPin(0, this.input.getNodes().getItemAt(0));
		
		for(int index = 0; index < this.outputSize; index++)
			ep.setOutputPin(index, this.output.getItemAt(index).getNodes().getItemAt(0));
		
		this.pastInput = false;
		this.currentCycle = 0;
		
		epl.insertItem(ep);
	}
	
	public void reset()
	{	
	}
	
	public Wrapper getParentWrapper()
	{
		return this;
	}

	public double getDelay()
	{
		return this.delay;
	}
	
	public void changeDelay(double newValue)
	{
		this.delay = newValue;
	}
	
/* ==================================================================
	Storage Part
	================================================================= */
	public String getSpecificParameters()
	{
		String result = Integer.toString(this.periodInCycles) + Wrapper.SEPARATOR + this.outputSize + Wrapper.SEPARATOR + this.delay + Wrapper.SEPARATOR;
		
		int index, loop;
		
		for(loop = 0; loop < this.outputSize; loop++)
			for(index = 0; index < this.periodInCycles; index++)
			{
				if(this.pulses[loop][index])
					result = result + "1";
				else
					result = result + "0";
			}
		
		result = result + Wrapper.SEPARATOR;
		
		return result;
	}
	
	public void loadWrapper(String[] specificParameters) throws SimException
	{
		int index, loop;
		
		if(specificParameters.length == this.getNumberOfSpecificParameters())
		{
			try
			{
				this.setParameters(Integer.valueOf(specificParameters[1]).intValue(), Integer.valueOf(specificParameters[0]).intValue());
				this.delay = Double.valueOf(specificParameters[2]).doubleValue();
				
				for(loop = 0; loop < this.outputSize; loop++)
					for(index = 0; index < this.periodInCycles; index++)
						this.pulses[loop][index] = specificParameters[3].charAt(index + this.periodInCycles * loop) == '1';
			}
			catch(NumberFormatException e)
			{
				throw (new SimException("incorrect parameter type"));
			}
		}
		else
			throw (new SimException("incorrect number of parameters"));
	}
	
	public int getNumberOfSpecificParameters()
	{
		return 4;
	}
	
/* ==================================================================
	Popup Part
	================================================================= */
	private boolean[][] oldPulses = null;
	
	public boolean hasProperties()
	{
		return true;
	}
	
	public Component getPropertyWindow()
	{
		return (new PulseGeneratorProperties(this.delay, this.outputSize, this.periodInCycles, this.pulses));
	}
		
	public void respondToChanges(Component property)
	{
		CentralPanel.ACTIVE_GRID.eraseComponent(this);
		
		PulseGeneratorProperties x = (PulseGeneratorProperties)property;
		this.delay = x.getDelay();
		this.oldPulses = this.pulses;
		
		this.setParameters(x.getOutputSize(), x.getPeriod());
		
		boolean[] value;
		
		for(int loop = 0; loop < this.outputSize; loop++)
			this.pulses[loop] = x.getPulse(loop);
		
		CentralPanel.ACTIVE_GRID.paintComponent(this);
	}
	
	public void restoreOriginalProperties()
	{
		if(this.oldPulses != null)
		{
			this.setParameters(this.oldPulses.length, this.oldPulses[0].length);
			this.pulses = this.oldPulses;
			this.oldPulses = null;
		}
	}
	
	@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");
				writer.write("clk : in std_logic;\n");
				for(int i = 0; i < this.outputSize; i++){
					if(i != 0)
						writer.write(",");
					writer.write("x" + 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 x : std_logic_vector("+(this.outputSize-1)+" downto 0);\n");
				writer.write("type cycles is array ("+(this.periodInCycles-1)+" downto 0,"+(this.outputSize-1)+" downto 0) of std_logic;\n");
				writer.write("signal temp_cycles : cycles := (");
				String temp;
				for(int i = 0; i < this.periodInCycles; i++){
					temp = "\"";
					for(int j = 0; j < this.outputSize; j++){
						temp += this.pulses[j][i] ? 1:0;
					}
					if(i != 0)
						writer.write(",");
					writer.write(temp+"\"");
				}
				writer.write(");\n");
				writer.write("begin\n");
				for(int i = 0; i < this.outputSize; i++){
					writer.write("x" + i + "<= x("+i+");\n");
				}
				writer.write("process clk is\n");
				writer.write("variable cnt : integer := 0;\n");
				writer.write("begin\n");
				writer.write("if rising_edge(clk) then\n");
				writer.write("for i in 0 to "+(this.outputSize-1)+" loop\n");
				writer.write("x(i) <= temp_cycles(i,cnt);\n");
				writer.write("end loop;\n");
				writer.write("cnt := cnt + 1;\n");
				writer.write("if cnt = "+this.periodInCycles+" then\n");
				writer.write("cnt := 0;\n");
				writer.write("end if;\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 "PULSEGEN_"+this.outputSize;
	}
}
