package sim.lib.others;

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

import gjt.BulletinLayout;

import sim.util.SimSeparator;
import sim.util.LightContainer;
import sim.Wrapper;
import sim.MainWindow;

public class PulseGeneratorProperties extends Container implements ActionListener, FocusListener, AdjustmentListener
{
	public static final int PULSE_WIDTH = 18;
	
	private TextField editDelay = new TextField(10);
	private TextField editOutputSize = new TextField(5);
	private TextField editPeriod = new TextField(5);
	private ScrollPane scroller = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
	private LightContainer patternHolder = new LightContainer(new GridBagLayout());
	private PatternDisplay[] editPatterns;
	
	private NameDisplay pulsesName;
	private ClockDisplay clock;
	private LightContainer nameHolder = new LightContainer(new BulletinLayout());
	private LightContainer clockHolder = new LightContainer(new BulletinLayout());
	
	private Label simulation = new Label("Simulation");
	private Label configuration = new Label("Configuration");
	
	private double delay;
	private int size;
	private int period;
	
	public PulseGeneratorProperties(double propagationDelay, int outputSize, int periodInCycles, boolean[][] pulses)
	{
		super();
		this.setLayout(new BorderLayout(0, 15));

		this.delay = propagationDelay;
		this.size = outputSize;
		this.period = periodInCycles;
		this.editPatterns = new PatternDisplay[this.size];
		
		this.pulsesName = new NameDisplay(this.size);
		this.clock = new ClockDisplay(this.period);
		
		this.scroller.setBackground(Color.white);
		this.scroller.getVAdjustable().addAdjustmentListener(this);
		this.scroller.getHAdjustable().addAdjustmentListener(this);
		
		this.editDelay.addActionListener(this);
		this.editDelay.setText(Double.toString(this.delay));
		this.editDelay.addFocusListener(this);
		this.editOutputSize.addActionListener(this);
		this.editOutputSize.setText(Integer.toString(this.size));
		this.editOutputSize.addFocusListener(this);
		this.editPeriod.addActionListener(this);
		this.editPeriod.setText(Integer.toString(this.period));
		this.editPeriod.addFocusListener(this);
		
		// configuration
		Panel big = new Panel();
		big.setLayout(new BorderLayout(0, 15));
		
		Panel p = new Panel(new BorderLayout());
		p.add(this.configuration, BorderLayout.WEST);
		p.add(new SimSeparator(), BorderLayout.CENTER);
		big.add(p, BorderLayout.NORTH);
		
		Panel p2 = new Panel(new GridBagLayout());
		
		p = new Panel(new FlowLayout(FlowLayout.LEFT, 0, 0));
		p.add(new Label("Number of Pulses"));
		p.add(this.editOutputSize);
		GridBagConstraints c = new GridBagConstraints();
		c.gridx = 0;
		c.gridy = 0;
		c.gridwidth = 1;
		c.gridheight = 1;
		c.fill = GridBagConstraints.NONE;
		c.anchor = GridBagConstraints.WEST;
		c.weightx = 1;
		c.weighty = 0;
		p2.add(p, c);
		
		p = new Panel(new FlowLayout(FlowLayout.LEFT, 0, 0));
		p.add(new Label("Period"));
		p.add(this.editPeriod);
		c.gridx = 1;
		c.anchor = GridBagConstraints.EAST;
		p2.add(p, c);
		big.add(p2, BorderLayout.CENTER);
		
		this.scroller.getVAdjustable().setBlockIncrement(PulseGeneratorProperties.PULSE_WIDTH * 2);
		this.scroller.getVAdjustable().setUnitIncrement(PulseGeneratorProperties.PULSE_WIDTH);
		this.scroller.getHAdjustable().setBlockIncrement(PulseGeneratorProperties.PULSE_WIDTH * 2);
		this.scroller.getHAdjustable().setUnitIncrement(PulseGeneratorProperties.PULSE_WIDTH);
		
		c.gridwidth = 1;
		c.gridheight = 1;
		c.insets = new Insets(5, 0, 5, 0);
		c.fill = GridBagConstraints.NONE;
		c.anchor = GridBagConstraints.WEST;
		c.weighty = 0;
		c.weightx = 0;
		c.gridx = 0;
		
		for(int loop = 0; loop < this.size; loop++)
		{
			this.editPatterns[loop] = new PatternDisplay(this.period, pulses[loop]);
			c.gridy = loop;
			this.patternHolder.add(this.editPatterns[loop], c);
		}
		
		this.patternHolder.setSize(this.editPatterns[0].getSize().width, (this.editPatterns[0].getSize().height + 10) * this.size);
		
		p = new Panel(new BulletinLayout());
		p.add(this.patternHolder);
		this.scroller.add(p);
		
		LightContainer lc = new LightContainer(new GridBagLayout());
		lc.setSize(290, 150);
		
		c.gridwidth = 1;
		c.gridheight = 1;
		c.insets = new Insets(0, 0, 0, 0);
		c.fill = GridBagConstraints.BOTH;
		c.anchor = GridBagConstraints.CENTER;
		c.weighty = 1;
		c.weightx = 1;
		c.gridx = 1;
		c.gridy = 1;
		
		lc.add(this.scroller, c);
		
		this.nameHolder.add(this.pulsesName);
		this.nameHolder.setSize(this.pulsesName.getSize().width, 1);
		
		c.fill = GridBagConstraints.VERTICAL;
		c.weighty = 1;
		c.weightx = 0;
		c.gridx = 0;
		
		lc.add(this.nameHolder, c);
		
		this.clockHolder.add(this.clock);
		this.clockHolder.setSize(1, this.clock.getSize().height);
		
		c.fill = GridBagConstraints.HORIZONTAL;
		c.weighty = 0;
		c.weightx = 1;
		c.gridy = 0;
		c.gridx = 1;
		
		lc.add(this.clockHolder, c);
		
		big.add(lc, BorderLayout.SOUTH);
		
		this.add(big, BorderLayout.NORTH);
		
		// simulation
		big = new Panel();
		big.setLayout(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(320, this.editDelay.getPreferredSize().height * 2 + this.simulation.getPreferredSize().height * 2 + 210);
		
		this.pulsesName.setLocation(0, this.scroller.getInsets().top - this.scroller.getScrollPosition().y);
		this.clock.setLocation(this.scroller.getInsets().left - this.scroller.getScrollPosition().x, 0);
	}
	
	public void actionPerformed(ActionEvent e)
	{
		Component source = (Component)e.getSource();
		
		if(source == this.editDelay)
			this.getDelay();
		else if(source == this.editOutputSize)
			this.getOutputSize();
		else if(source == this.editPeriod)
			this.getPeriod();
	}
	
	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.editOutputSize)
			this.getOutputSize();
		else if(source == this.editPeriod)
			this.getPeriod();
		
