/*!
 * Copyright 2017 - 2020 by ChartIQ, Inc.
 * All rights reserved.
 */
/**
 * Key combinations (sometimes referred to as "key chords") are defined as arrays. An example key combination is
 * `["shift", "ctrl", "f"]` which would trigger whenever a user presses those three keys simultaneously.
 *
 * Common keys include "shift", "ctrl", "alt", "backspace", "tab", "esc", "enter", "left", "right, "up", "down", "space",
 * "pgup", "pgdn". A complete list can be output by running:
 * ```javascript
 * console.log(FSBL.Clients.HotkeyClient.keyMap)
 * ```
 * Keep in mind that not every keyboard contains every key.
 *
 * Finsemble supports 2 types of hotkeys: **local hotkeys**, which are triggered only when the window that defined the
 * hotkey is in focus, and **global hotkeys**, which are triggered anytime regardless of whether the window is
 * in focus or not.
 *
 * For more info, see the [Hotkey tutorial](/docs/customize/customize-with-SDK/user-experience/Hotkeys).
 * @module HotkeyClient
 */
/**
This is how we want our API to work going foward, the way the world does it: import { processAndSet } from "configClient"
```
export const processAndSet = () => { ... }
```

This module's single exported object gives us backward compatibility when our core code does this: import { ConfigClient } from "configClient"
and provides the object that is attached to FSBL.Clients. This export should be kept as clean and simple as possible.
```
export const ConfigClient = {
   processAndSet
}
```

The default export gives us backward compatibility where our core code does this: import ConfigClient from "configClient"
This can be eliminated when our core core is converted to normal imports as in the first example.
```
export default ConfigClient;
```
*/
/**
 * Overview of how this works:
 * - hotkeys are added/removed via methods, passing an array of strings representing keys pressed, a handler method, and (optionally) a callback
 * - When adding a hotkey, a node js event emitter is created on the client side to trigger the hotkey handler, and a router message is sent to the service to register the key combination with the window name on the client side. Multiple hotkeys may be created for the same key combination, so long as they have different handler functions.
 * - When the service detects that all of the keys in the hotkey combination are pressed, it sends a message on the "HotkeyTriggered" channel (the method for this is "ListenForHotkeys") which contains the list of all windows registered with that hotkey combination. The client then reads the list of windows, and checks if it's one of those windows. If it is, it fires off the node js event emitter that was registered for that hotkey.
 * - Removing a hotkey clears the corresponding event emitter, and also sends a router message to the service to remove its window id from the array of windows registered for the hotkey combination - if the window is registered with that hotkey combination multiple times, it will only remove one, allowing other hotkeys on the same window with the same key combination to still be registered.
 */
