// Since 'web' compilation is the path recognized by VSCode, we need to make references here to prevent TS errors.
// References the base Keyman object (and consequently, the rest of the core objects).
/// <reference path="kmwbase.ts" />
/// <reference path="osk/embedded/keytip.ts" />
/// <reference path="osk/embedded/pendingLongpress.ts" />
/// <reference path="osk/embedded/globeHint.ts" />

// KeymanWeb 11.0
// Copyright 2019 SIL International

/*****************************************/
/*                                       */
/*   Embedded application-specific code  */
/*                                       */
/*****************************************/

namespace com.keyman.osk {
  VisualKeyboard.prototype.optionKey = function(this: VisualKeyboard, e: KeyElement, keyName: string, keyDown: boolean) {
    let keyman = com.keyman.singleton;

    if(keyName.indexOf('K_LOPT') >= 0) {
      if(keyDown) {
        this.menuEvent = e;
        if(typeof keyman['showKeyboardList'] == 'function') {
          keyman['showKeyboardList']();
        }
      } else {
        if(this.menuEvent) {
          this.highlightKey(this.menuEvent, false);
        }
        if(typeof(window['menuKeyUp']) == 'function') {
          window['menuKeyUp']();
        }
        this.menuEvent = null;
      }
    } else if(keyName.indexOf('K_ROPT') >= 0) {
      if(keyDown) {
        this.highlightKey(e,false);
        if(typeof keyman['hideKeyboard'] == 'function') {
          keyman['hideKeyboard']();
        }
      }
    }
  };

  // iOS now relies upon native-mode popup key management, so we only implement these hybrid-targeted
  // methods when embedding in Android.
  let device = com.keyman.singleton.util.device;

  if(device.OS == 'Android') { // assumption - if this file is being loaded, keyman.isEmbedded == true.
    // Send the subkey array to iOS, with centre,top of base key position
    /**
     * Create a popup key array natively
     *
     * @param {Object}  key   base key element
     */
    VisualKeyboard.prototype.startLongpress = function(this: VisualKeyboard, key: KeyElement): PendingGesture {
      if(typeof(window['oskCreatePopup']) == 'function') {
        var xBase = dom.Utils.getAbsoluteX(key) - dom.Utils.getAbsoluteX(this.kbdDiv) + key.offsetWidth/2,
            yBase = dom.Utils.getAbsoluteY(key);

        // #3718: No longer prepend base key to subkey array
        window['oskCreatePopup'](key['subKeys'], xBase, yBase, key.offsetWidth, key.offsetHeight);

        return new embedded.PendingLongpress(this, key);
      } else {
        // When embedded within our Android app, we expect the `oskCreatePopup` function to
        // exist; all subkey control is delegated to the app.
        //
        // No function = big problem.
        console.error("Missing `oskCreatePopup` function for engine integration.");
        return null;
      }
    };

    VisualKeyboard.prototype.createKeyTip = function(this: VisualKeyboard) {
      if(com.keyman.singleton.util.device.formFactor == 'phone') {
        this.keytip = new osk.embedded.KeyTip(window['oskCreateKeyPreview'], window['oskClearKeyPreview']);
      }
    };
  } // end Android-only block.

  VisualKeyboard.prototype.createGlobeHint = function(this: VisualKeyboard) {
    this.globeHint = new com.keyman.osk.embedded.GlobeHint(this);
    let keyman = com.keyman.singleton;
    keyman.osk._Box.appendChild(this.globeHint.element!);
  }

  SuggestionManager.prototype.platformHold = function(this: SuggestionManager, suggestionObj: BannerSuggestion, isCustom: boolean) {
    // Parallels VisualKeyboard.prototype.touchHold, but for predictive suggestions instead of keystrokes.
    let suggestionEle = suggestionObj.div;

    // Need to know if it's a <keep> option or not!

    var xBase = dom.Utils.getAbsoluteX(suggestionEle) - dom.Utils.getAbsoluteX(suggestionEle.parentElement) + suggestionEle.offsetWidth/2,
        yBase = dom.Utils.getAbsoluteY(suggestionEle) - dom.Utils.getAbsoluteY(suggestionEle.parentElement);

    window['suggestionPopup'](suggestionObj.suggestion, isCustom, xBase, yBase, suggestionEle.offsetWidth, suggestionEle.offsetHeight);
  }
}

namespace com.keyman.text {
  let nativeForBaseKeys = DefaultOutput.forBaseKeys;

