let Vec2 = require("./vec2");

function randint(min, max) {
	return Math.floor(Math.random() * (max - min + 1)) + min;
}

function random(min, max) {
	return Math.random() * (max - min + 1) + min;
}

function diff(n1, n2) {
	return Math.abs(n1 - n2);
}

function createImage(url) {
	var img = document.createElement("img");
	img.src = url;
	return img;
}

function background(ctx, camera, offset) {
	if (!background.cache) {
		let cache = [];
		let n = window.innerWidth / 2;
		for (let i = 0; i < n; ++i) {
			let parallax = random(5.6, 9);
			cache.push({
				x: randint(0, window.innerWidth * parallax),
				y: randint(0, window.innerHeight * parallax),
				p: parallax
			});
		}
		background.cache = cache;
	}

	let cx = camera.x;
	let cy = camera.y;
	let cache = background.cache;

	let fs = ctx.fillStyle;
	ctx.fillStyle = "#FFFFFF";
	for (let i = 0, len = cache.length; i < len; ++i) {
		let p = cache[i];
		let x = (((p.x - cx) / p.p) + offset.x) % window.innerWidth;
		let y = (((p.y - cy) / p.p) + offset.y) % window.innerHeight;
		if (x < 0)
			x += window.innerWidth;
		if (y < 0)
			y += window.innerHeight;

		ctx.beginPath();
		ctx.arc(x, y, 1, 0, 2*Math.PI);
		ctx.closePath();
		ctx.fill();
	}
	ctx.fillStyle = fs;
}

window.addEventListener("resize", () => background.cache = null);

class Animation {
	constructor({img, x, y, width, height, dwidth, dheight, wsteps, hsteps, nsteps, loop, fps, rot, offsetX, offsetY}) {
		loop = loop || false;
		fps = fps || 30;
		nsteps = nsteps || wsteps * hsteps;
		dwidth = dwidth || width;
		dheight = dheight || height;
		rot = rot || 0;
		offsetX = offsetX || 0;
		offsetY = offsetY || 0;

		this.img = img;
		this.x = x;
		this.y = y;
		this.offsetX = offsetX;
		this.offsetY = offsetY;
		this.rot = rot;
		this.width = width;
		this.height = height;
		this.dwidth = dwidth;
		this.dheight = dheight;
		this.wsteps = wsteps;
		this.hsteps = hsteps;
		this.wstep = 0;
		this.hstep = 0;
		this.step = 0;
		this.nsteps = nsteps;
		this.loop = loop;
		this.visible = true;
		this.onend = function(){};

		let interval = setInterval(() => {
			this.step += 1;
			if (this.step >= this.nsteps) {
				this.step = 0;
				this.wstep = 0;
				this.hstep = 0;

				if (!this.loop) {
					clearInterval(interval);
					this.onend();
				}

				return;
			}

			this.wstep += 1;
			if (this.wstep >= this.wsteps) {
				this.wstep = 0;
				this.hstep += 1;
			}

		}, 1000/fps);
	}

	animate(ctx) {
		if (!this.visible)
			return;

		ctx.translate(this.x, this.y);

		if (this.rot)
			ctx.rotate(this.rot);

		ctx.drawImage(
			this.img,
			this.wstep * this.width,
			this.hstep * this.width,
			this.width,
			this.height,
			this.offsetX,
			this.offsetY,
			this.dwidth,
			this.dheight
		);

		if (this.rot)
			ctx.rotate(-this.rot);
	}
}

class Entity {
	constructor(x, y, width, height, id, game) {
		this.pos = new Vec2(x, y);
		this.vel = new Vec2(0, 0);
		this.width = width;
		this.height = height;
		this.game = game;

		this.id = id;
	}

	draw(ctx) {}

	set(obj) {
		this.pos.set(obj.pos.x, obj.pos.y);
		this.vel.set(obj.vel.x, obj.vel.y);
	}

	update(dt) {
		this.pos.x += this.vel.x * dt;
		this.pos.y += this.vel.y * dt;
	}

	despawn() {}
}

let BulletImgs = {
	despawn: createImage("imgs/bullet_despawn.png")
};
let BulletSounds = {
	spawn: "sounds/bullet_spawn.wav",
	despawn: "sounds/bullet_despawn.wav"
};

class Bullet extends Entity {
	constructor(x, y, vel, id, ownerId, game) {
		super(x, y, 5, 5, id, game);
		this.vel.set(vel.x, vel.y);
		this.ownerId = ownerId;

		game.playSound(BulletSounds.spawn, this.pos);
	}

	draw(ctx, selfId) {
		if (selfId == this.ownerId) {
			ctx.fillStyle = "#FFFFFF";
		} else {
			ctx.fillStyle = "#FF0000";
		}

		ctx.beginPath();
		ctx.arc(-(this.width/2), -(this.height/2), this.width/2, 0, 2*Math.PI);
		ctx.closePath();
		ctx.fill();
	}