import { Logger } from "./logger";
import { RouterClient } from "./routerClient";
import { Startup } from "./Startup/Startup";
import { singleton } from "../common/Singleton";
import { EventEmitter } from "events";
const keyMap = {
    backspace: "Backspace",
    bs: "Backspace",
    bksp: "Backspace",
    tab: "Tab",
    escape: "Escape",
    esc: "Escape",
    clear: "Clear",
    enter: "Enter",
    return: "Enter",
    shift: "Shift",
    shft: "Shift",
    lshift: "Shift",
    lshft: "Shift",
    "left shift": "Shift",
    leftshift: "Shift",
    rshift: "Shift",
    rshft: "Shift",
    "right shift": "Shift",
    rightshift: "Shift",
    control: "Control",
    ctrl: "Control",
    commandorcontrol: "CommandOrControl",
    alt: "Alt",
    alternate: "Alt",
    pause: "Pause",
    "caps lock": "Capslock",
    capslock: "Capslock",
    spacebar: "Space",
    space: "Space",
    "space bar": "Space",
    "page up": "PageUp",
    pgup: "PageUp",
    "pg up": "PageUp",
    "page down": "PageDown",
    pgdn: "PageDown",
    "pg dn": "PageDown",
    pgdown: "PageDown",
    end: "End",
    home: "Home",
    "left arrow": "Left",
    left: "Left",
    "up arrow": "Up",
    up: "Up",
    "right arrow": "Right",
    right: "Right",
    "down arrow": "Down",
    down: "Down",
    select: "Select",
    slct: "Select",
    print: "PrintScreen",
    prnt: "PrintScreen",
    execute: "Execute",
    "print screen": "PrintScreen",
    printscreen: "PrintScreen",
    "print scrn": "PrintScreen",
    printscrn: "PrintScreen",
    "prnt scrn": "PrintScreen",
    prntscrn: "PrintScreen",
    "prt scrn": "PrintScreen",
    prtscrn: "PrintScreen",
    "prt scn": "PrintScreen",
    prtscn: "PrintScreen",
    "prt scr": "PrintScreen",
    prtscr: "PrintScreen",
    "prt sc": "PrintScreen",
    prtsc: "PrintScreen",
    "pr sc": "PrintScreen",
    prsc: "PrintScreen",
    insert: "Insert",
    ins: "Insert",
    delete: "Delete",
    del: "Delete",
    0: "0",
    1: "1",
    2: "2",
    3: "3",
    4: "4",
    5: "5",
    6: "6",
    7: "7",
    8: "8",
    9: "9",
    a: "A",
    b: "B",
    c: "C",
    d: "D",
    e: "E",
    f: "F",
    g: "G",
    h: "H",
    i: "I",
    j: "J",
    k: "K",
    l: "L",
    m: "M",
    n: "N",
    o: "O",
    p: "P",
    q: "Q",
    r: "R",
    s: "S",
    t: "T",
    u: "U",
    v: "V",
    w: "W",
    x: "X",
    y: "Y",
    z: "Z",
    windows: "Super",
    "left windows": "Super",
    "right windows": "Super",
    "numpad 0": "num0",
    "numpad 1": "num1",
    "numpad 2": "num2",
    "numpad 3": "num3",
    "numpad 4": "num4",
    "numpad 5": "num5",
    "numpad 6": "num6",
    "numpad 7": "num7",
    "numpad 8": "num8",
    "numpad 9": "num9",
    f1: "F1",
    fn1: "F1",
    "function 1": "F1",
    f2: "F2",
    fn2: "F2",
    "function 2": "F2",
    f3: "F3",
    fn3: "F3",
    "function 3": "F3",
    f4: "F4",
    fn4: "F4",
    "function 4": "F4",
    f5: "F5",
    fn5: "F5",
    "function 5": "F5",
    f6: "F6",
    fn6: "F6",
    "function 6": "F6",
    f7: "F7",
    fn7: "F7",
    "function 7": "F7",
    f8: "F8",
    fn8: "F8",
    "function 8": "F8",
    f9: "F9",
    fn9: "F9",
    "function 9": "F9",
    f10: "F10",
    fn10: "F10",
    "function 10": "F10",
    f11: "F11",
    fn11: "F11",
    "function 11": "F11",
    f12: "F12",
    fn12: "F12",
    "function 12": "F12",
    f13: "F13",
    fn: "F13",
    "function 13": "F13",
    f14: "F14",
    fn14: "F14",
    "function 14": "F14",
    f15: "F15",
    fn15: "F15",
    "function 15": "F15",
    f16: "F16",
    fn16: "F16",
    "function 16": "F16",
    "num lock": "Numlock",
    numlock: "Numlock",
    "number lock": "Numlock",
    "numeric lock": "Numlock",
    "scroll lock": "Scrolllock",
    sclk: "Scrolllock",
    scrlk: "Scrolllock",
    slk: "Scrolllock",
    "*": "*",
    "+": "+",
    "-": "-",
    "/": "/",
    ";": ";",
    "=": "=",
    ",": ",",
    _: "-",
    ".": ".",
    "`": "`",
    "[": "[",
    "]": "]",
    "'": "'",
};
/**
 * Translates an array representing a key combination, each element of which represents a key, using keyDict, an object containing key-value pairs where the untranslated key representations are the keys, and the translated versions ready to be used by the service are the values.
 *
 * If you'd like to create a keymap for translation, look at the values of the keymaps included in the common folder.
 * @param {object} params
 * @param {object} params.keys array representing untranslated key representations
 * @param {object} keyDict
 */