  // Overrides the 'native'-mode implementation with in-app friendly defaults prioritized over 'native' defaults.
  DefaultOutput.forBaseKeys = function(Lkc: KeyEvent): string {
    let Codes = com.keyman.text.Codes;
    let code = Lkc.Lcode;

    // Intentionally not assigning K_TAB or K_ENTER so KMW will pass them back
    // to the mobile apps to handle (insert characters or navigate forms).
    if (code == Codes.keyCodes.K_oE2) {
      // Using defaults of English US layout for the 102nd key
      if (Lkc.Lmodifiers == Codes.modifierCodes['SHIFT']) {
        return '|';
      } else {
        return '\\';
      }
    }

    // Use 'native'-mode defaults, determining the character from the OSK
    return nativeForBaseKeys(Lkc);
  }
}

(function() {
  // Declare KeymanWeb and related objects
  var keymanweb=window['keyman'], util=keymanweb['util'],device=util.device;
  var dom = com.keyman.dom;

  // Allow definition of application name
  keymanweb.options['app']='';

  // Flag to control refreshing of a keyboard that is already loaded
  keymanweb.mustReloadKeyboard = true;

  // Skip full page initialization - skips native-mode only code
  keymanweb.isEmbedded = true;

  // Set default device options
  keymanweb.setDefaultDeviceOptions = function(opt: com.keyman.OptionType) {
    opt['attachType'] = 'manual';
    device.app=opt['app'];
    device.touchable=true;
    device.formFactor = device.app.indexOf('Tablet') >= 0 ? 'tablet' : 'phone';
    device.browser='native';
  };

  // Get default style sheet path
  keymanweb.getStyleSheetPath = function(ssName) {
    return keymanweb.rootPath+ssName;
  };

  keymanweb.linkStylesheetResources = function() {
    const keyman = keymanweb as KeymanBase;
    let util = keyman.util;

    // Install the globe-hint stylesheet.
    util.linkStyleSheet(keymanweb.getStyleSheetPath('globe-hint.css'));

    // For now, the OSK will handle linking of the main OSK stylesheet separately.
  }

  // Get KMEI, KMEA keyboard path (overrides default function, allows direct app control of paths)
  keymanweb.getKeyboardPath = function(Lfilename, packageID) {
    return Lfilename + "?v=" + (new Date()).getTime(); /*cache buster*/
  };

  // Establishes keyboard namespacing.
  keymanweb.namespaceID = function(Pstub) {
    if(typeof(Pstub['KP']) != 'undefined') {
      // An embedded use case wants to utilize package-namespacing.
      Pstub['KI'] = Pstub['KP'] + "::" + Pstub['KI'];
    }
  }

  // In conjunction with the KeyboardManager's installKeyboard method and script IDs, preserves a keyboard's
  // namespaced ID.
  keymanweb.preserveID = function(Pk) {
    var trueID;

    // Find the currently-executing script tag; KR is called directly from each keyboard's definition script.
    if(document.currentScript) {
      trueID = document.currentScript.id;
    } else {
      var scripts = document.getElementsByTagName('script');
      var currentScript = scripts[scripts.length-1];

      trueID = currentScript.id;
    }

    // Final check that the script tag is valid and appropriate for the loading keyboard.
    if(trueID.indexOf(Pk['KI']) != -1) {
      Pk['KI'] = trueID;  // Take the script's version of the ID, which may include package namespacing.
    } else {
      console.error("Error when registering keyboard:  current SCRIPT tag's ID does not match!");
    }
  }

    /**
   * Force reload of resource
   *
   *  @param  {string}  s unmodified URL
   *  @return {string}    modified URL
   */
  util.unCached = function(s) {
    var t=(new Date().getTime());
    s = s + '?v=' + t;
    return s;
  };

  util.wait = function() {
    // Empty stub - this function should not be implemented or used within embedded code routes.
    console.warn("util.wait() call attempted in embedded mode!");  // Sends log message to embedding app.
  };

  util.alert = function() {
    // Empty stub - this function should not be implemented or used within embedded code routes.
    console.warn("util.alert() call attempted in embedded mode!");  // Sends log message to embedding app.
  };

  /**
   * Refresh element content after change of text (if required)
   *
   *  @param  {Object}  Pelem   input element
   */
  keymanweb.refreshElementContent = function(Pelem)
  {
    if('ontextchange' in keymanweb) keymanweb['ontextchange'](Pelem);
  };

  /**
   * Set target element text direction (LTR or RTL): not functional for KMEI, KMEA
   *
   * @param       {Object}      Ptarg      Target element
   */
  keymanweb.domManager._SetTargDir = function(Ptarg){};

  /**
   * Use rotation events to adjust OSK element positions and scaling if necessary
   */
  keymanweb.handleRotationEvents = function() {};

  /**
   * Caret position always determined from the active (but hidden) element
   *
   * @return  {boolean}
   **/
  keymanweb.isPositionSynthesized = function()
  {
    return false;
  };

  /**
   * correctOSKTextSize handles rotation event -- currently rebuilds keyboard and adjusts font sizes
   */
  keymanweb['correctOSKTextSize']=function() {
    keymanweb.osk?.refreshLayout();
  };

  /**
   * Register a lexical model
   *
   * @param {com.keyman.text.prediction.ModelSpec} model  Spec of the lexical model
   */
  keymanweb['registerModel']=function(model: com.keyman.text.prediction.ModelSpec) {
    keymanweb.modelManager.register(model);
  };

  /**
   * Function called by Android and iOS when a device-implemented keyboard popup
   * is displayed or hidden.  As this is controlled by the app, we use it as a
   * trigger for 'embedded'-mode gesture state management.
   *
   *  @param  {boolean}  isVisible
   *
   **/
  keymanweb['popupVisible'] = function(isVisible) {
    let osk = keymanweb.osk;
    let gesture = osk.vkbd.subkeyGesture as com.keyman.osk.embedded.SubkeyDelegator;
    let pendingLongpress = osk.vkbd.pendingSubkey;

    /*
     * If a longpress popup was visible, but is no longer, this means that the
     * associated longpress gesture was cancelled.  It is possible for the base
     * key to emit if selected at this time; detection of this is managed by
     * the `SubkeyDelegator` class.
     */
    if(!isVisible) {
      if(gesture) {
        gesture.resolve(null);
        osk.vkbd.subkeyGesture = null;
      } else if(pendingLongpress) {
        pendingLongpress.cancel();
        osk.vkbd.pendingSubkey = null;
      }
    }

    /*
     * If the popup was not visible, but now is, that means our previously-pending
     * longpress is now 'realized' (complete).  The OSK relies upon this state
     * information, which will be properly updated by `resolve`.
     *
     * Prominent uses of such state info helps prevent change of base key, key
     * previews, and key output from occurring while a subkey popup remains active.
     */
    if(isVisible && pendingLongpress) {
      // Fulfills the first-stage promise.
      pendingLongpress.resolve();
    }
  };

  /**
   *  Return position of language menu key to KeymanTouch
   *
   *  @return  {string}      comma-separated x,y,w,h of language menu key
   *
   **/
  keymanweb['touchMenuPos'] = function() {
    let osk = keymanweb.osk;
    if(osk == null || osk.vkbd == null || osk.vkbd.lgKey == null) {
      return '';
    }

    var key: HTMLElement = osk.vkbd.lgKey;
    // A CSS change of kmd-key-square from position:fixed to position:static was needed
    // for Android 4.3 to display the OSK correctly, but resulted in the position of
    // the menu key not being returned correctly.  The following line gets the
    // key element, instead of the key-square element, fixes this.  It should be
    // removed again when the key-square elements are all removed as planned.
    if(typeof key.firstChild != 'undefined' && key.firstChild != null && util.hasClass(key.firstChild,'kmw-key')) {
      key = <HTMLElement> key.firstChild;
    }

    var w=key.offsetWidth,
        h=key.offsetHeight,
        // Since the full OSKManager '_Box' is displayed within the keyboards' WebViews,
        // these calculations should be performed with respect to that, rather than osk.vkbd.kbdDiv.
        x=dom.Utils.getAbsoluteX(key) - dom.Utils.getAbsoluteX(osk._Box) + w/2,
        y=dom.Utils.getAbsoluteY(key) - dom.Utils.getAbsoluteY(osk._Box);

    return x+','+y+','+w+','+h;
  };

  keymanweb['showGlobeHint'] = function(text: string, onAutodismissal?: () => void) {
    const keyman = keymanweb as KeymanBase;
    const globeHint = keyman.osk?.vkbd?.globeHint;

    if(globeHint) {
      // Ensure localized text is properly in-place.
      globeHint.text = text;
      globeHint.show(keyman.osk.vkbd.currentLayer.globeKey.btn, onAutodismissal);
    }
  }

  keymanweb['hideGlobeHint'] = function() {
    const keyman = keymanweb as KeymanBase;
    keyman.osk?.vkbd?.globeHint?.hide(keyman.osk.vkbd.currentLayer.globeKey.btn);
  }

 /**
   *  Accept an external key ID (from KeymanTouch) and pass to the keyboard mapping
   *
   *  @param  {string}  keyName   key identifier which could contain a display layer and a "functional" layer
   *                              e.g: 'shift-K_E+rightalt-shift'
   **/
  keymanweb['executePopupKey'] = function(keyName: string) {
      let osk = keymanweb.osk;
      var origArg = keyName;
      if(!keymanweb.core.activeKeyboard || !osk.vkbd) {
        return false;
      }

      /* Clear any pending (non-popup) key */
      osk.vkbd.keyPending = null;

      // Changes for Build 353 to resolve KMEI popup key issues
      keyName=keyName.replace('popup-',''); //remove popup prefix if present (unlikely)

      // Regex for 'display layer'-'virtual key name'+'optional functional layer'
      // Can't just split on '-' because some layers like ctrl-shift contain it.
      // Virtual key name starts with T_, K_, or U_
      // matches[1]: displayLayer (not used)
      // matches[2]: keyId
      // matches[3]: optional functionalLayer
      let matches = keyName.match(/^(.+)-([TKU]_[^+]+)\+?(.+)?$/);
      if (matches == null) {
        return false;
      }
      keyName = matches[2] + (matches[3] ? '+' + matches[3] : '');

      // Note:  this assumes Lelem is properly attached and has an element interface.
      // Currently true in the Android and iOS apps.
      var Lelem=keymanweb.domManager.lastActiveElement;
      keymanweb.domManager.initActiveElement(Lelem);

      // This should be set if we're within this method... but it's best to guard against nulls here, just in case.
      if(osk.vkbd.subkeyGesture) {
        let gesture = osk.vkbd.subkeyGesture as com.keyman.osk.embedded.SubkeyDelegator;
        gesture.resolve(keyName);
        osk.vkbd.subkeyGesture = null;
      } else {
        console.warn("No base key exists for the subkey being executed: '" + origArg + "'");
      }
  };

  /**
   *  API endpoint for hardware keystroke events from Android external keyboards
   *
   *  @param  {number}  code   key identifier
   *  @param  {number}  shift  shift state (0x01=left ctrl 0x02=right ctrl 0x04=left alt 0x08=right alt
   *                                        0x10=shift 0x20=ctrl 0x40=alt)
   *  @param  {number}  lstates lock state (0x0200=no caps 0x0400=num 0x0800=no num 0x1000=scroll 0x2000=no scroll locks)
   *  @return {boolean} false when KMW _has_ fully handled the event and true when not.
   **/
  keymanweb['executeHardwareKeystroke'] = function(code, shift, lstates = 0): boolean {
    if(!keymanweb.core.activeKeyboard || code == 0) {
      return false;
    }

    // Clear any pending (non-popup) key
    keymanweb.osk.vkbd.keyPending = null;

    // Note:  this assumes Lelem is properly attached and has an element interface.
    // Currently true in the Android and iOS apps.
    var Lelem = keymanweb.domManager.lastActiveElement;

    keymanweb.domManager.initActiveElement(Lelem);

    // Check the virtual key
    var Lkc: com.keyman.text.KeyEvent = {
      Lmodifiers: shift,
      vkCode: code,
      Lcode: code,
      Lstates: lstates,
      LisVirtualKey: true,
      kName: '',
      device: keymanweb.util.physicalDevice.coreSpec, // As we're executing a hardware keystroke.
      isSynthetic: false
    };

    try {
      // Now that we've manually constructed a proper keystroke-sourced KeyEvent, pass control
      // off to the processor for its actual execution.

      // Should return 'false' when KMW _has_ fully handled the event and 'true' when not.
      const ruleBehavior: com.keyman.text.RuleBehavior = keymanweb.core.processKeyEvent(Lkc, com.keyman.dom.Utils.getOutputTarget(Lelem));

      return !ruleBehavior || ruleBehavior.triggerKeyDefault;
    } catch (err) {
      console.error(err.message, err);
      return false;
    }
  };
})();
