import { Logger } from '../Util/Log';
import * as Events from '../Events';
import { isCrossOriginIframe } from '../Util/IFrame';
import { EventEmitter } from '../EventEmitter';
/**
 * Enum representing physical input key codes
 */
export var Keys;
(function (Keys) {
    // NUMPAD
    Keys["Num0"] = "Numpad0";
    Keys["Num1"] = "Numpad1";
    Keys["Num2"] = "Numpad2";
    Keys["Num3"] = "Numpad3";
    Keys["Num4"] = "Numpad4";
    Keys["Num5"] = "Numpad5";
    Keys["Num6"] = "Numpad6";
    Keys["Num7"] = "Numpad7";
    Keys["Num8"] = "Numpad8";
    Keys["Num9"] = "Numpad9";
    Keys["NumAdd"] = "NumpadAdd";
    Keys["NumSubtract"] = "NumpadSubtract";
    Keys["NumMultiply"] = "NumpadMultiply";
    Keys["NumDivide"] = "NumpadDivide";
    // NumComma = 'NumpadComma', // not x-browser
    Keys["NumDecimal"] = "NumpadDecimal";
    Keys["Numpad0"] = "Numpad0";
    Keys["Numpad1"] = "Numpad1";
    Keys["Numpad2"] = "Numpad2";
    Keys["Numpad3"] = "Numpad3";
    Keys["Numpad4"] = "Numpad4";
    Keys["Numpad5"] = "Numpad5";
    Keys["Numpad6"] = "Numpad6";
    Keys["Numpad7"] = "Numpad7";
    Keys["Numpad8"] = "Numpad8";
    Keys["Numpad9"] = "Numpad9";
    Keys["NumpadAdd"] = "NumpadAdd";
    Keys["NumpadSubtract"] = "NumpadSubtract";
    Keys["NumpadMultiply"] = "NumpadMultiply";
    Keys["NumpadDivide"] = "NumpadDivide";
    // NumpadComma = 'NumpadComma', // not x-browser
    Keys["NumpadDecimal"] = "NumpadDecimal";
    // MODIFIERS
    Keys["NumLock"] = "NumLock";
    Keys["ShiftLeft"] = "ShiftLeft";
    Keys["ShiftRight"] = "ShiftRight";
    Keys["AltLeft"] = "AltLeft";
    Keys["AltRight"] = "AltRight";
    Keys["ControlLeft"] = "ControlLeft";
    Keys["ControlRight"] = "ControlRight";
    Keys["MetaLeft"] = "MetaLeft";
    Keys["MetaRight"] = "MetaRight";
    // NUMBERS
    Keys["Key0"] = "Digit0";
    Keys["Key1"] = "Digit1";
    Keys["Key2"] = "Digit2";
    Keys["Key3"] = "Digit3";
    Keys["Key4"] = "Digit4";
    Keys["Key5"] = "Digit5";
    Keys["Key6"] = "Digit6";
    Keys["Key7"] = "Digit7";
    Keys["Key8"] = "Digit8";
    Keys["Key9"] = "Digit9";
    Keys["Digit0"] = "Digit0";
    Keys["Digit1"] = "Digit1";
    Keys["Digit2"] = "Digit2";
    Keys["Digit3"] = "Digit3";
    Keys["Digit4"] = "Digit4";
    Keys["Digit5"] = "Digit5";
    Keys["Digit6"] = "Digit6";
    Keys["Digit7"] = "Digit7";
    Keys["Digit8"] = "Digit8";
    Keys["Digit9"] = "Digit9";
    // FUNCTION KEYS
    Keys["F1"] = "F1";
    Keys["F2"] = "F2";
    Keys["F3"] = "F3";
    Keys["F4"] = "F4";
    Keys["F5"] = "F5";
    Keys["F6"] = "F6";
    Keys["F7"] = "F7";
    Keys["F8"] = "F8";
    Keys["F9"] = "F9";
    Keys["F10"] = "F10";
    Keys["F11"] = "F11";
    Keys["F12"] = "F12";
    // LETTERS
    Keys["A"] = "KeyA";
    Keys["B"] = "KeyB";
    Keys["C"] = "KeyC";
    Keys["D"] = "KeyD";
    Keys["E"] = "KeyE";
    Keys["F"] = "KeyF";
    Keys["G"] = "KeyG";
    Keys["H"] = "KeyH";
    Keys["I"] = "KeyI";
    Keys["J"] = "KeyJ";
    Keys["K"] = "KeyK";
    Keys["L"] = "KeyL";
    Keys["M"] = "KeyM";
    Keys["N"] = "KeyN";
    Keys["O"] = "KeyO";
    Keys["P"] = "KeyP";
    Keys["Q"] = "KeyQ";
    Keys["R"] = "KeyR";
    Keys["S"] = "KeyS";
    Keys["T"] = "KeyT";
    Keys["U"] = "KeyU";
    Keys["V"] = "KeyV";
    Keys["W"] = "KeyW";
    Keys["X"] = "KeyX";
    Keys["Y"] = "KeyY";
    Keys["Z"] = "KeyZ";
    Keys["KeyA"] = "KeyA";
    Keys["KeyB"] = "KeyB";
    Keys["KeyC"] = "KeyC";
    Keys["KeyD"] = "KeyD";
    Keys["KeyE"] = "KeyE";
    Keys["KeyF"] = "KeyF";
    Keys["KeyG"] = "KeyG";
    Keys["KeyH"] = "KeyH";
    Keys["KeyI"] = "KeyI";
    Keys["KeyJ"] = "KeyJ";
    Keys["KeyK"] = "KeyK";
    Keys["KeyL"] = "KeyL";
    Keys["KeyM"] = "KeyM";
    Keys["KeyN"] = "KeyN";
    Keys["KeyO"] = "KeyO";
    Keys["KeyP"] = "KeyP";
    Keys["KeyQ"] = "KeyQ";
    Keys["KeyR"] = "KeyR";
    Keys["KeyS"] = "KeyS";
    Keys["KeyT"] = "KeyT";
    Keys["KeyU"] = "KeyU";
    Keys["KeyV"] = "KeyV";
    Keys["KeyW"] = "KeyW";
    Keys["KeyX"] = "KeyX";
    Keys["KeyY"] = "KeyY";
    Keys["KeyZ"] = "KeyZ";
    // SYMBOLS
    Keys["Semicolon"] = "Semicolon";
    Keys["Quote"] = "Quote";
    Keys["Comma"] = "Comma";
    Keys["Minus"] = "Minus";
    Keys["Period"] = "Period";
    Keys["Slash"] = "Slash";
    Keys["Equal"] = "Equal";
    Keys["BracketLeft"] = "BracketLeft";
    Keys["Backslash"] = "Backslash";
    Keys["BracketRight"] = "BracketRight";
    Keys["Backquote"] = "Backquote";
    // DIRECTIONS
    Keys["Up"] = "ArrowUp";
    Keys["Down"] = "ArrowDown";
    Keys["Left"] = "ArrowLeft";
    Keys["Right"] = "ArrowRight";
    Keys["ArrowUp"] = "ArrowUp";
    Keys["ArrowDown"] = "ArrowDown";
    Keys["ArrowLeft"] = "ArrowLeft";
    Keys["ArrowRight"] = "ArrowRight";
    // OTHER
    Keys["Space"] = "Space";
    Keys["Backspace"] = "Backspace";
    Keys["Delete"] = "Delete";
    Keys["Esc"] = "Escape";
    Keys["Escape"] = "Escape";
    Keys["Enter"] = "Enter";
    Keys["NumpadEnter"] = "NumpadEnter";
    Keys["ContextMenu"] = "ContextMenu";
})(Keys || (Keys = {}));
/**
 * Event thrown on a game object for a key event
 */
