namespace com.keyman.dom {
  export class PreProcessor {
    /**
     * Function     _GetEventKeyCode
     * Scope        Private
     * @param       {Event}       e         Event object
     * Description  Finds the key code represented by the event.
     */
    static _GetEventKeyCode(e: KeyboardEvent) {
      if (e.keyCode) {
        return e.keyCode;
      } else if (e.which) {
        return e.which;
      } else {
        return null;
      }
    }

    /**
     * Function     _GetKeyEventProperties
     * Scope        Private
     * @param       {Event}       e         Event object
     * @param       {boolean=}    keyState  true if call results from a keyDown event, false if keyUp, undefined if keyPress
     * @return      {Object.<string,*>}     KMW keyboard event object:
     * Description  Get object with target element, key code, shift state, virtual key state
     *                Lcode=keyCode
     *                Lmodifiers=shiftState
     *                LisVirtualKeyCode e.g. ctrl/alt key
     *                LisVirtualKey     e.g. Virtual key or non-keypress event
     */
    static _GetKeyEventProperties(e: KeyboardEvent, keyState?: boolean): text.KeyEvent {
      let keyman = com.keyman.singleton;
      let core = keyman.core;
      var s = new text.KeyEvent();

      if(e.cancelBubble === true) {
        return null; // I2457 - Facebook meta-event generation mess -- two events generated for a keydown in Facebook contentEditable divs
      }

      s.Lcode = this._GetEventKeyCode(e);
      if (s.Lcode == null) {
        return null;
      }

      // Stage 1 - track the true state of the keyboard's modifiers.
      var prevModState = core.keyboardProcessor.modStateFlags, curModState = 0x0000;
      var ctrlEvent = false, altEvent = false;

      let keyCodes = text.Codes.keyCodes;
      switch(s.Lcode) {
        case keyCodes['K_CTRL']:      // The 3 shorter "K_*CTRL" entries exist in some legacy keyboards.
        case keyCodes['K_LCTRL']:
        case keyCodes['K_RCTRL']:
        case keyCodes['K_CONTROL']:
        case keyCodes['K_LCONTROL']:
        case keyCodes['K_RCONTROL']:
          ctrlEvent = true;
          break;
        case keyCodes['K_LMENU']:     // The 2 "K_*MENU" entries exist in some legacy keyboards.
        case keyCodes['K_RMENU']:
        case keyCodes['K_ALT']:
        case keyCodes['K_LALT']:
        case keyCodes['K_RALT']:
          altEvent = true;
          break;
      }

      /**
       * Two separate conditions exist that should trigger chiral modifier detection.  Examples below use CTRL but also work for ALT.
       *
       * 1.  The user literally just pressed CTRL, so the event has a valid `location` property we can utilize.
       *     Problem: its layer isn't presently activated within the OSK.
       *
       * 2.  CTRL has been held a while, so the OSK layer is valid, but the key event doesn't tell us the chirality of the active CTRL press.
       *     Bonus issue:  RAlt simulation may cause erasure of this location property, but it should ONLY be empty if pressed in this case.
       *     We default to the 'left' variants since they're more likely to exist and cause less issues with RAlt simulation handling.
       *
       * In either case, `e.getModifierState("Control")` is set to true, but as a result does nothing to tell us which case is active.
       *
       * `e.location != 0` if true matches condition 1 and matches condition 2 if false.
       */

      curModState |= (e.getModifierState("Shift") ? 0x10 : 0);

      let modifierCodes = text.Codes.modifierCodes;
      if(e.getModifierState("Control")) {
        curModState |= ((e.location != 0 && ctrlEvent) ?
          (e.location == 1 ? modifierCodes['LCTRL'] : modifierCodes['RCTRL']) : // Condition 1
          prevModState & 0x0003);                                                       // Condition 2
      }
      if(e.getModifierState("Alt")) {
        curModState |= ((e.location != 0 && altEvent) ?
          (e.location == 1 ? modifierCodes['LALT'] : modifierCodes['RALT']) :   // Condition 1
          prevModState & 0x000C);                                                       // Condition 2
      }

      // Stage 2 - detect state key information.  It can be looked up per keypress with no issue.
      s.Lstates = 0;

      s.Lstates |= e.getModifierState('CapsLock') ? modifierCodes['CAPS'] : modifierCodes['NO_CAPS'];
      s.Lstates |= e.getModifierState('NumLock') ? modifierCodes['NUM_LOCK'] : modifierCodes['NO_NUM_LOCK'];
      s.Lstates |= (e.getModifierState('ScrollLock'))
        ? modifierCodes['SCROLL_LOCK'] : modifierCodes['NO_SCROLL_LOCK'];

      // We need these states to be tracked as well for proper OSK updates.
      curModState |= s.Lstates;

      // Stage 3 - Set our modifier state tracking variable and perform basic AltGr-related management.
      s.LmodifierChange = core.keyboardProcessor.modStateFlags != curModState;
      core.keyboardProcessor.modStateFlags = curModState;

      // For European keyboards, not all browsers properly send both key-up events for the AltGr combo.
      var altGrMask = modifierCodes['RALT'] | modifierCodes['LCTRL'];
      if((prevModState & altGrMask) == altGrMask && (curModState & altGrMask) != altGrMask) {
        // We just released AltGr - make sure it's all released.
        curModState &= ~ altGrMask;
      }
      // Perform basic filtering for Windows-based ALT_GR emulation on European keyboards.
      if(curModState & modifierCodes['RALT']) {
        curModState &= ~modifierCodes['LCTRL'];
      }

      let modifierBitmasks = text.Codes.modifierBitmasks;
      // Stage 4 - map the modifier set to the appropriate keystroke's modifiers.
      var activeKeyboard = core.activeKeyboard;
      if(activeKeyboard && activeKeyboard.isChiral) {
        s.Lmodifiers = curModState & modifierBitmasks.CHIRAL;

        // Note for future - embedding a kill switch here would facilitate disabling AltGr / Right-alt simulation.
        if(activeKeyboard.emulatesAltGr && (s.Lmodifiers & modifierBitmasks['ALT_GR_SIM']) == modifierBitmasks['ALT_GR_SIM']) {
          s.Lmodifiers ^= modifierBitmasks['ALT_GR_SIM'];
          s.Lmodifiers |= modifierCodes['RALT'];
        }
      } else {
        // No need to sim AltGr here; we don't need chiral ALTs.
        s.Lmodifiers =
          (curModState & 0x10) | // SHIFT
          ((curModState & (modifierCodes['LCTRL'] | modifierCodes['RCTRL'])) ? 0x20 : 0) |
          ((curModState & (modifierCodes['LALT'] | modifierCodes['RALT']))   ? 0x40 : 0);
      }


      /* Tweak the modifiers if an OS meta key is detected; this will allow meta-key-based
       * hotkeys to bypass Keyman processing.  We do this AFTER the chiral modifier filtering
       * because some keyboards specify their own modifierBitmask, which won't include it.
       * We don't currently use that reference in this method, but that may change in the future.
       */
      s.Lmodifiers |= (e.metaKey ? modifierCodes['META']: 0);

      // Physically-typed keys require use of a 'desktop' form factor and thus are based on a virtual "physical" Device.
      s.device = keyman.util.physicalDevice.coreSpec;

      // Perform any browser-specific key remapping before other remaps and mnemonic transforms.
      // (See https://github.com/keymanapp/keyman/issues/1125.)
      if(!keyman.isEmbedded && s.device.browser == utils.Browser.Firefox) {
      // Browser key identifiers are not completely consistent; Firefox has a few (for US punctuation)
      // that differ from the norm.  Refer to https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode.
        if(KeyMapping.browserMap.FF['k'+s.Lcode]) {
          s.Lcode = KeyMapping.browserMap.FF['k'+s.Lcode];
        }
      }

      // Mnemonic handling.
      if(activeKeyboard && activeKeyboard.isMnemonic) {
        // The following will never set a code corresponding to a modifier key, so it's fine to do this,
        // which may change the value of Lcode, here.
        text.KeyboardProcessor.setMnemonicCode(s, e.getModifierState("Shift"), e.getModifierState("CapsLock"));
      }

      // The 0x6F used to be 0x60 - this adjustment now includes the chiral alt and ctrl modifiers in that check.
      var LisVirtualKeyCode = (typeof e.charCode != 'undefined' && e.charCode != null  &&  (e.charCode == 0 || (s.Lmodifiers & 0x6F) != 0));
      s.LisVirtualKey = LisVirtualKeyCode || e.type != 'keypress';

      // This is based on a KeyboardEvent, so it's not considered 'synthetic' within web-core.
      s.isSynthetic = false;

      // Other minor physical-keyboard adjustments
      if(activeKeyboard && !activeKeyboard.isMnemonic) {
        // Positional Layout

        /* 13/03/2007 MCD: Swedish: Start mapping of keystroke to US keyboard */
        var Lbase = KeyMapping.languageMap[core.keyboardProcessor.baseLayout];
        if(Lbase && Lbase['k'+s.Lcode]) {
          s.Lcode=Lbase['k'+s.Lcode];
        }
        /* 13/03/2007 MCD: Swedish: End mapping of keystroke to US keyboard */

        if(!activeKeyboard.definesPositionalOrMnemonic && !(s.Lmodifiers & 0x60)) {
          // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic
          s = {
            Lcode: KeyMapping._USKeyCodeToCharCode(s),
            Lmodifiers: 0,
            LisVirtualKey: false,
            vkCode: s.Lcode, // Helps to merge OSK and physical keystroke control paths.
            Lstates: s.Lstates,
            kName: '',
            device: keyman.util.physicalDevice.coreSpec,
            isSynthetic: false
          };
        }
      }

      return s;
    }

