/**
 * Snakes - Singleton - Main class
 */
function Snakes(boardElementId, scoreElementId, width, height) {
	this.board = new Board(boardElementId, scoreElementId, width, height);

	this.board.draw();
	this.board.resize();

	this.board.addActor(new Snake(this.board, this.board.getRandomFreePosition()));
	this.board.addActor(new Food(this.board, this.board.getRandomFreePosition()));

	document.onkeydown = snakesKeyDownHandler;
	this.interval = window.setInterval("snakesTickHandler()", 150);
}

Snakes.prototype.tick = function () {
	this.board.tick();
}

Snakes.prototype.keyDownHandler = function (event) {
	if (!event) {
		// IE is special as usual
		var event = window.event;
	}
	
	var keyCode = event['keyCode'];
	if (keyCode == 16 || keyCode == 17 || keyCode == 18) {
		return;
	}

	this.board.keyDownHandler(event);
}

Snakes.prototype.gameOver = function () {
	this.board.element.innerHTML = "<h1><br/>Game Over</h1><h2><br/>Final score:</h2><div style='font-size:4em;font-weight:bold;color:blue;'>" + this.board.score + "</div><p>Press <span style='font-weight:bold;font-size:1.5em;'>F5</span> to play again!</p>";
	window.clearInterval(this.interval);
}

function snakesKeyDownHandler(event) {
	snakes.keyDownHandler(event);
}

function snakesTickHandler() {
	snakes.tick();
}

/**
 * Board - Game board
 */
function Board(elementId, scoreElementId, width, height) {
	this.element = document.getElementById(elementId);
	if (!this.element) {
		alert("Could not find required element '" + elementId + "'");
	}
	this.scoreElement = document.getElementById(scoreElementId);
	if (!this.scoreElement) {
		alert("Could not find required element '" + scoreElementId + "'");
	}
	this.element.originalWidth = this.element.style.width;
	this.element.originalHeight = this.element.style.height;

	this.width = width;
	this.height = height;

	this.positions = new Array();

	this.actors = new Array(); // List of BoardActors

	this.score = 0;

	this.directions = new Array();
	this.directions.north = 0;
	this.directions.south = 1;
	this.directions.east = 2;
	this.directions.west = 3;
}

Board.prototype.addActor = function (actor) {
	this.actors.push(actor);
}

/**
 * Draw the board
 */
Board.prototype.draw = function () {
	var positionIdPrefix = this.element.id + "Position";
	var boardString = "<table id='" + this.element.id + "Board'>";
	for (var i = 0; i < this.height; ++i) {
		boardString += "<tr>";
		for (var n = 0; n < this.width; ++n) {
			var positionNumber = i * this.width + n;
			boardString += "<td id='" + positionIdPrefix + positionNumber + "' class='" + positionIdPrefix + "'></td>";
		}
		boardString += "</tr>";
	}
	boardString += "</table>";
	
	this.element.innerHTML = boardString;
}

/**
 * Set the actual size of the board cells.
 * This is needed by IE, as it for some weird reason makes the right column
 * wider than the rest.
 */
Board.prototype.resize = function () {
	// Restore the elements original width and height (We setting it to px hereunder)
	this.element.style.width = this.element.originalWidth;
	this.element.style.height = this.element.originalHeight;

	// Calculate the actual cell width and height
	var cellWidth = Math.floor(this.element.clientWidth / this.width);
	var cellHeight = Math.floor(this.element.clientHeight / this.height);
	

	// This makes Mozilla draw the board at the correct size
	this.element.style.width = cellWidth * this.width + "px";
	this.element.style.height = cellHeight * this.height + "px";

	// Find the style that applies to cells
	var found = false;
	for (var i = 0; i < document.styleSheets.length; ++i) {
		// Mozilla uses "cssRules", IE uses "rules"
		if (!document.styleSheets[i].cssRules && document.styleSheets[i].rules) {
			document.styleSheets[i].cssRules = document.styleSheets[i].rules;
		}
		for (var n = 0; n < document.styleSheets[i].cssRules.length; ++n) {
			if (document.styleSheets[i].cssRules[n].selectorText == "." + this.element.id + "Position") {
				document.styleSheets[i].cssRules[n].style.width = Math.floor(100 / this.width) + "%";
				document.styleSheets[i].cssRules[n].style.height = Math.floor(100 / this.height) + "%";
				found = true;
				break;
			}
		}
		if (found) {
			break;
		}
	}
}

Board.prototype.getRandomFreePosition = function () {
	// Find out how many free positions there are
	var freePositions = this.width * this.height;
	for (var i = 0; i < this.actors.length; ++i) {
		freePositions -= this.actors[i].getPositions().length
	}

	// Select a random free position
	var randomFreePosition = Math.round(Math.random() * freePositions);
	for (var i = 0; i < this.width * this.height; ++i) {
		if (this.isPositionEmpty(i)) {
			if (--randomFreePosition <= 0) {
				return i;
			}
		}
	}

	return -1;
}

Board.prototype.isPositionEmpty = function (position) {
	if (!this.positions[position] || this.positions[position].actor == null) {
		return true;
	} else {
		return false;
	}
}

Board.prototype.setPositionActor = function (position, actor) {
	this.positions[position] = new Object();
	this.positions[position].actor = actor;

	var positionElement = document.getElementById(this.element.id + "Position" + position)
	if (positionElement) {
		positionElement.style.backgroundColor = actor ? actor.color : "";
		positionElement.style.border = actor ? actor.border : ""; 
	}
}