const translateKeys = (params) => {
    const translatedKeys = [];
    params.keys.forEach((key) => {
        if (typeof key !== "string") {
            return Logger.system.error("FSBL.Clients.HotkeyClient - one of the keys passed into a function was not a string: ", key);
        }
        key = key.toLowerCase();
        const mappedKey = keyMap[key];
        if (mappedKey) {
            translatedKeys.push(mappedKey);
        }
        else {
            return Logger.system.error(`FSBL.Clients.HotkeyClient - At least one of the key codes does not map to a supported key - registering hotkey unsuccessful. Unsupported keys: ${key}`);
        }
    });
    return translatedKeys;
};
class KeyStroke {
    constructor() {
        // Used to keep track of which browser key combinations are registered locally
        this.keyStrokeHandlers = [];
        // Current key pressed down
        this.key = null;
        this.shift = false;
        this.ctrl = false;
        this.alt = false;
        this.cmd = false;
        this.capsLock = false;
        this.noKeyCapture = false;
        this.downValue = null;
    }
    registerKeyHandler(entry) {
        this.keyStrokeHandlers.push(entry);
    }
    unregisterKeyHandler(entry) {
        this.keyStrokeHandlers = this.keyStrokeHandlers.filter((localEntry) => localEntry.keyString !== entry.keyString || localEntry.handler !== entry.handler);
    }
    unregisterAllUsersKeyHandlers() {
        this.keyStrokeHandlers = this.keyStrokeHandlers.filter((entry) => entry.addedByUser !== true);
    }
    keyup(e) {
        switch (e.key) {
            case "Shift":
                this.shift = false;
                this.gotKeyStroke(e.key);
                return;
            case "Control":
            case "Alt":
                this.ctrl = false;
                this.gotKeyStroke(e.key);
                return;
            case "Meta":
            case "Win":
                this.cmd = false;
                this.gotKeyStroke(e.key);
                return;
            default:
                break;
        }
        // This is where we handle the keystroke, regardless of whether we captured the key with a down or press event
        // The exception to this is the arrow keys, which are processed in keydown
        if (this.key)
            this.gotKeyStroke(this.key);
    }
    keydown(e) {
        if (this.noKeyCapture)
            return;
        this.key = e.key;
        switch (e.key) {
            case "Meta":
            case "Win":
                this.cmd = true;
                break;
            case "Shift":
                this.shift = true;
                break;
            case "Control":
            case "Alt":
                this.ctrl = true;
                break;
            case "CapsLock":
                this.capsLock = !this.capsLock;
                break;
            case "ArrowUp":
            case "ArrowDown":
            case "ArrowLeft":
            case "ArrowRight":
            case "Up":
            case "Down":
            case "Left":
            case "Right":
                // If you hold a key down, then keydown will repeat. These are the keys
                // that we want to capture repeat action.
                this.key = null;
                this.gotKeyStroke(e.key);
                break;
            default:
                break;
        }
    }
    keypress(e) {
        if (this.noKeyCapture)
            return;
        const keyCode = e.which;
        if (keyCode < 32 || keyCode > 222)
            return; // handled by keydown
        this.key = e.key;
    }
    gotKeyStroke(key) {
        const myKeyArray = [key];
        if (this.ctrl)
            myKeyArray.push("control");
        if (this.shift)
            myKeyArray.push("shift");
        if (this.alt)
            myKeyArray.push("alt");
        if (this.cmd)
            myKeyArray.push("command");
        const myKeyString = myKeyArray.sort().toString();
        this.keyStrokeHandlers.forEach((entry) => {
            const keystringToMatch = entry.keyString.toUpperCase();
            const keystringFromUser = myKeyString.toUpperCase();
            // Cheap solution to match CommandOrControl, since this function is private
            // If we use this more, or if we make this public, this should be made more robust.
            // or switch to https://www.electronjs.org/docs/tutorial/keyboard-shortcuts
            if (keystringFromUser === keystringToMatch ||
                keystringFromUser.replace(/(CONTROL|COMMAND)/, "COMMANDORCONTROL") === keystringToMatch) {
                entry.handler();
            }
        });
    }
    initialize() {
        document.addEventListener("keyup", (e) => {
            this.keyup(e);
        });
        document.addEventListener("keydown", (e) => {
            this.downValue = e.key;
            this.keydown(e);
        });
        document.addEventListener("keypress", (e) => {
            this.keypress(e);
        });
        window.addEventListener("blur", () => {
            // Prevents ctrl-t when switching Chrome tabs from causeing this.ctrl to get stuck
            this.ctrl = false;
            this.gotKeyStroke("Control");
        });
    }
}
/**
 * The controller is responsible for maintaining a list of local listeners and registering with the
 * hot key service for inbound hot key events. The controller is initialized lazily, whenever it is first
 * used by an API call.
 */
