// Global variable holding the ID of the running interval
// so it can be started and stopped.
var intervalID;

// Object that holds parameters and state of the simulation.
var params;

// Class holding the parameters  and state of the simulation.
function GoLParams(h, w, d, b, s, dl) {
	this.height = h;	// # of cells high
	this.width = w;		// # of cells wide
	this.density = d;	// % of cells initiall alive
	this.birth = b;		// Array of states for birthing a cell
	this.survive = s;	// Array of states for a cell surviving
	this.delay = dl * 1000;	// Time between iteratons coverted from ceconds to milliseconds
	this.world = [];	// 2D array of booleans holding the current state of each cell
	for (var x=0;  x<this.width; ++x) {
		this.world[x] = [];
		for (var y=0; y<this.height; ++y) {
			var r = Math.floor(Math.random()*100);
			this.world[x][y] = (r<=this.density);
		}
	}

	this.cell_colour  = "rgb(110,  95,  62)"; // Colour of alive cells
	this.cell_outline = "rgb(255, 255, 255)"; // Colour of border around live cells
	this.cell_size = 10;					// Size of cells in pixels.
	
	this.iter_count = 0;
}

// Starts the simulation running. This function gets the parameters from
// the control form, creates the parameter object, sets up the original world,
// then starts an interval that calls iterate to run the simulation.
function startSim(width, height, density, birth, survive, delay) {

	// Check to see if Canvas is available. If not, just return.
	var canvas = document.getElementById('canvas');
	if (canvas==null || !(canvas.getContext)) {
		return;
	}

	// Create the params object to hold the current state. It will initialize the world too, 
	// creating the alive cells for the first iteration.
	params = new GoLParams(width, height, density, birth, survive, delay);
	
	// Get the canvas and set it's size appropriately
	var canvas = document.getElementById('canvas');
	canvas.width = params.width * params.cell_size;
	canvas.height = params.height * params.cell_size;
	
	// Draw the initial state of the world, colouring alive cells.
	drawWorld(params);
	
	// Call iterate to start the simulation
	intervalID = setInterval(iterate, params.delay);
	
	return;
}

// Run one iteration of the CA, calculating the next step based on the current one.
// The function counts the number of alive neighbours each cell has, and based on 
// the survival and birth rules in the params decided if the cell will be alive in the next 
// iteration. It then copies the new state to the param object and draws the new world.
// If all of the cells are dead it will stop the simulation.
function iterate() {
	// For each cell:
	//	count it's living neighbours, wrapping at edges.
	//	If cell is dead and count is in birth: cell is alive
	//	else if cell is alive and count is in survice, cell is alive
	// 	else cell is dead
	
	// copy the world array so we can make the next iteration 
	// without damaginge the current one.
	var newWorld = [];
	for (var x=0; x<params.width; ++x) {
		newWorld[x] = params.world[x].slice();
	}
	
	// Examine each cell.
	for (var x=0; x<params.width; ++x) {
		for (var y=0; y<params.height; ++y) {
		
			// Is the target cell alive?
			var alive = params.world[x][y];
			// How many living neighbours does it have?
			var count = 0;
			
			// Count the living neighbours (8 cell neighbourhood), wrapping at the edges.
			for (var dx = -1; dx<2; ++dx) {
				for (var dy = -1; dy<2; ++dy) {
					if (dx != 0 || dy != 0) { // Don't count target cell
						// Raw coordinates of neighbour
						var nx = x + dx;
						var ny = y + dy;
						
						// Edge collision handling
						if (nx < 0) { nx = params.width - 1; }
						if (nx >= params.width) { nx = 0; }
						if (ny < 0) { ny = params.height - 1; }
						if (ny >= params.height) { ny = 0; }
						
						// If the neighbour is alive, increment count
						var nVal = params.world[nx][ny];
						if (nVal) { count = count + 1; }
					}
				}
			}
			
			// Set the next state based on the survival and birth rules.
			if (alive && params.survive.indexOf(count)>=0) {
				newWorld[x][y] = true;
			}
			else if ((!alive) && params.birth.indexOf(count)>=0) {
				newWorld[x][y] = true;
			}
			else {
				newWorld[x][y] = false;
			}
		}
	}
	
	// Copy the new world state into the params object
	for (var i=0; i<params.width; ++i) {
		params.world[i] = newWorld[i].slice();
	}
	
	// Redraw the world.
	drawWorld(params);
	
	// Increment the iteration counter.
	params.iter_count = params.iter_count + 1;

	return;
}

// Redraw all the cells in canvas based on the world
// matrix in the params object. 
function drawWorld() {
	var size = params.cell_size;
	var context = document.getElementById('canvas').getContext('2d');
	context.fillStyle = params.cell_colour;
	context.strokeStyle = params.cell_outline;
	
	// Scan through all the cells and if they are alive, colour them in, if not, erase them.
	for (var x=0; x<params.width; ++x) {
		for (var y=0; y<params.height; ++y) {
			if (params.world[x][y]) { // Alive
				context.fillRect(x*size, y*size, size, size);
				context.strokeRect(x*size, y*size, size, size);						
			}
			else { // Dead
				context.clearRect(x*size, y*size, size, size);
			}
		}
	}
	return;
}

			
