/* 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";

const windowUtils = require("sdk/deprecated/window-utils");
const { Class } = require("sdk/core/heritage");
const { validateOptions } = require("sdk/deprecated/api-utils");
const { on, emit, once, off } = require("sdk/event/core");
const { isBrowser } = require("sdk/window/utils");
const { EventTarget } = require('sdk/event/target');
const { unload } = require("unload+");

const menuitemNS = require("sdk/core/namespace").ns();
const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

function MenuitemOptions(options) {
  return validateOptions(options, {
    id: { is: ['string'] },
    menuid: { is: ['undefined', 'string'] },
    insertbefore: { is: ['undefined', 'string', 'object'] },
    label: { is: ["string"] },
    disabled: { is: ["undefined", "boolean"], map: function(v) !!v},
    accesskey: { is: ["undefined", "string"] },
    key: { is: ["undefined", "string"] },
    checked: { is: ['undefined', 'boolean'] },
    className: { is: ["undefined", "string"] },
    onCommand: { is: ['undefined', 'function'] }
  });
}

let Menuitem = Class({
  extends: EventTarget,
  initialize: function(options) {
    options = menuitemNS(this).options = MenuitemOptions(options);
    EventTarget.prototype.initialize.call(this, options);

    menuitemNS(this).destroyed = false;
    menuitemNS(this).unloaders = [];
    menuitemNS(this).menuitems = addMenuitems(this, options).menuitems;
  },
  get id() menuitemNS(this).options.id,
  get label() menuitemNS(this).options.label,
  set label(val) updateProperty(this, 'label', val),
  get checked() menuitemNS(this).options.checked,
  set checked(val) updateProperty(this, 'checked', !!val),
  get disabled() menuitemNS(this).options.disabled,
  set disabled(val) updateProperty(this, 'disabled', !!val),
  get key() menuitemNS(this).options.key,
  set key(val) updateProperty(this, 'key', val),
  clone: function (overwrites) {
    let opts = Object.clone(menuitemNS(this).options);
    for (let key in overwrites) {
      opts[key] = ovrewrites[key];
    }
    return Menuitem(opts);
  },
  get menuid() menuitemNS(this).options.menuid,
  set menuid(val) {
    let options = menuitemNS(this).options;
    options.menuid = val;

    forEachMI(function(menuitem, i, $) {
      updateMenuitemParent(menuitem, options, $);
    });
  },
  destroy: function() {
    if (!menuitemNS(this).destroyed) {
      menuitemNS(this).destroyed = true;
      menuitemNS(this).unloaders.forEach(function(u) u());
      menuitemNS(this).unloaders = null;
      menuitemNS(this).menuitems = null;
    }
    return true;
  }
});

function addMenuitems(self, options) {
  let menuitems = [];

  // setup window tracker
  windowUtils.WindowTracker({
    onTrack: function (window) {
      if (!isBrowser(window) || menuitemNS(self).destroyed) return;

      // add the new menuitem to a menu
      var menuitem = updateMenuitemAttributes(
          window.document.createElementNS(NS_XUL, "menuitem"), options);
      var menuitems_i = menuitems.push(menuitem) - 1;

      // add the menutiem to the ui
      updateMenuitemParent(menuitem, options, function(id) window.document.getElementById(id));

      menuitem.addEventListener("command", function() {
        if (!self.disabled)
          emit(self, 'command');
      }, true);

      // add unloader
      let unloader = function unloader() {
        menuitem.parentNode && menuitem.parentNode.removeChild(menuitem);
        menuitems[menuitems_i] = null;
      };
      menuitemNS(self).unloaders.push(function() {
        remover();
        unloader();
      });
      let remover = unload(unloader, window);
    }
  });
  return {menuitems: menuitems};
}

function updateMenuitemParent(menuitem, options, $) {
  // add the menutiem to the ui
  if (Array.isArray(options.menuid)) {
      let ids = options.menuid;
      for (var len = ids.length, i = 0; i < len; i++) {
        if (tryParent($(ids[i]), menuitem, options.insertbefore))
          return true;
      }
  }
  else {
    return tryParent($(options.menuid), menuitem, options.insertbefore);
  }
  return false;
}

function updateMenuitemAttributes(menuitem, options) {
  menuitem.setAttribute("id", options.id);
  menuitem.setAttribute("label", options.label);

  if (options.accesskey)
    menuitem.setAttribute("accesskey", options.accesskey);

  if (options.key)
    menuitem.setAttribute("key", options.key);

  menuitem.setAttribute("disabled", !!options.disabled);

  if (options.image) {
    menuitem.classList.add("menuitem-iconic");
    menuitem.style.listStyleImage = "url('" + options.image + "')";
  }

  if (options.checked)
    menuitem.setAttribute('checked', options.checked);

  if (options.className)
    options.className.split(/\s+/).forEach(function(name) menuitem.classList.add(name));

  return menuitem;
}

function updateProperty(menuitem, key, val) {
  menuitemNS(menuitem).options[key] = val;

  forEachMI(function(menuitem) {
    menuitem.setAttribute(key, val);
  }, menuitem);
  return val;
}

function forEachMI(callback, menuitem) {
  menuitemNS(menuitem).menuitems.forEach(function(mi, i) {
    if (!mi) return;
    callback(mi, i, function(id) mi.ownerDocument.getElementById(id));
  });
}

function tryParent(parent, menuitem, before) {
  if (parent) parent.insertBefore(menuitem, insertBefore(parent, before));
  return !!parent;
}

function insertBefore(parent, insertBefore) {
  if (typeof insertBefore == "number") {
    switch (insertBefore) {
    case MenuitemExport.FIRST_CHILD:
      return parent.firstChild;
    }
    return null;
  }
  else if (typeof insertBefore == "string") {
    return parent.querySelector("#" + insertBefore);
  }
  return insertBefore;
}

function MenuitemExport(options) {
  return Menuitem(options);
}
MenuitemExport.FIRST_CHILD = 1;

exports.Menuitem = MenuitemExport;
