package sim.lib.others;

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

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

public class TristateBuffer extends RotatableFlippableWrapperPainted implements EngineModule
{
/* ==================================================================
	Creation Part
	================================================================= */
	private static Image ICON = GuiFileLink.getImage("sim/lib/others/TristateIcon.gif");
	
	public Image getIcon()
	{
		return TristateBuffer.ICON;;
	}
	
	public Wrapper createWrapper()
	{
		return this.getCopy();
	}
	
	public Wrapper createWrapper(Point gridPosition)
	{
		TristateBuffer result = this.getCopy();
		result.setGridLocation(gridPosition);
		return result;
	}
	
	public String getBubbleHelp()
	{
		return "Tristate Buffer";
	}
	
/* ==================================================================
	GUI part
	================================================================= */
	private int busSize;
	
	public TristateBuffer()
	{
		super();
		
		this.setBusSize(8);
	}
	
	public TristateBuffer getCopy()
	{
		TristateBuffer result = new TristateBuffer();
		result.setBusSize(this.busSize);
		result.changeDelay(this.delay);
		
		return result;
	}
	
	public void setBusSize(int size)
	{
		this.busSize = size;
	}
	
	public void initializeGridSize()
	{
		this.setGridSize(4, 3);
	}
	
/* ==================================================================
	Maintanance Part
	================================================================= */
	private Junction input = null;
	private Junction output = null;
	private Junction drive = null;
	
	public void selected()
	{
		this.input.removePin();
		this.output.removePin();
		this.drive.removePin();
		
		this.changeColor(Color.green);
	}
	
	public void checkAfterSelected()
	{
		Wrapper.checkPin(this.input);
		Wrapper.checkPin(this.output);
		Wrapper.checkPin(this.drive);
	}
/* ==================================================================
	Simulation part
	================================================================= */
	protected double delay = 0;
	
	public void evaluateOutput(double currentTime, Data[] currentInputs, EnginePeer peer)
	{
		double time = this.delay + currentTime;
		int index;
		
		if(currentInputs[this.busSize].isUndefined())
		{
			for(index = 0; index < this.busSize; index++)
				peer.setOutputPinUndefined(index, time);
		}
		else if(currentInputs[this.busSize].getValue())
		{
			for(index = 0; index < this.busSize; index++)
			{
				if(currentInputs[index].isUndefined())
					peer.setOutputPinUndefined(index, time);
				else
					peer.setOutputPinValue(index, currentInputs[index].getValue(), time);
			}
		}
		else
		{
			for(index = 0; index < this.busSize; index++)
				peer.floatOutputPin(index, time);
		}
	}
	
	public void createEnginePeer(EnginePeerList epl)
	{
		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.drive.getNodes().getItemAt(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.busSize + Wrapper.SEPARATOR);
	}
	
	public int getNumberOfSpecificParameters()
	{
		return 2;
	}
	
	public void loadWrapper(String[] specificParameters) throws SimException
	{
		if(specificParameters.length == this.getNumberOfSpecificParameters())
		{
			try
			{
				this.delay = Double.valueOf(specificParameters[0]).doubleValue();
				this.busSize = Integer.valueOf(specificParameters[1]).intValue();
			}
			catch(NumberFormatException e)
			{
				throw (new SimException("incorrect parameter type"));
			}
		}
		else
			throw (new SimException("incorrect number of parameters"));
	}
	
/* ==================================================================
	Rotation abd Flipping Part
	================================================================= */
	
	protected void paintNormal_0(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		
		g.drawLine(gridGap + increment, gridGap + increment, gridGap + increment, 11 * increment);
		
		g.drawLine(gridGap + increment, gridGap + increment, 11 * increment, 2 * gridGap);
		g.drawLine(gridGap + increment, 11 * increment, 11 * increment + 1, 2 * gridGap);
		
		g.drawLine(2 * gridGap, 0, 2 * gridGap, 6 * increment);
		
		if(this.busSize == 1)
		{
			g.drawLine(0, 2 * gridGap, gridGap + increment, 2 * gridGap);
			g.drawLine(11 * increment, 2 * gridGap, 4 * gridGap, 2 * gridGap);
		}
		else
		{
			g.fillRect(0, 2 * gridGap - 1, gridGap + increment, 3);
			g.fillRect(11 * increment, 2 * gridGap - 1, gridGap + increment, 3);
		}
	}
	
