//constants
var LEFT_KEY = 37;
var UP_KEY = 38;
var RIGHT_KEY = 39;
var DOWN_KEY = 40;
var FIRE_KEY = 32;

var CELL_WIDTH = 32;

//utils
function extend(Child, Parent) {
	var F = function() { }
	F.prototype = Parent.prototype
	Child.prototype = new F()
	Child.prototype.constructor = Child
	Child.superclass = Parent.prototype
}

//game logic
function Game(width, height) {
	this.width = width;
	this.height = height;
	this.player = undefined;
	this.shots = new Array();

	//init map
	this.map = new Array(width);
	for (var i = 0; i < width; i++) {
    	this.map[i] = new Array(height);
  	}
  	for (var x = 0; x < this.width; x++) {
  		for (var y = 0; y < this.height; y++) {
  			this.map[x][y] = new Cell(x, y);
  		};
  	};
}

Game.prototype.generateRandomMap = function () {
	var playerX = Math.floor(this.width / 2) - 2;
  	var playerY = this.height - 1;
  	this.player = new Tank(this, this.map[playerX][playerY]);
  	this.map[playerX][playerY].obj = this.player;

  	this.map[playerX + 2][this.height - 1].obj = new FriendBase(this.map[playerX + 2][this.height - 1]);
  	this.map[playerX + 2][this.height - 2].obj = new Briks(this.map[playerX + 2][this.height - 2]);
  	this.map[playerX + 3][this.height - 1].obj = new Briks(this.map[playerX + 3][this.height - 1]);
  	this.map[playerX + 3][this.height - 2].obj = new Briks(this.map[playerX + 3][this.height - 2]);
  	this.map[playerX + 1][this.height - 1].obj = new Briks(this.map[playerX + 1][this.height - 1]);
  	this.map[playerX + 1][this.height - 2].obj = new Briks(this.map[playerX + 1][this.height - 2]);

	this.generateEmpiricSomethingOnMap(function(cell) {
		if (cell.obj) return;
		cell.obj = new ArmorWall();
	}, 3,20,2,9,3);

	this.generateEmpiricSomethingOnMap(function(cell) {
		if (cell.obj && !(cell.obj instanceof ArmorWall)){
			return;
		}
		cell.obj = new Briks(cell);
	}, 3,20,2,9,3);

	this.generateEmpiricSomethingOnMap(function(cell) {
		if (cell.obj){
			return;
		}
		cell.obj = new Water();
	}, 8,20,6,100,1);

	this.generateEmpiricSomethingOnMap2(function(cell) {
		if (cell.obj && !(cell.obj instanceof Tank)){
			return;
		}
		cell.layer2Obj = new Forest();
	}, 4, 50, 10);

	var cell = this.map[0][0];
	cell.obj = undefined;
	cell.layer2Obj = new Respawn(this, cell);
	cell = this.map[Math.floor(this.width / 2)][0];
	cell.obj = undefined;
	cell.layer2Obj = new Respawn(this, cell);
	cell = this.map[this.width - 1][0];
	cell.obj = undefined;
	cell.layer2Obj = new Respawn(this, cell);
}

Game.prototype.generateEmpiricSomethingOnMap2 = function (constructor, dencityFactor, dencityFactor2, dencityFactor3) {
	var that = this;
  	var clearMap = function() {
  		for (var x = 0; x < that.width; x++) {
	  		for (var y = 0; y < that.height; y++) {
	  			that.map[x][y].checked = false;
	  		};
	  	};
  	}

	var cellsToCheck;
	var nearCells;
	var addCell = function(x, y) {
		if (cellsToFill <= 0 || that.isOutOfMap(x, y)) {
			return false;
		}
		var cell = that.map[x][y];
		if (cell.checked) {
			return false;
		}
		cell.checked = true;
		nearCells.push(cell);
		constructor(cell);
		return true;
	}
	for (var i = 0; i < dencityFactor; i++) {
		clearMap();
		cellsToCheck = [];
		nearCells = [];
		var cellsToFill = Math.floor(Math.random() * dencityFactor2) + dencityFactor3;
		var fromX = Math.floor(Math.random() * this.width);
		var fromY = Math.floor(Math.random() * this.height);
		var cell = this.map[fromX][fromY];
		cellsToCheck.push(cell);
		while (cellsToCheck.length > 0 && cellsToFill > 0) {
			for (var j = cellsToCheck.length - 1; j >= 0; j--) {
				var cell = cellsToCheck[j];
				addCell(cell.x + 1, cell.y) ? cellsToFill-- : 0;
				addCell(cell.x - 1, cell.y) ? cellsToFill-- : 0;
				addCell(cell.x, cell.y + 1) ? cellsToFill-- : 0;
				addCell(cell.x, cell.y - 1) ? cellsToFill-- : 0;
			};
			cellsToCheck = nearCells;
			nearCells = [];
		}
	};
}

