import Room1 from './rooms';
import {
	addV,
	copy,
	distance,
	multiply,
	normalize,
	subtractV,
	Vector,
} from './vector';
import Unit from './unit';
import Generator from './characters/generator';
import resources from './resources';
import Interval from './interval';
import Hero from './hero';
import { Weapon } from './weapons/weapon';
import { Knife } from './weapons';
import Coins from './coins';
import Flask from './weapons/flask';
import Mace from './weapons/mace';

const VIEW_DISTANCE = 90;

class World {
	private enemies: Unit[] = [];
	private weapons: Weapon[];
	private coins = new Coins();

	private enemyHitInterval = new Interval(500);

	private monstersGenerator: Generator;

	constructor(private room: Room1, private hero: Hero) {
		this.monstersGenerator = new Generator(resources.getImage('dungeon'));
		this.weapons = [new Knife()];
	}

	reset() {
		this.monstersGenerator = new Generator(resources.getImage('dungeon'));
		this.weapons = [new Knife()];
		this.hero.reset();
		this.monstersGenerator.reset();
		this.enemies = [];
		this.coins = new Coins();
		this.enemyHitInterval.reset();
	}

	move(unit: Unit, dir: Vector): Vector {
		const dest = copy(unit.position);
		addV(dest, dir);

		if (dest[0] < 8) dest[0] = 8;
		else if (dest[0] > this.room.width - 8) dest[0] = this.room.width - 8;
		if (dest[1] < 16) dest[1] = 16;
		else if (dest[1] > this.room.height - 4) dest[1] = this.room.height - 4;

		if (this.hero === unit) return dest;

		const origin = copy(unit.position);
		const nearest = this.enemies.filter((enemy) => {
			return (
				enemy !== unit &&
				distance(enemy.position, origin) <= (unit.width + enemy.width) / 6
			);
		});

		const canMove =
			nearest.length === 0 ||
			nearest.every(
				(enemy) =>
					distance(enemy.position, dest) >= distance(enemy.position, origin),
			);

		return canMove ? dest : unit.position;
	}

	draw(ctx: CanvasRenderingContext2D) {
		this.room.draw(ctx);
		this.coins.draw(ctx);
		this.enemies.forEach((enemy) => enemy.draw(ctx));
		this.hero.draw(ctx);
		this.weapons.forEach((weapon) => weapon.draw(ctx));
	}

	update(dt: number) {
		let newLevel;

		this.monstersGenerator.updateBodies(dt);
		this.hero.update(dt);

		this.coins.update(dt);

		this.enemies = this.enemies.filter((enemy) => {
			const dead = enemy.health <= 0;
			if (dead) this.coins.add(enemy.position);
			return !dead;
		});
		this.enemies.forEach((enemy) => {
			const dir = multiply(
				normalize(subtractV(copy(this.hero.position), enemy.position)),
				enemy.speed * dt,
			);

			enemy.move(this.move(enemy, dir));
		});

		this.weapons.forEach((weapon) => {
			weapon.filterProjectiles(0, 0, this.room.width, this.room.height);
			weapon.update(dt, {
				position: this.hero.position,
				direction: this.hero.direction,
				enemies: this.enemies,
			});

			this.enemies.forEach((enemy) => weapon.checkHit(enemy));
		});

		const collectedCoins = this.coins.collect(this.hero.position);
		newLevel = this.hero.addExp(collectedCoins);

		const enemies = this.monstersGenerator.make(dt);
		enemies.forEach((enemy) => this.addEnemy(enemy));

		if (this.enemyHitInterval.next(dt)) {
			this.checkEnemyAttack();
			this.invalidateEnemies();
		}

		return { newLevel };
	}

	findEnemyPosition() {
		let position: Vector = [0, 0];

		for (let i = 0; i < 10; i++) {
			position = addV(
				multiply(
					normalize([Math.random() * 2 - 1, Math.random() * 2 - 1]),
					VIEW_DISTANCE,
				),
				this.hero.position,
			);

			if (
				position[0] > 8 &&
				position[0] < this.room.width - 8 &&
				position[1] > 16 &&
				position[1] < this.room.height - 4
			) {
				return position;
			}
		}

		return null;
	}

	addEnemy(enemy: Unit) {
		const position = this.findEnemyPosition();
		if (position === null) return;
		enemy.move(position);
		this.enemies.push(enemy);
	}

	checkEnemyAttack() {
		const ATTACK_DISTANCE = 10;

		const nearest = this.enemies.filter(
			(enemy) => distance(this.hero.position, enemy.position) < ATTACK_DISTANCE,
		);

		nearest.forEach((enemy) => {
			this.hero.hit(enemy.power);
		});
	}

	upgradeWeapon(weaponKey: string) {
		const weapon = this.weapons.find((weapon) => weapon.key === weaponKey);
		if (weapon) {
			weapon.upgrade();
		} else {
			if (weaponKey === 'knife') this.weapons.push(new Knife());
			else if (weaponKey === 'mace') this.weapons.push(new Mace());
			else if (weaponKey === 'flask') this.weapons.push(new Flask());
		}
	}

	invalidateEnemies() {
		this.enemies.forEach((enemy) => {
			if (distance(enemy.position, this.hero.position) > VIEW_DISTANCE * 1.5) {
				const position = this.findEnemyPosition();
				if (position === null) return;
				enemy.move(position);
			}
		});
	}
}

export default World;
