text: // EnumMeta.js -- helper module that maps related enum values to printable names and ids
  
  /* eslint-env commonjs */
  /* global DriveKeys, console */
  
  var VERSION = '0.0.1';
  var WANT_DEBUG = false;
  
  function _debugPrint() {
      // eslint-disable-next-line no-console
      (typeof Script === 'object' ? print : console.log)('EnumMeta | ' + [].slice.call(arguments).join(' '));
  }
  
  var debugPrint = WANT_DEBUG ? _debugPrint : function(){};
  
  try {
      /* global process */
      if (process.title === 'node') {
          _defineNodeJSMocks();
      }
  } catch (e) { /* eslint-disable-line empty-block */ }
  
  _debugPrint(VERSION);
  
  // FIXME: C++ emits this action event, but doesn't expose it yet to scripting
  //   (ie: as Actions.ZOOM or Actions.TranslateCameraZ)
  var ACTION_TRANSLATE_CAMERA_Z = {
      actionName: 'TranslateCameraZ',
      actionID: 12,
      driveKeyName: 'ZOOM'
  };
  
  module.exports = {
      version: VERSION,
      DriveKeyNames: invertKeys(DriveKeys),
      Controller: {
          Hardware: Object.keys(Controller.Hardware).reduce(function(names, prop) {
              names[prop+'Names'] = invertKeys(Controller.Hardware[prop]);
              return names;
          }, {}),
          ActionNames: _getActionNames(),
          StandardNames: invertKeys(Controller.Standard)
      },
      getDriveKeyNameFromActionName: getDriveKeyNameFromActionName,
      getActionNameFromDriveKeyName: getActionNameFromDriveKeyName,
      eventKeyText2KeyboardName: eventKeyText2KeyboardName,
      keyboardName2eventKeyText: keyboardName2eventKeyText,
      ACTION_TRANSLATE_CAMERA_Z: ACTION_TRANSLATE_CAMERA_Z,
      INVALID_ACTION_ID: Controller.findAction('INVALID_ACTION_ID_FOO')
  };
  
  _debugPrint('///'+VERSION, Object.keys(module.exports));
  
  var actionsMapping = {}, driveKeyMapping = {};
  
  initializeMappings(actionsMapping, driveKeyMapping);
  
  function invertKeys(object) {
      if (!object) {
          return object;
      }
      return Object.keys(object).reduce(function(out, key) {
          out[object[key]] = key;
          return out;
      }, {});
  }
  
  function _getActionNames() {
      var ActionNames = invertKeys(Controller.Hardware.Actions);
      ActionNames[ACTION_TRANSLATE_CAMERA_Z.actionID] = ACTION_TRANSLATE_CAMERA_Z.actionName;
      function mapActionName(actionName) {
          var actionKey = Controller.Hardware.Actions[actionName],
              actionID = Controller.findAction(actionName),
              similarName = eventKeyText2KeyboardName(actionName),
              existingName = ActionNames[actionID];
  
          var keyName = actionName;
  
          if (actionID === module.exports.INVALID_ACTION_ID) {
              _debugPrint('actionID === INVALID_ACTION_ID', actionName);
          }
          switch (actionName) {
              case 'StepTranslateX': actionName = 'StepTranslate'; break;
              case 'StepTranslateY': actionName = 'StepTranslate'; break;
              case 'StepTranslateZ': actionName = 'StepTranslate'; break;
              case 'ACTION1': actionName = 'PrimaryAction'; break;
              case 'ACTION2': actionName = 'SecondaryAction'; break;
          }
          debugPrint(keyName, actionName, actionKey, actionID);
  
          similarName = similarName.replace('Lateral','Strafe').replace(/^(?:Longitudinal|Vertical)/, '');
          if (actionID in ActionNames) {
              // check if overlap is just BoomIn <=> BOOM_IN
              if (similarName !== existingName && actionName !== existingName) {
                  throw new Error('assumption failed: overlapping actionID:'+JSON.stringify({
                      actionID: actionID,
                      actionKey: actionKey,
                      actionName: actionName,
                      similarName: similarName,
                      keyName: keyName,
                      existingName: existingName
                  },0,2));
              }
          } else {
              ActionNames[actionID] = actionName;
              ActionNames[actionKey] = keyName;
          }
      }
      // first map non-legacy (eg: Up and not VERTICAL_UP) actions
      Object.keys(Controller.Hardware.Actions).filter(function(name) {
          return /[a-z]/.test(name);
      }).sort().reverse().forEach(mapActionName);
      // now legacy actions
      Object.keys(Controller.Hardware.Actions).filter(function(name) {
          return !/[a-z]/.test(name);
      }).sort().reverse().forEach(mapActionName);
  
      return ActionNames;
  }
  
  // attempts to brute-force translate an Action name into a DriveKey name
  //   eg: _translateActionName('TranslateX') === 'TRANSLATE_X'
  //   eg: _translateActionName('Yaw') === 'YAW'
  function _translateActionName(name, _index) {
      name = name || '';
      var key = name;
      var re = new RegExp('[A-Z][a-z0-9]+', 'g');
      key = key.replace(re, function(Word) {
          return Word.toUpperCase()+'_';
      })
          .replace(/_$/, '');
  
      if (key in DriveKeys) {
          debugPrint('getDriveKeyFromEventName', _index, name, key, DriveKeys[key]);
          return key;
      }
  }
  
  function getActionNameFromDriveKeyName(driveKeyName) {
      return driveKeyMapping[driveKeyName];
  }
  // maps an action lookup value to a DriveKey name
  //  eg: actionName: 'Yaw' === 'YAW'
  //      actionKey:  Controller.Actions.Yaw => 'YAW'
  //      actionID:   Controller.findAction('Yaw') => 'YAW'
  function getDriveKeyNameFromActionName(lookupValue) {
      if (lookupValue === ACTION_TRANSLATE_CAMERA_Z.actionName ||
          lookupValue === ACTION_TRANSLATE_CAMERA_Z.actionID) {
          return ACTION_TRANSLATE_CAMERA_Z.driveKeyName;
      }
      if (typeof lookupValue === 'string') {
          lookupValue = Controller.findAction(lookupValue);
      }
      return actionsMapping[lookupValue];
  }
  
  // maps a Controller.key*Event event.text -> Controller.Hardware.Keyboard[name]
  //   eg: ('Page Up') === 'PgUp'
  //   eg: ('LEFT') === 'Left'
  function eventKeyText2KeyboardName(text) {
      if (eventKeyText2KeyboardName[text]) {
          // use memoized value
          return eventKeyText2KeyboardName[text];
      }
      var keyboardName = (text||'').toUpperCase().split(/[ _]/).map(function(WORD) {
          return WORD.replace(/([A-Z])(\w*)/g, function(_, A, b) {
              return (A.toUpperCase() + b.toLowerCase());
          });
      }).join('').replace('Page','Pg');
      return eventKeyText2KeyboardName[text] = eventKeyText2KeyboardName[keyboardName] = keyboardName;
  }
  
  // maps a Controller.Hardware.Keyboard[name] -> Controller.key*Event event.text
  //   eg: ('PgUp') === 'PAGE UP'
  //   eg: ('Shift') === 'SHIFT'
  function keyboardName2eventKeyText(keyName) {
      if (keyboardName2eventKeyText[keyName]) {
          // use memoized value
          return keyboardName2eventKeyText[keyName];
      }
      var text = keyName.replace('Pg', 'Page');
      var caseWords = text.match(/[A-Z][a-z0-9]+/g) || [ text ];
      var eventText = caseWords.map(function(str) {
          return str.toUpperCase();
      }).join('_');
      return keyboardName2eventKeyText[keyName] = eventText;
  }
  
  function initializeMappings(actionMap, driveKeyMap) {
      _debugPrint('creating mapping');
      var ref = ACTION_TRANSLATE_CAMERA_Z;
      actionMap[ref.actionName] = actionMap[ref.actionID] = ref.driveKeyName;
      actionMap.BoomIn = 'ZOOM';
      actionMap.BoomOut = 'ZOOM';
  
      Controller.getActionNames().sort().reduce(
          function(out, name, index, arr) {
              var actionKey = arr[index];
              var actionID = Controller.findAction(name);
              var value = actionID in out ? out[actionID] : _translateActionName(name, index);
              if (value !== undefined) {
                  var prefix = (actionID in out ? '+++' : '---');
                  debugPrint(prefix + ' Action2DriveKeyName['+name+'('+actionID+')] = ' + value);
                  driveKeyMap[value] = driveKeyMap[value] || name;
              }
              out[name] = out[actionID] = out[actionKey] = value;
              return out;
          }, actionMap);
  }
  
  // ----------------------------------------------------------------------------
  // mocks for litmus testing using Node.js command line tools
  function _defineNodeJSMocks() {
      /* eslint-disable no-global-assign */
      DriveKeys = {
          TRANSLATE_X: 12345
      };
      Controller = {
          getActionNames: function() {
              return Object.keys(this.Hardware.Actions);
          },
          findAction: function(name) {
              return this.Hardware.Actions[name] || 4095;
          },
          Hardware: {
              Actions: {
                  TranslateX: 54321
              },
              Application: {
                  Grounded: 1
              },
              Keyboard: {
                  A: 65
              }
          }
      };
      /* eslint-enable no-global-assign */
  }
  