Game.prototype.generateEmpiricSomethingOnMap = function (constructor, dencityFactor, dencityFactor2, dencityFactor3, dencityFactor4, dencityFactor5) {
  	var dynamicDencityFactor;
  	var dynamicDencityFactor2;
  	var start;
  	var start2;
  	for (var y = 0; y < this.height; y++) {
  		if (y % dencityFactor == 0) {
  			dynamicDencityFactor = Math.floor(Math.random() * this.width / dencityFactor3);
  		} else {
  			dynamicDencityFactor = Math.min(this.width - 1, Math.floor(Math.random() * this.width / dencityFactor4) + dencityFactor5);
  		}
  		start = Math.floor(Math.random() * this.width);
  		dynamicDencityFactor2 = Math.floor(Math.random() * dencityFactor2);
  		start2 = Math.floor(Math.random() * dynamicDencityFactor2) + 1;
  		while (dynamicDencityFactor > 0) {
  			var cell = this.map[start][y];
  			if (start2 == 0){
  				start2 = Math.floor(Math.random() * dynamicDencityFactor2) + 1;
  				start += Math.floor(Math.random() * this.width / dencityFactor4);
  			} else {
	  			constructor(cell);
	  			start ++;
	  			start2 --;
	  			dynamicDencityFactor --;
  			}  			
  			if (start >= this.width) {
  				start = 0;
  			}
  			
  		}		
  	};

  	for (var x = 0; x < this.width; x++) {
  		if (x % dencityFactor == 0) {
  			dynamicDencityFactor = Math.floor(Math.random() * this.height / dencityFactor3);
  		} else {
  			dynamicDencityFactor = Math.min(this.height - 1, Math.floor(Math.random() * this.height / dencityFactor4) + dencityFactor5);
  		}
  		start = Math.floor(Math.random() * this.height);
  		dynamicDencityFactor2 = Math.floor(Math.random() * dencityFactor2);
  		start2 = Math.floor(Math.random() * dynamicDencityFactor2) + 1;
  		while (dynamicDencityFactor > 0) {
  			var cell = this.map[x][start];
  			if (start2 == 0){
  				start2 = Math.floor(Math.random() * dynamicDencityFactor2) + 1;
  				start += Math.floor(Math.random() * this.height / dencityFactor4);
  			} else {
	  			constructor(cell);
	  			start ++;
	  			start2 --;
	  			dynamicDencityFactor --;
  			}  			
  			if (start >= this.height) {
  				start = 0;
  			}
  			
  		}		
  	};
}

Game.prototype.isOutOfMap = function (x, y) {
	return x < 0 || x >= this.width || y < 0 || y >= this.height;
}

Game.prototype.movePlayer = function (lastKey) {
	if (!this.player.isDead) {
		this.moveTank(lastKey, this.player);
	}
}

Game.prototype.moveTank = function (directionKey, tank) {
	if (!directionKey || tank.offsetX != 0 || tank.offsetY != 0) {
		return;
	}
	var playerDestX = tank.currentCell.x;
	var playerDestY = tank.currentCell.y;
	if (directionKey == UP_KEY) {
		playerDestY--;
		tank.angle = 0;
	} else if (directionKey == DOWN_KEY) {
		playerDestY++;
		tank.angle = 180;
	} else if (directionKey == LEFT_KEY) {
		playerDestX--;
		tank.angle = 270;
	} else if (directionKey == RIGHT_KEY) {
		playerDestX++;
		tank.angle = 90;
	}
	if (this.isOutOfMap(playerDestX, playerDestY)) {
		return;
	}
	var destCell = this.map[playerDestX][playerDestY];
	if (destCell.obj){
		return;
	}
	tank.offsetX = (tank.currentCell.x - destCell.x) * CELL_WIDTH;
	tank.offsetY = (tank.currentCell.y - destCell.y) * CELL_WIDTH;
	tank.currentCell.obj = undefined;
	tank.currentCell = destCell;
	destCell.obj = tank;
}

