/**
 * @author Paul Reiners
 * @version $$Revision: 1.6 $$ submitted $$DateTime: 2009/05/22 17:14:07 $$ by : $$
 */

import org.armedbear.lisp.*;
import org.armedbear.lisp.Package;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Rectangle2D;

public class LifeGUI extends JApplet {

	private static final long serialVersionUID = 9148653980779332403L;
	private static final Color bg = Color.white;
	private static final Color fg = Color.black;
	private static final int UNIVERSE_WIDTH = 96;
	private Cons lUniverse;
	private Package defaultPackage;
	private static final int APPLET_WIDTH = 1024;
	private static final int DELAY = 64;
	private Function updateUniverseFunction;

	@Override
	public void init() {
		// Initialize drawing colors
		setBackground(bg);
		setForeground(fg);
	}

	@Override
	public void paint(Graphics g) {
		final Graphics2D g2 = (Graphics2D) g;
		final Dimension d = getSize();

		final int rectWidth = d.width / UNIVERSE_WIDTH;
		final int rectHeight = d.height / UNIVERSE_WIDTH;

		final int[][] universe = unboxUniverse(lUniverse);
		for (int row = 0; row < UNIVERSE_WIDTH; row++) {
			final int y = row * rectHeight;
			for (int col = 0; col < UNIVERSE_WIDTH; col++) {
				final int x = col * rectWidth;
				if (universe[row][col] == 0) {
					g2.setPaint(Color.white);
				} else {
					g2.setPaint(Color.black);
				}
				g2.fill(new Rectangle2D.Double(x, y, rectWidth, rectHeight));
			}
		}
	}

	public static void main(String[] s) {
		final JFrame f = new JFrame("Conway's Game of Life");
		f.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});

		final LifeGUI applet = new LifeGUI();
		applet.initialize();

		f.getContentPane().add("Center", applet);
		applet.init();
		f.pack();
		f.setSize(new Dimension(APPLET_WIDTH, APPLET_WIDTH));
		f.setVisible(true);

		applet.startActions();
	}

	private void startActions() {
		final Action updateUniverseAction = new AbstractAction() {
			private static final long serialVersionUID = -2031659119321336950L;

			public void actionPerformed(ActionEvent e) {
				try {
					lUniverse = updateUniverse(lUniverse);
				} catch (ConditionThrowable conditionThrowable) {
					conditionThrowable.printStackTrace();
				}
				repaint();
			}
		};

		new Timer(DELAY, updateUniverseAction).start();
	}

	private void initialize() {
		try {
			defaultPackage = getDefaultPackage();

			lUniverse = genRandUniverse();
			final Symbol updateUniverseSym = defaultPackage
					.findAccessibleSymbol("UPDATE-UNIVERSE");
			updateUniverseFunction = (Function) updateUniverseSym
					.getSymbolFunction();

		} catch (Throwable t) {
			System.out.println("abcl exception!:" + t);
		}
	}

	private Cons genRandUniverse() throws ConditionThrowable {
		final Symbol genRandUniverseSym = defaultPackage
				.findAccessibleSymbol("GEN-RAND-UNIVERSE");
		final Function genRandUniverseFunction = (Function) genRandUniverseSym
				.getSymbolFunction();

		return (Cons) genRandUniverseFunction.execute(Fixnum
				.getInstance(UNIVERSE_WIDTH), Fixnum
				.getInstance(UNIVERSE_WIDTH));
	}

	private Cons updateUniverse(Cons universe) throws ConditionThrowable {
		return (Cons) updateUniverseFunction.execute(universe);
	}

	private static int[][] unboxUniverse(Cons result) {
		final int[][] unboxedUniverse;
		try {
			final LispObject[] resultArray = result.copyToArray();
			final int height = resultArray.length;
			unboxedUniverse = new int[height][];
			for (int i = 0; i < height; i++) {
				final LispObject[] rowArray = resultArray[i].copyToArray();
				final int width = rowArray.length;
				unboxedUniverse[i] = new int[width];
				for (int j = 0; j < width; j++) {
					unboxedUniverse[i][j] = rowArray[j].intValue();
				}
			}
		} catch (ConditionThrowable e) {
			System.err.println("Error: " + e);

			return new int[UNIVERSE_WIDTH][UNIVERSE_WIDTH];
		}

		return unboxedUniverse;
	}

	private static org.armedbear.lisp.Package getDefaultPackage()
			throws ConditionThrowable {
		final Interpreter interpreter = Interpreter.createInstance();
		interpreter.eval("(load \"life.lisp\")");

		// the function is not in a separate package, thus the
		// correct package is CL-USER. Symbol names are
		// upper case. Package needs the prefix, because java
		// also has a class named Package.
		return Packages.findPackage("CL-USER");
	}
}

