import { AssetPreloaderService } from "../../../assetPreloaderService";
import { GdRoot } from "../data/GdRoot";
import { ECellType } from "../sim/ECellType";
import { SimCell } from "../sim/SimCell";
import { SimEnemy } from "../sim/SimEnemy";
import { SimLevel } from "../sim/SimLevel";
import { SimMain } from "../sim/SimMain";
import { SimProjectile } from "../sim/SimProjectile";
import { Hex } from "../util/Hex";
import { Vector2 } from "../util/Vector2";
import { VisEnemy } from "./VisEnemy";
import { VisMain } from "./VisMain";
import { VisProjectile } from "./VisProjectile";

export class VisLevel {
    private screenCellWidth: number = -1;
    private screenCellHeight: number = -1;
    private screenXOffset: number = -1;
    private screenYOffset: number = -1;
    private hexSize: number = -1;
    private lastStep: number = -1;
    private projectileMap: Map<SimProjectile, VisProjectile>;
    private enemyMap: Map<SimEnemy, VisEnemy>;
    private background: HTMLCanvasElement | null = null;
    private simLevel: SimLevel | null = null;
    private visMain: VisMain;
    private simMain: SimMain;
    private gdRoot: GdRoot;
    assets: AssetPreloaderService;

    constructor(visMain: VisMain, simMain: SimMain, gdRoot: GdRoot, assets: AssetPreloaderService) {
        this.assets = assets;
        this.visMain = visMain;
        this.simMain = simMain;
        this.gdRoot = gdRoot
        this.enemyMap = new Map<SimEnemy, VisEnemy>();
        this.projectileMap = new Map<SimProjectile, VisProjectile>();
        this.reset();
    }

    public reset() {
        this.projectileMap.clear();
        this.enemyMap.clear();
        this.background = null;
    }

    public draw() {
        const ctx = this.visMain.context;
        const simLevel = this.simMain.currentLevel;
        if (simLevel == null) {
            return;
        }
        const gdLevel = this.gdRoot.levels[simLevel.index];

        if (simLevel != this.simLevel) {
            this.reset();
            this.simLevel = simLevel;
        }

        this.drawBackground();
        ctx.globalCompositeOperation = "source-over";

        simLevel.cells.forEach((cell: SimCell) => {
            if (cell.distance > gdLevel.radius) {
                return;
            }
            this.drawCell(cell);
        });

        simLevel.enemies.forEach((enemy: SimEnemy) => {
            this.drawEnemy(enemy);
        });

        simLevel.projectiles.forEach((projectile: SimProjectile) => {
            this.drawProjectile(projectile);
        });

        ctx.fillStyle = "white";
        ctx.fillText("Currency: " + simLevel.currency, 5, 15);
        ctx.fillText("Current wave: " + simLevel.currentWave, 5, 35);
        ctx.fillText("Enemies left: " + simLevel.enemiesLeftToSpawn, 5, 55);
        ctx.fillText("Current step: " + simLevel.currentStep, 5, 75);
    }

    public updateSize() {
        const simLevel = this.simMain.currentLevel;
        if (simLevel == null) {
            return;
        }

        const gdLevel = this.gdRoot.levels[simLevel.index];
        const minSize = Math.min(this.visMain.canvas.height, this.visMain.canvas.width * Math.sqrt(3) / 2);
        this.hexSize = Math.floor(minSize / (4 * (gdLevel.radius - 1) - 4));
        this.screenCellHeight = Math.ceil(2 * this.hexSize);
        this.screenCellHeight = Math.ceil(this.screenCellHeight * 0.5) * 2;
        this.screenCellWidth = Math.ceil(Math.sqrt(3) / 2 * this.screenCellHeight);
        this.screenCellWidth = Math.ceil(this.screenCellWidth * 0.5) * 2;
        this.screenXOffset = this.screenCellWidth * (gdLevel.radius + 0.5);
        this.screenYOffset = this.screenCellHeight * (gdLevel.radius + 0.5);

        const width = this.screenCellWidth * (gdLevel.radius * 2 + 1);
        const height = this.screenCellHeight * (gdLevel.radius * 2 + 1);
        this.screenXOffset += (this.visMain.canvas.width - width) * 0.5;
        this.screenYOffset += (this.visMain.canvas.height - height) * 0.5;

        this.screenXOffset = Math.floor(this.screenXOffset);
        this.screenYOffset = Math.floor(this.screenYOffset);
        this.background = null;
        this.enemyMap.clear();
    }

