(function() {
  const Cu = Components.utils;
  const Cc = Components.classes;
  const Ci = Components.interfaces;

  var ns = MOA.ns("LM4.AppIntent");
  var jsm = { };
  if (typeof XPCOMUtils == "undefined") {  
    Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  }

  XPCOMUtils.defineLazyGetter(jsm, "utils", function() {
    let obj = { };
    Cu["import"]("resource://livemargins/utils.jsm", obj);
    return obj.utils;
  });

  XPCOMUtils.defineLazyGetter(jsm, "AppCenter", function() {
    let obj = { };
    Cu["import"]("resource://livemargins/appcenter.jsm", obj);
    return obj.AppCenter;
  });

  XPCOMUtils.defineLazyGetter(jsm, "AppCenterDAO", function() {
    let obj = { };
    Cu["import"]("resource://livemargins/dao.jsm", obj);
    return obj.AppCenterDAO;
  });

  XPCOMUtils.defineLazyGetter(jsm, "AppIntent", function() {
    let obj = { };
    Cu["import"]("resource://livemargins/intent.jsm", obj);
    return obj.AppIntent;
  });

  XPCOMUtils.defineLazyGetter(jsm, "Tracker", function() {
    let obj = { };
    Cu["import"]("resource://livemargins/track.jsm", obj);
    return obj.Tracker;
  });

  XPCOMUtils.defineLazyGetter(jsm, "SNS", function() {
    let obj = { };
    try {
      Cu["import"]("resource://share_all_cn/sns.jsm", obj);
    } catch (e) { }
    return obj.SNS;
  });

  XPCOMUtils.defineLazyGetter(jsm, "ioService", function() {
    return Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
  });

  XPCOMUtils.defineLazyGetter(jsm, "styleSheetService", function() {
    return Cc["@mozilla.org/content/style-sheet-service;1"]
             .getService(Ci.nsIStyleSheetService);
  });

  XPCOMUtils.defineLazyGetter(jsm, "logger", function() {
    return new jsm.utils.logger("LM4.appcenter.intent");
  });

  var _lastSel = "";
  // record the mouse position, to judge the direction of mouse moving.
  var _lastPos = null;

  // load style sheets
  function loadCSSText(cssText) {
    var uri = jsm.ioService.newURI("data:text/css;base64," + btoa(cssText),
                                   null, null);
    let cssService = jsm.styleSheetService;
    if (!cssService.sheetRegistered(uri, cssService.USER_SHEET)) {
      cssService.loadAndRegisterSheet(uri, cssService.USER_SHEET);
    }
  }

  /* div can not be shown in the frameset and xul document. */
  function _isParentValid(wnd) {
    return wnd.parent.document instanceof HTMLDocument &&
           wnd.parent.document.body instanceof HTMLBodyElement;
  }

  function _getTopHTMLWnd(wnd) {
    if (!_isParentValid(wnd)) {
      return wnd;
    }

    if (wnd.parent == wnd) {
      return wnd;
    }

    return _getTopHTMLWnd(wnd.parent);
  }

  /**
   * get the sandbox object related to the top level document
   * of the five window object.
   */
  function _getTopSandbox(wnd) {
    let topWnd = _getTopHTMLWnd(wnd);
    if (topWnd.__id__) {
      return _sandboxes[topWnd.__id__];
    } else {
      return null;
    }
  }

  /**
   * Get the position of the given element related to the top left corner
   * of the top level HTMLDocument.
   */
  function _getDocPos(doc, element) {
    element = element ? element : doc.documentElement;
    var wnd = doc.defaultView;
    var clientRect = element ? element.getBoundingClientRect() :
                               doc.documentElement.getBoundingClientRect();
    if (wnd.parent != wnd && _isParentValid(wnd)) {
       var parentClientRect = _getDocPos(wnd.parent.document, wnd.frameElement);
       clientRect = {
         top: parentClientRect.top + clientRect.top,
         left: parentClientRect.left + clientRect.left
       }
    }

    return {
      top: clientRect.top,
      left: clientRect.left
    };
  }

  /**
   * Get user selection, get selection from window.getSelection()
   * or selection in the textarea.
   */
  function _getSelection(event) {
    if (event.originalTarget instanceof XULElement) {
      return ["", null];
    }

    if (event.target instanceof HTMLInputElement &&
        event.target.type != "text") {
      return ["", null];
    }

    var selStr = "";
    var selDOM = null;
    var elem = event.target;
    // If the element is an input or textarea, get the selection
    // from the input/textarea.
    if (elem instanceof HTMLInputElement ||
        elem instanceof HTMLTextAreaElement) {
      if (!jsm.utils.getPref("ignoreinput", true)) {
        selStr = elem.value.substring(elem.selectionStart,
                                      elem.selectionEnd).trim();
      }
    } else {
      var selection = elem.ownerDocument.defaultView.getSelection();
      selStr = selection.toString().trim();
      selDOM = elem.ownerDocument.createElement("div");
      for (var i = 0, l = selection.rangeCount; i < l; i++) {
        selDOM.appendChild(selection.getRangeAt(i).cloneContents());
      }
      selDOM = selDOM.innerHTML;
    }

    return [selStr, selDOM];
  }

  var _showIntentTimeout = null;

  /**
   * Capture all the mouseup/keyup event, and show the app intent pie menu 
   * in the top level document which is HTMLDocument.
   * Why HTMLDocument? Like about:appcenter, the top level document is
   * a XULDocument in which the piemenu can not be displayed.
   * 
   * Only pageX, pageY, clientX, clientY, screenX and screenY
   * can be fetched from event object,
   * but we need to get the mouse position related to the top left corner
   * of the browser (top level html document precisely),
   * and "_getDocPos" provide this .
   */
  function _showIntentIfTextSelected(event) {
    if (!jsm.utils.getPref("showpiemenu", true)) {
      return;
    }

    // exclude mouse right clicking
    if (typeof event.button != "undefined" && event.button !== 0) {
      return;
    }

    // Exclude the mouse event fired by scrolling elements, resizing textarea etc.
    if (event.originalTarget instanceof XULElement) {
      return;
    }

    // ignoring non-text input including password.
    if (event.target instanceof HTMLInputElement && event.target.type != "text") {
      return;
    }

    let _mousePos = {
      x: event.pageX,
      y: event.pageY
    };

    window.clearTimeout(_showIntentTimeout);
    _showIntentTimeout = window.setTimeout(function() {
      var wnd = event.originalTarget.ownerDocument.defaultView;
      var sandbox = _getTopSandbox(wnd);

      // Initialize the sandbox for top window in the first time invoking.
      // Because init function is invoked in the timeout, the tabs opened
      // when firefox startup can not be mornitored.
      // Do not initialize the sandbox in the DOMContentLoaded event listener.
      if (!sandbox) {
        sandbox = _injectAppIntent(_getTopHTMLWnd(wnd));
      }

      if (sandbox) {
        var [selStr, selDOM] = _getSelection(event);
        // Check if current selection equals last selection, if yes,
        // do not show the intent pie menu.
        // To prevent popuping menu too much times, such as: 
        //   a) right click to show context menu;
        //      and left click to hide context menu
        //   b) text editor like coop.mozilla.com.cn
        if (!selStr || selStr == _lastSel) {
          sandbox.appIntent.hide(event);
          return;
        }

        // Calculate the direction of piemenu icon related to the mouse.
        var _dir = "top_left";

        var doc = event.originalTarget.ownerDocument;

        // If user select text in an input or textarea, we can not get the 
        // bounding client rect, so ignore it.
        if (event.target instanceof HTMLInputElement ||
            event.target instanceof HTMLTextAreaElement) {
          sandbox.appIntent.show(event, selStr, selDOM,
                                 _getDocPos(doc), _dir, null);
        } else {
          let selection = wnd.getSelection();
          let rangeCount = selection.rangeCount;
          let rangeRect = selection.getRangeAt(rangeCount - 1)
                                   .getBoundingClientRect();

          // expose read only props to the sandbox
          let docPos = jsm.utils.exposeReadOnly(_getDocPos(doc));
          sandbox.appIntent.show(event, selStr, selDOM, docPos, _dir, {
            top: rangeRect.top + wnd.scrollY,
            left: rangeRect.left + wnd.scrollX,
            height: rangeRect.height,
            width: rangeRect.width
          });
        }
      }
    }, 10);
    return false;
  }

  function _onMenuPopupShowing(event) {
    if (!gContextMenu) {
      return;
    }

    // Only add intent menu when the first level popup menu is shown.
    if (event.target.id !== "contentAreaContextMenu") {
      return;
    }

    let menu = document.getElementById("appIntentMenuItem");
    // reset menu item
    menu.hidden = true;

    if (!jsm.utils.getPref("showintentmenu", true)) {
      return;
    }

    let onSelection = gContextMenu.isTextSelected;
    let onImage = gContextMenu.onImage;
    let onLink = gContextMenu.onLink;
    let onTextInput = gContextMenu.onTextInput;

    if (!onSelection && !onImage && !onLink && !onTextInput) {
      return;
    }

    function _createMenuItem(intent) {
      var item = document.createElement("menuitem");
      item.setAttribute("label", label);
      item.activity = activity;
      return item;
    }

    function _buildMenuPopup(intents, selStr, selDOM, imgsrc, href, title) {
      var menuPopup = document.getElementById("appIntentMenuPopup");
      // reset intent menu items.
      while (menuPopup.childNodes.length > 0) {
        menuPopup.removeChild(menuPopup.childNodes[0]);
      }

      intents.forEach(function(intent) {
        // Because the main menu has entry for search engine, ignore this.
        if (intent.action == "$app-search-engine$") {
          return;
        }

        // Ignore share entry for selection if SNS Share Tool is installed.
        if (intent.type == "text/*" && jsm.AppIntent.isSNSInstalled() &&
            intent.action == "$app-share-tool$") {
          return;
        }

        let menuitem = document.createElement("menuitem");
        // If labelStr is defined, use the bundle string as intent label.
        menuitem.setAttribute("label", intent.label);
        menuitem.setAttribute("image", intent.icon);
        menuitem.setAttribute("class", "menuitem-iconic");
        menuitem.activity = {
          action: intent.action,
          message: intent.message,
          data: {
            type: intent.type,
            text: selStr,
            html: selDOM,
            imgsrc: imgsrc,
            href: href,
            title: title,
            referrer: document.commandDispatcher.focusedWindow
                              .document.location.href
          },
          from: "menu"
        };
        menuPopup.appendChild(menuitem);
      });
    }

    /**
     * Add menu item to toggle piememu.
     */
    function _addToggleItem() {
      let menuitem = document.createElement("menuitem");
      menuitem.setAttribute("label",
                            document.getElementById("livemarginsStrings")
                                    .getString("intent.showpiemenu"));
      menuitem.setAttribute("type", "checkbox");
      menuitem.setAttribute("checked", jsm.utils.getPref("showpiemenu", true));
      menuitem.addEventListener("command", function() {
        let show = !jsm.utils.getPref("showpiemenu", true);
        jsm.utils.setPref("showpiemenu", show);
        let iconIntentTracker = jsm.Tracker.intent.actions.SHOW_PIE_ICON;
        let hidePieIconIntentTracker = jsm.Tracker.intent.actions.HIDE_PIE_ICON;
        jsm.Tracker.track(jsm.Tracker.intent.type,
                          show ? iconIntentTracker : hidePieIconIntentTracker,
                          "");
      }, false);
      document.getElementById("appIntentMenuPopup").appendChild(menuitem);
    }

    function _addCustomizeItem() {
      let menuitem = document.createElement("menuitem");
      let label = document.getElementById("livemarginsStrings")
                          .getString("intent.customizepiemenu");
      menuitem.setAttribute("label", label);
      menuitem.addEventListener("command", function() {
        var features = "chrome,titlebar,toolbar,centerscreen";
        try {
          var instantApply = Cc["@mozilla.org/preferences-service;1"]
                               .getService(Ci.nsIPrefService)
                               .getBranch("browser.preferences.")
                               .getBoolPref("instantApply");
          features += instantApply ? ",dialog=no" : ",modal";
        } catch (e) {
          features += ",modal";
        }
        window.openDialog("chrome://livemargins/content/options.xul",
                          "livemarginsOptions", features, "", 1).focus();
        jsm.Tracker.track(jsm.Tracker.intent.type,
                          jsm.Tracker.intent.actions.CUSTOMIZE, "");
      }, false);
      document.getElementById("appIntentMenuPopup").appendChild(menuitem);
    }

    function _addSeperator() {
      document.getElementById("appIntentMenuPopup")
              .appendChild(document.createElement("menuseparator"));
    }

    if (onImage) {
      let imgSrc = gContextMenu.mediaURL ? gContextMenu.mediaURL :
                                           gContextMenu.imageURL;
      let intents = jsm.AppIntent.getIntentsByImage(imgSrc);
      if (intents && intents.length > 0) {
        _buildMenuPopup(intents, "", null, imgSrc, "", "");
        menu.hidden = false;  
      }
    } else if (onSelection) {
      let selection = document.commandDispatcher.focusedWindow.getSelection();
      let selStr = selection.toString().trim();
      let selDOM = document.commandDispatcher
                           .focusedWindow.document.createElement('div');
      for (var i = 0, l = selection.rangeCount; i < l; i++) {
        selDOM.appendChild(selection.getRangeAt(i).cloneContents());
      }
      selDOM = selDOM.innerHTML;
      if (!selStr) {
        return;
      }
      let intents = jsm.AppIntent.getIntentsByText(selStr, selDOM);
      if (intents && intents.length > 0) {
        let topDoc = document.commandDispatcher.focusedWindow.top.document; 
        if (topDoc.location.href.indexOf("http") == 0) {
          _buildMenuPopup(intents, selStr, selDOM, "",
                          topDoc.location.href,
                          topDoc.title);
        } else {
          _buildMenuPopup(intents, selStr, selDOM, "", "", "");
        }
        menu.hidden = false;
      }
      _addSeperator();
      _addToggleItem();
      _addSeperator();
      _addCustomizeItem();
    } else if (onTextInput) {
      // checkif text is selected in an input or textarea.
      let elem = gContextMenu.target;
      let selStr = elem.value.substring(elem.selectionStart, elem.selectionEnd);
      selStr = selStr.trim();
      if (!selStr) {
        return;
      }
      let intents = jsm.AppIntent.getIntentsByText(selStr, null);
      if (intents && intents.length > 0) {
        _buildMenuPopup(intents, selStr, null, "", "", "");
        menu.hidden = false;
      }
      _addSeperator();
      _addToggleItem();
      _addSeperator();
      _addCustomizeItem();
    } else if (onLink) {
      let link = gContextMenu.target;
      let intents = jsm.AppIntent.getIntentsByLink(link.textContent, link.href);
      if (intents && intents.length > 0) {
        _buildMenuPopup(intents, link.textContent, null,
                        "", link.href, link.textContent);
        menu.hidden = false;
      }
    }
  }

  ns.init = function(event) {
    // Add handler to show app intents in the context menu.
    document.getElementById("contentAreaContextMenu")
            .addEventListener("popupshowing", _onMenuPopupShowing, false);

    // load global ccs style for piemenu.
    let cssUrl = "chrome://livemargins/skin/css/intent_bubble.css";
    loadCSSText(jsm.utils.getContentFromURL(cssUrl));

    function _hidePiemenu(event) {
      var wnd = event.originalTarget.ownerDocument.defaultView;
      var sandbox = _getTopSandbox(wnd);
      if (sandbox) {
        sandbox.appIntent.hide(event);
      }
    }

    /**
     * Add listeners to "content" instead of appcontent.
     * Or the workspace of Firebug will also be monitored.
     */
    let contentElem = document.getElementById("content");
    contentElem.addEventListener("mouseup", _showIntentIfTextSelected, false);
    contentElem.addEventListener("mousedown", function(event) {
      _hidePiemenu(event);
      // record current selection
      [_lastSel] = _getSelection(event);
      _lastPos = {
        x: event.pageX,
        y: event.pageY 
      };
    }, false);

    contentElem.addEventListener("keyup", function(event) {
      _hidePiemenu(event);
    }, false);
  };


  var contentAPI = {
    startActivity: function(activity) {
      jsm.AppIntent.startActivity(activity);
    },

    getIntents: function(event, selStr, selDOM) {
      var intents = jsm.AppIntent.getIntentsByText(selStr, selDOM);
      var result = [];
      intents.forEach(function(i) {
        var intent = { };

        for (var attr in i) {
          intent[attr] = i[attr];
        }

        result.push(jsm.utils.exposeReadOnly(intent));
      });

      return jsm.utils.exposeReadOnly(result);
    },

    /**
     * Get page zoom factor. 
     */
    getZoom: function(win) {
      if (!win) {
        return -1;
      }

      let zoom = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                       .getInterface(Components.interfaces.nsIDOMWindowUtils)
                       .screenPixelsPerCSSPixel;
      return zoom;
    },

    getString: function(name) {
      return document.getElementById("livemarginsStrings").getString(name);
    }
  };

  ns.onContextCommand = function(event, menu) {
    if (event.target.activity) {
      jsm.AppIntent.startActivity(event.target.activity);
    }
  };

  var _sandboxId = 1;
  var _sandboxes = { };
  function _injectAppIntent(wnd) {
    // only inject the top level.
    if (wnd.parent != wnd && _isParentValid(wnd)) {
      return;
    }
    if (/^(about|chrome):/.test(wnd.location.protocol)) {
      return;
    }

    let sandbox = new Cu.Sandbox(wnd);
    sandbox.unsafeWindow = wnd.wrappedJSObject;
    sandbox.window = wnd;
    sandbox.document = sandbox.window.document;
    sandbox.__proto__ = sandbox.window;
    // We need set __exposedProps__ since FF17
    sandbox.contentAPI = jsm.utils.exposeReadOnly(contentAPI);

    let piejsUrl = "resource://livemargins/scripts/piemenu.js";
    Cu.evalInSandbox(jsm.utils.getContentFromURL(piejsUrl), sandbox, "1.8");
    let intentjsUrl = "resource://livemargins/scripts/intent.js";
    Cu.evalInSandbox(jsm.utils.getContentFromURL(intentjsUrl), sandbox, "1.8");

    sandbox.__id__ = _sandboxId++;
    sandbox.window.__id__ = sandbox.__id__;
    // index sandbox
    _sandboxes[sandbox.__id__] = sandbox;

    // Listen to the change of default search engine.
    let _sePrefListener = new jsm.utils.prefListener("browser.search.",
                                                     function(branch, name) {
      if (name == "selectedEngine") {
        sandbox.appIntent.rebuildPieMenu();
      }
    });
    _sePrefListener.register();

    // Listen to the change of share tool.
    let _sharePrefListener = null;

    if (jsm.SNS && jsm.SNS.getSNSPrefName()) {
      _sharePrefListener = new jsm.utils.prefListener(jsm.SNS.getSNSPrefName(),
                                                      function(branch, name) {
        sandbox.appIntent.rebuildPieMenu();
      });
      _sharePrefListener.register();
    }

    sandbox.window.addEventListener("unload", function(event) {
      _sePrefListener.unregister();
      if (_sharePrefListener) {
        _sharePrefListener.unregister();
      }

      delete _sandboxes[sandbox.__id__];
      delete sandbox.__proto__;
      // Sometimes an exception is caused: "Component is not available."
      try {
        for (var attr in sandbox) {
          delete sandbox[attr];
        }
      } catch (e) { }
    }, false);
    return sandbox;
  }

  window.addEventListener("load", function() {
    window.setTimeout(ns.init, 1000);
    window.removeEventListener("load", arguments.callee, false);
  }, false);
})();

