"use strict";
var GameBoard = function() {
	var board = this;

	// All objects on board
	this.objects = [];
	this.cnt = {};
	// Objects marked for removal
	this.removed = [];

	/**
	 * Adds element to the board
	 * 
	 * @param {object} obj - element to add
	 * @param {number} zIndex - elements placement on the board, element with lower zIndex will be rendered under element with higher zIndex
	 * @returns {boolean/object} if method fails it returns false in other cases it returns added object
	 */
	this.add = function(obj, zIndex) {
		if (typeof zIndex !== "number") {
			game.log("ERROR: zIndex MUST be a number");
			return false;
		}
		obj.board = this;
		if (typeof this.objects[zIndex] === "undefined") {
			this.objects[zIndex] = [ obj ];
		} else {
			this.objects[zIndex].push(obj);
		}
		this.cnt[obj.type] = (this.cnt[obj.type] || 0) + 1;
		return obj;
	};

	/**
	 * Returns zIndex of passed object
	 * 
	 * @param {object} object to find zIndex for
	 * @returns {number/boolean} zIndex of object or false if the object cannot be found
	 */
	this.getZIndex = function(obj) {
		for ( var i = 0; i < this.objects.length; i++) {
			if (typeof this.objects[i] === "undefined")
				continue;
			var idx = this.objects[i].indexOf(obj);
			if (idx !== -1) {
				return i;
			}
		}
		return false;
	}

	/**
	 * Marks object for removal - object is not removed immediately
	 * 
	 * @param {object} object to remove
	 * @returns {boolean} true if success
	 */
	this.remove = function(obj) {
		if (!obj)
			return false;
		var forRemoval = {
			obj : obj,
			zIndex : this.getZIndex(obj)
		};
		var idx = this.removed.indexOf(forRemoval);
		if (idx === -1) {
			this.removed.push(forRemoval);
			return true;
		} else {
			return false;
		}
	};

	/**
	 * Marks all board elements for removal
	 */
	this.removeAll = function() {
		for ( var i = 0; i < this.objects.length; i++) {
			if (typeof this.objects[i] === "undefined")
				continue;
			for ( var a = 0; a < this.objects[i].length; a++) {
				this.removed.push({
					obj : this.objects[i][a],
					zIndex : this.getZIndex(this.objects[i][a])
				});
			}
		}
	};

	/**
	 * Removes from current board only objects of a given type
	 * 
	 * @param {Array} objects types for removal
	 */
	this.removeByTypes = function(types) {
		for ( var i = 0; i < this.objects.length; i++) {
			if (typeof this.objects[i] === "undefined")
				continue;
			for ( var a = 0; a < this.objects[i].length; a++) {
				for ( var k = 0; k < types.length; k++) {
					if (this.objects[i][a].type === types[k])
						this.removed.push({
							obj : this.objects[i][a],
							zIndex : this.getZIndex(this.objects[i][a])
						});
				}
			}
		}
	};

	/**
	 * Clear the list of objects marked for removal
	 */
	this.resetRemoved = function() {
		this.removed = [];
	};

	// Removed an objects marked for removal from the list
	this.finalizeRemoved = function() {
		for ( var i = 0, len = this.removed.length; i < len; i++) {
			var zIndex = this.removed[i].zIndex;
			if (zIndex) {
				var idx = this.objects[zIndex].indexOf(this.removed[i].obj);
				if (idx !== -1) {
					this.cnt[this.removed[i].obj.type]--;
					this.objects[zIndex].splice(idx, 1);
				}
			}
		}
	}

	// Call the same method on all current objects
	this.iterate = function(funcName) {
		var args = Array.prototype.slice.call(arguments, 1);
		for ( var k = 0; k < this.objects.length; k++) {
			if (typeof this.objects[k] === "undefined")
				continue;
			for ( var i = 0, len = this.objects[k].length; i < len; i++) {
				var obj = this.objects[k][i];
				if (obj[funcName] && funcName === "draw") {
					if (obj.drawUnder)
						obj['drawUnder'].apply(obj, args);
					obj[funcName].apply(obj, args);
					if (obj.drawOver)
						obj['drawOver'].apply(obj, args);
				} else if (obj[funcName]) {
					obj[funcName].apply(obj, args);
				}
			}
		}
	};

	// Find the first object for which func is true
	this.detect = function(func) {
		for ( var k = 0; k < this.objects.length; k++) {
			if (typeof this.objects[k] === "undefined")
				continue;
			for ( var i = 0, val = null, len = this.objects[k].length; i < len; i++) {
				if (func.call(this.objects[k][i]))
					return this.objects[k][i];
			}
		}
		return false;
	};

	// Call step on all objects and them delete
	// any object that have been marked for removal
	this.step = function(dt) {
		this.iterate('step', dt);
		this.finalizeRemoved();
		this.resetRemoved();
	};

	// Draw all the objects
	this.draw = function(ctx) {
		this.iterate('draw', ctx);
	};

	// Check for a collision between the
	// bounding rects of two objects
	this.overlap = function(o1, o2) {
		return !((o1.y + o1.h - 1 < o2.y) || (o1.y > o2.y + o2.h - 1) || (o1.x + o1.w - 1 < o2.x) || (o1.x > o2.x + o2.w - 1));
	};

	// Find the first object that collides with obj
	// match against an optional type
	this.collide = function(obj, type) {
		return this.detect(function() {
			if (obj != this) {
				var col = (!type || this.type & type) && board.overlap(obj, this)
				return col ? this : false;
			}
		});
	};

};