package sim.lib.memory;

import java.awt.*;
import java.awt.event.*;
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.util.SimSeparator;
import sim.lib.wires.Junction;

public class Dflipflop extends WrapperPainted implements EngineModule
{
/* ==================================================================
	Creation Part
	================================================================= */
	private static Image ICON = GuiFileLink.getImage("sim/lib/memory/DIcon.gif");
	
	public Image getIcon()
	{
		return Dflipflop.ICON;
	}
	
	public Wrapper createWrapper()
	{
		return this.getCopy();
	}
	
	public Wrapper createWrapper(Point gridPosition)
	{
		Dflipflop result = this.getCopy();
		result.setGridLocation(gridPosition);
		return result;
	}
	
	public String getBubbleHelp()
	{
		return "D flip flop";
	}
	
/* ==================================================================
	GUI part
	================================================================= */
	public Dflipflop()
	{
		super();
	}
	
	private Dflipflop getCopy()
	{
		Dflipflop result = new Dflipflop();
		
		result.changeDelay(this.delay);
		result.setInvertD(this.invertD);
		result.setInvertCLK(this.invertCLK);
		
		return result;
	}
	
	public void initializeGridSize()
	{
		this.setGridSize(7, 6);
	}
	
	public void paint(Graphics g)
	{
		// draw if visible
		if(this.isVisible())
		{
			int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
			int increment = gridGap / 4;
			int diameter = 2 * increment;
			int tripple = 3 * gridGap;
			int four = 4 * gridGap;
			int five = 5 * gridGap;
			
			int end = 2 * gridGap - increment;
			
			g.setColor(this.brush);
			
			g.drawRect(end, diameter, four + increment, five);
			
			g.drawLine(6 * gridGap, gridGap, 7 * gridGap, gridGap);
			g.drawLine(6 * gridGap, five, 7 * gridGap, five);
			
			if(this.invertD)
			{
				g.drawLine(0, gridGap, end - diameter - 1, gridGap);
				g.drawOval(end - diameter - 1, gridGap - increment, diameter, diameter);
			}
			else
				g.drawLine(0, gridGap, end, gridGap);
			
			if(this.invertCLK)
			{
				g.drawLine(0, tripple, end - diameter - 1, tripple);
				g.drawOval(end - diameter - 1, tripple - increment, diameter, diameter);
			}
			else
				g.drawLine(0, tripple, end, tripple);
			
			g.drawLine(end, tripple + diameter, end + diameter, tripple);
			g.drawLine(end, tripple - diameter, end + diameter, tripple);
			
			g.setFont(new Font(Wrapper.FONT_NAME, Font.PLAIN, 3 * increment));
			
			g.drawString("D", 2 * gridGap + increment, gridGap + diameter);
			
			g.drawString("Q", five, gridGap + diameter);
			
			increment = g.getFontMetrics(g.getFont()).getAscent();
			g.drawLine(five, five - increment, five + g.getFontMetrics(g.getFont()).stringWidth("Q"), five - increment);
			g.drawString("Q", five, five);
		}
	}
	
/* ==================================================================
	Maintanance Part
	================================================================= */
	private Junction inputD = null;
	private Junction outputQ = null;
	private Junction outputNotQ = null;
	private Junction clock = null;
	
	private boolean invertD = false;
	private boolean invertCLK = false;
	
	public void setInvertD(boolean invert)
	{
		this.invertD = invert;
	}
	
	public void setInvertCLK(boolean invert)
	{
		this.invertCLK = invert;
	}
	
	public boolean canDrop()
	{
		boolean result = Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 1, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 3, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 7, this.gridLocation.y + 1, 1);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 7, this.gridLocation.y + 5, 1);
		
		return result;
	}
		
	public void droped()
	{
		this.inputD = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 1, 1);
		this.clock = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 3, 1);
		this.outputQ = Wrapper.setPinAt(this.gridLocation.x + 7, this.gridLocation.y + 1, 1);
		this.outputNotQ = Wrapper.setPinAt(this.gridLocation.x + 7, this.gridLocation.y + 5, 1);
		
		this.changeColor(Color.black);
	}
	
	public void selected()
	{
		this.inputD.removePin();
		this.clock.removePin();
		this.outputQ.removePin();
		this.outputNotQ.removePin();
		
		this.changeColor(Color.green);
	}
	
	public void checkAfterSelected()
	{
		Wrapper.checkPin(this.inputD);
		Wrapper.checkPin(this.clock);
		Wrapper.checkPin(this.outputQ);
		Wrapper.checkPin(this.outputNotQ);
	}
	