		source.setText(source.getText());
	}
	
	public void adjustmentValueChanged(AdjustmentEvent e)
	{
		Adjustable source = (Adjustable )e.getSource();
		
		if(source == this.scroller.getVAdjustable())
			this.pulsesName.setLocation(0, this.scroller.getInsets().top - this.scroller.getScrollPosition().y);
		else
			this.clock.setLocation(this.scroller.getInsets().left - this.scroller.getScrollPosition().x, 0);
	}
	
	public double getDelay()
	{
		double newDelay;
		
		try
		{
			newDelay = Double.valueOf(this.editDelay.getText()).doubleValue();
			
			if(newDelay >= 0)
				this.delay = newDelay;
			else
				this.editDelay.setText(Double.toString(this.delay));
		}
		catch(NumberFormatException nfe)
		{
			this.editDelay.setText(Double.toString(this.delay));
		}
		
		return this.delay;
	}
	
	public boolean[] getPulse(int index)
	{	
		return this.editPatterns[index].getPattern();
	}
	
	public int getOutputSize()
	{
		int newSize;
		
		try
		{
			newSize = Integer.valueOf(this.editOutputSize.getText()).intValue();
			
			if((newSize > 0) && (newSize != this.size) && (newSize < 101))
			{
				this.size = newSize;
				
				boolean[] zeroPattern = new boolean[this.period];
				for(newSize = 0; newSize < this.period; newSize++)
					zeroPattern[newSize] = false;
				
				PatternDisplay[] old = this.editPatterns;
				this.editPatterns = new PatternDisplay[this.size];
				this.patternHolder.removeAll();
				
				GridBagConstraints c = new GridBagConstraints();
				
				c.gridwidth = 1;
				c.gridheight = 1;
				c.insets = new Insets(5, 0, 5, 0);
				c.fill = GridBagConstraints.NONE;
				c.anchor = GridBagConstraints.WEST;
				c.weighty = 0;
				c.weightx = 0;
				c.gridx = 0;
				
				for(int loop = 0; loop < this.size; loop++)
				{
					if(loop < old.length)
						this.editPatterns[loop] = new PatternDisplay(this.period, old[loop].getPattern());
					else
						this.editPatterns[loop] = new PatternDisplay(this.period, zeroPattern);
					
					c.gridy = loop;
					this.patternHolder.add(this.editPatterns[loop], c);
				}
				
				this.patternHolder.setSize(this.editPatterns[0].getSize().width, (this.editPatterns[0].getSize().height + 10) * this.size);
				this.pulsesName.setInputSize(this.size);
				
				this.nameHolder.setSize(this.pulsesName.getSize().width, 1);
				this.clockHolder.setSize(1, this.clock.getSize().height);
				this.scroller.setScrollPosition(0, 0);
				this.scroller.setSize(1, 1);
				this.validate();
			}
			else if(newSize != this.size)
			{
				this.editOutputSize.setText(Integer.toString(this.size));
			}
		}
		catch(NumberFormatException nfe)
		{
			this.editOutputSize.setText(Integer.toString(this.size));
		}
		
		return this.size;
	}
	
	public int getPeriod()
	{
		int newPeriod;
		
		try
		{
			newPeriod = Integer.valueOf(this.editPeriod.getText()).intValue();
			
			if((newPeriod > 1) && (newPeriod != this.period) && (newPeriod < 101))
			{
				this.period = newPeriod;
					
				for(int loop = 0; loop < this.size; loop++)
					this.editPatterns[loop].setPeriod(this.period);
				
				this.patternHolder.setSize(this.editPatterns[0].getSize().width, (this.editPatterns[0].getSize().height + 10) * this.size);
				this.clock.setCycles(this.period);
				
				this.scroller.validate();
			}
			else if(newPeriod != this.period)
			{
				this.editPeriod.setText(Integer.toString(this.period));
			}
		}
		catch(NumberFormatException nfe)
		{
			this.editPeriod.setText(Integer.toString(this.period));
		}
		
		return this.period;
	}
	
	public Dimension getPreferredSize()
	{
		return this.getSize();
	}
	