Board.prototype.getPositionActor = function (position) {
	if (this.positions[position]) {
		return this.positions[position].actor;
	} else {
		return null;
	}
}

Board.prototype.keyDownHandler = function (event) {
	for (var i = 0; i < this.actors.length; ++i) {
		this.actors[i].keyDownHandler(event);
	}
}

Board.prototype.tick = function () {
	for (var i = 0; i < this.actors.length; ++i) {
		this.actors[i].tick();
	}

	if (this.score < 0) {
		this.score = 0;
	}
	this.scoreElement.innerHTML = this.score;
}

/**
 * direction2Position - Return the position that results in moving
 * in "direction" from "position".
 */
Board.prototype.direction2Position = function (position, direction) {
	var newPosition = position;
	switch (direction) {
	case this.directions.north:
		if (position > this.width - 1) {
			newPosition = position - this.width; 
		} else {
			// Wrap-around goes here
			newPosition = position + (this.height - 1) * this.width;
		}
		break;
			
	case this.directions.south:
		if (position < this.width * this.height - this.width) {
			newPosition = position + this.width;			
		} else {
			// Wrap-around goes here
			newPosition = position % this.width;
		}
		break;

	case this.directions.east:
		if (position % this.width != this.width - 1) {
			newPosition = position + 1;			
		} else {
			// Wrap-around goes here
			newPosition = position - (position % this.width);
		}
		break;

	case this.directions.west:
		if (position % this.width != 0) {
			newPosition = position - 1;
		} else {
			// Wrap-around goes here
			newPosition = position + this.width - position % this.width - 1;
		}
		break;
	}

	return newPosition;
}

/**
 * BoardActor - Elements that can be on the Board
 */
function BoardActor(board, type, color, border) {
	// Defaults for subclasses
	this.board = null;
	this.type = "N/A";
	this.color = "black";
	this.border = "";

	if (arguments.length > 0) {
		this.construct(board, type, color, border);
	}
}

BoardActor.prototype.construct = function (board, type, color, border) {
	this.board = board;
	this.type = type;
	this.color = color;
	this.border = border;
}

BoardActor.prototype.getPositions = function () {
	alert("Actor has not implemented 'getPositions'");
}

BoardActor.prototype.keyDownHandler = function (event) {
}

BoardActor.prototype.tick = function () {
}

/**
 * Snake - The snake that moves around
 */
Snake.prototype = new BoardActor();
Snake.prototype.constructor = Snake;
Snake.superclass = BoardActor.prototype;
function Snake(board, position) {
	this.positions = new Array();

	if (arguments.length > 0) {
		this.construct(board, position);
	}
}

Snake.prototype.construct = function (board, position) {
	Snake.superclass.construct.call(this, board, "Snake", "blue", "");	
	this.length = 3;
	this.direction = null;
	this.nextDirection = null;
	this.positions.unshift(position);
	this.board.setPositionActor(position, this);
}

Snake.prototype.getPositions = function () {
	return this.positions;
}

Snake.prototype.keyDownHandler = function (event) {
	switch (event['keyCode']) {
	case 38:
		if (this.direction != this.board.directions.south) {
			this.nextDirection = this.board.directions.north;
		}
		break;
	case 40:
		if (this.direction != this.board.directions.north) {
			this.nextDirection = this.board.directions.south;
		}
		break;
	case 39:
		if (this.direction != this.board.directions.west) {
			this.nextDirection = this.board.directions.east;
		}
		break;
	case 37:
		if (this.direction != this.board.directions.east) {
			this.nextDirection = this.board.directions.west;
		}
		break;
	}
}

Snake.prototype.tick = function () {
	this.direction = this.nextDirection;
	if (this.direction != null) {
		this.move(this.direction); 
	}
}

Snake.prototype.move = function (direction) {
	var newPosition = this.board.direction2Position(this.positions[0], direction);
	// If we cannot move, return now
	if (newPosition == this.positions[0]) {
		return;
	}	

	// Check for collision
	var collisionActor = this.board.getPositionActor(newPosition);
	if (collisionActor) {
		if (collisionActor == this) {
			// Cannot move over our selves
			snakes.gameOver();
			return;
		} else if (collisionActor.type == "Food") {
			this.length += 2;
			this.board.score += (this.board.width + this.board.height) * 2;
			collisionActor.collision(this);
		}
	}

	// Move the snake
	if (this.positions.length >= this.length) {
		this.board.setPositionActor(this.positions[this.positions.length - 1], null);
		this.positions.pop();
	}

	this.board.score -= 1;
	this.positions.unshift(newPosition);
	this.board.setPositionActor(newPosition, this);

}

/**
 * Food - These makes the snake grow
 */
Food.prototype = new BoardActor();
Food.prototype.constructor = Food;
Food.superclass = BoardActor.prototype;
function Food(board, position) {
	Food.superclass.construct.call(this, board, "Food", "red", "");
	this.positions = [position];
	this.board.setPositionActor(position, this);
}

Food.prototype.getPositions = function () {
	return this.positions;
}

Food.prototype.collision = function (collisionActor) {
	var oldPosition = this.positions[0];
	this.positions[0] = this.board.getRandomFreePosition();
	this.board.setPositionActor(oldPosition, null);
	if (this.positions[0] == -1) {
		return;
	}
	this.board.setPositionActor(this.positions[0], this);
}

