import type {Acting, ChanceEntry, Drawable, Healthy, Placeable, Shooting} from "./interfaces.ts";
import {drawDot, moveInDirectionOf} from "./utils.ts";
import {Vector} from "./base.ts";
import {World} from "./World.ts";
import type {Projectile} from "./projectile.ts";
import {ProjectileStats, StraightProjectile} from "./projectile.ts";
import {HealthPack, ItemDrop, LevelDrop, MoneyDrop} from "./drop.ts";
import {ItemManagement} from "./items.ts";

export abstract class Enemy implements Placeable, Drawable, Acting, Healthy {
    protected _position: Vector;
    protected speed: number;
    protected world: World;
    protected size: number
    protected status: EnemyStatus = new EnemyStatus(10);
    protected drops: KillChanceTable;

    constructor(position: Vector) {
        this.drops = new KillChanceTable();
        this.drops.addDrop( {chance: 10, creationMethod: this.spawnMoney})
        this._position = position.clone();
    }

    draw(ctx: CanvasRenderingContext2D) {
    }

    act() {
        this.move()
    }

    move() {
    }


    getPosition(): Vector {
        return this._position;
    }

    takeDamage(damage: number) {
        this.status.health -= damage;
        if(this.status.dead) {
            this.die()
            this.world.removeEnemy(this)
        }
    }

    die() {
        let draw = this.drops.draw();
        if(draw) {
            draw.creationMethod(this)
        }
    }

    spawnMoney(enemy: Enemy) {
        MoneyDrop.spawnMoneyDrop(enemy.world, enemy._position);
    }

    getSize() {
        return this.size;
    }

    dead() {
        return this.status.dead
    }

}

export class BasicEnemy extends Enemy {

    constructor(position: Vector) {
        super(position);
    }

    protected color: string;
    protected impactDamage: number;
    protected impactCooldown: number = 0;
    protected impactInterval: number = 60;

    draw(ctx: CanvasRenderingContext2D) {
        drawDot(this._position, this.getSize(), this.color, ctx)
    }


    move() {
        this._position = moveInDirectionOf(this._position, this.world.player.position, this.speed)
    }

    act() {
        super.act();
        if(this._position.distanceTo(this.world.player.position) < this.getSize() && this.impactCooldown <= 0) {
            this.world.player.takeDamage(this.impactDamage)
            this.impactCooldown = this.impactInterval;
        }
        this.impactCooldown -= 1;
    }

    static spawnBasicEnemy(world: World, position?: Vector) {
        world.addEnemy(this.generateBasicEnemy(world, position))
    }

    static generateBasicEnemy(world: World, position?: Vector): BasicEnemy {
        if(position === undefined) {
            position = world.randomPlace()
        }
        let basicEnemy = new BasicEnemy(position);
        basicEnemy.size = 5;
        basicEnemy.world = world;
        basicEnemy.speed = 0.5;
        basicEnemy.color = 'orange'
        basicEnemy.impactDamage = 2;
        return basicEnemy;
    }

}

export class ShootingEnemy extends BasicEnemy implements Shooting {
    private shootCooldown: number = 0;
    private shootInterval: number;
    private projectiles: Projectile[] = []

    constructor(position: Vector) {
        super(position);
    }

    removeProjectile(projectile: Projectile) {
        this.projectiles = this.projectiles.filter(item => item !== projectile)
    }

    act() {
        super.act();
        if(this.shootCooldown <= 0) {
            this.createProjectile()
            this.shootCooldown = this.shootInterval;
        }
        this.shootCooldown -= 1;
    }

    createProjectile() {
        let stats = new ProjectileStats()
            .withPiercings(0)
            .withSize(1)
            .withDamage(5)
            .withSpeed(2)
        let projectile = StraightProjectile.createStraightProjectile(this.world, this._position, this.world.player.position, this, stats)
        this.projectiles.push(projectile)
        return projectile
    }

    static spawnShootingEnemy(world: World, position?: Vector) {
        world.addEnemy(this.generateShootingEnemy(world, position))
    }