	protected void paintNormal_90(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		g.drawLine(gridGap + increment, 11 * increment, 11 * increment, 11 * increment);
		
		g.drawLine(gridGap + increment, 11 * increment, 2 * gridGap, gridGap + increment);
		g.drawLine(11 * increment, 11 * increment, 2 * gridGap, gridGap + increment);
		
		g.drawLine(0, 2 * gridGap, 6 * increment, 2 * gridGap);
		
		if(this.busSize == 1)
		{
			g.drawLine(2 * gridGap, 0, 2 * gridGap, gridGap + increment);
			g.drawLine(2 * gridGap, 11 * increment, 2 * gridGap, 4 * gridGap);
		}
		else
		{
			g.fillRect(2 * gridGap - 1, 0, 3, gridGap + increment + 2);
			g.fillRect(2 * gridGap - 1, 11 * increment, 3, gridGap + increment);
		}
	}
	
	protected void paintNormal_180(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		
		g.drawLine(11 * increment, increment, 11 * increment, 7 * increment);
		
		g.drawLine(gridGap + increment, gridGap, 11 * increment, increment);
		g.drawLine(gridGap + increment - 1, gridGap, 11 * increment, 7 * increment);
		
		g.drawLine(2 * gridGap, 6 * increment, 2 * gridGap, 3 * gridGap);
		
		if(this.busSize == 1)
		{
			g.drawLine(0, gridGap, gridGap + increment, gridGap);
			g.drawLine(11 * increment, gridGap, 4 * gridGap, gridGap);
		}
		else
		{
			g.fillRect(0, gridGap - 1, gridGap + increment + 1, 3);
			g.fillRect(11 * increment, gridGap - 1, gridGap + increment, 3);
		}
	}
	
	protected void paintNormal_270(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		
		g.drawLine(increment, gridGap + increment, 7 * increment, gridGap + increment);
		
		g.drawLine(gridGap, 11 * increment, increment, gridGap + increment);
		g.drawLine(gridGap, 11 * increment, 7 * increment, gridGap + increment);
		
		g.drawLine(6 * increment, 2 * gridGap, 3 * gridGap, 2 * gridGap);
		
		if(this.busSize == 1)
		{
			g.drawLine(gridGap, 0, gridGap, gridGap + increment);
			g.drawLine(gridGap, 11 * increment, gridGap, 4 * gridGap);
		}
		else
		{
			g.fillRect(gridGap - 1, 0, 3, gridGap + increment);
			g.fillRect(gridGap - 1, 11 * increment, 3, gridGap + increment);
		}
	}
	
	protected void paintFlipped_0(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		
		g.drawLine(gridGap + increment, increment, gridGap + increment, 7 * increment);
		
		g.drawLine(11 * increment, gridGap, gridGap + increment, increment);
		g.drawLine(11 * increment + 1, gridGap, gridGap + increment, 7 * increment);
		
		g.drawLine(2 * gridGap, 6 * increment, 2 * gridGap, 3 * gridGap);
		
		if(this.busSize == 1)
		{
			g.drawLine(0, gridGap, gridGap + increment, gridGap);
			g.drawLine(11 * increment, gridGap, 4 * gridGap, gridGap);
		}
		else
		{
			g.fillRect(0, gridGap - 1, gridGap + increment, 3);
			g.fillRect(11 * increment, gridGap - 1, gridGap + increment, 3);
		}
	}
	
	protected void paintFlipped_90(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		
		g.drawLine(gridGap + increment, gridGap + increment, 11 * increment, gridGap + increment);
		
		g.drawLine(gridGap + increment, gridGap + increment, 2 * gridGap, 11 * increment);
		g.drawLine(11 * increment, gridGap + increment, 2 * gridGap, 11 * increment);
		
		g.drawLine(0, 2 * gridGap, 6 * increment, 2 * gridGap);
		
		if(this.busSize == 1)
		{
			g.drawLine(2 * gridGap, 0, 2 * gridGap, gridGap + increment);
			g.drawLine(2 * gridGap, 11 * increment, 2 * gridGap, 4 * gridGap);
		}
		else
		{
			g.fillRect(2 * gridGap - 1, 0, 3, gridGap + increment);
			g.fillRect(2 * gridGap - 1, 11 * increment, 3, gridGap + increment);
		}
	}
	