    public updateEveryFrame(currentStep: number) {
        const simLevel = this.simMain.currentLevel;
        if (simLevel == null) {
            return;
        }

        const t = currentStep - Math.floor(currentStep);

        const deadEnemies: SimEnemy[] = [];
        simLevel.enemies.forEach((simEnemy: SimEnemy) => {
            if (simEnemy.dead) {
                deadEnemies.push(simEnemy);
                return;
            }

            const visEnemy = this.enemyMap.get(simEnemy);
            if (!visEnemy) {
                this.enemyMap.set(simEnemy, new VisEnemy(this.gdRoot, this.assets, simEnemy, this.screenCellWidth, this.screenCellHeight));
            }
            else if (Math.floor(currentStep) != Math.floor(this.lastStep)) {
                visEnemy.advanceStep();
            }
            else {
                visEnemy.update(t);
            }
        });
        for (const deadEnemy of deadEnemies) {
            this.enemyMap.delete(deadEnemy);
        }

        const deadProjectiles: SimProjectile[] = [];
        simLevel.projectiles.forEach((simProjectile: SimProjectile) => {
            if (simProjectile.dead) {
                deadProjectiles.push(simProjectile);
                return;
            }

            const visProjectile = this.projectileMap.get(simProjectile);
            if (!visProjectile) {
                this.projectileMap.set(simProjectile, new VisProjectile(simProjectile));
            }
            else if (Math.floor(currentStep) != Math.floor(this.lastStep)) {
                visProjectile.advanceStep();
            }
        });
        for (const deadProjectile of deadProjectiles) {
            this.projectileMap.delete(deadProjectile);
        }

        this.lastStep = currentStep;
    }

    public getScreenCoords(hex: Hex): Vector2 {
        const coord = Hex.toPixel(hex, this.hexSize);
        return new Vector2(coord.x + this.screenXOffset - this.screenCellWidth / 2, coord.y + this.screenYOffset - this.screenCellHeight / 2);
    }

    public getHexFromScreenCoords(coords: Vector2): Hex {
        const x = coords.x - this.screenXOffset;
        const y = coords.y - this.screenYOffset;
        return Hex.fromPixel(new Vector2(x, y), this.hexSize);
    }

    private drawBackground() {
        const ctx = this.visMain.context;
        const simLevel = this.simMain.currentLevel;
        if (simLevel == null) {
            return;
        }

        const gdLevel = this.gdRoot.levels[simLevel.index];
        if (this.background == null) {
            const backgroundCanvas = this.visMain.canvas.cloneNode() as HTMLCanvasElement;
            const backgroundContext = backgroundCanvas.getContext("2d")!;
            this.background = backgroundCanvas;
            simLevel.cells.forEach((cell: SimCell) => {
                if (cell.distance > gdLevel.radius) {
                    return;
                }
                if (cell.blockedType != -1 && cell.type == ECellType.Blocked) {
                    this.drawCellImage(backgroundContext, cell, "cell-blocked-" + (cell.blockedType | 0) + ".svg");
                }
            });

            backgroundContext.globalCompositeOperation = "source-atop";
            backgroundContext.fillStyle = this.visMain.wallPattern;
            backgroundContext.fillRect(0, 0, this.visMain.canvas.width, this.visMain.canvas.height);

            const cellCanvas = this.visMain.canvas.cloneNode() as HTMLCanvasElement;
            const cellContext = cellCanvas.getContext("2d")!;
            simLevel.cells.forEach((cell: SimCell) => {
                if (cell.distance > gdLevel.radius) {
                    return;
                }
                if (cell.type != ECellType.Entry) {
                    this.drawCellImage(cellContext, cell, "cell.svg");
                }
            });
            backgroundContext.globalCompositeOperation = "destination-over";
            backgroundContext.drawImage(cellCanvas, 0, 0);
            backgroundContext.globalCompositeOperation = "source-over";
        }

        ctx.drawImage(this.background, 0, 0);
    }