	set(obj) {
		super.set(obj);
	}

	despawn() {
		this.game.animate(new Animation({
			img: BulletImgs.despawn,
			x: this.pos.x,
			y: this.pos.y,
			width: 64,
			height: 64,
			dwidth: 16,
			dheight: 16,
			wsteps: 5,
			hsteps: 5
		}));
		this.game.playSound(BulletSounds.despawn, this.pos);
	}
}

let PlayerImgs = {
	thrust_back: createImage("imgs/player_thrust_back.png"),
	despawn: createImage("imgs/player_despawn.png")
};
let PlayerSounds = {
	despawn: "sounds/player_despawn.wav",
	thrust: "sounds/player_thrust.wav"
};

class Player extends Entity {
	constructor(x, y, id, rot, name, game) {
		super(x, y, 25, 60, id, game);
		this.rot = rot;
		this.rotVel = 0;
		this.keys = {};
		this.health = 0;
		this.name = name;

		this.thrustAnim = new Animation({
			img: PlayerImgs.thrust_back,
			x: this.pos.x,
			y: this.pos.y,
			width: 128,
			height: 128,
			dwidth: 64,
			dheight: 64,
			wsteps: 4,
			hsteps: 4,
			fps: 60,
			loop: true
		});
		this.thrustAnim.visible = false;
		game.animate(this.thrustAnim);

		if (this.id === game.id) {
			this.thrustSound = document.createElement("audio");
			this.thrustSound.src = PlayerSounds.thrust;
			this.thrustSound.loop = true;
			this.thrustSound.play();
			this.thrustSound.volume = 0;
		}
	}

	draw(ctx, selfId) {
		let h = Math.round((this.health * 2.4) + 15);

		if (selfId == this.id) {
			ctx.fillStyle = "rgb("+h+", "+h+", "+h+")";
		} else {
			ctx.fillStyle = "rgb("+h+", 0, 0)";
		}

		ctx.rotate(this.rot);

		if (this.keys.up) {
			this.thrustAnim.rot = this.rot;
			this.thrustAnim.x = this.pos.x;
			this.thrustAnim.y = this.pos.y;
			this.thrustAnim.offsetX = -this.thrustAnim.dwidth/2;
			this.thrustAnim.offsetY = this.thrustAnim.dwidth/2;

			if (this.keys.sprint) {
				this.thrustAnim.dheight = 100;
				if (this.id == selfId)
					this.game.screenShake(10);
			} else {
				this.thrustAnim.dheight = 64;
			}
		}

		ctx.beginPath();
		ctx.moveTo(0, -(this.height/2));
		ctx.lineTo(-this.width, this.height/2);
		ctx.lineTo(this.width, this.height/2);
		ctx.closePath();
		ctx.fill();

		{

			let h = (this.height/2) + 10;
			if (this.thrustAnim.visible)
				h += this.thrustAnim.dheight;

		}

		ctx.rotate(-this.rot);

		ctx.textAlign = "center";
		ctx.textBaseline = "middle";
		ctx.font = "bold 30px Arial";
		ctx.strokeStyle = "#000000";
		ctx.fillStyle = "#ffffff";
		ctx.lineWidth = 2;
		ctx.fillText(this.name, 0, 0);
		ctx.strokeText(this.name, 0, 0);

		//Draw pointers to far away players
		if (selfId == this.id) {
			this.game.entities.forEach((e) => {

				//Only draw players
				if (!(e instanceof Player))
					return;

				//Only draw far away players
				if (
					diff(e.pos.x, this.pos.x) < window.innerWidth / 2 &&
					diff(e.pos.y, this.pos.y) < window.innerHeight / 2
				) {
					return;
				}

				let pos = e.pos.clone().sub(this.pos).normalize().scale(130);

				ctx.fillStyle = "rgb(255, 0, 0)";
				ctx.beginPath();
				ctx.arc(pos.x, pos.y, 5, 0, 2*Math.PI);
				ctx.closePath();
				ctx.fill();
			});
		}
	}

	set(obj) {
		super.set(obj);
		let lastHealth = this.health;

		this.rot = obj.rot;
		this.rotVel = obj.rotVel;
		this.keys = obj.keys;
		this.health = obj.health;

		if (obj.name)
			this.name = obj.name;

		if (this.id == this.game.id && lastHealth > obj.health) {
			this.game.screenShake(50);
		}
	}

	update(dt) {
		super.update(dt);
		this.rot += this.rotVel * dt;

		if (this.keys.up) {
			this.thrustAnim.visible = true;

			if (this.keys.sprint && this.thrustSound)
				this.thrustSound.volume = 0.6;
			else if (this.id === this.game.id)
				this.thrustSound.volume = 0.3;
		} else {
			this.thrustAnim.visible = false;

			if (this.thrustSound)
				this.thrustSound.volume = 0;
		}
	}

