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

// background shape (line/square/circle) or color or size
// dot shape (line/square/circle) or color or size
// rotate figure/change figure size/change figure shape
// spin speed

class Properties {
	final static int SHAPE_LINE = 0;
	final static int SHAPE_BOX = 1;
	final static int SHAPE_OVAL = 2;
	final static int SHAPE_3D_BOX = 3;

	final static int SHAPE_MAX = 3;
	
	Color backColor_;
	
	Color color_;
	int dotSize_;
	double density_;
	boolean filled_;
	int dotShape_;

	public Properties() {
		backColor_ = Color.white;
		
		color_ = Color.gray;
		dotSize_ = 10;
		density_ = 0.8;
		filled_ = true;
		dotShape_ = SHAPE_LINE;
	}

	public Properties(Properties other) {
		backColor_ = other.backColor_;
		
		color_ = other.color_;
		dotSize_ = other.dotSize_;
		density_ = other.density_;
		filled_ = other.filled_;
		dotShape_ = other.dotShape_;
	}
}

abstract class Shape {
	Properties props_;
	int num_;
	double atX_, atY_, w_, h_;
	double x_[], y_[], xDotSize_[], yDotSize_[], x2_[], y2_[];

	double holeLastX_, holeLastY_;
	Shape hole_;

	Shape(Properties p, int atX, int atY, int width, int height) {
		props_ = new Properties(p);
		w_ = width;
		h_ = height;
		atX_ = atX;
		atY_ = atY;
		hole_ = null;
	}

	void setHole(Shape hole) {
		hole_ = hole;
	}

	synchronized void setProperties(Properties p) {
		if (props_.dotSize_ != p.dotSize_ || props_.density_ != p.density_)
			randomize();
		else if (p.dotShape_ != props_.dotShape_ &&
				 p.dotShape_ == Properties.SHAPE_LINE)
			fillInEndPoints();

		props_ = new Properties(p);
	}

	void fillInEndPoints() {
		for (int i = 0; i < num_; ++i) {
			x2_[i] = x_[i] + xDotSize_[i];
			y2_[i] = y_[i] + yDotSize_[i];
		}
	}

	synchronized void draw(Graphics g) {
		drawBack(g);

		if (hole_ != null) {
			double nowAtX = hole_.atX_;
			double nowAtY = hole_.atY_;

			hole_.atX_ = holeLastX_;
			hole_.atY_ = holeLastY_;
			hole_.draw(g);
			
			hole_.atX_ = nowAtX;
			hole_.atY_ = nowAtY;
		}
		
		Illusion.setBar("Filling...", 0.0);
		g.translate((int)atX_, (int)atY_);
		g.setColor(props_.color_);
		
		if (props_.dotShape_ == Properties.SHAPE_LINE) {
			for (int i = 0; i < num_; ++i) {
				if (i % 100 == 0)
					Illusion.setBar(null, (double)i / (double)(num_ - 1));
				g.drawLine((int)x_[i], (int)y_[i],
						   (int)(x2_[i]), (int)(y2_[i]));
			}
		}
		else {
			for (int i = 0; i < num_; ++i) {
				if (i % 100 == 0)
					Illusion.setBar(null, (double)i / (double)(num_ - 1));

				int x = (int)x_[i];
				int y = (int)y_[i];
				int xSize = (int)xDotSize_[i];
				int ySize = (int)yDotSize_[i];

				if (xSize < 0) {
					x += xSize;
					xSize *= -1;
				}
				
				if (ySize < 0) {
					y += ySize;
					ySize *= -1;
				}

				if (props_.filled_) {
					switch (props_.dotShape_) {
					case Properties.SHAPE_BOX:
						g.fillRect(x, y, xSize, ySize); break;
					case Properties.SHAPE_OVAL:							
						g.fillOval(x, y, xSize, ySize); break;
					case Properties.SHAPE_3D_BOX:
						g.fill3DRect(x, y, xSize, ySize, true); break;
					}
				}
				else {
					switch (props_.dotShape_) {
					case Properties.SHAPE_BOX:
						g.drawRect(x, y, xSize, ySize); break;
					case Properties.SHAPE_OVAL:							
						g.drawOval(x, y, xSize, ySize);	break;
					case Properties.SHAPE_3D_BOX:
						g.draw3DRect(x, y, xSize, ySize, true); break;
					}
				}
			}
		}
		
		Illusion.setBar(null, 1.0);
		g.translate((int)-atX_, (int)-atY_);
		Illusion.setBar(null, 0.0);		
	}