Game.prototype.update = function (deltaTime) {
	for (var x = 0; x < this.width; x++) {
  		for (var y = 0; y < this.height; y++) {
  			var obj = this.map[x][y].obj;
  			if (obj) {
  				obj.update(deltaTime);
  				if (obj instanceof Water) {
  					continue;
  				}
  				for (var i = this.shots.length - 1; i >= 0; i--) {
  					var shot = this.shots[i];
  					if (shot.player instanceof AITank && obj instanceof AITank) {
  						continue;
  					}
  					if (!shot.exploded && obj != shot.player && shot.intersectDrawableTile(x, y, obj)) {
  						shot.explode();
  						if (obj.makeWounded) {
  							obj.makeWounded(shot);
  						}
  					}
  				};
  			}
  			var layer2Obj = this.map[x][y].layer2Obj;
  			if (layer2Obj) {
  				layer2Obj.update(deltaTime);
  			}
  		};
  	};
  	for (var i = this.shots.length - 1; i >= 0; i--) {
  		var shot = this.shots[i];
  		shot.update(deltaTime);
  		if (shot.isOutOfMap(this.width * CELL_WIDTH, this.height * CELL_WIDTH)) {
  			shot.explode();
  		}
  		if (shot.timeToDead <= 0) {
  			this.shots.splice(i, 1);
  		}
  	};
}

function Shot(player, centerX, centerY, speedX, speedY, width) {
	this.centerX = centerX;
	this.centerY = centerY;
	this.player = player;
	this.width = width;
	this.height = width;
	this.speedX = speedX;
	this.speedY = speedY;
	this.explodeTime = 100;
	this.exploded = false;
	this.timeToDead = this.explodeTime;
}

Shot.prototype.update = function (deltaTime) {
	if (this.exploded) {
		this.timeToDead -= deltaTime;
	} else {
		this.centerX += this.speedX * deltaTime / 1000;
		this.centerY += this.speedY * deltaTime / 1000;
	}
}

Shot.prototype.isOutOfMap = function (mapWidth, mapHeight) {
	return this.centerX < 0 || this.centerX > mapWidth || 
		this.centerY < 0 || this.centerY > mapHeight;
}

Shot.prototype.explode = function () {
	this.exploded = true;
}

Shot.prototype.intersectDrawableTile = function (x, y, tile) {
	var r1 = {left: this.centerX - this.width / 2,
		right: this.centerX + this.width / 2,
		top: this.centerY - this.height / 2,
		bottom: this.centerY + this.height / 2};
	var r2 = {left: x * CELL_WIDTH + tile.offsetX,
		right: x * CELL_WIDTH + tile.offsetX + CELL_WIDTH,
		top: y * CELL_WIDTH + tile.offsetY,
		bottom: y * CELL_WIDTH + tile.offsetY + CELL_WIDTH};
	return !(r2.left > r1.right || 
	       r2.right < r1.left || 
	       r2.top > r1.bottom ||
	       r2.bottom < r1.top);
}

Shot.prototype.getColor = function () {
	if (this.exploded) {
		var color = 100 + (this.explodeTime - this.timeToDead) / this.explodeTime * 155;
		return "rgb(" + color+ "," + color + "," + color + ")"
	}
	return "rgb(100,100,100)";
}

function Cell(x, y) {
	this.x = x;
	this.y = y;
	this.obj = undefined;
	this.layer2Obj = undefined;
	//fields used by algoryhms
	this.checked = false;
}

function DrawableObject(tileNumber, minTileNumber, maxTileNumber, animated, animPeriod) {
	this.tileNumber = tileNumber;
	this.minTileNumber = minTileNumber;
	this.maxTileNumber = maxTileNumber;
	this.animated = animated;
	this.animPeriod = animPeriod;
	this.animationElapsedTime = 0;
	this.offsetX = 0;
	this.offsetY = 0;
	this.angle = 0;
	this.isDead = false;
	this.isHidden = false;
}