	protected void paintFlipped_180(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		
		g.drawLine(11 * increment, gridGap + increment, 11 * increment, 11 * increment);
		
		g.drawLine(11 * increment, gridGap + increment, gridGap + increment, 2 * gridGap);
		g.drawLine(11 * increment, 11 * increment, gridGap + increment - 1, 2 * gridGap);
		
		g.drawLine(2 * gridGap, 0, 2 * gridGap, 6 * increment);
		
		if(this.busSize == 1)
		{
			g.drawLine(0, 2 * gridGap, gridGap + increment, 2 * gridGap);
			g.drawLine(11 * increment, 2 * gridGap, 4 * gridGap, 2 * gridGap);
		}
		else
		{
			g.fillRect(0, 2 * gridGap - 1, gridGap + increment + 1, 3);
			g.fillRect(11 * increment, 2 * gridGap - 1, gridGap + increment, 3);
		}
	}
	
	protected void paintFlipped_270(Graphics g)
	{
		int gridGap = CentralPanel.ACTIVE_GRID.getCurrentGridGap();
		int increment = gridGap / 4;
		g.setColor(this.brush);
		
		
		g.drawLine(increment, 11 * increment, 7 * increment, 11 * increment);
		
		g.drawLine(gridGap, gridGap + increment, increment, 11 * increment);
		g.drawLine(gridGap, gridGap + increment, 7 * increment, 11 * increment);
		
		g.drawLine(6 * increment, 2 * gridGap, 3 * gridGap, 2 * gridGap);
		
		if(this.busSize == 1)
		{
			g.drawLine(gridGap, 0, gridGap, gridGap + increment);
			g.drawLine(gridGap, 11 * increment, gridGap, 4 * gridGap);
		}
		else
		{
			g.fillRect(gridGap - 1, 0, 3, gridGap + increment + 2);
			g.fillRect(gridGap - 1, 11 * increment, 3, gridGap + increment);
		}
	}
	
	
	
	
	protected boolean canDropNormal_0()
	{
		boolean result = Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 2, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 4, this.gridLocation.y + 2, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 2, this.gridLocation.y , 1);
		
		return result;
	}
	
	protected boolean canDropNormal_90()
	{
		boolean result = Wrapper.canDropJuncion(this.gridLocation.x + 2, this.gridLocation.y, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 2, this.gridLocation.y + 4, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 2, 1);
		
		return result;
	}
	
	protected boolean canDropNormal_180()
	{
		boolean result = Wrapper.canDropJuncion(this.gridLocation.x + 4, this.gridLocation.y + 1, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x, this.gridLocation.y + 1, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 2, this.gridLocation.y + 3, 1);
		
		return result;
	}
	
	protected boolean canDropNormal_270()
	{
		boolean result = Wrapper.canDropJuncion(this.gridLocation.x + 2, this.gridLocation.y + 4, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 2, this.gridLocation.y, this.busSize);
		result = result && Wrapper.canDropJuncion(this.gridLocation.x + 3, this.gridLocation.y + 2, 1);
		
		return result;
	}
	
	protected boolean canDropFlipped_0()
	{
		return this.canDropNormal_180();
	}
	
	protected boolean canDropFlipped_90()
	{
		return this.canDropNormal_90();
	}
	
	protected boolean canDropFlipped_180()
	{
		return this.canDropNormal_0();
	}
	
	protected boolean canDropFlipped_270()
	{
		return this.canDropNormal_270();
	}
	
	
	
	
	protected void dropedNormal_0()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 2, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x + 4, this.gridLocation.y + 2, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
	
	protected void dropedNormal_90()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y + 4, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 2, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
	
	protected void dropedNormal_180()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x + 4, this.gridLocation.y + 1, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 1, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y + 3, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
	
	protected void dropedNormal_270()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x + 1, this.gridLocation.y, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x + 1, this.gridLocation.y + 4, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x + 3, this.gridLocation.y + 2, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
	
	protected void dropedFlipped_0()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 1, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x + 4, this.gridLocation.y + 1, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y + 3, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
	
	protected void dropedFlipped_90()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y + 4, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 2, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
	
	protected void dropedFlipped_180()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x + 4, this.gridLocation.y + 2, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x, this.gridLocation.y + 2, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x + 2, this.gridLocation.y, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
	
	protected void dropedFlipped_270()
	{
		this.input = Wrapper.setPinAt(this.gridLocation.x + 1, this.gridLocation.y + 4, this.busSize);
		this.output = Wrapper.setPinAt(this.gridLocation.x + 1, this.gridLocation.y, this.busSize);
		this.drive = Wrapper.setPinAt(this.gridLocation.x + 3, this.gridLocation.y + 2, 1);
		
		this.changeColor(Color.black);
		this.oldBusSize = 0;
	}
		
	
	
	
	protected void adjustToChanges()
	{
		if(this.angle % 180 == 0)
			this.setGridSize(4, 3);
		else
			this.setGridSize(3, 4);
	}
	