export class KeyEvent extends Events.GameEvent {
    /**
     * @param key  The key responsible for throwing the event
     * @param value The key's typed value the browser detected
     * @param originalEvent The original keyboard event that Excalibur handled
     */
    constructor(key, value, originalEvent) {
        super();
        this.key = key;
        this.value = value;
        this.originalEvent = originalEvent;
    }
}
export const KeyEvents = {
    Press: 'press',
    Hold: 'hold',
    Release: 'release'
};
/**
 * Provides keyboard support for Excalibur.
 */
export class Keyboard {
    constructor() {
        this.events = new EventEmitter();
        this._enabled = true;
        /**
         * Keys that are currently held down
         */
        this._keys = [];
        /**
         * Keys up in the current frame
         */
        this._keysUp = [];
        /**
         * Keys down in the current frame
         */
        this._keysDown = [];
        this._releaseAllKeys = (ev) => {
            for (const code of this._keys) {
                const keyEvent = new KeyEvent(code, ev.key, ev);
                this.events.emit('up', keyEvent);
                this.events.emit('release', keyEvent);
            }
            this._keysUp = Array.from(new Set(this._keys.concat(this._keysUp)));
            this._keys.length = 0;
        };
        this._handleKeyDown = (ev) => {
            if (!this._enabled) {
                return;
            }
            // handle macos meta key issue
            // https://github.com/excaliburjs/Excalibur/issues/2608
            if (!ev.metaKey && (this._keys.includes(Keys.MetaLeft) || this._keys.includes(Keys.MetaRight))) {
                this._releaseAllKeys(ev);
            }
            const code = ev.code;
            if (this._keys.indexOf(code) === -1) {
                this._keys.push(code);
                this._keysDown.push(code);
                const keyEvent = new KeyEvent(code, ev.key, ev);
                this.events.emit('down', keyEvent);
                this.events.emit('press', keyEvent);
            }
        };
        this._handleKeyUp = (ev) => {
            if (!this._enabled) {
                return;
            }
            const code = ev.code;
            const key = this._keys.indexOf(code);
            this._keys.splice(key, 1);
            this._keysUp.push(code);
            const keyEvent = new KeyEvent(code, ev.key, ev);
            // alias the old api, we may want to deprecate this in the future
            this.events.emit('up', keyEvent);
            this.events.emit('release', keyEvent);
            // handle macos meta key issue
            // https://github.com/excaliburjs/Excalibur/issues/2608
            if (ev.key === 'Meta') {
                this._releaseAllKeys(ev);
            }
        };
    }
    emit(eventName, event) {
        this.events.emit(eventName, event);
    }
    on(eventName, handler) {
        return this.events.on(eventName, handler);
    }
    once(eventName, handler) {
        return this.events.once(eventName, handler);
    }
    off(eventName, handler) {
        this.events.off(eventName, handler);
    }
    /**
     * Initialize Keyboard event listeners
     */
    init(keyboardOptions) {
        let { global } = keyboardOptions;
        const { grabWindowFocus } = keyboardOptions;
        if (!global) {
            if (isCrossOriginIframe()) {
                global = window;
                // Workaround for iframes like for itch.io or codesandbox
                // https://www.reddit.com/r/gamemaker/comments/kfs5cs/keyboard_inputs_no_longer_working_in_html5_game/
                // https://forum.gamemaker.io/index.php?threads/solved-keyboard-issue-on-itch-io.87336/
                if (grabWindowFocus) {
                    window.focus();
                }
                Logger.getInstance().warn('Excalibur might be in a cross-origin iframe, in order to receive keyboard events it must be in focus');
            }
            else {
                global = window.top;
            }
        }
        global.addEventListener('blur', () => {
            this._keys.length = 0; // empties array efficiently
        });
        // key up is on window because canvas cannot have focus
        global.addEventListener('keyup', this._handleKeyUp);
        // key down is on window because canvas cannot have focus
        global.addEventListener('keydown', this._handleKeyDown);
    }
    toggleEnabled(enabled) {
        this._enabled = enabled;
    }
    update() {
        // Reset keysDown and keysUp after update is complete
        this._keysDown.length = 0;
        this._keysUp.length = 0;
        // Emit synthetic "hold" event
        for (let i = 0; i < this._keys.length; i++) {
            this.events.emit('hold', new KeyEvent(this._keys[i]));
        }
    }
    /**
     * Gets list of keys being pressed down
     */
    getKeys() {
        return this._keys;
    }
    /**
     * Tests if a certain key was just pressed this frame. This is cleared at the end of the update frame.
     * @param key Test whether a key was just pressed
     */
    wasPressed(key) {
        return this._keysDown.indexOf(key) > -1;
    }
    /**
     * Tests if a certain key is held down. This is persisted between frames.
     * @param key  Test whether a key is held down
     */
    isHeld(key) {
        return this._keys.indexOf(key) > -1;
    }
    /**
     * Tests if a certain key was just released this frame. This is cleared at the end of the update frame.
     * @param key  Test whether a key was just released
     */
    wasReleased(key) {
        return this._keysUp.indexOf(key) > -1;
    }
    /**
     * Trigger a manual key event
     * @param type
     * @param key
     * @param character
     */
    triggerEvent(type, key, character) {
        if (type === 'down') {
            this._handleKeyDown(new KeyboardEvent('keydown', {
                code: key,
                key: character !== null && character !== void 0 ? character : null
            }));
        }
        if (type === 'up') {
            this._handleKeyUp(new KeyboardEvent('keyup', {
                code: key,
                key: character !== null && character !== void 0 ? character : null
            }));
        }
    }
}
//# sourceMappingURL=Keyboard.js.map