    private drawProjectile(simProjectile: SimProjectile) {
        const visProjectile = this.projectileMap.get(simProjectile);
        if (!visProjectile) {
            return;
        }
        const t = this.lastStep - Math.floor(this.lastStep);
        const positions = visProjectile.positions;
        if (!positions) {
            return;
        }
        const pos = Vector2.lerp(positions[0], positions[1], t);
        const width = this.screenCellWidth * simProjectile.size;
        const height = this.screenCellHeight * simProjectile.size;
        this.visMain.context.drawImage(this.assets.getImage("images/projectile.svg"),
            this.screenXOffset + pos.x * this.hexSize - width / 2,
            this.screenYOffset + pos.y * this.hexSize - height / 2,
            width,
            height);
    }

    private drawEnemy(simEnemy: SimEnemy) {
        const visEnemy = this.enemyMap.get(simEnemy);
        if (!visEnemy) {
            return;
        }
        const t = this.lastStep - Math.floor(this.lastStep);
        const positions = visEnemy.positions;
        if (!positions) {
            return;
        }
        const pos = Vector2.lerp(positions[0], positions[1], t);
        this.visMain.context.drawImage(
            visEnemy.image,
            this.screenXOffset + pos.x * this.hexSize - this.screenCellWidth / 2,
            this.screenYOffset + pos.y * this.hexSize - this.screenCellHeight / 2,
            this.screenCellWidth,
            this.screenCellHeight);
    }

    private drawCell(cell: SimCell) {
        const simLevel = this.simMain.currentLevel;
        if (simLevel == null) {
            return;
        }

        this.visMain.context.fillStyle = "rgba(192, 192, 192, 0.25)";
        if (cell.type == ECellType.Entry) {
            this.drawCellImage(this.visMain.context, cell, "cell-entry-" + (cell.blockedType | 0) + ".svg");
        }

        const highlightedCell = simLevel.cells[simLevel.highlightedIndex];
        if (!!highlightedCell) {
            let draw = highlightedCell.index == cell.index;
            if (draw && highlightedCell.pathsToTarget != null) {
                for (const routeIdx in highlightedCell.pathsToTarget) {
                    for (const idx in highlightedCell.pathsToTarget[routeIdx]) {
                        if (highlightedCell.pathsToTarget[routeIdx][idx] == highlightedCell.index) {
                            draw = true;
                            break;
                        }
                    }
                }
            }
            if (draw) {
                this.drawCellImage(this.visMain.context, highlightedCell, "cell-highlighted.svg");
                this.visMain.context.fillStyle = "rgba(0, 0, 0, 1)";
            }
        }
        if (cell.tower != null) {
            this.drawCellImage(this.visMain.context, cell, "tower-" + (cell.tower.index | 0) + ".svg");
        }

        const coords = this.getScreenCoords(cell.hex);
        this.visMain.context.fillText("(" + cell.hex.col + ", " + cell.hex.row + ")", coords.x + 10, coords.y + this.screenCellHeight / 2 + 5);
    }

    private drawCellImage(context: CanvasRenderingContext2D, cell: SimCell, name: string) {
        const coords = this.getScreenCoords(cell.hex);
        context.drawImage(this.assets.getImage("images/" + name), coords.x, coords.y, this.screenCellWidth, this.screenCellHeight);
    }
}
