import java.awt.*;
import java.awt.event.*;

public abstract class RenderArea extends Component {

	protected Image offscreen;
	protected int width, height;
	protected Graphics oig;
	protected Thread renderer;
	protected boolean antialias;
	protected int aatresh, aares;
	protected int[][] data;

	public RenderArea(int w, int h) {
		width = max(w, 2);
		height = max(h, 2);
		setSize(width, height);
		data = new int[width][height];
	}

	public void paint(Graphics g) {
		if (offscreen == null) initOffscreen();
		g.drawImage(offscreen, 0, 0, this);
	}
	
	protected void initOffscreen() {
		offscreen = createImage(width, height);
		oig = offscreen.getGraphics();
	}
	
	public void update(Graphics g) {
		paint(g);
	}
	
	public void start() {
		stop();
		renderer = new Thread() {
			public void run() {
				if (offscreen == null) initOffscreen();
				int pw, r0 = (int)(Math.log(min(width, height)/2)/Math.log(2)), n = 0, c;
				for (int r = r0; r >= 0; r--) {
					pw = (int)Math.pow(2, r);
					for (int y = 0; y < height; y+=pw) {
						for (int x = 0; x < width; x+=pw) {
							if (x%(2*pw) != 0 || y%(2*pw) != 0 || r == r0) {
								c = render(x, y);
								oig.setColor(new Color(c));
								oig.fillRect(x-pw/2, y-pw/2, pw, pw);
								data[x][y] = c;
								if (n++%200 == 0) paint(getGraphics());
							}
						}
					}
				}
				paint(getGraphics());
				if (antialias) {
					int f;
					int[][] mc = new int[aares][aares];
					for (int y = 0; y < height; y++) {
						for (int x = 0; x < width; x++) {
							f = 0;
							for (int dx = -1; dx <= 1; dx++) {
								for (int dy = -1; dy <= 1; dy++) {
									try { if (colordiff(data[x][y], data[x+dx][y+dy]) >= aatresh) f++; } catch (ArrayIndexOutOfBoundsException e) {}
								}
							}
							if (f > 0) {
								for (int mix = 0; mix < aares; mix++) {
									for (int miy = 0; miy < aares; miy++) {
										mc[mix][miy] = render(x - .5 + .5/aares + (double)mix/aares, y - .5 + .5/aares + (double)miy/aares);
									}
								}
								oig.setColor(new Color(coloravg(mc)));
								oig.drawLine(x, y, x, y);
							}
							if (n++%200 == 0) paint(getGraphics());
						}
					}
				}
				renderer = null;
				stopped();
			}
		};
		renderer.start();
	}
	
	public void stop() {
		if (renderer != null) {
			renderer.stop();
			renderer = null;
		}
	}
	
	public void setAntiAlias(boolean aa) {
		antialias = aa;
	}
	
	public void setAAParams(int t, int r) {
		aatresh = t;
		aares = r;
	}
	
	public abstract void stopped();
	
	protected abstract int render(double x, double y);
	
	public Dimension getPreferredSize() {
		return new Dimension(width, height);
	}
	
	public Dimension getMinimumSize() {
		return getPreferredSize();
	}
	
	protected static int min(int x, int y) {
		return (x < y) ? x : y;
	}
	
	protected static int max(int x, int y) {
		return (x < y) ? y : x;
	}
	
	protected static int colordiff(int a, int b) {
		return max(max(Math.abs((a & 0x000000FF) - (b & 0x000000FF)), Math.abs(((a & 0x0000FF00) >> 8) - ((b & 0x0000FF00) >> 8))), Math.abs(((a & 0x00FF0000) >> 16) - ((b & 0x00FF0000) >> 16)));
	}
	
	protected static int coloravg(int[][] a) {
		int r = 0, g = 0, b = 0;
		for (int i = 0; i < a.length; i++) {
			for (int j = 0; j < a[0].length; j++) {
				r += ((a[i][j] & 0x00FF0000) >> 16);
				g += ((a[i][j] & 0x0000FF00) >> 8);
				b += ((a[i][j] & 0x000000FF));
			}
		}
		r /= a.length*a[0].length;
		g /= a.length*a[0].length;
		b /= a.length*a[0].length;
		return r << 16 | g << 8 | b;
	}

}