class HotkeyClientController {
    constructor() {
        // Local hotkeys need to only fire if the window is focused. The object below is a map of handlers passed in by the user.
        // The keys are the handler, and the value is the wrapped method that checks for focus.
        this.localListeners = {};
        this.keyStrokes = null;
        this.eventEmitter = new EventEmitter();
        this.initialize();
    }
    /**
     * Adds a global listener for when a hot key is triggered. Every window receives every hot key event
     * so each window is responsible for filtering by its window name
     */
    async initialize() {
        const { windowName } = await Startup.getInstance().getStartupData();
        RouterClient.addListener("HotkeyTriggered", (error, response) => {
            if (error) {
                console.error(`Hotkey Channel Error: ${JSON.stringify(error)}`);
            }
            else if (response === null || response === void 0 ? void 0 : response.data.windows.includes(windowName)) {
                // if this is one of the windows that the service means to trigger here
                this.eventEmitter.emit(response.data.keys);
            }
        });
    }
}
const getControllerInstance = () => singleton("HotkeyClient", HotkeyClientController);
/**
 * @deprecated
 * Adds a local hotkey, firing only when the window calling the method is in focus. If you execute this function more than once for the same key combination, both hotkeys will coexist, and would need to be remove separately.
 * This function uses browser key capture, so it will work when assimilation is not running.
 *
 * This is exported because it is called by LinkerButton until that can be rewritten to use HTML5 events
 *
 * ```javascript
 * var myFunction = function () {}
 * FSBL.Clients.HotkeyClient.addBrowserHotkey(["ctrl","shift","s"],myFunction)
 * ```
 * @param {Array} [keyArr] Array of strings representing hotkey key combination.
 * @param {function} [handler] Function to be executed when the hotkey combination is pressed. It is recommended that you define a variable to represent the handler function, as the same function must be passed in order to remove the hotkey.
 * @param {boolean} [addedByUser] Indicates that Hotkey was added as Local Hotkey by user.
 * @internal
 * @private
 * @ignore
 */
export const addBrowserHotkey = (keyArr, handler, addedByUser) => {
    Logger.system.deprecated("HotkeyClient.addBrowserHotkey() is deprecated.");
    const controller = getControllerInstance();
    // Lazily create a keystroke handler for this web page if one doesn't already exist
    if (!controller.keyStrokes) {
        controller.keyStrokes = new KeyStroke();
        controller.keyStrokes.initialize();
    }
    const keyString = translateKeys({ keys: keyArr }).sort().toString();
    controller.keyStrokes.registerKeyHandler({ keyString, handler, addedByUser });
};
/**
 * @language javascript
 * @deprecated Use HTML5 key events when a window is in focus.
 *
 * Adds a local hotkey which fires only when the window calling the method is in focus. If you execute this function more than once for the same key combination, both hotkeys will coexist, and would need to be removed separately.
 *
 * ```javascript
 * var myFunction = function () { }
 * FSBL.Clients.HotkeyClient.addLocalHotkey(["ctrl", "shift", "s"], myFunction, cb)
 * ```
 * @param {Array.<string>} keyArr Array of strings representing hotkey key combination.
 * @param {function} handler Function to be executed when the hotkey combination is pressed. It is recommended that you define a variable to represent the handler function, as the same function must be passed in order to remove the hotkey.
 * @param {StandardCallback} cb Callback to be called after local hotkey is added.
 */
