/src/BlinkenEngine.java
import javax.microedition.io.*;
import javax.microedition.rms.*;
import java.io.*;
import java.util.Vector;

public class BlinkenEngine {
	public int width;
	public int height;
	public int[][] values;
	private int[][] acc;
	private int[][] store;
	private boolean running;

	// instruction codes
	private static final int AND = 0;
	private static final int OR = 1;
	private static final int NOT = 2;
	private static final int XOR = 3;
	private static final int ADD = 4;
	private static final int SUB = 5;
	private static final int GTI = 6;
	private static final int LTI = 7;
	private static final int EQI = 8;
	private static final int NEI = 9;
	private static final int STO = 10;
	private static final int RCL = 11;
	private static final int SWP = 12;
	private static final int ZERO = 13;
	private static final int INC = 14;
	private static final int DEC = 15;
	
	// Memory reference codes
	private static final int NORTH = 0;
	private static final int EAST = 1;
	private static final int SOUTH = 2;
	private static final int WEST = 3;
	private static final int NORTHEAST = 4;
	private static final int SOUTHEAST = 5;
	private static final int SOUTHWEST = 6;
	private static final int NORTHWEST = 7;
	private static final int SELF = 8;
	private static final int STORE = 9;

	private class BlinkenCode {
		public int i;
		public int m;

		public BlinkenCode(int instruction, int memory) {
			this.i = instruction;
			this.m = memory;
		}
	}

	private BlinkenCode[] code = null;
	
	public BlinkenEngine(int w, int h) {
		width = w;
		height = h;

		values = new int[w][h];
		acc = new int[w][h];
		store = new int[w][h];
		running = false;
	}

	public BlinkenEngine() {
		this(16,16);
	}

	private BlinkenCode parseLine(InputStream f) throws BlinkenEngineException {
		String s = "";
		String cmd = null, arg = null;
		Vector words = new Vector();
		int c, a;

		while (true) {
			try {
				c = f.read();
			} catch (IOException e) {
				c = -1;
			}
			if (c == -1 || c == '\r' || c == '\n') {
				words.addElement(s);
				break;
			} else if (c == ' ' || c == '	') {
				words.addElement(s);
				s = "";
			} else {
				s += (char)c;
			}
		}
		if (words.size() >= 1) {
			cmd = ((String)words.elementAt(0)).toLowerCase();
			if (cmd.length() == 0) {
				if (c == -1) return null;
				else return parseLine(f);
			}
		}
		if (words.size() >= 2) {
			arg = ((String)words.elementAt(1)).toLowerCase();
		}

                if (cmd.equals("and")) {
                        c = AND;
		} else if (cmd.equals("or")) {
                        c = OR;
                } else if (cmd.equals("not")) {
                        c = NOT;
                } else if (cmd.equals("xor")) {
                        c = XOR;
                } else if (cmd.equals("add")) {
                        c = ADD;
                } else if (cmd.equals("sub")) {
                        c = SUB;
                } else if (cmd.equals("gti")) {
                        c = GTI;
                } else if (cmd.equals("lti")) {
                        c = LTI;
		} else if (cmd.equals("eqi")) {
			c = EQI;
		} else if (cmd.equals("nei")) {
			c = NEI;
		} else if (cmd.equals("sto")) {
			c = STO;
		} else if (cmd.equals("rcl")) {
			c = RCL;
		} else if (cmd.equals("swp")) {
			c = SWP;
		} else if (cmd.equals("zero")) {
			c = ZERO;
		} else if (cmd.equals("inc")) {
			c = INC;
		} else if (cmd.equals("dec")) {
			c = DEC;
		} else {
			throw new BlinkenEngineException("Unknown instruction " + cmd);
		}

		switch (c) {
		case NOT:
		case STO:
		case RCL:
		case SWP:
		case ZERO:
		case INC:
		case DEC:
			a = 0;
			break;
		case LTI:
		case GTI:
		case EQI:
		case NEI:
			a = Integer.parseInt(arg);
			break;
		default:
			arg = arg.toLowerCase();
			if (arg.equals("n")) {
				a = NORTH;
			} else if (arg.equals("e")) {
				a = EAST;
			} else if (arg.equals("s")) {
				a = SOUTH;
			} else if (arg.equals("w")) {
				a = WEST;
			} else if (arg.equals("ne")) {
				a = NORTHEAST;
			} else if (arg.equals("se")) {
				a = SOUTHEAST;
			} else if (arg.equals("nw")) {
				a = NORTHWEST;
			} else if (arg.equals("sw")) {
				a = SOUTHWEST;
			} else if (arg.equals("self")) {
				a = SELF;
			} else if (arg.equals("o")) {
				a = STORE;
			} else {
				throw new BlinkenEngineException("Invalid memory reference " + arg);
			}
		}

		return new BlinkenCode(c, a);
	}

	public void compile(InputStream f) throws BlinkenEngineException {
		BlinkenCode c;
		Vector v = new Vector();

		while (true) {
			c = parseLine(f);
			if (c == null) break;
			v.addElement(c);
		}
		code = new BlinkenCode[v.size()];
		v.copyInto(code);
	}

	public void run() {
		running = true;
	}

	public void stop() {
		running = false;
	}