/* ==================================================================
	Clock display
	================================================================= */
	private class ClockDisplay extends Component implements MouseListener
	{
		private int cycleNumber;
		
		public ClockDisplay(int cycles)
		{
			super();
			
			this.cycleNumber = cycles;
			this.setSize(PulseGeneratorProperties.PULSE_WIDTH * cycles + 20, PulseGeneratorProperties.PULSE_WIDTH + 11);
			this.addMouseListener(this);
		}
		
		public void paint(Graphics g)
		{
			g.setColor(Color.lightGray);
			g.fillRect(0, 0, this.getSize().width, this.getSize().height);
			
			g.setColor(Color.black);
			
			boolean value = true;
			
			int start = 10;
			
			g.drawLine(start, PulseGeneratorProperties.PULSE_WIDTH + 5, start + PulseGeneratorProperties.PULSE_WIDTH, PulseGeneratorProperties.PULSE_WIDTH + 5);
			
			start = start + PulseGeneratorProperties.PULSE_WIDTH;
			
			for(int index = 1; index < this.cycleNumber; index++)
			{
				g.drawLine(start, 5, start, PulseGeneratorProperties.PULSE_WIDTH + 5);
				
				if(value)
					g.drawLine(start, 5, start + PulseGeneratorProperties.PULSE_WIDTH, 5);
				else
					g.drawLine(start, PulseGeneratorProperties.PULSE_WIDTH + 5, start + PulseGeneratorProperties.PULSE_WIDTH, PulseGeneratorProperties.PULSE_WIDTH + 5);
				
				value = !value;
				start = start + PulseGeneratorProperties.PULSE_WIDTH;
			}
			
			if(!value)
				g.drawLine(start, 5, start, PulseGeneratorProperties.PULSE_WIDTH + 5);
		}
		
		public void setCycles(int cycle)
		{
			this.cycleNumber = cycle;
			this.setSize(PulseGeneratorProperties.PULSE_WIDTH * cycle + 20 , PulseGeneratorProperties.PULSE_WIDTH + 11);
		}
		
		public Dimension getPreferredSize()
		{
			return this.getSize();
		}
		
		public void mouseClicked(MouseEvent e)
		{
		}
		
		public void mouseEntered(MouseEvent e)
		{
		}
		
		public void mouseExited(MouseEvent e)
		{
		}
		
		public void mousePressed(MouseEvent e)
		{
		}
		
		public void mouseReleased(MouseEvent e)
		{
		}
	}
	