	int countNumItems(double area, double size, double density) {
		return (int)(area * density * 6.0 / (size * size));
	}

	synchronized void rotate(double angle) {
		double sin = Math.sin(angle);
		for (int i = 0; i < num_; ++i) {
			x_[i]  = x_[i]  -  y_[i] * sin;
			y_[i]  = y_[i]  +  x_[i] * sin;
		}

		if (props_.dotShape_ == Properties.SHAPE_LINE) {
			for (int i = 0; i < num_; ++i) {			
				x2_[i] = x2_[i] - y2_[i] * sin;
				y2_[i] = y2_[i] + x2_[i] * sin;
			}
		}
	}

	synchronized void translate(double addX, double addY) {
		atX_ += addX;
		atY_ += addY;
	}

	void moveTo(int x, int y) {
		atX_ = x;
		atY_ = y;
	}

	void randomize() {
		if (hole_ != null)
			randomizeMinusShape();
		else
			randomizeNormal();

		if (props_.dotShape_ == Properties.SHAPE_LINE)
			fillInEndPoints();
	}
	
	void randomizeNormal() {
		double size = props_.dotSize_;
		num_ = countNumItems(getArea(), size, props_.density_);

		size *= 2.0;
		x_ = new double[num_];
		y_ = new double[num_];
		xDotSize_ = new double[num_];
		yDotSize_ = new double[num_];
		
		x2_ = new double[num_];
		y2_ = new double[num_];

		Illusion.setBar("Picking...", 0.0);
		for (int i = 0; i < num_; ++i) {
			if (i % 100 == 0)
				Illusion.setBar(null, (double)i / (double)(num_ - 1));
			
			double x, y;
			do {
				x = w_ * (Math.random() - 0.5);
				y = h_ * (Math.random() - 0.5);
			} while (!insideLocal(x, y));

			x_[i] = x;
			y_[i] = y;
			xDotSize_[i] = size * (Math.random() - 0.5);
			yDotSize_[i] = size * (Math.random() - 0.5);			
		}
		Illusion.setBar(null, 1.0);		
		Illusion.setBar(null, 0.0);		
	}

	void randomizeMinusShape() {
		double area = getArea() - hole_.getArea();
		double halfW = w_ / 2.0;
		double halfH = h_ / 2.0;		
		double size = props_.dotSize_;
		double density = props_.density_;
		
		num_ = countNumItems(area, size, density);

		size *= 2.0;
		x_ = new double[num_];
		y_ = new double[num_];
		xDotSize_ = new double[num_];
		yDotSize_ = new double[num_];		
		
		x2_ = new double[num_];
		y2_ = new double[num_];

		holeLastX_ = hole_.atX_;
		holeLastY_ = hole_.atY_;
		
		Illusion.setBar("Picking...", 0.0);
		for (int i = 0; i < num_; ++i) {
			if (i % 100 == 0)
				Illusion.setBar(null, (double)i / (double)(num_ - 1));
			
			double x, y;
			do {
				x = w_ * Math.random();
				y = h_ * Math.random();
			} while (hole_.insideGlobal(x, y));

			x_[i] = x - halfW;
			y_[i] = y - halfH;
			xDotSize_[i] = size * (Math.random() - 0.5);
			yDotSize_[i] = size * (Math.random() - 0.5);			
		}
		Illusion.setBar(null, 0.0);
	}

	abstract double getArea();
	abstract boolean insideGlobal(double x, double y);
	abstract boolean insideLocal(double x, double y);
	abstract void drawBack(Graphics g);
}

class Box extends Shape {
	Box(Properties p, int atX, int atY, int width, int height) {
		super(p, atX, atY, width, height);
	}
	