	despawn() {
		this.game.animate(new Animation({
			img: PlayerImgs.despawn,
			x: this.pos.x,
			y: this.pos.y,
			width: 64,
			height: 64,
			dwidth: 256,
			dheight: 256,
			offsetX: -128,
			offsetY: -128,
			wsteps: 5,
			hsteps: 5
		}));
		this.thrustAnim.visible = false;
		this.thrustAnim.loop = false;
		this.game.playSound(PlayerSounds.despawn, this.pos);
		if (this.thrustSound)
			this.thrustSound.stop();
	}
}

function createEntity(obj, game) {
	if (obj.type == "player") {
		return new Player(obj.pos.x, obj.pos.y, obj.id, obj.rot, obj.name, game);
	} else if (obj.type == "bullet") {
		return new Bullet(obj.pos.x, obj.pos.y, obj.vel, obj.id, obj.ownerId, game);
	} else {
		throw new Error("Unknown entity type: "+obj.type);
		return false;
	}
}

export default class Game {
	constructor(sock, canvas, name) {
		this.sock = sock;
		this.canvas = canvas;
		this.ctx = canvas.getContext("2d");
		this.id = null;
		this.camera = new Vec2(0, 0);
		this.raf = null;
		this.prevTime = new Date().getTime();
		this.player = null;
		this.onloss = function(){};

		this.shake = 0;
		this.shakedec = 0.5;

		this.keymap = [];
		this.keymap[87] = "up";
		this.keymap[83] = "down";
		this.keymap[65] = "left";
		this.keymap[68] = "right";
		this.keymap[32] = "shoot";
		this.keymap[16] = "sprint";

		this.entities = [];
		this.animations = [];

		sock.on("ready", () => {
			sock.send("get_id", {
				name: name
			}, (err, res) => {
				this.id = res.id;
			});
		});

		sock.on("set", (msg) => {
			msg.forEach((m) => {
				if (this.entities[m.id]) {
					this.entities[m.id].set(m);
				} else {
					if (!m.type)
						return;

					let ent = createEntity(m, this);
					this.entities[m.id] = ent;
				}
			});
		});

		sock.on("despawn", (msg) => {
			console.log(msg);
			if (!this.entities[msg.id])
				return;

			this.entities[msg.id].despawn();
			delete this.entities[msg.id];
			if (msg.id == this.id) {
				this.screenShake(400);
				setTimeout(() => {
					this.stop();
					this.onloss();
				}, 1200);
			}
		});

		window.addEventListener("keydown", (evt) => {
			if (this.keymap[evt.keyCode]) {
				evt.preventDefault();
				evt.stopPropagation();
				this.sock.send("keydown", {
					key: this.keymap[evt.keyCode]
				});
			}
		});

		window.addEventListener("keyup", (evt) => {
			if (this.keymap[evt.keyCode]) {
				this.sock.send("keyup", {
					key: this.keymap[evt.keyCode]
				});
			}
		});

		this.update();
	}

	update() {
		let dt = new Date().getTime() - this.prevTime;
		this.prevTime = new Date().getTime();

		this.canvas.width = window.innerWidth;
		this.canvas.height = window.innerHeight;

		let player = this.entities[this.id];
		if (player) {
			this.camera.set(
				player.pos.x - (window.innerWidth / 2),
				player.pos.y - (window.innerHeight / 2)
			);
		}

		let shakeOffset = new Vec2(0, 0);
		if (this.shake > 0) {
			shakeOffset.set(
				(Math.random() - 0.5) * this.shake,
				(Math.random() - 0.5) * this.shake
			);
			this.shake -= dt * this.shakedec;
		} else  {
			shakeOffset.set(0, 0);
		}

		let cam = this.camera.clone().add(shakeOffset);

		background(this.ctx, this.camera, shakeOffset);

		this.ctx.translate(-cam.x, -cam.y);
		this.entities.forEach((ent) => {
			this.ctx.save();
			this.ctx.translate(ent.pos.x, ent.pos.y);
			ent.draw(this.ctx, this.id);
			this.ctx.restore();

			ent.update(dt);
		});
		this.animations.forEach((a) => {
			this.ctx.save();
			a.animate(this.ctx);
			this.ctx.restore();
		});
		this.ctx.translate(cam.x, cam.y);

		this.raf = window.requestAnimationFrame(this.update.bind(this));
	}

	stop() {
		window.cancelAnimationFrame(this.raf);
	}

	screenShake(n) {
		if (this.shake < n)
			this.shake = n;
	}

	animate(animation) {
		let i = this.animations.length;
		this.animations.push(animation);
		animation.onend = () => {
			delete this.animations[i];
		};
	}

	playSound(url, pos) {
		let player = this.entities[this.id];
		if (!player)
			return;

		let dist = player.pos.clone().sub(pos);

		let sound = document.createElement("audio");
		sound.src = url;
		sound.volume = Math.max(1 - (dist.length() / 1000), 0);
		sound.play();
	}
}
