import { EmitableEvent, EventEmitter } from '../core/common/eventEmitter';

// ----- 加载事件
interface GameLoadEvent extends EmitableEvent {
    coreLoaded: () => void;
    autotileLoaded: () => void;
    coreInit: () => void;
    materialLoaded: () => void;
}

class GameLoading extends EventEmitter<GameLoadEvent> {
    private autotileLoaded: number = 0;
    private autotileNum?: number;
    private autotileListened: boolean = false;

    private materialsNum: number = main.materials.length;
    private materialsLoaded: number = 0;

    constructor() {
        super();
        this.on(
            'coreInit',
            () => {
                this.autotileNum = Object.keys(
                    core.material.icons.autotile
                ).length;
            },
            { immediate: true }
        );
        this.on('materialLoaded', () => {
            core.loader._loadMaterials_afterLoad();
        });
    }

    addMaterialLoaded() {
        this.materialsLoaded++;
        if (this.materialsLoaded === this.materialsNum) {
            this.emit('materialLoaded');
        }
    }

    addAutotileLoaded() {
        this.autotileLoaded++;
        if (this.autotileLoaded === this.autotileNum) {
            this.emit('autotileLoaded');
        }
    }

    /**
     * 当自动原件加载完毕时
     * @param autotiles 自动原件数组
     */
    onAutotileLoaded(
        autotiles: Partial<Record<AllIdsOf<'autotile'>, HTMLImageElement>>
    ) {
        if (this.autotileListened) return;
        this.autotileListened = true;
        this.on('autotileLoaded', () => {
            const keys = Object.keys(
                core.material.icons.autotile
            ) as AllIdsOf<'autotile'>[];

            keys.forEach(v => {
                core.material.images.autotile[v] = autotiles[v]!;
            });

            setTimeout(() => {
                core.maps._makeAutotileEdges();
            });
        });
    }
}

export const loading = new GameLoading();

export interface GameEvent extends EmitableEvent {
    /** Emitted in events.prototype.resetGame. */
    reset: () => void;
    /** Emitted in src/App.vue setup. */
    mounted: () => void;
    /** Emitted in plugin/ui.js */
    statusBarUpdate: () => void;
    /** Emitted in core/index.ts */
    renderLoaded: () => void;
    // /** Emitted in libs/events.js */
    afterGetItem: (
        itemId: AllIdsOf<'items'>,
        x: number,
        y: number,
        isGentleClick: boolean
    ) => void;
    afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void;
    afterChangeFloor: (floorId: FloorIds) => void;
    moveOneStep: (x: number, y: number, floorId: FloorIds) => void;
}

export const hook = new EventEmitter<GameEvent>();

interface ListenerEvent extends EmitableEvent {
    // block
    hoverBlock: (block: Block, ev: MouseEvent) => void;
    leaveBlock: (block: Block, ev: MouseEvent, leaveGame: boolean) => void;
    clickBlock: (block: Block, ev: MouseEvent) => void;
    // mouse
    mouseMove: (ev: MouseEvent) => void;
}

class GameListener extends EventEmitter<ListenerEvent> {
    static num: number = 0;

    num: number = GameListener.num++;

    constructor() {
        super();
        if (main.replayChecking) return;
        if (!!window.core) {
            this.init();
        } else {
            loading.once('coreInit', () => {
                this.init();
            });
        }
    }

    private init() {
        // ----- block
        let lastHoverX = -1;
        let lastHoverY = -1;

        const data = core.canvas.data.canvas;

        const getBlockLoc = (px: number, py: number, size: number) => {
            return [
                Math.floor(((px * 32) / size + core.bigmap.offsetX) / 32),
                Math.floor(((py * 32) / size + core.bigmap.offsetY) / 32)
            ];
        };

        // hover & leave & mouseMove
        data.addEventListener('mousemove', e => {
            if (
                core.status.lockControl ||
                !core.isPlaying() ||
                !core.status.floorId
            )
                return;
            this.emit('mouseMove', e);
            const {
                x: px,
                y: py,
                size
            } = core.actions._getClickLoc(e.clientX, e.clientY);
            const [bx, by] = getBlockLoc(px, py, size);
            const blocks = core.getMapBlocksObj();
            if (lastHoverX !== bx || lastHoverY !== by) {
                const lastBlock = blocks[`${lastHoverX},${lastHoverY}`];
                const block = blocks[`${bx},${by}`];
                if (!!lastBlock) {
                    this.emit('leaveBlock', lastBlock, e, false);
                }
                if (!!block) {
                    this.emit('hoverBlock', block, e);
                    lastHoverX = bx;
                    lastHoverY = by;
                } else {
                    lastHoverX = -1;
                    lastHoverY = -1;
                }
            }
        });
        data.addEventListener('mouseleave', e => {
            if (
                core.status.lockControl ||
                !core.isPlaying() ||
                !core.status.floorId
            )
                return;
            const blocks = core.getMapBlocksObj();
            const lastBlock = blocks[`${lastHoverX},${lastHoverY}`];
            if (!!lastBlock) {
                this.emit('leaveBlock', lastBlock, e, true);
            }
            lastHoverX = -1;
            lastHoverY = -1;
        });
        // click
        data.addEventListener('click', e => {
            if (
                core.status.lockControl ||
                !core.isPlaying() ||
                !core.status.floorId
            )
                return;
            const {
                x: px,
                y: py,
                size
            } = core.actions._getClickLoc(e.clientX, e.clientY);
            const [bx, by] = getBlockLoc(px, py, size);
            const blocks = core.getMapBlocksObj();
            const block = blocks[`${bx},${by}`];
            if (!!block) {
                this.emit('clickBlock', block, e);
            }
        });

        // ----- mouse
    }
}

export const gameListener = new GameListener();

declare global {
    interface Main {
        loading: GameLoading;
    }
}