	double getArea() {
		return w_ * h_;
	}

	boolean insideGlobal(double x, double y) {
		double diffX = x - (atX_ - w_  / 2);
		double diffY = y - (atY_ - h_  / 2);		

		return (diffX >= 0 && diffX < w_ &&
				diffY >= 0 && diffY < h_);
	}

	boolean insideLocal(double x, double y) {
		return true;
	}

	void drawBack(Graphics g) {
		g.setColor(props_.backColor_);
		g.fillRect((int)(atX_ + -w_ / 2), (int)(atY_ + -h_ / 2),
				   (int)w_, (int)h_);
	}
}

class Circle extends Shape {
	double radius_;
	double radSq_;	
	
	Circle(Properties p, int atX, int atY, int radius) {
		super(p, atX, atY, radius * 2, radius * 2);
		radius_ = radius;
		radSq_ = radius_ * radius_;
	}
	
	double getArea() {
		return (int)(Math.PI * radSq_);
	}

	boolean insideGlobal(double x, double y) {
		x -= atX_;
		y -= atY_;

		return (x * x + y * y < radSq_);
	}

	boolean insideLocal(double x, double y) {
		return (x * x + y * y < radSq_);
	}

	void drawBack(Graphics g) {
		g.setColor(props_.backColor_);
		g.fillOval((int)(atX_ + -w_ / 2), (int)(atY_ + -h_ / 2),
				   (int)w_, (int)h_);
	}
}

public class Illusion extends Applet implements Runnable {
	final static int BAR_HEIGHT = 21;
	final static int BAR_WIDTH = 150;
	
	boolean translate_ = false, rotate_ = false;

	double addX_ = 2.0, addY_ = 2.0;
	int delay_ = 50;
	int foreSize_ = 70;
	
	int width_, height_;
	Image image_;
	Graphics offscreen_;

	boolean updateBackground_ = false;
	Properties props_;
	Image background_;
	Graphics backG_;

	Shape foreShape_;
	Shape backShape_;

	boolean startAfterUpdate_ = false;
	Thread ticker_;
	AMDProgressBarEmbed bar_;

	static boolean showBar_ = false;
	static Illusion instance_;
	
	public void init() {
		instance_ = this;
		props_ = new Properties();
		bar_ = new AMDProgressBarEmbed(this);
	}

	public void start() {
		requestFocus();
	}

	public void stop() {
		stopTicker();
	}

	public void run() {
        long time = System.currentTimeMillis();
		while (true) {
			if (rotate_)
				foreShape_.rotate(0.02);
			if (translate_) {
				double newX = foreShape_.atX_ + addX_ - foreShape_.w_ / 2;
				double newY = foreShape_.atY_ + addY_ - foreShape_.h_ / 2;
				
				if (newX < 0)
					addX_ *= -1;
				else {
					double maxX = newX + foreShape_.w_;
					if (maxX > width_)
						addX_ *= -1;
				}
				
				if (newY < 0)
					addY_ *= -1;
				else {
					double maxY = newY + foreShape_.h_;
					if (maxY > height_)
						addY_ *= -1;
				}
				foreShape_.translate(addX_, addY_);
			}
			repaint();
			try {
                time += delay_;
                long toSleep = time - System.currentTimeMillis();

                if (toSleep > 0)
                    Thread.sleep(toSleep);
                else {
                    Thread.sleep(delay_);
                    time = System.currentTimeMillis();
                }
			} catch (InterruptedException e) { ; }
		}
	}

	public static void setBar(String text, double percent) {
		if (showBar_) {
			if (text != null)
				instance_.bar_.setText(text);
			instance_.bar_.setPercent(percent);
			instance_.bar_.paint(instance_.getGraphics());
		}
	}
	
	public void update(Graphics g) {
		paint(g);
	}