    public static getEventOutputTarget(e: Event): text.OutputTarget {
      let keyman = com.keyman.singleton;
      let target = keyman.util.eventTarget(e) as HTMLElement;
      if (target == null) {
        return null;
      } else if (target.nodeType == 3) {// defeat Safari bug
        target = target.parentNode as HTMLElement;
      }

      return dom.Utils.getOutputTarget(target);
    }

    /**
     * Function     keyDown
     * Scope        Public
     * Description  Processes keydown event and passes data to keyboard.
     *
     * Note that the test-case oriented 'recorder' stubs this method to facilitate keystroke
     * recording for use in test cases.  If changing this function, please ensure the recorder is
     * not affected.
     */
    static keyDown(e: KeyboardEvent): boolean {
      let core = com.keyman.singleton.core;
      DOMEventHandlers.states.swallowKeypress = false;

      // Get event properties
      var Levent = this._GetKeyEventProperties(e, true);
      if(Levent == null) {
        return true;
      }

      let outputTarget = PreProcessor.getEventOutputTarget(e);
      const ruleBehavior = core.processKeyEvent(Levent, outputTarget);
      const LeventMatched = ruleBehavior && !ruleBehavior.triggerKeyDefault;

      if(LeventMatched) {
        if(e  &&  e.preventDefault) {
          e.preventDefault();
          e.stopPropagation();
        }

        DOMEventHandlers.states.swallowKeypress = !!Levent.Lcode;
        // Don't swallow backspaces on keypresses; this allows physical BKSP presses to repeat.
        if(Levent.Lcode == 8) {
          DOMEventHandlers.states.swallowKeypress = false;
        }
      } else {
        DOMEventHandlers.states.swallowKeypress = false;
      }

      return !LeventMatched;
    }

