(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 = "";
    var _lastPos = null;        // record the mouse position, to judge the direction of mouse moving.
    
    // load style sheets
    function loadCSSText(cssText) {
        var uri = jsm.ioService.newURI("data:text/css;base64," + btoa(cssText), null, null);
        if (!jsm.styleSheetService.sheetRegistered(uri, jsm.styleSheetService.USER_SHEET)) {
            jsm.styleSheetService.loadAndRegisterSheet(uri, jsm.styleSheetService.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 "";
        }
        
        if (event.target instanceof HTMLInputElement && event.target.type == "password") {
            return "";
        }
        
        var selStr = "";
        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) {
            selStr = elem.value.substring(elem.selectionStart, elem.selectionEnd).trim();
        } else {
            selStr = elem.ownerDocument.defaultView.getSelection().toString().trim();
        }
        
        return selStr;
    }
    
    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 and 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 the password input.
        if (event.target instanceof HTMLInputElement && event.target.type == "password") {
            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 = _getSelection(event);
                // Check if current selection equals last selection, if yes, then 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, _getDocPos(doc), _dir, null);
                } else {
                    let selection = wnd.getSelection();
                    let rangeCount = selection.rangeCount;
                    let rangeRect = selection.getRangeAt(rangeCount - 1).getBoundingClientRect();
                    sandbox.appIntent.show(event, selStr, _getDocPos(doc), _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, 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,
                        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);
                jsm.Tracker.track(jsm.Tracker.intent.type, show ? jsm.Tracker.intent.actions.SHOW_PIE_ICON : jsm.Tracker.intent.actions.HIDE_PIE_ICON, "");
            }, 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, "", imgSrc, "", "");
                menu.hidden = false;    
            }
        } else if (onSelection) {
            let selStr = document.commandDispatcher.focusedWindow.getSelection().toString().trim();
            if (!selStr) {
                return;
            }
            let intents = jsm.AppIntent.getIntentsByText(selStr);
            if (intents && intents.length > 0) {
                let topDoc = document.commandDispatcher.focusedWindow.top.document; 
                if (topDoc.location.href.indexOf("http") == 0) {
                    _buildMenuPopup(intents, selStr, "", topDoc.location.href, topDoc.title);
                } else {
                    _buildMenuPopup(intents, selStr, "", "", "");
                }
                menu.hidden = false;
            }
            _addSeperator();
            _addToggleItem();
        } 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).trim();
            if (!selStr) {
                return;
            }
            let intents = jsm.AppIntent.getIntentsByText(selStr);
            if (intents && intents.length > 0) {
                _buildMenuPopup(intents, selStr, "", "", "");
                menu.hidden = false;
            }
            _addSeperator();
            _addToggleItem();
        } 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, "", 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);
        
        // Piemenu can only be displayed correctly after Gecko2.0
        if (!jsm.utils.isFFVersionGET4()) {
            return;
        }
           
        // load global ccs style for piemenu.        
        loadCSSText(jsm.utils.getContentFromURL("chrome://livemargins/skin/css/intent_bubble.css"));
        
        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.
         */
        document.getElementById("content").addEventListener("mouseup", _showIntentIfTextSelected, false);
        document.getElementById("content").addEventListener("mousedown", function(event) {
            _hidePiemenu(event);
            // record current selection
            _lastSel = _getSelection(event);
            _lastPos = {
                x: event.pageX,
                y: event.pageY 
            };
        }, false);
        document.getElementById("content").addEventListener("keyup", function(event) {
            _hidePiemenu(event);
        }, false);
    };

    
    var contentAPI = {
        startActivity: function(activity) {
            jsm.AppIntent.startActivity(activity);
        },
        
        getIntents: function(event, selStr) {
            var intents = jsm.AppIntent.getIntentsByText(selStr);
            var result = [];
            intents.forEach(function(i) {                
                var intent = { };
                
                for (var attr in i) {
                    intent[attr] = i[attr];
                }
                result.push(intent);
            });
            return 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;
        }
        
        let sandbox = new Cu.Sandbox(wnd);
        sandbox.unsafeWindow = wnd.wrappedJSObject;
        sandbox.window = wnd;
        sandbox.document = sandbox.window.document;
        sandbox.__proto__ = sandbox.window;
        sandbox.contentAPI = contentAPI;
        sandbox.JSON = JSON;
        Cu.evalInSandbox(jsm.utils.getContentFromURL("resource://livemargins/scripts/piemenu.js"), sandbox, "1.8");
        Cu.evalInSandbox(jsm.utils.getContentFromURL("resource://livemargins/scripts/intent.js"), 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);
})();