	public void paint(Graphics g) {
		if (image_ == null) {
			width_ = size().width;
			height_ = size().height;
			image_ = createImage(width_, height_);
			offscreen_ = image_.getGraphics();

			bar_.reshape((width_ - BAR_WIDTH) / 2,
						 (height_ - BAR_HEIGHT) / 2,
						 BAR_WIDTH, BAR_HEIGHT);
			
			background_ = createImage(width_, height_);
			backG_ = background_.getGraphics();

			backShape_ = new Box(props_, width_ / 2, height_ / 2,
								 width_, height_);
			foreShape_ = new Circle(props_, 0, 0, foreSize_ / 2);
			
			backShape_.setHole(foreShape_);
			
			randomize();
		}

		if (updateBackground_)
			updateBackground();
		
		offscreen_.drawImage(background_, 0, 0, null);
		foreShape_.draw(offscreen_);

		g.drawImage(image_, 0, 0, null);
	}

	void updateBackground() {
		showBar_ = true;
		backG_.setColor(Color.white);
		backG_.fillRect(0, 0, width_, height_);
		
		backShape_.draw(backG_);
		updateBackground_ = false;
		showBar_ = false;

		if (startAfterUpdate_) {
			startTicker();
			startAfterUpdate_ = false;
		}
	}

	void randomize() {
		if (ticker_ != null) {
			stopTicker();			
			startAfterUpdate_ = true;
		}
		
		showBar_ = true;
		int atX = (int)(Math.random() * (double)(width_ - foreSize_ * 2)) +
			foreSize_;
		int atY = (int)(Math.random() * (double)(height_ - foreSize_ * 2)) +
			foreSize_;

		foreShape_.moveTo(atX, atY);
		foreShape_.randomize();
		backShape_.randomize();
		
		updateBackground_ = true;
		repaint();
		showBar_ = false;
	}

	public boolean mouseDown(Event e, int x, int y) {
		if (ticker_ == null) {
			if (!rotate_ && !translate_)
				translate_ = true;
			startTicker();
		}
		else {
			rotate_ = false;
			translate_ = false;
			stopTicker();
		}
		return true;
	}

	void startTicker() {
		if (ticker_ == null) {
			ticker_ = new Thread(this);
			ticker_.start();
		}
	}

	void stopTicker() {
		if (ticker_ != null) {
			ticker_.stop();
			ticker_ = null;
		}
	}
	
	void updateProps() {
		if (ticker_ != null) {
			stopTicker();
			startAfterUpdate_ = true;
		}
		
		showBar_ = true;
		foreShape_.setProperties(props_);
		backShape_.setProperties(props_);
		updateBackground_ = true;
		repaint();
		showBar_ = false;
	}
	
	public boolean keyDown(Event e, int key) {
		key = Character.toUpperCase((char)key);
		
		switch (key) {
			// re-randomize
		case ' ':
			randomize();

			break;
			
		case 'R':
			if (!rotate_ && !translate_)
				startTicker();
			rotate_ = !rotate_;
			if (!rotate_ && !translate_)
				stopTicker();
			break;
			
		case 'T':
			if (!rotate_ && !translate_)
				startTicker();
			translate_ = !translate_;			
			if (!rotate_ && !translate_)
				stopTicker();
			break;
			
		case '>':
		case '<':
			double newDense = props_.density_ + ((key == '<') ? -0.1 : 0.1);
			if (newDense > 0.9)
				newDense = 0.9;
			else if (newDense < 0.2)
				newDense = 0.2;
			props_.density_ = newDense;
			updateProps();
			
			break;

		case 'D':
			++props_.dotShape_;
			if (props_.dotShape_ > Properties.SHAPE_MAX)
				props_.dotShape_ = 0;
			updateProps();
			break;

		case 'S':
			if (foreShape_ instanceof Circle)
				foreShape_ = new Box(props_, 0, 0, foreSize_, foreSize_);
			else
				foreShape_ = new Circle(props_, 0, 0, foreSize_ / 2);
			backShape_.setHole(foreShape_);
			randomize();
			
			break;
			
		case 'C':
			props_.color_ = new Color((int)(Math.random() * 256),
									  (int)(Math.random() * 256),
									  (int)(Math.random() * 256));

			updateProps();
			break;
			
		case 'F':
			props_.filled_ = !props_.filled_;
			updateProps();
			
			break;
		}

		return true;
	}
}