/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

var SelectHelper = {
  _uiBusy: false,

  handleEvent: function(event) {
    this.handleClick(event.target);
  },

  handleClick: function(target) {
    // if we're busy looking at a select we want to eat any clicks that
    // come to us, but not to process them
    if (this._uiBusy || !this._isMenu(target) || this._isDisabledElement(target)) {
      return;
    }

    this._uiBusy = true;
    this.show(target);
    this._uiBusy = false;
  },

  // This is a callback function to be provided to prompt.show(callBack).
  // It will update which Option elements in a Select have been selected
  // or unselected and fire the onChange event.
  _promptCallBack: function(data, element) {
    let selected = data.list;

    if (element instanceof Ci.nsIDOMXULMenuListElement) {
      if (element.selectedIndex != selected[0]) {
        element.selectedIndex = selected[0];
        this.fireOnCommand(element);
      }
    } else if (element instanceof HTMLSelectElement) {
      let changed = false;
      let i = 0; // The index for the element from `data.list` that we are currently examining.
      this.forVisibleOptions(element, function(node) {
        if (node.selected && selected.indexOf(i) == -1) {
          changed = true;
          node.selected = false;
        } else if (!node.selected && selected.indexOf(i) != -1) {
          changed = true;
          node.selected = true;
        }
        i++;
      });

      if (changed) {
        this.fireOnChange(element);
      }
    }
  },

  show: function(element) {
    let list = this.getListForElement(element);
    let p = new Prompt({
      window: element.ownerDocument.defaultView
    });

    if (element.multiple) {
      p.addButton({
        label: Strings.browser.GetStringFromName("selectHelper.closeMultipleSelectDialog")
      }).setMultiChoiceItems(list);
    } else {
      p.setSingleChoiceItems(list);
    }

    p.show((data) => {
      this._promptCallBack(data,element)
    });
  },

  _isMenu: function(element) {
    return (element instanceof HTMLSelectElement || element instanceof Ci.nsIDOMXULMenuListElement);
  },

  // Return a list of Option elements within a Select excluding
  // any that were not visible.
  getListForElement: function(element) {
    let index = 0;
    let items = [];
    this.forVisibleOptions(element, function(node, options,parent) {
      let item = {
        label: node.text || node.label,
        header: options.isGroup,
        disabled: node.disabled,
        id: index,
        selected: node.selected,
      };

      if (parent) {
        item.child = true;
        item.disabled = item.disabled || parent.disabled;
      }
      items.push(item);
      index++;
    });
    return items;
  },

  // Apply a function to all visible Option elements in a Select
  forVisibleOptions: function(element, aFunction, parent = null) {
    if (element instanceof Ci.nsIDOMXULMenuListElement) {
      element = element.menupopup;
    }
    let children = element.children;
    let numChildren = children.length;


    // if there are no children in this select, we add a dummy row so that at least something appears
    if (numChildren == 0) {
      aFunction.call(this, {label: ""}, {isGroup: false}, parent);
    }

    for (let i = 0; i < numChildren; i++) {
      let child = children[i];
      let style = window.getComputedStyle(child, null);
      if (style.display !== "none") {
        if (child instanceof HTMLOptionElement ||
            child instanceof Ci.nsIDOMXULSelectControlItemElement) {
          aFunction.call(this, child, {isGroup: false}, parent);
        } else if (child instanceof HTMLOptGroupElement) {
          aFunction.call(this, child, {isGroup: true});
          this.forVisibleOptions(child, aFunction, child);
        }
      }
    }
  },

  fireOnChange: function(element) {
    let event = element.ownerDocument.createEvent("Events");
    event.initEvent("change", true, true, element.defaultView, 0,
        false, false, false, false, null);
    setTimeout(function() {
      element.dispatchEvent(event);
    }, 0);
  },

  fireOnCommand: function(element) {
    let event = element.ownerDocument.createEvent("XULCommandEvent");
    event.initCommandEvent("command", true, true, element.defaultView, 0,
        false, false, false, false, null);
    setTimeout(function() {
      element.dispatchEvent(event);
    }, 0);
  },

  _isDisabledElement : function(element) {
    let currentElement = element;
    while (currentElement) {
      // Must test with === in case a form has a field named "disabled". See bug 1263589.
      if (currentElement.disabled === true) {
        return true;
      }
      currentElement = currentElement.parentElement;
    }
    return false;
  }
};