DrawableObject.prototype.update = function (deltaTime) {
	if (this.animated) {
		this.animationElapsedTime += deltaTime;
		if (this.animationElapsedTime > this.animPeriod) {
			this.tileNumber++;
			if (this.tileNumber > this.maxTileNumber) {
				this.tileNumber = this.minTileNumber;
			}
			this.animationElapsedTime = this.animationElapsedTime - this.animPeriod;
		}
	}
}

function Forest() {
	Forest.superclass.constructor.apply(this, [21, 21, 21, false, 0]);
}
extend(Forest, DrawableObject);

function Water() {
	Water.superclass.constructor.apply(this, [22, 22, 23, true, 2000]);
}
extend(Water, DrawableObject);

function ArmorWall() {
	ArmorWall.superclass.constructor.apply(this, [20, 20, 20, false, 0]);
}
extend(ArmorWall, DrawableObject);

function Briks(cell) {
	Briks.superclass.constructor.apply(this, [0, 0, 0, false, 0]);
	this.life = 4;
	this.cell = cell;
}
extend(Briks, DrawableObject);

Briks.prototype.makeWounded = function () {
	this.life--;
	this.tileNumber++;
	if (this.life <= 0) {
		this.cell.obj = undefined;
	}
}

function FriendBase(cell) {
	FriendBase.superclass.constructor.apply(this, [24, 24, 27, true, 1000]);
	this.cell = cell;
	this.life = 4;
	this.deadCountDown = 1500;
	this.isDead = false;
}
extend(FriendBase, DrawableObject);

FriendBase.prototype.makeWounded = function () {
	this.life--;
	this.tileNumber = 28 + 4 - this.life;
	this.animated = false;
	if (this.life <= 0) {
		this.isDead = true;
		this.tileNumber = 32;
		this.minTileNumber = 32;
		this.maxTileNumber = 35;
		this.animated = true;
		this.animPeriod = 100;
	}
}

FriendBase.prototype.update = function (deltaTime) {
	FriendBase.superclass.update.apply(this, arguments);
	if (this.isDead) {
		this.deadCountDown -= deltaTime;
		if (this.deadCountDown <= 0) {
			this.cell.obj = undefined;
		} 
	}
}

function Tank(game, currentCell) {
	Tank.superclass.constructor.apply(this, [4, 4, 7, true, 100]);
	this.game = game;
	this.currentCell = currentCell;
	this.life = 1;
	this.deadCountDown = 1500;
	this.isDead = false;
	this.speed = 64;//pixel per second
	this.isFire = false;
	this.firePeriod = 1000;
	this.lastFireElapsedTime = 0;
	this.shotSpeed = 192;
	this.shotWidth = 6;
}
extend(Tank, DrawableObject);

Tank.prototype.update = function (deltaTime) {
	Tank.superclass.update.apply(this, arguments);

	if (this.isDead) {
		this.deadCountDown -= deltaTime;
		if (this.deadCountDown <= 0) {
			this.currentCell.obj = undefined;
		}
	} else {

		if (this.offsetX == 0 && this.offsetY == 0) {
			this.animated = false;
		} else {
			this.animated = true;
			var deltaPos = this.speed * deltaTime / 1000;
			if (this.offsetX > 0) {
				this.offsetX = Math.max(0, this.offsetX - deltaPos);
			} else if (this.offsetX < 0) {
				this.offsetX = Math.min(0, this.offsetX + deltaPos);
			} else if (this.offsetY > 0) {
				this.offsetY = Math.max(0, this.offsetY - deltaPos);
			} else if (this.offsetY < 0) {
				this.offsetY = Math.min(0, this.offsetY + deltaPos);
			}
		}
		this.lastFireElapsedTime -= deltaTime;
		if (this.isFire) {
			if (this.lastFireElapsedTime <= 0) {
				this.lastFireElapsedTime = this.firePeriod;
				this.makeShot();
			}
		}

	}

}

