/**
 * Hint menu for Kalamar.
 * Based on menu object.
 *
 * @author Nils Diewald
 */
/*
 * TODO: Check for cnx/syn=
 * TODO: List can be shown when prefix is like 'base/s=pcorenlp/'
 * TODO: Sometimes the drop-down box down vanish when list is shown
 * TODO: Create should expect an input text field
 * TODO: Embed only one single menu (not multiple)
 *       By holding the current menu in _active
 * TODO: show() should accept a context field (especially for no-context fields,
 *       like fragments)
 * TODO: Improve context analyzer from hint!
 * TODO: Marked annotations should be addable to "fragments"
 */

"use strict";

define([
  'hint/input',
  'hint/menu',
  'hint/contextanalyzer',
  'hint/alert',
  'util'
], function (inputClass, 
             menuClass,
             analyzerClass,
             alertClass) {

  // Initialize hint array

  /**
   * KorAP.Hint.create({
   *   inputField : node,
   *   context : context regex
   * });
   */
  return {

    /**
     * Create new hint helper.
     */
    create : function (param) {
      return Object.create(this)._init(param);
    },


    // Initialize hint helper
    _init : function (param) {
      param = param || {};

      // Holds all menus per prefix context
      this._menuCollection   = {};
      this._alert  = alertClass.create();

      // Active may either hold an alert or a menu
      this._active = null;

      // No annotation helper available
      if (!KorAP.annotationHelper) {
        console.log("No annotationhelper defined");
        return;
      };

      // Apply configured foundry filter from data-hint-foundries attribute
      if (KorAP.annotationHelper.filterByConfig) {
        KorAP.annotationHelper.filterByConfig();
      };
      
      /**
       * @define {regex} Regular expression for context
       */
      KorAP.context = KorAP.context ||
        "(?:^|[^-_a-zA-Z0-9])" +   // Anchor
        "((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
        "(?:" +
        "(?:[-_a-zA-Z0-9]+?)=" +   // Layer
        "(?:"+
        "(?:[^:=\/ ]+?):|" +       // Key
        "(?:[^-=\/ ]+?)-" +        // Node
        ")?" +
        ")?" +
        ")$";

      // Get input field
      const qfield = param["inputField"] || document.getElementById("q-field");
      if (!qfield)
        return null;

      // Create input field
      this._inputField = inputClass.create(qfield);

      // create alert
      const that = this;

      const c = this._inputField.container();
      c.appendChild(this._alert.element());
      c.addEventListener('click', function (e) {
        if (!this.classList.contains('active')) {
          that.show(false);
        };
        e.halt();
      });

      // Move infobox
      const inputFieldElement = this._inputField.element();
      inputFieldElement.addEventListener("keyup", this.update.bind(this));
      inputFieldElement.addEventListener("click", this.update.bind(this));

      // Add event listener for key pressed down
      let _down = function (e) {
        if (_codeFromEvent(e) === 40) {
          this.show(false);
          e.halt();
        };
      };

      inputFieldElement.addEventListener(
        "keydown", _down.bind(this), false
      );

      // Add touch events
      inputFieldElement.addEventListener(
        'touchstart',
        this._touch.bind(this),
        false
      );
      inputFieldElement.addEventListener(
        'touchend',
        this._touch.bind(this),
        false
      );
      inputFieldElement.addEventListener(
        'touchmove',
        this._touch.bind(this),
        false
      );

      // Set Analyzer for context
      this._analyzer = analyzerClass.create(
        param["context"] || KorAP.context
      );

      this.update();
      return this;
    },


    /**
     * Return the input field attached to the hint helper.
     */
    inputField : function () {
      return this._inputField;
    },


    /**
     * Return selection range of the input field.
     */
    selectionRange : function () {
      return this._inputField.selectionRange();
    },


    /**
     * Alert at a specific character position.
     */
    alert : function (charPos, msg) {
      const t = this;
      if (arguments.length === 0)
        return t._alert;

      // Do not alert if already alerted!
      if (t._alert.active)
        return false;

      // Move to the correct position
      t._inputField.moveto(charPos);

      // Set container to active (aka hide the hint helper button)

      t._alert.show(msg);
      t.active(t._alert);
      return true;
    },
    

    /**
     * Update input field.
     */
    update : function () {
      this._inputField.update();
      if (this._alert.hide())
        this.active(null);
    },


    /**
     * Return hint menu and probably init based on an action
     */
    menu : function (action) {

      if (this._menuCollection[action] === undefined) {
        
        // No matching hint menu
        if (KorAP.annotationHelper[action] === undefined)
          return;

        // Create matching hint menu
        this._menuCollection[action] = menuClass.create(
          this, action, KorAP.annotationHelper[action]
        );
      };

      // Return matching hint menu
      return this._menuCollection[action];
    },


    /**
     * Get the correct menu based on the context
     */
    contextMenu : function (ifContext) {
      const noC = ifContext ? undefined : this.menu("-");
      
      // Get context (aka left text)
      let context = this._inputField.context();

      if (context === undefined || context.length === 0) {
        return noC;
      };

      // Get context (aka left text matching regex)
      context = this._analyzer.test(context);

      if (context === undefined || context.length == 0) {
        return noC;
      };

      return this.menu(context) || noC;
    },


    /**
     * Activate a certain menu.
     * If a menu is passed, the menu will be activated.
     * If null is passed, the active menu will be deactivated.
     * If nothing is passed, returns the active menu.
     */
    active : function (obj) {
      
      // A menu or null was passed
      if (arguments.length === 1) {        
        const c = this._inputField.container();

        // Make the menu active
        if (obj !== null) {
          c.classList.add('active');
          this._active = obj;
        }

        // Make the menu inactive
        else {
          c.classList.remove('active');
          this._active = null;
        }
      };

      // Return
      return this._active;
    },


    /**
     * Show the menu.
     * Remove all old menus.
     *
     * @param {boolean} Boolean value to indicate if context
     *        is necessary (true) or if the main context should
     *        be shown if context fails.
     *        
     */
    show : function (ifContext) {

      // Remove the active object
      this._unshow();
      
      // Get the menu
      let menu;
      if (menu = this.contextMenu(ifContext)) {

        this.active(menu);

        let e = menu.element();
        // Chrome may send a blur on the old menu here
        this._active.element().blur();
        this._inputField.container().appendChild(e);

        menu.show();
        menu.focus();
        // Focus on input field
        // this.inputField.element.focus();
      }

      else {
        this._inputField.element().focus();
      };
    },
    

    // This will get the context of the field
    getContext : function () {},


    /**
     * Deactivate the current menu and focus on the input field.
     */
    unshow : function () {
      this._unshow();
      this._inputField.element().focus();
    },


    // Catch touch events
    _touch : function (e) {
      if (e.type === 'touchstart') {
        this._lastTouch = e.touches[0].clientY;
      }

      else if (e.type === 'touchend') {
        this._lastTouch = undefined;
      }
      
      else if (e.type == 'touchmove') {
        if ((this._lastTouch + 10) < e.touches[0].clientY) {
          this.show();
          this._lastTouch = undefined;
        };
        e.halt();
      }
    },


    // Unshow the hint menu
    _unshow : function () {
      if (this.active() !== null) {
       
        // This does not work for alert currently!
        if (!this._alert.active) {

          // TODO: This does not work for webkit?
          this._inputField
            .container()
            .removeChild(this._active.element());
        }

        else {
          this._unshowAlert();
        };
        
        this.active(null);
      };
    },

    // Unshow alert
    _unshowAlert : function () {
      this._alert.hide();
      this.active(null);
    }
  };
});