/* ==================================================================
	Simulation part
	================================================================= */
	protected double delay = 1;
	protected boolean pastClock;
	protected int valueQ;
	
	public void evaluateOutput(double currentTime, Data[] currentInputs, EnginePeer peer)
	{
		double time = this.delay + currentTime;
		
		if(!currentInputs[0].isUndefined())
		{
			if((this.pastClock ^ currentInputs[0].getValue()) && (this.invertCLK ^ currentInputs[0].getValue()))
			{
				if(currentInputs[1].isUndefined())
				{
					this.valueQ = -1;
					peer.setOutputPinUndefined(0, time);
					peer.setOutputPinUndefined(1, time);
				}
				else if(currentInputs[1].getValue() ^ this.invertD)
				{
					this.valueQ = 1;
					peer.setOutputPinValue(0, true, time);
					peer.setOutputPinValue(1, false, time);
				}
				else
				{
					this.valueQ = 0;
					peer.setOutputPinValue(0, false, time);
					peer.setOutputPinValue(1, true, time);
				}
			}
			
			this.pastClock = currentInputs[0].getValue();
		}
		else
			this.pastClock = !this.invertCLK;
	}
	
	public void createEnginePeer(EnginePeerList epl)
	{
		EnginePeer ep = new EnginePeer(2, 2, this);
		
		this.pastClock = !this.invertCLK;
		
		ep.setInputPin(0, this.clock.getNodes().getItemAt(0));
		ep.setInputPin(1, this.inputD.getNodes().getItemAt(0));
		
		ep.setOutputPin(0, this.outputQ.getNodes().getItemAt(0));
		ep.setOutputPin(1, this.outputNotQ.getNodes().getItemAt(0));
		
		this.valueQ = 0;
		
		ep.setOutputPinValue(0, false, 0);
		ep.setOutputPinValue(1, true, 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()
	{
		return (Double.toString(this.delay) + Wrapper.SEPARATOR + this.invertD + Wrapper.SEPARATOR + this.invertCLK + Wrapper.SEPARATOR);
	}
	
	public void loadWrapper(String[] specificParameters) throws SimException
	{
		if(specificParameters.length == this.getNumberOfSpecificParameters())
		{
			try
			{
				this.delay = Double.valueOf(specificParameters[0]).doubleValue();
				this.setInvertD(Boolean.valueOf(specificParameters[1]).booleanValue());
				this.setInvertCLK(Boolean.valueOf(specificParameters[2]).booleanValue());
			}
			catch(NumberFormatException e)
			{
				throw (new SimException("incorrect parameter type"));
			}
		}
		else
			throw (new SimException("incorrect number of parameters"));
	}
	
	public int getNumberOfSpecificParameters()
	{
		return 3;
	}
	
/* ==================================================================
	Popup Part
	================================================================= */
	public boolean hasProperties()
	{
		return true;
	}
	
	public Component getPropertyWindow()
	{
		return (new Dproperties(this.delay, this.invertD, this.invertCLK));
	}
		
	public void respondToChanges(Component property)
	{
		CentralPanel.ACTIVE_GRID.eraseComponent(this, false);
		
		Dproperties x = (Dproperties)property;
		this.delay = x.getDelay();
		this.invertD = x.getInvertD();
		this.invertCLK = x.getInvertCLK();
		
		CentralPanel.ACTIVE_GRID.paintComponent(this);
	}
	
	private class Dproperties extends Container implements ActionListener, FocusListener
	{
		private TextField editDelay = new TextField(10);
		private Checkbox editD = new Checkbox("Invert D");
		private Checkbox editCLK = new Checkbox("Invert Clock");
		private double old;
		
		private Label pins = new Label("Pins");
		private Label simulation = new Label("Simulation");
		
		public Dproperties(double initial, boolean invertD, boolean invertCLK)
		{
			super();
			this.setLayout(new BorderLayout(0, 15));
			
			this.editD.setState(invertD);
			this.editCLK.setState(invertCLK);
			this.old = initial;
			this.editDelay.addActionListener(this);
			this.editDelay.addFocusListener(this);
			this.editDelay.setText(Double.toString(initial));
			
			Panel big = new Panel(new BorderLayout(0, 15));
			Panel p = new Panel(new BorderLayout());
			p.add(this.pins, BorderLayout.WEST);
			p.add(new SimSeparator(), BorderLayout.CENTER);
			big.add(p, BorderLayout.NORTH);
			
			p = new Panel(new GridLayout(1, 2));
			p.add(this.editD);
			p.add(this.editCLK);
			big.add(p, BorderLayout.CENTER);
			
			this.add(big, BorderLayout.NORTH);
			
			
			big = new Panel(new BorderLayout(0, 15));
			p = new Panel(new BorderLayout());
			p.add(this.simulation, BorderLayout.WEST);
			p.add(new SimSeparator(), BorderLayout.CENTER);
			big.add(p, BorderLayout.NORTH);
			
			p = new Panel(new FlowLayout(FlowLayout.LEFT, 0, 0));
			p.add(new Label("Propagation Delay"));
			p.add(this.editDelay);
			big.add(p, BorderLayout.CENTER);
			
			this.add(big, BorderLayout.CENTER);
		}
		
		public void addNotify()
		{
			super.addNotify();
			this.setSize(290, this.editD.getPreferredSize().height + this.editDelay.getPreferredSize().height + this.pins.getPreferredSize().height * 2 + 45);
		}
		
		public void actionPerformed(ActionEvent e)
		{
			double newDelay;
			
			try
			{
				newDelay = Double.valueOf(this.editDelay.getText()).doubleValue();
				
				if(newDelay < 0)
					this.editDelay.setText(Double.toString(this.old));
				else
					this.old = newDelay;
			}
			catch(NumberFormatException nfe)
			{
				this.editDelay.setText(Double.toString(this.old));
			}
		}
		
		public void focusGained(FocusEvent e)
		{
		}
		
		public void focusLost(FocusEvent e)
		{
			double newDelay;
			
			try
			{
				newDelay = Double.valueOf(this.editDelay.getText()).doubleValue();
				
				if(newDelay < 0)
					this.editDelay.setText(Double.toString(this.old));
				else
				{
					this.old = newDelay;
					this.editDelay.setText(this.editDelay.getText());
				}
			}
			catch(NumberFormatException nfe)
			{
				this.editDelay.setText(Double.toString(this.old));
			}
		}
		
		public double getDelay()
		{
			double newDelay;
			
			try
			{
				newDelay = Double.valueOf(this.editDelay.getText()).doubleValue();
				
				if(newDelay >= 0)
					this.old = newDelay;
			}
			catch(NumberFormatException nfe)
			{
			}
			
			return this.old;
		}
		
		public boolean getInvertD()
		{
			return this.editD.getState();
		}
		
		public boolean getInvertCLK()
		{
			return this.editCLK.getState();
		}
		
		public Dimension getPreferredSize()
		{
			return this.getSize();
		}
		
		public Dimension getMinimumSize()
		{
			return this.getSize();
		}
	
		public Dimension getMaximumSize()
		{
			return this.getSize();
		}
	}

	@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( clk: in std_logic;\n");
				writer.write("d : in std_logic;\n");
				writer.write("q : out std_logic;\n");
				writer.write("notq : 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");
				if (this.invertCLK)
					writer.write("if falling_edge(clk) then\n");
				else
					writer.write("if rising_edge(clk) then\n");
				if (this.invertD){
					writer.write("q <= d;");
					writer.write("notq <= not d;");
				}else{
					writer.write("q <= not d;");
					writer.write("notq <= d;");
				}
				writer.write("end if;");
				writer.write("end process;");
				writer.write("end behav;\n");
				writer.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

	@Override
	public String getVHDLName() {
		return "Dflipflop";
	}
}
