const Cast = require('../util/cast');

class Keyboard {
    constructor (runtime) {
        /**
         * List of currently pressed scratch keys.
         * A scratch key is:
         * A key you can press on a keyboard, excluding modifier keys.
         * An uppercase string of length one;
         *     except for special key names for arrow keys and space (e.g. 'left arrow').
         * Can be a non-english unicode letter like: æ ø ש נ 手 廿.
         * @type{Array.<string>}
         */
        this._keysPressed = [];
        /**
         * Reference to the owning Runtime.
         * Can be used, for example, to activate hats.
         * @type{!Runtime}
         */
        this.runtime = runtime;
    }

    /**
     * Names used for a set of special keys in Scratch.
     * @type {Array.<string>}
     */
    static get SPECIAL_KEY_NAMES () {
        return [
            'space',
            'left arrow',
            'up arrow',
            'right arrow',
            'down arrow'
        ];
    }

    /**
     * Convert from a keyboard event key name to a Scratch key name.
     * @param  {string} keyString the input key string.
     * @return {string} the corresponding Scratch key, or an empty string.
     */
    _keyStringToScratchKey (keyString) {
        keyString = Cast.toString(keyString);
        // Convert space and arrow keys to their Scratch key names.
        switch (keyString) {
        case ' ': return 'space';
        case 'ArrowLeft':
        case 'Left': return 'left arrow';
        case 'ArrowUp':
        case 'Up': return 'up arrow';
        case 'Right':
        case 'ArrowRight': return 'right arrow';
        case 'Down':
        case 'ArrowDown': return 'down arrow';
        }
        // Ignore modifier keys
        if (keyString.length > 1) {
            return '';
        }
        return keyString.toUpperCase();
    }

    /**
     * Convert from a block argument to a Scratch key name.
     * @param  {string} keyArg the input arg.
     * @return {string} the corresponding Scratch key.
     */
    _keyArgToScratchKey (keyArg) {
        // If a number was dropped in, try to convert from ASCII to Scratch key.
        if (typeof keyArg === 'number') {
            // Check for the ASCII range containing numbers, some punctuation,
            // and uppercase letters.
            if (keyArg >= 48 && keyArg <= 90) {
                return String.fromCharCode(keyArg);
            }
            switch (keyArg) {
            case 32: return 'space';
            case 37: return 'left arrow';
            case 38: return 'up arrow';
            case 39: return 'right arrow';
            case 40: return 'down arrow';
            }
        }

        keyArg = Cast.toString(keyArg);

        // If the arg matches a special key name, return it.
        if (Keyboard.SPECIAL_KEY_NAMES.includes(keyArg)) {
            return keyArg;
        }

        // Use only the first character.
        if (keyArg.length > 1) {
            keyArg = keyArg[0];
        }

        // Check for the space character.
        if (keyArg === ' ') {
            return 'space';
        }

        return keyArg.toUpperCase();
    }

    /**
     * Keyboard DOM event handler.
     * @param  {object} data Data from DOM event.
     */
    postData (data) {
        if (!data.key) return;
        const scratchKey = this._keyStringToScratchKey(data.key);
        if (scratchKey === '') return;
        const index = this._keysPressed.indexOf(scratchKey);
        if (data.isDown) {
            // If not already present, add to the list.
            if (index < 0) {
                this._keysPressed.push(scratchKey);
            }
            // Always trigger hats, even if it was already pressed.
            this.runtime.startHats('event_whenkeypressed', {
                KEY_OPTION: scratchKey
            });
            this.runtime.startHats('event_whenkeypressed', {
                KEY_OPTION: 'any'
            });
        } else if (index > -1) {
            // If already present, remove from the list.
            this._keysPressed.splice(index, 1);
        }
    }

    /**
     * Get key down state for a specified key.
     * @param  {Any} keyArg key argument.
     * @return {boolean} Is the specified key down?
     */
    getKeyIsDown (keyArg) {
        if (keyArg === 'any') {
            return this._keysPressed.length > 0;
        }
        const scratchKey = this._keyArgToScratchKey(keyArg);
        return this._keysPressed.indexOf(scratchKey) > -1;
    }
}

module.exports = Keyboard;

@@ -786,30 +786,26 @@
  @type %7B
-Array.%3Cstring%3E
+ScratchKey
 %7D%0A     *
@@ -821,24 +821,16 @@
 tic get 
-SPECIAL_
 KEY_NAME
@@ -851,17 +851,17 @@
  return 
-%5B
+%7B
 %0A       
@@ -864,16 +864,23 @@
         
+ SPACE:
  'space'
@@ -892,16 +892,22 @@
         
+ LEFT:
  'left a
@@ -924,16 +924,20 @@
         
+ UP:
  'up arr
@@ -952,16 +952,23 @@
         
+ RIGHT:
  'right 
@@ -986,16 +986,22 @@
         
+ DOWN:
  'down a
@@ -1014,17 +1014,17 @@
         
-%5D
+%7D
 ;%0A    %7D%0A
@@ -1441,39 +1441,56 @@
 ase ' ': return 
-'space'
+Keyboard.KEY_NAMES.SPACE
 ;%0A        case '
@@ -1525,36 +1525,47 @@
 ft': return 
-'left arrow'
+Keyboard.KEY_NAMES.LEFT
 ;%0A        ca
@@ -1600,34 +1600,45 @@
 Up': return 
-'up arrow'
+Keyboard.KEY_NAMES.UP
 ;%0A        ca
@@ -1679,37 +1679,48 @@
 ht': return 
-'right arrow'
+Keyboard.KEY_NAMES.RIGHT
 ;%0A        ca
@@ -1759,36 +1759,47 @@
 wn': return 
-'down arrow'
+Keyboard.KEY_NAMES.DOWN
 ;%0A        %7D%0A
@@ -2562,39 +2562,56 @@
 case 32: return 
-'space'
+Keyboard.KEY_NAMES.SPACE
 ;%0A            ca
@@ -2624,28 +2624,39 @@
  return 
-'left arrow'
+Keyboard.KEY_NAMES.LEFT
 ;%0A      
@@ -2677,26 +2677,37 @@
  return 
-'up arrow'
+Keyboard.KEY_NAMES.UP
 ;%0A      
@@ -2728,29 +2728,40 @@
  return 
-'right arrow'
+Keyboard.KEY_NAMES.RIGHT
 ;%0A      
@@ -2782,28 +2782,39 @@
  return 
-'down arrow'
+Keyboard.KEY_NAMES.DOWN
 ;%0A      
@@ -2942,19 +2942,49 @@
         
-if 
+const keyNameList = Object.values
 (Keyboar
@@ -2989,16 +2989,8 @@
 ard.
-SPECIAL_
 KEY_
@@ -2994,16 +2994,42 @@
 EY_NAMES
+);%0A        if (keyNameList
 .include
@@ -3287,23 +3287,40 @@
  return 
-'space'
+Keyboard.KEY_NAMES.SPACE
 ;%0A      

Access key name strings from a getter