//---------------------------------------------------------------------
//	Tessellation.java
//	Copyright 2000, Jeremy Biddle, biddle@cs.bu.edu
//
//	Original idea from a Clifford Pickover article on
//	Tessellation Automata.
//
//---------------------------------------------------------------------

import java.applet.*;
import java.awt.*;
import java.awt.event.*;

import java.awt.image.IndexColorModel;
import java.awt.image.MemoryImageSource;

public class Tessellation
	extends Applet
	implements Runnable, MouseListener, MouseMotionListener
{
	//------------------------------------------------------------------
	int		width;
	int		height;

	byte	cur_frame[];
	byte	prev_frame[];
	byte	white = (byte) 127;
	byte	black = (byte) 0;
	int		iteration;

	long	lastDrag;
	boolean	fading;

	Thread	runner = null;

	IndexColorModel colModel;
	MemoryImageSource source;
	Image screen;

	//------------------------------------------------------------------
	public void init ()
	{
		addMouseListener (this);
		addMouseMotionListener (this);

		lastDrag = System.currentTimeMillis ();
		fading = false;

		// get the dimensions of the applet
		Dimension d = getSize ();
		width = d.width;
		height = d.height;

		// set the color palette (gray scale)
		byte red[] = new byte[256];
		byte green[] = new byte[256];
		byte blue[] = new byte[256];
		for (int i = 0; i < 127; i++)
			red[i] = green[i] = blue[i] = (byte) (i * 2);
		for (int i = 127; i < 256; i++)
			red[i] = green[i] = blue[i] = (byte) 255;
			
		colModel = new IndexColorModel (8, 256, red, green, blue);

		// create the animation context
		cur_frame = new byte[width * height];
		prev_frame = new byte[width * height];
		source = new MemoryImageSource (width, height, colModel, cur_frame, 0, width);
		source.setAnimated (true);
		screen = createImage (source);

		// clear it out
		resetCanvas ();

	}

	public void destroy ()
	{
		removeMouseMotionListener (this);
		removeMouseListener (this);
	}

	public void start ()
	{
		runner = new Thread (this);
		runner.start ();
	}

	public void stop ()
	{
		runner.stop ();
	}

	public void paint (Graphics g)
	{
		source.newPixels (cur_frame, colModel, 0, width);
		prepareImage (screen, this);
		g.drawImage(screen, 0, 0, this);
	}

    public void update(Graphics g)
    {
        paint(g);
    }

	//------------------------------------------------------------------
	public void run ()
	{
		int max = (width > height) ? width : height;
		int totalChanged = 1;

		long lastFrame = 0;
		double	fps = 15.0;
		long frameTime = (long) (1000.0/fps);

		while (true)
		{
			if ((iteration >= max) || (totalChanged == 0) || fading)
			{
				if (!fadeDown())
					fading = true;
				else
				{
					fading = false;
					resetCanvas ();
					totalChanged = 1;
				}
			}
			else
				totalChanged = doNextFrame ();

			// frame limiter
			long curFrame = System.currentTimeMillis();
			long frameDelta = curFrame - lastFrame;
			if (frameDelta < frameTime)
			{
				try {
					Thread.sleep (frameTime - frameDelta);
				} catch (InterruptedException e)
				{
				}
			}
			lastFrame = curFrame;

			repaint ();
		}
	}

	//------------------------------------------------------------------
	public void mousePressed(MouseEvent e)
	{
		if (fading == false)
		{
			int i = e.getY() * width + e.getX();
			cur_frame[i] = white;
			prev_frame[i] = white;
		}
	}

	public void mouseClicked(MouseEvent e) {}
	public void mouseReleased(MouseEvent e) {}
	public void mouseEntered(MouseEvent e) {}
	public void mouseExited(MouseEvent e) {}

	public void mouseDragged (MouseEvent e)
	{
		long dragDelay = 200;
		long curDrag = System.currentTimeMillis ();
		if ((fading == false) && (lastDrag + dragDelay < curDrag))
		{
			int i = e.getY() * width + e.getX();
			cur_frame[i] = white;
			prev_frame[i] = white;
			lastDrag = curDrag;
		}
	}
	public void mouseMoved (MouseEvent e) {}

	//------------------------------------------------------------------
	public int doNextFrame ()
	{
		// fore-to-back
		byte swap[] = cur_frame;
		cur_frame = prev_frame;
		prev_frame = swap;
		
		int totalChanged = 0;
		boolean odd = ((iteration & 1) == 1) ? true : false;

		// calculate new frame
		for (int j = 1; j < (height-1); j++)
			for (int i = 1; i < (width-1); i++)
			{
				int index = j*height + i;

				int count = (prev_frame[index - 1]		!= black)?1:0; // left
				count += (prev_frame[index - height]	!= black)?1:0; // top
				count += (prev_frame[index + 1]			!= black)?1:0; // right
				count += (prev_frame[index + height]	!= black)?1:0; // bottom

				if (odd)
				{
					count += (prev_frame[index - height - 1] != black)?1:0; // u-l
					count += (prev_frame[index - height + 1] != black)?1:0; // u-r
					count += (prev_frame[index + height + 1] != black)?1:0; // l-r
					count += (prev_frame[index + height - 1] != black)?1:0; // l-l
				}
				
				if (count == 1)
					cur_frame[index] = white;
				else
					cur_frame[index] = prev_frame[index];

				if (cur_frame[index] != prev_frame[index])
					totalChanged++;
			}

		iteration++;

		return totalChanged;
	}

	public boolean fadeDown ()
	{
		int total = 0;

		int col;

		for (int i = 0; i < (width * height); i++)
		{
			col = cur_frame[i];
			if (col != black)
			{
				cur_frame[i] = (byte) (col - 1);
				total++;
			}
		}

		// true  --> completely faded
		// false --> still fading
		return (total == 0) ? true : false;
	}

	public void resetCanvas ()
	{
		iteration = 0;

		// clear out
		for (int i = 0; i < width * height; i++)
			cur_frame[i] = prev_frame[i] = black;

		// random plop
		int plop = (int) (Math.random() * width * height);
		cur_frame[plop] = prev_frame[plop] = white;
	}


	//------------------------------------------------------------------
	void err (String s) {System.err.println (s);}
	void er (String s) {System.err.print (s);}


}