	public void clear() {
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				values[i][j] = store[i][j] = 0;
			}
		}
	}

	public boolean running() {
		return running;
	}

	public boolean loaded() {
		return code != null;
	}

	public void step() throws BlinkenEngineException {
		int PC;

		if (!running) return;

		// First, copy values into acc
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				acc[i][j] = values[i][j];
			}
		}

		for (PC = 0; PC < code.length; PC++) {
			for (int i = 0; i < width; i++) {
				for (int j = 0; j < height; j++) {
					int v = 0;

					switch (code[PC].i) {
					case NOT:
					case GTI:
					case LTI:
					case EQI:
					case NEI:
					case STO:
					case RCL:
					case SWP:
					case ZERO:
					case INC:
					case DEC:
						break;
					default:
						if (code[PC].m == STORE) {
							v = store[i][j];
						} else {
							int ii = i;
							int jj = j;

							switch (code[PC].m) {
							case NORTH:
								jj--;
								break;
							case EAST:
								ii++;
								break;
							case SOUTH:
								jj++;
								break;
							case WEST:
								ii--;
								break;
							case NORTHEAST:
								jj--;
								ii++;
								break;
							case SOUTHEAST:
								jj++;
								ii++;
								break;
							case SOUTHWEST:
								jj++;
								ii--;
								break;
							case NORTHWEST:
								jj--;
								ii--;
								break;
							case SELF:
							}
							if (ii < 0)
								ii += width;
							else
								ii %= width;
							if (jj < 0)
								jj += height;
							else
								jj %= height;

							v = values[ii][jj];
						}
					}

					switch (code[PC].i) {
					case AND:
						acc[i][j] = acc[i][j] & v;
						break;
					case OR:
						acc[i][j] = acc[i][j] | v;
						break;
					case NOT:
						acc[i][j] = (~acc[i][j]) & 1;
						break;
					case XOR:
						acc[i][j] = acc[i][j] ^ v;
						break;
					case ADD:
						acc[i][j] += v;
						break;
					case SUB:
						acc[i][j] -= v;
						break;
					case GTI:
						acc[i][j] = (acc[i][j] > code[PC].m ? 1 : 0);
						break;
					case LTI:
						acc[i][j] = (acc[i][j] < code[PC].m ? 1 : 0);
						break;
					case EQI:
						acc[i][j] = (acc[i][j] == code[PC].m ? 1 : 0);
						break;
					case NEI:
						acc[i][j] = (acc[i][j] != code[PC].m ? 1 : 0);
						break;
					case STO:
						store[i][j] = acc[i][j];
						break;
					case RCL:
						acc[i][j] = store[i][j];
						break;
					case SWP:
						int tmp = acc[i][j];
						acc[i][j] = store[i][j];
						store[i][j] = tmp;
						break;
					case ZERO:
						acc[i][j] = 0;
						break;
					case INC:
						acc[i][j]++;
						break;
					case DEC:
						acc[i][j]--;
						break;
					default:
						throw new BlinkenEngineException("Bad instruction code: " + code[PC].i);
					}
				}
			}
		}

		// Swap values and acc
		int[][] tmp = values;
		values = acc;
		acc = tmp;
	}

	// Save the engine state in a RecordStore
	public void saveState() {
		RecordStore rs = null;
		byte[] tmp;

		try {
			RecordStore.deleteRecordStore("state");
		} catch (RecordStoreException e) {
			System.out.println("Could not delete RecordStore. No biggie. " + e.toString());
		}
		try {
			rs = RecordStore.openRecordStore("state", true);
		} catch (RecordStoreException e) {
			System.out.println("Could not open RecordStore: " + e.toString());
			return;
		}

		try {
			ByteArrayOutputStream b = new ByteArrayOutputStream();
			DataOutputStream f = new DataOutputStream(b);
			for (int i = 0; i < width; i++) {
				for (int j = 0; j < height; j++) {
					f.writeInt(values[i][j]);
					f.writeInt(store[i][j]);
				}
			}
			tmp = b.toByteArray();
			f.close();
			b.close();
		} catch (IOException e) {
			System.out.println("IO Error storing data: " + e.toString());
			tmp = new byte[0];
		}
		try {
			rs.addRecord(tmp, 0, tmp.length);
		} catch (RecordStoreException e) {
			System.out.println("Error writing data: " + e.toString());
		}

		if (code != null) {
			tmp = new byte[code.length * 2];
			for (int i = 0; i < code.length; i++) {
				tmp[i*2]   = (byte) code[i].i;
				tmp[i*2+1] = (byte) code[i].m;
			}
		} else {
			tmp = new byte[0];
		}
		try {
			rs.addRecord(tmp, 0, tmp.length);
		} catch (RecordStoreException e) {
			System.out.println("Failure adding code to RecordStore: " + e.toString());
		}

		try {
			rs.closeRecordStore();
		} catch (RecordStoreException e) {
		}
	}

	// Load state from RecordStore
	public void loadState() {
		RecordStore rs;
		byte[] tmp;

		try {
			rs = RecordStore.openRecordStore("state", false);
		} catch (RecordStoreException e) {
			System.out.println("Could not open RecordStore: " + e.toString());
			return;
		}

		try {
			tmp = rs.getRecord(1);
			ByteArrayInputStream b = new ByteArrayInputStream(tmp);
			DataInputStream f = new DataInputStream(b);

			for (int i = 0; i < width; i++) {
				for (int j = 0; j < height; j++) {
					values[i][j] = f.readInt();
					store[i][j] = f.readInt();
				}
			}
			f.close();
			b.close();
		} catch (RecordStoreException e) {
			System.out.println("Error retrieving data record: " + e.toString());
		} catch (IOException e) {
			System.out.println("Error reading data: " + e.toString());
		}

		try {
			tmp = rs.getRecord(2);
			if (tmp != null) {
				code = new BlinkenCode[tmp.length / 2];
				for (int i = 0; i < code.length; i++) {
					code[i] = new BlinkenCode((int)tmp[i*2], (int)tmp[i*2+1]);
				}
			}
		} catch (RecordStoreException e) {
			System.out.println("Could not read Code: " + e.toString());
		}

		try {
			rs.closeRecordStore();
		} catch (RecordStoreException e) {
			System.out.println("Failure closing RecordStore: " + e.toString());
		}
	}
}