    static generateShootingEnemy(world: World, position?: Vector) {
        if(position === undefined) {
            position = world.randomPlace()
        }
        let shootingEnemy = new ShootingEnemy(position);
        shootingEnemy.size = 5;
        shootingEnemy.world = world;
        shootingEnemy.speed = 0.5;
        shootingEnemy.color = 'green'
        shootingEnemy.impactDamage = 2;
        shootingEnemy.shootInterval = 100
        return shootingEnemy
    }
}

export class EnemyStatus {
    constructor(private _health: number) {
    }


    get health(): number {
        return this._health;
    }

    get dead(): boolean {
        return this._health <= 0;
    }

    set health(value: number) {
        this._health = value;
    }
}

export class HealthEnemy extends Enemy {

    constructor(position: Vector) {
        super(position);
    }

    protected color: string;

    draw(ctx: CanvasRenderingContext2D) {
        drawDot(this._position, this.size, this.color, ctx)
    }


    move() {
    }

    act() {
        super.act();
    }

    die() {
        HealthPack.spawnHealthPack(this.world, this._position)
    }

    static spawnHealthEnemy(world: World, position?: Vector) {
        world.addEnemy(this.createHealthEnemy(world, position))
    }

    static createHealthEnemy(world: World, position?: Vector) {
        if(position === undefined) {
            position = world.randomPlace()
        }
        let basicEnemy = new HealthEnemy(position);
        basicEnemy.size = 5;
        basicEnemy.world = world;
        basicEnemy.speed = 0;
        basicEnemy.color = 'purple'
        return basicEnemy;
    }


    getSize() {
        return this.size
    }
}

export class ContainerEnemy extends Enemy {

    private drops: KillChanceTable;
    constructor(position: Vector) {
        super(position);
        this.status.health = 5;
        this.drops = new KillChanceTable();
        ItemManagement.getItemsWithRarityFactor().forEach(drop => {
            this.drops.addDrop(drop)
        })
        this.drops.calculateProbs()
    }

    protected color: string;

    draw(ctx: CanvasRenderingContext2D) {
        drawDot(this._position, this.size, this.color, ctx)
    }

    move() {
    }

    act() {
        super.act();
    }

    die() {
        let draw = this.drops.draw();
        if(draw) {
            let item = draw.creationMethod(this);
            ItemDrop.spawnItemDrop(this.world, item, this._position)
        }
    }

    spawnHealthPack(enemy: ContainerEnemy) {
        HealthPack.spawnHealthPack(enemy.world, enemy._position)
    }

    spawnLevelUp(enemy: ContainerEnemy) {
        LevelDrop.spawnLevelDrop(enemy.world, enemy._position)
    }

    spawnEnemy(enemy: ContainerEnemy) {
        ShootingEnemy.spawnShootingEnemy(enemy.world, enemy._position)
    }

    static spawnContainerEnemy(world: World, position?: Vector) {
        world.addEnemy(this.createContainerEnemy(world, position))
    }

    static createContainerEnemy(world: World, position?: Vector) {
        if(position === undefined) {
            position = world.randomPlace()
        }
        let basicEnemy = new ContainerEnemy(position);
        basicEnemy.size = 5;
        basicEnemy.world = world;
        basicEnemy.speed = 0;
        basicEnemy.color = 'brown'
        return basicEnemy;
    }


    getSize() {
        return this.size
    }
}


export class KillChanceTable {
    private chances: ChanceEntry[] = []

    addDrop(entry: ChanceEntry) {
        this.chances.push(entry)
    }

    calculateProbs() {
        let sum = this.chances.reduce((sum, entry) => sum + entry.chance, 0)
        this.chances.forEach(value => value.chance /= sum)
    }

    draw() {
        if(this.chances.length === 0) {
            return undefined;
        }
        let change = Math.random();
        for (const value of this.chances) {
            change -= value.chance;
            if(change <= 0) {
                return value;
            }
        }
        return this.chances[this.chances.length - 1]
    }
}