/* ==================================================================
	Popup Part
	================================================================= */
	private int oldBusSize = 0;
	
	public boolean hasProperties()
	{
		return true;
	}
	
	public Component getPropertyWindow()
	{
		return (new TristateProperties(this.busSize, this.delay));
	}
		
	public void respondToChanges(Component property)
	{
		this.delay = ((TristateProperties)property).getDelay();
		
		if(this.oldBusSize == 0)
			this.oldBusSize = this.busSize;
		
		CentralPanel.ACTIVE_GRID.eraseComponent(this);
		this.setBusSize(((TristateProperties)property).getBusSize());
		CentralPanel.ACTIVE_GRID.paintComponent(this);
		
	}
	
	public void restoreOriginalProperties()
	{
		if(this.oldBusSize != 0)
		{
			this.setBusSize(this.oldBusSize);
			this.oldBusSize = 0;
		}
	}
	
	private class TristateProperties extends Container implements ActionListener, FocusListener
	{
		private TextField editBus = new TextField(10);
		private TextField editDelay = new TextField(10);
		private double oldDelay;
		private int oldBus;
		private Label pins = new Label("Pins");
		private Label simulation = new Label("Simulation");
		
		public TristateProperties(int bus, double delay)
		{
			super();
			this.setLayout(new BorderLayout(0, 15));
			
			this.oldBus = bus;
			this.editBus.addActionListener(this);
			this.editBus.addFocusListener(this);
			this.editBus.setText(Integer.toString(bus));
			
			this.oldDelay = delay;
			this.editDelay.addActionListener(this);
			this.editDelay.addFocusListener(this);
			this.editDelay.setText(Double.toString(delay));
			
			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 FlowLayout(FlowLayout.LEFT, 0, 0));
			p.add(new Label("Bus Size"));
			p.add(this.editBus);
			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.editBus.getPreferredSize().height * 2 + this.pins.getPreferredSize().height * 2 + 45);
		}
		
		public void actionPerformed(ActionEvent e)
		{
			TextField source = (TextField)e.getSource();
			
			if(source == this.editDelay)
				this.getDelay();
			else if(source == this.editBus)
				this.getBusSize();
		}
		
		public void focusGained(FocusEvent e)
		{
		}
		
		public void focusLost(FocusEvent e)
		{
			TextField source = (TextField)e.getSource();
			
			if(source == this.editDelay)
				this.getDelay();
			else if(source == this.editBus)
				this.getBusSize();
			
			source.setText(source.getText());
		}
		
		public int getBusSize()
		{
			int newBus;
			
			try
			{
				newBus = Integer.valueOf(this.editBus.getText()).intValue();
				
				if(newBus >= 1)
					this.oldBus = newBus;
				else
					this.editBus.setText(Integer.toString(this.oldBus));
			}
			catch(NumberFormatException nfe)
			{
				this.editBus.setText(Integer.toString(this.oldBus));
			}
			
			return this.oldBus;
		}
		
		public double getDelay()
		{
			double newDelay;
			
			try
			{
				newDelay = Double.valueOf(this.editDelay.getText()).doubleValue();
				
				if(newDelay >= 0)
					this.oldDelay = newDelay;
				else
					this.editDelay.setText(Double.toString(this.oldBus));
			}
			catch(NumberFormatException nfe)
			{
				this.editDelay.setText(Double.toString(this.oldBus));
			}
			
			return this.oldDelay;
		}
		
		public Dimension getPreferredSize()
		{
			return this.getSize();
		}
		
		public Dimension getMinimumSize()
		{
			return this.getSize();
		}
	
		public Dimension getMaximumSize()
		{
			return this.getSize();
		}
	}
}