// Copyright (c) 1996 Jared Smith-Mickelson http://web.mit.edu/jaredsm/www/

import java.applet.*;
import java.awt.*;
import java.awt.image.*;

public class Wave extends Applet implements Runnable {
	Image			img[], offscreenI;
	IndexColorModel	palette[];
	MediaTracker	tracker;
	Thread			runner;
	boolean			loading;
	String			status;
	Graphics		offscreenG, realG;
	int				width, height, frame, color1, color2;
	int				frames, colors, neg, pos, speed, density;
	byte			pix[];
	double			fraction;
	Color			barColor;

	public void init() {
		System.err.println("Wave, by Jared Smith-Mickelson - " +
			"http://web.mit.edu/jaredsm/www/");
		loading = true;
		realG = getGraphics();
		width = bounds().width;
		height = bounds().height;
		// create offscreen images for animation buffering
		offscreenI = createImage(width, height);
		offscreenG = offscreenI.getGraphics();
		barColor = new Color(0xff3900);
		// parse the HTML parameters
		try { color1 = Integer.parseInt(getParameter("color1"), 16); }
		catch (NumberFormatException e) { color1 = 0x000000;
			System.out.println("Couldn't parse color1.");	}
		try { color2 = Integer.parseInt(getParameter("color2"), 16); }
		catch (NumberFormatException e) { color2 = 0xff5000;
			System.out.println("Couldn't parse color2."); }
		try { frames = Integer.parseInt(getParameter("frames")); }
		catch (NumberFormatException e) { frames = 32;
			System.out.println("Couldn't parse frames."); }
		try { colors = Integer.parseInt(getParameter("colors")); }
		catch (NumberFormatException e) { colors = 256;
			System.out.println("Couldn't parse colors."); }
		try { neg = Integer.parseInt(getParameter("neg")); }
		catch (NumberFormatException e) { neg = 3;
			System.out.println("Couldn't parse neg."); }
		try { pos = Integer.parseInt(getParameter("pos")); }
		catch (NumberFormatException e) { pos = 3;
			System.out.println("Couldn't parse pos."); }
		try { speed = Integer.parseInt(getParameter("speed")); }
		catch (NumberFormatException e) { speed = 20;
			System.out.println("Couldn't parse speed."); }
		try { density = Integer.parseInt(getParameter("density")); }
		catch (NumberFormatException e) { density = 20;
			System.out.println("Couldn't parse density."); }
	}

	public void run() {
		// initialize all the positive and negative points
		double posX[] = new double[neg];
		double posY[] = new double[neg];
		double negX[] = new double[pos];
		double negY[] = new double[pos];
		for(int i = 0; i < neg; i++) {
			posX[i] = width * Math.random();
			posY[i] = height * Math.random();
		}
		for(int i = 0; i < pos; i++) {
			negX[i] = width * Math.random();
			negY[i] = height * Math.random();
		}
		// build the original image
		pix = new byte[width * height];
		int index = 0;
		double potential;
		for(int y = 0; y < height; y++) {
			// update the status every 10 lines
			if(y % 10 == 0) {
				fraction = (double)(y) / height / 2;
				status("Building original image...");
			}
			for(int x = 0; x < width; x++) {
				potential = 0;
				// the following two functions are arbitrary.  different
				// functions result in totaly different swirls and whirls
				// of color.  get creative!  go crazy!
				for(int i = 0; i < neg; i++)
					potential+= Math.log((y - posY[i]) * (y - posY[i]) +
									 (x - posX[i]) * (x - posX[i]));
				for(int i = 0; i < pos; i++)
					potential-= Math.log((y - negY[i]) * (y - negY[i]) +
									 (x - negX[i]) * (x - negX[i]));
				// asign potentials to real color indices
				potential = ((potential * density) % colors + colors) % colors;
				pix[index++] = (byte)(potential);
			}
		}
		// build palettes based on color1 and color2
		palette = new IndexColorModel[frames];
		byte baseR[] = new byte[colors];
		byte baseG[] = new byte[colors];
		byte baseB[] = new byte[colors];
		int r1 = (color1 & 0xff0000) >> 16;
		int r2 = (color2 & 0xff0000) >> 16;
		int g1 = (color1 & 0x00ff00) >> 8;
		int g2 = (color2 & 0x00ff00) >> 8;
		int b1 = color1 & 0x0000ff;
		int b2 = color2 & 0x0000ff;
		int mid = colors / 2;
		for(int i = 0; i < mid; i++) {
			baseR[i] = (byte)(r1 + i * (r2 - r1) / mid );
			baseG[i] = (byte)(g1 + i * (g2 - g1) / mid );
			baseB[i] = (byte)(b1 + i * (b2 - b1) / mid );
		}
		for(int i = mid; i < colors; i++) {
			baseR[i] = (byte)(r2 - ((i - mid) * (r2 - r1) / (colors - mid)));
			baseG[i] = (byte)(g2 - ((i - mid) * (g2 - g1) / (colors - mid)));
			baseB[i] = (byte)(b2 - ((i - mid) * (b2 - b1) / (colors - mid)));
		}
		byte shadeR[] = new byte[colors];
		byte shadeG[] = new byte[colors];
		byte shadeB[] = new byte[colors];
		for(int mod = 0; mod < frames; mod++) {
			for(int i = 0; i < colors; i++) {
				shadeR[i] = baseR[(i + mod * colors / frames) % colors];	
				shadeG[i] = baseG[(i + mod * colors / frames) % colors];	
				shadeB[i] = baseB[(i + mod * colors / frames) % colors];	
			}
			palette[mod] = new IndexColorModel(8, colors,
													shadeR, shadeG, shadeB);
		}
		// create images
		img = new Image[frames];
		tracker = new MediaTracker(this);
		for(int i = 0; i < frames; i++) {
			img[i] = createImage(new MemoryImageSource(width, height,
												palette[i], pix, 0, width));
			tracker.addImage(img[i], i);
		}
		for(int i = 0; i < frames; i++) {
			fraction = .5 + (double)(i) / frames / 2;
			status("Creating animation images...");
			try { tracker.waitForID(i); } catch (InterruptedException e) { ; }
		}
		loading = false;
		frame = 0;
		// animate forever
		while(true) {
			offscreenG.drawImage(img[frame], 0, 0, this);
			update(realG);
			frame = (frame + 1) % frames;
			wait(speed);
		}
	}

	void wait(int time) {
		try { runner.sleep(time); } catch (InterruptedException e) { ; }
	}

	void status(String text) {
		status = text;
		paint(offscreenG);
		update(realG);
		wait(100);
	}
	
	public void start() {
		if(runner == null) {
			runner = new Thread(this);
			runner.start();
		}
	}

	public void stop() {
		if(runner != null) {
			runner.stop();
			runner = null;
		}
	}

	public void paint(Graphics g) {
		if(g == null)
			return;
		if(loading) {
			// draw a nifty status bar
			int y = height / 2;
			g.setColor(Color.black);
			g.fillRect(0, 0, width, height);
			g.setColor(Color.white);
			g.drawString(status, 10, y - 4);
			g.drawRect(10, y, width - 20, 12);
			g.setColor(barColor);
			g.fillRect(11, y + 2, (int)(fraction * (width - 22)), 9);
		} else
			g.drawImage(img[frame], 0, 0, this);
	}

	public void update(Graphics g) {
		g.drawImage(offscreenI, 0, 0, this);
	}
}
