package sim.lib.memory;

import java.awt.*;
import java.awt.event.*;

import sim.*;
import sim.engine.*;
import sim.util.SimSeparator;
import sim.lib.wires.Junction;

public class JKflipflop extends WrapperPainted implements EngineModule
{
/* ==================================================================
	Creation Part
	================================================================= */
	private static Image ICON = GuiFileLink.getImage("sim/lib/memory/JkIcon.gif");
	
	public Image getIcon()
	{
		return JKflipflop.ICON;;
	}
	
	public Wrapper createWrapper()
	{
		return this.getCopy();
	}
	
	public Wrapper createWrapper(Point gridPosition)
	{
		JKflipflop result = this.getCopy();
		result.setGridLocation(gridPosition);
		return result;
	}
	
	public String getBubbleHelp()
	{
		return "JK flip flop";
	}
	
/* ==================================================================
	GUI part
	================================================================= */
	public JKflipflop()
	{
		super();
	}
	
	private JKflipflop getCopy()
	{
		JKflipflop result = new JKflipflop();
		
		result.changeDelay(this.delay);
		result.setInvertJ(this.invertJ);
		result.setInvertK(this.invertK);
		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.invertJ)
			{
				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.invertK)
			{
				g.drawLine(0, five, end - diameter - 1, five);
				g.drawOval(end - diameter - 1, five - increment, diameter, diameter);
			}
			else
				g.drawLine(0, five, end, five);
			
			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("J", 2 * gridGap + increment, gridGap + diameter);
			g.drawString("K", 2 * gridGap + increment, five);
			
			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 inputJ = null;
	private Junction inputK = null;
	private Junction outputQ = null;
	private Junction outputNotQ = null;
	private Junction clock = null;
	
	private boolean invertJ = false;
	private boolean invertK = false;
	private boolean invertCLK = true;
	
	public void setInvertJ(boolean invert)
	{
		this.invertJ = invert;
	}
	
	public void setInvertCLK(boolean invert)
	{
		this.invertCLK = invert;
	}
	
	public void setInvertK(boolean invert)
	{
		this.invertK = 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, this.gridLocation.y + 5, 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.inputJ = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 1, 1);
		this.inputK = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 5, 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.inputJ.removePin();
		this.inputK.removePin();
		this.clock.removePin();
		this.outputQ.removePin();
		this.outputNotQ.removePin();
		
		this.changeColor(Color.green);
	}
	
	public void checkAfterSelected()
	{
		Wrapper.checkPin(this.inputJ);
		Wrapper.checkPin(this.inputK);
		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() || currentInputs[2].isUndefined())
				{
					peer.setOutputPinUndefined(0, time);
					peer.setOutputPinUndefined(1, time);
					
					this.valueQ = -1;
				}
				else if((!currentInputs[1].getValue() ^ this.invertJ) && (!currentInputs[2].getValue() ^ this.invertK))
				{
					if(this.valueQ == -1)
					{
						peer.setOutputPinUndefined(0, time);
						peer.setOutputPinUndefined(1, time);
					}
					else
					{
						peer.setOutputPinValue(0, this.valueQ == 1, time);
						peer.setOutputPinValue(1, this.valueQ == 0, time);
					}
				}
				else if((currentInputs[1].getValue() ^ this.invertJ) && (currentInputs[2].getValue() ^ this.invertK))
				{
					if(this.valueQ == -1)
					{
						peer.setOutputPinUndefined(0, time);
						peer.setOutputPinUndefined(1, time);
					}
					else
					{
						this.valueQ = (this.valueQ + 1) % 2;
						peer.setOutputPinValue(0, this.valueQ == 1, time);
						peer.setOutputPinValue(1, this.valueQ == 0, time);
					}
				}
				else if(currentInputs[1].getValue() ^ this.invertJ)
				{
					this.valueQ = 1;
					peer.setOutputPinValue(0, true, time);
					peer.setOutputPinValue(1, false, time);
				}
				else if(currentInputs[2].getValue() ^ this.invertK)
				{
					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(3, 2, this);
		
		this.pastClock = !this.invertCLK;
		
		ep.setInputPin(0, this.clock.getNodes().getItemAt(0));
		ep.setInputPin(1, this.inputJ.getNodes().getItemAt(0));
		ep.setInputPin(2, this.inputK.getNodes().getItemAt(0));
		
		ep.setOutputPin(0, this.outputQ.getNodes().getItemAt(0));
		ep.setOutputPin(1, this.outputNotQ.getNodes().getItemAt(0));
		
		this.valueQ = -1;
		
		ep.setOutputPinUndefined(0, 0);
		ep.setOutputPinUndefined(1, 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.invertJ + Wrapper.SEPARATOR + this.invertK + 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.setInvertJ(Boolean.valueOf(specificParameters[1]).booleanValue());
				this.setInvertK(Boolean.valueOf(specificParameters[2]).booleanValue());
				this.setInvertCLK(Boolean.valueOf(specificParameters[3]).booleanValue());
			}
			catch(NumberFormatException e)
			{
				throw (new SimException("incorrect parameter type"));
			}
		}
		else
			throw (new SimException("incorrect number of parameters"));
	}
	
	public int getNumberOfSpecificParameters()
	{
		return 4;
	}
	
/* ==================================================================
	Popup Part
	================================================================= */
	public boolean hasProperties()
	{
		return true;
	}
	
	public Component getPropertyWindow()
	{
		return (new JKproperties(this.delay, this.invertJ, this.invertK, this.invertCLK));
	}
		
	public void respondToChanges(Component property)
	{
		CentralPanel.ACTIVE_GRID.eraseComponent(this, false);
		
		JKproperties x = (JKproperties)property;
		this.delay = x.getDelay();
		this.invertJ = x.getInvertJ();
		this.invertK = x.getInvertK();
		this.invertCLK = x.getInvertCLK();
		
		CentralPanel.ACTIVE_GRID.paintComponent(this);
	}
	
	private class JKproperties extends Container implements ActionListener, FocusListener
	{
		private TextField editDelay = new TextField(10);
		private Checkbox editK = new Checkbox("Invert K");
		private Checkbox editJ = new Checkbox("Invert J");
		private Checkbox editCLK = new Checkbox("Invert Clock");
		private double old;
		
		private Label pins = new Label("Pins");
		private Label simulation = new Label("Simulation");
		
		public JKproperties(double initial, boolean invertJ, boolean invertK, boolean invertCLK)
		{
			super();
			this.setLayout(new BorderLayout(0, 15));
			
			this.editJ.setState(invertJ);
			this.editK.setState(invertK);
			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(2, 2));
			p.add(this.editJ);
			p.add(this.editK);
			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.editJ.getPreferredSize().height * 2 + 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 getInvertJ()
		{
			return this.editJ.getState();
		}
		
		public boolean getInvertK()
		{
			return this.editK.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();
		}
	}
}