export const addLocalHotkey = async (keyArr, handler, cb) => {
    Logger.system.deprecated("FSBL.Clients.HotkeyClient.addLocalHotkey is no longer supported and will likely go away in the future. Use HTML5 key events when a window is in focus instead.");
    Logger.system.info("HotkeyClient.addLocalHotkey");
    Logger.system.debug("HotkeyClient.addLocalHotkey, keyArr: ", keyArr);
    addBrowserHotkey(keyArr, handler, true);
    // leaves `err` for backward compatibilities
    const err = null;
    cb === null || cb === void 0 ? void 0 : cb(err);
    return { err };
};
/**
 * Removes a local hotkey.
 *
 * ```javascript
 * FSBL.Clients.HotkeyClient.removeBrowserHotkey(["ctrl", "shift", "s"], myFunction)
 * ```
 * @param {Array.<string>} keyArr Array of strings representing hotkey key combination.
 * @param {function} handler Handler registered for the hotkey to be removed.
 */
const removeBrowserHotkey = (keyArr, handler) => {
    var _a;
    const controller = getControllerInstance();
    Logger.system.info("HotkeyClient.removeBrowserHotkey");
    Logger.system.debug("HotkeyClient.removeBrowserHotkey, keyArr: ", keyArr);
    const keyString = translateKeys({ keys: keyArr }).sort().toString();
    (_a = controller.keyStrokes) === null || _a === void 0 ? void 0 : _a.unregisterKeyHandler({ keyString, handler });
};
/**
 * @language javascript
 * @deprecated Use HTML5 key events when a window is in focus.
 *
 * Removes a local hotkey.
 *
 * ```javascript
 * FSBL.Clients.HotkeyClient.removeLocalHotkey(["ctrl", "shift", "s"], myFunction, cb)
 * ```
 * @param {Array.<string>} keyArr Array of strings representing hotkey key combination.
 * @param {function} handler Handler registered for the hotkey to be removed.
 * @param {StandardCallback} cb Callback to be called after local hotkey is removed.
 */
export const removeLocalHotkey = async (keyArr, handler, cb) => {
    Logger.system.deprecated("FSBL.Clients.HotkeyClient.removeLocalHotkey is no longer supported and will likely go away in the future. Use HTML5 key events when a window is in focus instead.");
    Logger.system.info("HotkeyClient.removeLocalHotkey");
    Logger.system.debug("HotkeyClient.removeLocalHotkey, keyArr: ", keyArr);
    removeBrowserHotkey(keyArr, handler);
    // leaves `err` for backward compatibilities
    const err = null;
    cb === null || cb === void 0 ? void 0 : cb(err);
    return { err };
};
/**
 * @language javascript, java, dotnet
 * Adds a global hotkey which fires regardless of what window is in focus. If you execute this function more than once for the same key combination, both hotkeys will coexist, and would need to be removed separately.
 *
 * ```javascript
 * var myFunction = function () {}
 * FSBL.Clients.HotkeyClient.addGlobalHotkey(["ctrl", "shift", "s"], myFunction, (err) => {
 *   if(er){
 *     // Hotkey failed to register
 *   }
 * });
 *
 * const {err} = await FSBL.Clients.HotkeyClient.addGlobalHotkey(["ctrl", "shift", "s"], myFunction);
 * if(err){
 *   // Hotkey failed to register
 * }
 * ```
 * @param {Array.<string>} keyArr Array of strings representing a hotkey key combination.
 * @param {function} handler Function to be executed when the hotkey combination is pressed. It is recommended that you define a variable to represent the handler function, as the same function must be passed in order to remove the hotkey.
 * @param {StandardCallback} cb Callback to be called after local hotkey is added.
 */
