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) {
		}
	}
}
