var keymap = {
	32: "jump",
	touch: "jump"
};

/*
 * Collision box
 */

function Box(width, height, pos) {
	pos = pos || {};
	this.width = width;
	this.height = height;
	this.pos = new Vec2(pos.x, pos.y);
}
Box.prototype.collidesWith = function(box, ent1pos, ent2pos) {
	var x1 = ent1pos.x + this.pos.x;
	var x2 = ent2pos.x + box.pos.x;
	var y1 = ent1pos.y + this.pos.y;
	var y2 = ent2pos.y + box.pos.y;
	var w1 = this.width;
	var w2 = box.width;
	var h1 = this.height;
	var h2 = box.height;

	return ((x1 + w1 >= x2 && x1 <= x2 + w2) &&
			(y1 + h1 >= y2 && y1 <= y2 + h2));
}
Box.prototype.trace = function(ctx) {
	ctx.beginPath();
	ctx.moveTo(this.pos.x, this.pos.y);
	ctx.lineTo(this.pos.x, this.pos.y + this.height);
	ctx.lineTo(this.pos.x + this.width, this.pos.y + this.height);
	ctx.lineTo(this.pos.x + this.width, this.pos.y);
	ctx.closePath();
}

/*
 * Collision shape
 */

function Shape(ent) {
	this.ent = ent;
	this.boxes = [];
	this._height = -1;
	this._width = -1;
}
Shape.prototype.push = function(box) {
	this._height = -1;
	this._width = -1;
	this.boxes.push(box);
}
Shape.prototype.pop = function() {
	this._height = -1;
	this._width = -1;
	return this.boxes.pop();
}
Shape.prototype.draw = function(ctx) {
	for (var i = 0; i < this.boxes.length; ++i) {
		var box = this.boxes[i];
		box.trace(ctx);
		ctx.stroke();
		ctx.fill();
	}
}
Shape.prototype.collidesWith = function(shape) {
	for (var i = 0; i < this.boxes.length; ++i) {
		var box = this.boxes[i];
		for (var j = 0; j < shape.boxes.length; ++j) {
			var otherBox = shape.boxes[j];
			if (box.collidesWith(otherBox, this.ent.pos, shape.ent.pos))
				return true;
		}
	}

	return false;
}
Shape.prototype.width = function() {
	if (this._width !== -1)
		return this._width;

	var minX = 0;
	var maxX = 0;

	this.boxes.forEach(function(box) {
		if (box.pos.x < minX)
			minX = box.pos.x;
		if (box.pos.x + box.width > maxX)
			maxX = box.pos.x + box.width;
	});

	this._width = Math.abs(maxX - minX);
	return this._width;
}
Shape.prototype.height = function() {
	if (this._height !== -1)
		return this._height;

	var minY = 0;
	var maxY = 0;

	this.boxes.forEach(function(box) {
		if (box.pos.y < minY)
			minY = box.pos.y;
		if (box.pos.y + box.height > maxY)
			maxY = box.pos.y + box.height;
	});

	this._height = Math.abs(maxY - minY);
	return this._height;
}

// Make entity from object
function makeEnt(obj, game, mass) {
	obj.pos = new Vec2();
	obj.vel = new Vec2();
	obj.force = new Vec2();
	obj.game = game;
	obj.shape = new Shape(obj);
	obj.moves = false;
	obj.dead = false;
	obj.inputListener = false;
	obj.collude = true;
	obj.layer = 0;

	obj.mass = mass || 0;
	obj.forceScalar = 1 / obj.mass;
}

/*
 * Game
 */

function Game(canvas) {
	this.canvas = canvas;
	this.ctx = canvas.getContext("2d");
	this.raf = null;
	this.prevTime = null;
	this.stopped = true;
	this.camera = new Vec2();
	this.worldgen = null;

	this.entities = [];
	this.layers = [];
	this.inputListeners = [];

	this.keys = {};
	this.onkey = function onkey(evt) {
		var down = (evt.type === "keydown" || evt.type === "touchstart");
		var code = evt.keyCode || "touch";

		var name = keymap[code];
		if (name) {
			this.keys[name] = down;

			for (var i = 0; i < this.inputListeners.length; ++i) {
				var ent = this.inputListeners[i];
				if (ent === null) {
					continue;
				} else if (ent.dead) {
					delete this.inputListeners[i];
					continue;
				}

				this.inputListeners[i].onInput(name, down);
			}
		}
	}.bind(this);
}

Game.prototype.start = function(worldgen) {
	window.addEventListener("keydown", this.onkey);
	window.addEventListener("keyup", this.onkey);
	window.addEventListener("touchstart", this.onkey);
	window.addEventListener("touchend", this.onkey);
	this.prevTime = new Date().getTime();
	this.stopped = false;
	this.worldgen = worldgen;

	this.update();
}
Game.prototype.spawn = function(ent) {
	this.entities.push(ent);

	if (!this.layers[ent.layer])
		this.layers[ent.layer] = [];
	this.layers[ent.layer].push(ent);

	if (ent.inputListener)
		this.inputListeners.push(ent);
}
Game.prototype.update = function() {
	var time = new Date().getTime();
	var dt = time - this.prevTime;

	if (this.stopped)
		return;

	// Go through and update
	var xRatio = 1 / (1 + (dt * 0.005));
	for (var i = 0; i < this.entities.length; ++i) {
		var ent = this.entities[i];

		// Remove dead entities, replace them with the last entity
		if (ent.dead) {
			if (i + 1 === this.entities.length) {
				this.entities.pop();
			} else {
				this.entities[i] = this.entities.pop();
				ent = this.entities[i];
			}
		}

		if (ent.update)
			ent.update();

		if (ent.moves) {
			ent.force.scale(ent.forceScalar * dt);
			ent.vel.add(ent.force);
			ent.force.set({ x: 0, y: 0 });
			ent.vel.scale(xRatio);
			ent.pos.add(ent.vel.clone().scale(dt));
		}

		if (ent.move)
			ent.move();
	}

	// Exit if stopped
	if (this.stopped)
		return;

	// Tick worldgen
	this.worldgen.update();

	// Go through and draw
	this.canvas.width = this.canvas.width;
	for (var i = 0; i < this.layers.length; ++i) {
		var layer = this.layers[i];
		for (var j = 0; j < layer.length; ++j) {
			var ent = layer[j];

			// Remove dead entities, replace them with the last entity
			if (ent.dead) {
				if (i + 1 === this.entities.length) {
					layer.pop();
				} else {
					layer[j] = layer.pop();
					ent = layer[j];
				}
			}

			if (ent.collude && ent.pos.x + ent.shape.width() < this.camera.x)
				continue;
			if (ent.collude && ent.pos.x > this.camera.x + this.canvas.width)
				continue;

			this.ctx.save();
			this.ctx.translate(
				ent.pos.x - this.camera.x,
				ent.pos.y - this.camera.y);
			ent.draw(this.ctx);
			this.ctx.restore();
		}
	}

	// Clear presses
	for (var i in this.presses) {
		this.presses[i] = false;
	}

	this.prevTime = time;
	if (!this.stopped)
		this.raf = reqAnimFrame(this.update.bind(this));
}
Game.prototype.stop = function(score) {
	this.stopped = true;
	cancelAnimFrame(this.raf);
	window.removeEventListener("keyup", this.onkey);
	window.removeEventListener("keydown", this.onkey);

	if (this.onstop)
		this.onstop(Math.floor(score));
}
