import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

public class ArabeskeCanvas extends Canvas implements ImageProducer, MouseListener, MouseMotionListener {

	/**
	 * Width of the CA state array.
	 */
	private final static int width = 322;

	/**
	 * Height of the CA state array.
	 */
	private final static int height = 202;

	/**
	 * Height of the CA state array.
	 */
	private final static int maxAge = 65;

	/**
	 * Current CA state array.
	 */
	private int current[];

	/**
	 * Destination CA state array, used by step(), swapped with 'current', after
	 * filled.
	 */
	private int next[];

	/**
	 * Display with and height for one CA cell.
	 */
	private final static int zoom = 2;

	/**
	 * Image buffer.
	 */
	private final int pixels[];

	/**
	 * ColorModel for the pixels array.
	 */
	private final ColorModel cm = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);

	/**
	 * Image for displaying the CA.
	 */
	private Image image;

	/**
	 * Reference to the consumer of the image for displaying the CA.
	 */
	private ImageConsumer consumer;

	/**
	 * Creates a new automaton canvas.
	 */
	public ArabeskeCanvas() {
		// init arrays
		pixels = new int[width * height * zoom * zoom];
		init();

		// add listener
		addMouseListener(this);
		addMouseMotionListener(this);

		// init CA
		init();

		// create rendering image with this as ImageProducer
		image = createImage(this);
	}

	/**
	 * Clears the CA array and initialized it with my serialized initials.
	 */
	public void init() {
		current = new int[width * height];
		next = new int[width * height];
	}

	/**
	 * Calculate next CA step.
	 */
	public void step() {
		for (int y = 1; y < height - 1; y++) {
			for (int x = 1; x < width - 1; x++) {
				int cell = current[x + y * width];
				if (cell > 0) {
					// if the state of the cell is > 0, just count until maxAge, then reset to 0
					cell++;
					if (cell == maxAge)
						cell = 0;
				} else {
					// von Neumann neighborhood (9 neighbors)
					// if it is exactly 1, then a new cell is born
					int count = 0;
					count = current[x + 1 + y * width];
					count += current[x + 1 + y * width];
					count += current[x - 1 + y * width];
					count += current[x + (y + 1) * width];
					count += current[x + (y - 1) * width];
					count += current[x + 1 + (y + 1) * width];
					count += current[x - 1 + (y + 1) * width];
					count += current[x + 1 + (y - 1) * width];
					count += current[x - 1 + (y - 1) * width];
					if (count == 1) {
						cell = 1;
					}
				}
				next[x + y * width] = cell;
			}
		}

		// swap buffers
		int tmp[] = next;
		next = current;
		current = tmp;
	}

	/**
	 * Show current CA state array.
	 * @see java.awt.Component#update(Graphics)
	 */
	synchronized public void update(Graphics g) {
		// copy to pixels
		int adr = width + 1;
		for (int y = 1; y < height - 1; y++) {
			int adr2 = zoom * zoom * width * y + zoom;
			for (int x = 1; x < width - 1; x++) {
				int t = current[adr++];
				int c;
				if (t < 30) {
					c = 8 * t;
				} else if (t < 50) {
					c = (t - 30 * 10) << 8;
				} else {
					c = t - 50;
					c = t | (t << 8) | (t << 16);
				}
				int i = adr2;
				for (int cy = 0; cy < zoom; cy++) {
					for (int cx = 0; cx < zoom; cx++) {
						pixels[i++] = c;
					}
					i += (width - 1) * zoom;
				}
				adr2 += zoom;
			}
			adr += 2;
		}

		// draw
		if (consumer != null)
			startProduction(consumer);
		g.drawImage(image, 0, 0, null);
	}

	/**
	 * Set states.
	 * @param x x coordinate in the state array.
	 * @param y y coordinate in the state array.
	 * @param pixel true, if state should be set, false otherwise.
	 */
	private void mouseCellAction(int x, int y, boolean pixel) {
		x /= zoom;
		y /= zoom;
		if (x >= 0 && x < width && y >= 0 && y < height) {
			current[x + y * width] = pixel ? 0 : 1;
		}
		repaint();
	}

	// Java specific code (listeners etc.)

	/**
	 * @see java.awt.Component#getPreferredSize()
	 */
	public Dimension getPreferredSize() {
		return new Dimension(width * zoom, height * zoom);
	}

	/**
	 * @see java.awt.Component#getMinimumSize()
	 */
	public Dimension getMinimumSize() {
		return getPreferredSize();
	}

	/**
	 * @see java.awt.Component#getMaximumSize()
	 */
	public Dimension getMaximumSize() {
		return getPreferredSize();
	}

	/**
	 * @see java.awt.image.ImageProducer#addConsumer(java.awt.image.ImageConsumer)
	 */
	public void addConsumer(ImageConsumer c) {
	}

	/**
	 * @see java.awt.image.ImageProducer#isConsumer(java.awt.image.ImageConsumer)
	 */
	public boolean isConsumer(ImageConsumer consumer) {
		return consumer != null;
	}

	/**
	 * @see java.awt.image.ImageProducer#removeConsumer(java.awt.image.ImageConsumer)
	 */
	public void removeConsumer(ImageConsumer consumer) {
	}

	/**
	 * @see java.awt.image.ImageProducer#startProduction(java.awt.image.ImageConsumer)
	 */
	public void startProduction(ImageConsumer c) {
		if (c != null)
			consumer = c;
		if (consumer != null) {
			consumer.setDimensions(width * zoom, height * zoom);
			consumer.setProperties(null);
			consumer.setColorModel(cm);
			consumer.setHints(ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES | ImageConsumer.SINGLEPASS
					| ImageConsumer.SINGLEFRAME);
			consumer.setPixels(0, 0, width * zoom, height * zoom, cm, pixels, 0, width * zoom);
			consumer.imageComplete(ImageConsumer.SINGLEFRAMEDONE);
		}
	}

	/**
	 * @see java.awt.image.ImageProducer#requestTopDownLeftRightResend(java.awt.image.ImageConsumer)
	 */
	public void requestTopDownLeftRightResend(ImageConsumer consumer) {
	}

	/**
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	public void mouseClicked(MouseEvent e) {
	}

	/**
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	public void mousePressed(MouseEvent e) {
		mouseCellAction(e.getX(), e.getY(), (e.getModifiers() & MouseEvent.META_MASK) > 0);
	}

	/**
	 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	public void mouseReleased(MouseEvent e) {
	}

	/**
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	public void mouseEntered(MouseEvent e) {
	}

	/**
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	public void mouseExited(MouseEvent e) {
	}

	/**
	 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
	 */
	public void mouseDragged(MouseEvent e) {
		mouseCellAction(e.getX(), e.getY(), (e.getModifiers() & MouseEvent.META_MASK) > 0);
	}

	/**
	 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	public void mouseMoved(MouseEvent e) {
	}
}