    // KeyUp basically exists for two purposes:
    // 1)  To detect browser form submissions (handled in kmwdomevents.ts)
    // 2)  To detect modifier state changes.
    static keyUp(e: KeyboardEvent): boolean {
      let core = com.keyman.singleton.core;
      var Levent = this._GetKeyEventProperties(e, false);
      if(Levent == null) {
        return true;
      }

      let outputTarget = PreProcessor.getEventOutputTarget(e);
      return core.keyboardProcessor.doModifierPress(Levent, outputTarget, false);
    }

    static keyPress(e: KeyboardEvent): boolean {
      let keyman = com.keyman.singleton;
      let core = keyman.core;

      var Levent = this._GetKeyEventProperties(e);
      if(Levent == null || Levent.LisVirtualKey) {
        return true;
      }

      // _Debug('KeyPress code='+Levent.Lcode+'; Ltarg='+Levent.Ltarg.tagName+'; LisVirtualKey='+Levent.LisVirtualKey+'; _KeyPressToSwallow='+keymanweb._KeyPressToSwallow+'; keyCode='+(e?e.keyCode:'nothing'));

      /* I732 START - 13/03/2007 MCD: Swedish: Start positional keyboard layout code: prevent keystroke */
      if(!core.activeKeyboard.isMnemonic) {
        if(!DOMEventHandlers.states.swallowKeypress) {
          return true;
        }
        if(Levent.Lcode < 0x20 || ((<any>keyman)._BrowserIsSafari  &&  (Levent.Lcode > 0xF700  &&  Levent.Lcode < 0xF900))) {
          return true;
        }

        return false;
      }
      /* I732 END - 13/03/2007 MCD: Swedish: End positional keyboard layout code */

      // Only reached if it's a mnemonic keyboard.
      let outputTarget = PreProcessor.getEventOutputTarget(e);
      if(DOMEventHandlers.states.swallowKeypress || core.keyboardInterface.processKeystroke(outputTarget, Levent)) {
        DOMEventHandlers.states.swallowKeypress = false;
        if(e && e.preventDefault) {
          e.preventDefault();
          e.stopPropagation();
        }
        return false;
      }

      DOMEventHandlers.states.swallowKeypress = false;
      return true;
    }
  }
}