Tank.prototype.makeWounded = function () {
	this.life--;
	if (this.life <= 0) {
		this.isDead = true;
		this.tileNumber = 32;
		this.minTileNumber = 32;
		this.maxTileNumber = 35;
		this.animated = true;
	}
}

Tank.prototype.makeShot = function () {
	var centerX, centerY;
	var speedX = speedY = 0;
	switch (this.angle) {
		case 0:
			centerX = this.currentCell.x * CELL_WIDTH + CELL_WIDTH / 2;
			centerY = this.currentCell.y * CELL_WIDTH;
			speedY = -this.shotSpeed;
			break;
		case 90:
			centerX = (this.currentCell.x + 1) * CELL_WIDTH - this.shotWidth;
			centerY = this.currentCell.y * CELL_WIDTH + CELL_WIDTH / 2;
			speedX = this.shotSpeed;
			break;
		case 180:
			centerX = this.currentCell.x * CELL_WIDTH + CELL_WIDTH / 2;
			centerY = (this.currentCell.y + 1) * CELL_WIDTH - this.shotWidth;
			speedY = this.shotSpeed;
			break;
		case 270:
			centerX = this.currentCell.x * CELL_WIDTH;
			centerY = this.currentCell.y * CELL_WIDTH + CELL_WIDTH / 2;
			speedX = -this.shotSpeed;
			break;
	}
	var shot = new Shot(this, centerX, centerY, speedX, speedY, this.shotWidth);
	this.game.shots.push(shot);
}

Tank.prototype.updateFireState = function (isFire) {
	if (this.isFire != isFire) {
		this.isFire = isFire;
	}
}

function AITank(game, currentCell) {
	AITank.superclass.constructor.apply(this, [game, currentCell]);
	this.turnSpeedFactor = undefined;
	this.timeToNextTurn = Math.floor(Math.random() * 5000 + 500);
	this.nextTurnCountDown = this.timeToNextTurn;
	this.currentDirectionKey = DOWN_KEY;
	this.angle = 180;
	this.isFire = true;
	switch (Math.floor(Math.random() * 3)) {
		case 0:
			this.tileNumber = 8;
			this.minTileNumber = 8;
			this.maxTileNumber = 11;
			this.firePeriod = 500;
			this.speed = 128;
			this.turnSpeedFactor = 10;
			break;
		case 1:
			this.tileNumber = 12;
			this.minTileNumber = 12;
			this.maxTileNumber = 15;
			this.firePeriod = 1500;
			this.speed = 64;
			this.life = 3;
			this.turnSpeedFactor = 500;
			break;
		case 2:
			this.tileNumber = 16;
			this.minTileNumber = 16;
			this.maxTileNumber = 19;
			this.firePeriod = 1000;
			this.speed = 64;
			this.turnSpeedFactor = 1000;
			break;
	}
}
extend(AITank, Tank);

AITank.prototype.update = function (deltaTime) {
	AITank.superclass.update.apply(this, arguments);
	if (this.isDead) {
		return;
	}
	this.nextTurnCountDown -= deltaTime;
	this.game.moveTank(this.currentDirectionKey, this);
	if (this.nextTurnCountDown <= 0){
		this.nextTurnCountDown = Math.floor(Math.random() * this.turnSpeedFactor * 10 + this.turnSpeedFactor);
		switch (Math.floor(Math.random() * 4)) {
			case 0:
				this.currentDirectionKey = UP_KEY;
				break;
			case 1:
				this.currentDirectionKey = DOWN_KEY;
				break;
			case 2:
				this.currentDirectionKey = LEFT_KEY;
				break;
			case 3:
				this.currentDirectionKey = RIGHT_KEY;
				break;
		}
	}
}

function Respawn(game, cell) {
	Respawn.superclass.constructor.apply(this, [36, 36, 39, true, 100]);
	this.game = game;
	this.cell = cell;
	this.tankCount = 10;
	this.respawnInterwal = 15000;
	this.respawnTime = 3000;
	this.respawnCountDown = this.respawnTime;
	this.timeToNextRespawn = 0;
}
extend(Respawn, DrawableObject);