export const addGlobalHotkey = async (keyArr, handler, cb) => {
    const controller = getControllerInstance();
    Logger.system.info("HotkeyClient.addGlobalHotkey");
    Logger.system.debug("HotkeyClient.addGlobalHotkey, keyArr: ", keyArr);
    const keyString = translateKeys({ keys: keyArr }).sort().join("+");
    controller.eventEmitter.addListener(keyString, handler);
    const { windowName } = await Startup.getInstance().getStartupData();
    const { err } = await RouterClient.query("hotkeysService.registerGlobalHotkey", {
        keys: keyString,
        windowName,
    });
    cb === null || cb === void 0 ? void 0 : cb(err);
    return { err };
};
/**
 * @language javascript, java, dotnet
 * Removes a global hotkey.
 *
 * ```javascript
 * FSBL.Clients.HotkeyClient.removeGlobalHotkey(["ctrl", "shift", "s"], myFunction, cb);
 *
 * const {err} = await FSBL.Client.HotkeyClient.removeGlobalHotkey(["ctrl", "shift", "s"]);
 * ```
 * @param {Array.<string>} keyArr Array of strings representing hotkey key combination.
 * @param {function} handler Handler registered for the hotkey to be removed.
 * @param {StandardCallback} cb Callback to be called after local hotkey is removed.
 */
export const removeGlobalHotkey = async (keyArr, handler, cb) => {
    const controller = getControllerInstance();
    Logger.system.info("HotkeyClient.removeGlobalHotkey");
    Logger.system.debug("HotkeyClient.removeGlobalHotkey, keyArr: ", keyArr);
    const keyString = translateKeys({ keys: keyArr }).sort().join("+");
    controller.eventEmitter.removeListener(keyString, handler);
    const { windowName } = await Startup.getInstance().getStartupData();
    const { err } = await RouterClient.query("hotkeysService.unregisterGlobalHotkey", {
        keys: keyString,
        windowName,
    });
    cb === null || cb === void 0 ? void 0 : cb(err);
    return { err };
};
/**
 * @language javascript, java, dotnet
 * Unregister all hotkeys, both locally and service-side.
 * @param {StandardCallback} cb Optional callback function
 */
export const removeAllHotkeys = async (cb) => {
    var _a;
    const controller = getControllerInstance();
    controller.eventEmitter.removeAllListeners();
    const { windowName } = await Startup.getInstance().getStartupData();
    const { err } = await RouterClient.query("hotkeysService.removeAllHotkeysForWindow", {
        windowName,
    });
    // remove locals hotkeys
    (_a = controller.keyStrokes) === null || _a === void 0 ? void 0 : _a.unregisterAllUsersKeyHandlers();
    cb === null || cb === void 0 ? void 0 : cb(err);
    return { err };
};
/**
 * Automatically unregister all hotkeys when the window containing the client closes
 * @ignore
 */
export const onClose = (cb) => {
    removeAllHotkeys(cb);
};
/**
 * @ignore
 */
export const HotkeyClient = {
    /**
     * For backward compatibility
     * @private
     * @deprecated
     */
    initialize: () => {
        Logger.system.deprecated("It is no longer necessary to call HotkeyClient.initialize()");
    },
    /**
     * For backward compatibility
     * @private
     * @deprecated
     */
    onReady: (cb) => {
        Logger.system.deprecated("It is no longer necessary to call HotkeyClient.onReady()");
        cb === null || cb === void 0 ? void 0 : cb();
    },
    onClose,
    addGlobalHotkey,
    removeAllHotkeys,
    removeGlobalHotkey,
    addLocalHotkey,
    removeLocalHotkey,
    addBrowserHotkey,
};
/**
 * @ignore
 */
export default HotkeyClient;
//# sourceMappingURL=hotkeyClient.js.map