/* ==================================================================
	Name Display
	================================================================= */
	
	private class NameDisplay extends Component implements MouseListener
	{
		private int size;
		
		public NameDisplay(int s)
		{
			super();
			
			this.size = s;
			
			this.setFont(new Font(Wrapper.FONT_NAME, Font.PLAIN, 8));
			FontMetrics fm = MainWindow.MAIN_WINDOW.getFontMetrics(this.getFont());
			this.setSize(fm.stringWidth(Integer.toString(s - 1)) + 10, (PulseGeneratorProperties.PULSE_WIDTH + 11) * s);
			this.addMouseListener(this);
		}
		
		public void paint(Graphics g)
		{
			g.setColor(Color.lightGray);
			g.fillRect(0, 0, this.getSize().width, this.getSize().height);
			
			g.setColor(Color.black);
			
			FontMetrics fm = this.getFontMetrics(g.getFont());
			
			int start = 5 + fm.getHeight() + (PulseGeneratorProperties.PULSE_WIDTH + 1 - fm.getHeight()) / 2;
			String toDraw;
			
			
			for(int index = 0; index < this.size; index++)
			{
				toDraw = Integer.toString(index);
				g.drawString(toDraw, (this.getSize().width - fm.stringWidth(toDraw)) / 2, start);
				start = start + 11 + PulseGeneratorProperties.PULSE_WIDTH;
			}
		}
		
		public void setInputSize(int s)
		{
			this.size = s;
			FontMetrics fm = this.getFontMetrics(this.getFont());
			this.setSize(fm.stringWidth(Integer.toString(s - 1)) + 10, (PulseGeneratorProperties.PULSE_WIDTH + 11) * s);
		}
		
		public Dimension getPreferredSize()
		{
			return this.getSize();
		}
		
		public void mouseClicked(MouseEvent e)
		{
		}
		
		public void mouseEntered(MouseEvent e)
		{
		}
		
		public void mouseExited(MouseEvent e)
		{
		}
		
		public void mousePressed(MouseEvent e)
		{
		}
		
		public void mouseReleased(MouseEvent e)
		{
		}
	}
	
/* ==================================================================
	Pulse display
	================================================================= */
	private class PatternDisplay extends Component implements MouseListener
	{
		private boolean[] pattern;
		private int cycleNumber;
		
		public PatternDisplay(int cycles, boolean[] pulse)
		{
			super();
			
			this.cycleNumber = cycles;
			this.pattern = new boolean[cycles];
			
			for(int index = 0; index < cycles; index++)
				this.pattern[index] = pulse[index];
			
			this.setSize(PulseGeneratorProperties.PULSE_WIDTH * cycles + 20, PulseGeneratorProperties.PULSE_WIDTH + 1);
			this.addMouseListener(this);
		}
		
		public void paint(Graphics g)
		{
			g.setColor(Color.black);
			
			int start = 10;
			
			if(this.pattern[0])
			{
				g.drawLine(start, 0, start, PulseGeneratorProperties.PULSE_WIDTH);
				g.drawLine(start, 0, start + PulseGeneratorProperties.PULSE_WIDTH, 0);
			}
			else
				g.drawLine(start, PulseGeneratorProperties.PULSE_WIDTH, start + PulseGeneratorProperties.PULSE_WIDTH, PulseGeneratorProperties.PULSE_WIDTH);
			
			start = start + PulseGeneratorProperties.PULSE_WIDTH;
			
			for(int index = 1; index < this.cycleNumber; index++)
			{
				if(this.pattern[index] ^ this.pattern[index - 1])
					g.drawLine(start, 0, start, PulseGeneratorProperties.PULSE_WIDTH);
				
				if(this.pattern[index])
					g.drawLine(start, 0, start + PulseGeneratorProperties.PULSE_WIDTH, 0);
				else
					g.drawLine(start, PulseGeneratorProperties.PULSE_WIDTH, start + PulseGeneratorProperties.PULSE_WIDTH, PulseGeneratorProperties.PULSE_WIDTH);
				
				start = start + PulseGeneratorProperties.PULSE_WIDTH;
			}
			
			if(this.pattern[this.cycleNumber - 1])
				g.drawLine(start, 0, start, PulseGeneratorProperties.PULSE_WIDTH);
		}
		
		public boolean[] getPattern()
		{
			return this.pattern;
		}
		
		public void setPeriod(int period)
		{
			if(period != this.cycleNumber)
			{
				boolean[] old = this.pattern;
				
				this.pattern = new boolean[period];
				
				int index;
				
				for(index = 0; index < period; index++)
					this.pattern[index] = false;
				
				for(index = 0; (index < period) && (index < old.length); index++)
					this.pattern[index] = old[index];
			}
			
			this.cycleNumber = period;
			this.setSize(PulseGeneratorProperties.PULSE_WIDTH * period + 20, PulseGeneratorProperties.PULSE_WIDTH + 1);
		}
		
		public Dimension getPreferredSize()
		{
			return this.getSize();
		}
		
		public void mouseClicked(MouseEvent e)
		{
		}
		
		public void mouseEntered(MouseEvent e)
		{
		}
		
		public void mouseExited(MouseEvent e)
		{
		}
		
		public void mousePressed(MouseEvent e)
		{
			int where = (e.getPoint().x - 10) / PulseGeneratorProperties.PULSE_WIDTH;
			
			if(where < this.cycleNumber)
			{
				this.pattern[where] = !this.pattern[where];
				this.repaint();
			}
		}
		
		public void mouseReleased(MouseEvent e)
		{
		}
	}
}