Respawn.prototype.update = function (deltaTime) {
	Respawn.superclass.update.apply(this, arguments);
	this.timeToNextRespawn -= deltaTime;
	if (this.timeToNextRespawn <= 0 && this.tankCount > 0) {
		this.isHidden = false;
		this.respawnCountDown -= deltaTime;
		if (this.respawnCountDown <= 0 && !this.cell.obj) {
			this.respawnCountDown = this.respawnTime;
			this.timeToNextRespawn = this.respawnInterwal;
			this.isHidden = true;
			this.genTank();
		}
	}
}

Respawn.prototype.genTank = function () {
	this.tankCount--;
	var tank = new AITank(this.game, this.cell);
	this.cell.obj = tank;
}

function GameView(game, canvas, resourcesImg) {
	this.game = game;
	this.canvas = canvas;
	this.resourcesImg = resourcesImg;
	this.backgroundColor = "#310";

	this.keysPressed = new Array();
	this.lastKey = undefined;
	
	this.canvas.width = this.game.width * CELL_WIDTH;
	this.canvas.height = this.game.height * CELL_WIDTH;

	var that = this;
	this.canvas.onkeydown = function(evt) {
	    evt = evt || window.event;
	    that.onKeyDown(evt);
	};

	this.canvas.onkeyup = function(evt) {
	    evt = evt || window.event;
	    that.onKeyUp(evt);
	};
}

GameView.prototype.onKeyDown = function (event) {
	event.preventDefault();
	this.lastKey = event.keyCode;
	this.keysPressed[event.keyCode] = true;
	console.log(this.keysPressed);
}

GameView.prototype.onKeyUp = function (event) {
	event.preventDefault();
	this.keysPressed[event.keyCode] = false;
	this.lastKey = undefined;
}

GameView.prototype.draw = function () {
	var context=this.canvas.getContext("2d");
	context.fillStyle = this.backgroundColor;
	context.fillRect(0, 0, this.canvas.width, this.canvas.height);
	for (var x = 0; x < this.game.width; x++) {
		for (var y = 0; y < this.game.height; y++) {
			var cell = this.game.map[x][y];
			var obj = cell.obj;
			if (obj){
				this.drawDrawableTile(context, x, y, obj);
			}
		}
	};
	for (var x = 0; x < this.game.width; x++) {
		for (var y = 0; y < this.game.height; y++) {
			var cell = this.game.map[x][y];
			var layer2Obj = cell.layer2Obj;
			if (layer2Obj) {
				this.drawDrawableTile(context, x, y, layer2Obj);
			}
		}
	};
	for (var i = this.game.shots.length - 1; i >= 0; i--) {
		this.drawShot(context, this.game.shots[i]);
	};
}

GameView.prototype.drawShot = function (context, shot) {
	context.fillStyle = shot.getColor();
	context.fillRect(Math.floor(shot.centerX - shot.width / 2), Math.floor(shot.centerY - shot.height / 2), shot.width, shot.height);
}

GameView.prototype.drawDrawableTile = function (context, x, y, tile) {
	if (tile.isHidden) {
		return;
	}
	var tileX = tile.tileNumber % 4;
	var tileY = (tile.tileNumber - tileX) / 4;
	var objX = x * CELL_WIDTH + tile.offsetX;
	var objY = y * CELL_WIDTH + tile.offsetY;

	if (tile.angle != 0) {
		this.drawRotatedImage(context, this.resourcesImg, tileX * CELL_WIDTH, tileY * CELL_WIDTH, CELL_WIDTH, CELL_WIDTH, objX, objY, CELL_WIDTH, CELL_WIDTH, tile.angle);
	} else {
		context.drawImage(this.resourcesImg, tileX * CELL_WIDTH, tileY * CELL_WIDTH, CELL_WIDTH, CELL_WIDTH, objX, objY, CELL_WIDTH, CELL_WIDTH);
	}
}

var TO_RADIANS = Math.PI/180; 
GameView.prototype.drawRotatedImage = function (context, image, sx, sy, swidth, sheight, x, y, width, height, angle) { 
	context.save(); 
	context.translate(x + width/2, y + height/2);
	context.rotate(angle * TO_RADIANS);
	context.drawImage(image, sx, sy, swidth, sheight, -(width/2), -(height/2), width, height);
	context.restore(); 
}