function Board() {
	this.allCells = [];
	this.constrainedCells = [];
	this.unconstrainedCells = [];
	this.highlighted = null;
	this.startTime = null;
	this.gameListeners = [];
	this.solution = [];
	this.solved = false;
	this.givenHints = false;
	this.init();
}

Board.prototype.addGameListener = function(listener) {
	this.gameListeners.push(listener);
}

Board.prototype.notifyListeners = function(event, args) {
	var board = this;
	setTimeout(function() {
			for (var i = 0; i < board.gameListeners.length; ++i) {
				board.gameListeners[i][event].apply(board.gameListeners[i], args);
			}
		}, 0);
}

Board.prototype._shuffle = function(array) {
	// Fisher-Yates shuffle as popularized by Knuth.
	for (var i = array.length - 1; i > 0; --i) {
		var j = this._random(i);
		var tmp = array[i];
		array[i] = array[j];
		array[j] = tmp;
	}
};

Board.prototype._randomColoring = function(westSq, northSq) {
	var westCol;
	var northCol;
	if (westSq == null) {
		westCol = this._random(10);
	} else {
		westCol = westSq.east;
	}
	if (northSq == null) {
		northCol = this._random(10);
	} else {
		northCol = northSq.south;
	}
	return new CellColoring(northCol, this._random(10), this._random(10), westCol);
};

Board.prototype._random = function(arrayLength) {
	return Math.floor(Math.random() * arrayLength);
}

Board.prototype.init = function() {
	this.buildBoard();
	this.newPuzzle();
}

Board.prototype.buildBoard = function() {
	var squares = [];
	
	var SIDE=44;
	var GAP=7;
	var LEFT=14;
	var TOP=8;
	var BOTTOM_TOP=220;
	
	// Spaces
	var lastRow = [];
	var lastSquare = null;
	for (var row = 0; row < 4; row++) {
		var tmpRow = [];
		for (var col = 0; col < 4; col++) {
			var sq = new Cell(new P(col * (SIDE+GAP) + (SIDE / 2) + LEFT, row * (SIDE+GAP) + (SIDE / 2) + TOP), SIDE, null);
			sq.setNeighbour("west", lastSquare);
			sq.setNeighbour("north", lastRow[col]);
			lastSquare = sq;
			squares.push(sq);
			this.constrainedCells.push(sq);
			tmpRow.push(sq);
		}
		lastRow = tmpRow;
		lastSquare = null;
	};
	// Random squares
	for (var row = 0; row < 4; row++) {
		for (var col = 0; col < 4; col++) {
			var cell = new Cell(new P(col * (SIDE+GAP) + (SIDE / 2) + LEFT, row * (SIDE+GAP) + (SIDE / 2) + TOP + BOTTOM_TOP), SIDE, null);
			this.unconstrainedCells.push(cell);
			squares.push(cell);
		}
	}
	this.allCells = squares;
}

Board.prototype.newPuzzle = function() {
	this.solved = false;
	var colorings = [];
	var lastColor = null;
	var lastRowOfColors = [];
	for (var row = 0; row < 4; row++) {
		var tmpRow = [];
		for (var col = 0; col < 4; col++) {
			var coloring = this._randomColoring(lastColor, lastRowOfColors[col]);
			colorings.push(coloring);
			tmpRow.push(coloring);
			lastColor = coloring;
		}
		lastRowOfColors = tmpRow;
		lastColor = null;
	};
	this.solution = colorings.slice();
	// Now shuffle solution
	this._shuffle(colorings);

	// Blank the constrainedCells
	for (var i=0; i < this.constrainedCells.length; ++i) {
		var cell = this.constrainedCells[i];
		cell.coloring = null;
	};
	
	// Now fill in the unconstrainedCells
	for (var i=0; i < this.unconstrainedCells.length; ++i) {
		var cell = this.unconstrainedCells[i];
		var col = colorings.pop();
		cell.coloring = col;
	}
	this.givenHints = false;
	this.startTime = new Date().getTime();
}

Board.prototype.hint = function() {
	if (this.solved == true) {
		return;
	}

	this.givenHints = true;
	for (var i =0; i < this.constrainedCells.length; ++i) {
		var cell = this.constrainedCells[i];
		if (cell.coloring != null && cell.coloring != this.solution[i]) {
			this.placeInUnconstrainedCells([cell.coloring]);
			cell.coloring = null;
			if (this.hasWon() == true) {
				this.finished();
			}
			return;
		}
	}
	
	for (var i=0; i < this.constrainedCells.length; ++i) {
		var cell = this.constrainedCells[i];
		if (cell.coloring == null) {
			var answer = this.findUnconstrainedCellWithColoring(this.solution[i]);
			cell.coloring = answer.coloring;
			answer.coloring = null;
			if (this.hasWon() == true) {
				this.finished();
			}
			return;
		}
	}
}

Board.prototype.hasWon = function() {
	for (var i =0; i < this.constrainedCells.length; ++i) {
		if (this.constrainedCells[i].coloring == null) {
			return false;
		}
	}
	return true;
}

Board.prototype.placeInUnconstrainedCells = function(stillToPlace) {
	for (var i =0; i < this.unconstrainedCells.length; ++i) {
		var cell = this.unconstrainedCells[i];
		if (cell.coloring == null) {
			cell.coloring = stillToPlace.pop();
		}
	}
}

Board.prototype.findUnconstrainedCellWithColoring = function(coloring) {
	for (var i =0; i < this.unconstrainedCells.length; ++i) {
		var cell = this.unconstrainedCells[i];
		if (cell.coloring == coloring) {
			return cell;
		}
	}
	return null;
}

Board.prototype.reset = function() {
	if (this.solved == true) {
		return;
	}
	var stillToPlace = [];
	// Blank the constrainedCells
	for (var i=0; i < this.constrainedCells.length; ++i) {
		var cell = this.constrainedCells[i];
		if (cell.coloring != null) {
			stillToPlace.push(cell.coloring);
			cell.coloring = null;
		}
	};
	this.placeInUnconstrainedCells(stillToPlace);
}

Board.prototype.findSquareContaining = function(p) {
	for (var i = 0; i < this.allCells.length; ++i) {
		if (this.allCells[i].contains(p)) {
			return this.allCells[i];
		}
	}
	return null;
}

Board.prototype.render = function(g) {
	for (var i =0; i < this.allCells.length; ++i) {
		var cell = this.allCells[i];
		cell.render(g);
	}
}

Board.prototype.finished = function() {
	this.solved = true;
	var elapsedTime = new Date().getTime() - this.startTime;
	this.notifyListeners("solved", [elapsedTime, this.givenHints]);
}

Board.prototype.selectCellAt = function(p) {
	if (this.solved === true) {
		return;
	}
	var oldHighlighted = null;
	if (this.highlighted != null) {
		oldHighlighted = this.highlighted;
		this.highlighted = null;
		oldHighlighted.render(g);
	}
	var selected = this.findSquareContaining(p);
	if (selected != null) {
		if (oldHighlighted == null) {
			this.highlighted = selected;
			selected.render(g, true);
		} else {
			this.highlighted = null;
			var worked = selected.swap(oldHighlighted);
			oldHighlighted.render(g);
			selected.render(g);
			if (worked === true && this.hasWon() == true) {
				this.finished();
			}
		}
	}
}
