//	Genetic art applet
//
//	Author	Matthew Caryl
//	Created 16.10.96

package AutomaticArt;

import java.awt.*;
import java.awt.image.*;
import java.applet.*;
import ADT.*;
import java.io.*;

public final class Artist extends Applet implements Runnable {
	private static final Color backgroundColor = Color.black;
	
	private static final ColorVector	r = new ColorVector(1f, 0f, 0f);
	private static final ColorVector	g = new ColorVector(0f, 1f, 0f);
	private static final ColorVector	b = new ColorVector(0f, 0f, 1f);

	private Thread		animation;
	private Graphics	graphics;
	private Random		random = new Random();
	private Image[]		images;
	private int			widthInPictures, heightInPictures;
	
	public void init() {
		setBackground(backgroundColor);
		
		// number of images to fit in width
		String widthString = this.getParameter("widthInPictures");
		try {
			widthInPictures = Integer.parseInt(widthString);
			if (widthInPictures < 1)
				throw new NumberFormatException("Picture width must be postive");
			}
		catch (NumberFormatException e) {
			widthInPictures = 1;
			}
		
		// number of images to fit in height
		String heightString = this.getParameter("heightInPictures");
		try {
			heightInPictures = Integer.parseInt(heightString);
			if (heightInPictures < 1)
				throw new NumberFormatException("Picture height must be postive");
			}
		catch (NumberFormatException e) {
			heightInPictures = 1;
			}

		// Save graphics context for later
		// Note: this seems to reduce garbage collection problems
		graphics = this.getGraphics();
		}
	
	public void destroy() {
		stop();
		}
	
	public void start() {
		if (animation == null) {
			animation = new Thread(this);
			animation.start();
			}
		}
	
	public void stop() {
		if (animation != null && animation.isAlive())
			animation.stop();
		animation = null;
		}
	
	public void run() {
		images = new Image[widthInPictures * heightInPictures];
		paint(graphics);
		ColorTree t;
		switch (random.range(3)) {
			case 0:
				t = r;
				break;
			case 1:
				t = b;
				break;
			default:
				t = g;
				break;
			}
		int last = -1;
		for (int y = 0; y < heightInPictures; y++) 
			for (int x = 0; x < widthInPictures; x++) {
				images[xy2i(x, y)] = newImage(t, width(), height());
				paintImage(graphics, x, y);
				int current = random.range(3);
				while (current == last)
					current = random.range(3);
				switch (current) {
					case 0:
						t = new ColorNode(t, ColorNode.YROTATE);
						break;
					case 1:
						t = new ColorNode(t, ColorNode.XROTATE);
						break;
					default:
						t = new ColorNode(t, ColorNode.ZROTATE);
						break;
					}
				last = current;
				}
		}
		
	public void paint(Graphics g) {
		g.setColor(backgroundColor);
		g.fillRect(0, 0, width(), height());
		for (int x = widthInPictures - 1; x >= 0; x--)
			for (int y = heightInPictures - 1; y >= 0; y--)
				paintImage(g, x, y);
		}
	
	public boolean mouseDown(Event e, int x, int y) {
		stop();
		start();
		return true;
		}
	
	private void paintImage(Graphics g, int x, int y) {
		int i = xy2i(x, y);
		int w = width() / widthInPictures;
		int h = height() / heightInPictures;
		if (images[i] != null)
			synchronized (images[i]) {
				g.drawImage(images[i], x * w, y * h, w, h, this);
				}
		}
	
	private int xy2i(int x, int y) {
		return y * widthInPictures + x;
		}
	
	int width() {
		return size().width;
		}
	
	int height() {
		return size().height;
		}
	
	private Image newImage(ColorTree n, int w, int h) {
		int[]	pixels;
		int		index = 0;
		ColorVector vector = new ColorVector();
		
		pixels = new int[w * h];
		for (int y = h - 1; y >= 0; y--) {
			float	real_y = (float) y / (float) h;
			
			for (int x = w - 1; x >= 0; x--) {
				float	real_x = (float) x / (float) w;
				
				n.vector(vector, real_x, real_y);
				pixels[index++] = vector.color();
				}
			}
		
		return createImage(new MemoryImageSource(w, h, ColorModel.getRGBdefault(), pixels, 0, w));
		}
	}

abstract class ColorTree {
	abstract public void vector(ColorVector v, float x, float y);
	}

class ColorVector extends ColorTree {
	Vector vector;
	
	public ColorVector() {
		vector = new Vector();
		}


	public ColorVector(float x, float y, float z) {
		vector = new Vector(x, y, z);
		}
	
	public ColorVector(ColorVector v) {
		vector = new Vector(vector);
		}
	
	public void vector(ColorVector v, float x, float y) {
		v.vector.set(vector);
		}
	
	public int color() {
		int r = (int) (127f * vector.x) + 127; // into range [0, 254]
		int g = (int) (127f * vector.y) + 127;
		int b = (int) (127f * vector.z) + 127;
		
		return (255 << 24) | (r << 16) | (g << 8) | (b << 0);
		}
}

class ColorNode extends ColorTree {
	public static final int XROTATE = 1;
	public static final int YROTATE = 2;
	public static final int ZROTATE = 3;
	
	Matrix m;
	ColorVector i;
	ColorTree t;
	int operand;
	
	public ColorNode(ColorTree T, int O) {
		m = new Matrix();
		i = new ColorVector();
		t = T;
		operand = O;
		switch (operand) {
			case XROTATE:
				m.setRotateX(0f);
				break;
			case YROTATE:
				m.setRotateY(0f);
				break;
			case ZROTATE:
				m.setRotateZ(0f);
				break;
			}
		}
	
	public void vector(ColorVector v, float x, float y) {
		switch (operand) {
			case XROTATE:
				m.resetRotateX(2f * x * (float) (Math.PI));
				break;
			case YROTATE:
				m.resetRotateY(2f * x * (float) (Math.PI));
				break;
			case ZROTATE:
				m.resetRotateZ(2f * y * (float) (Math.PI));
				break;
			}
		t.vector(i, x, y);
		m.transform(i.vector, v.vector);
		}
	}
