/*global apf*/
define(function(require, exports, module) {
    main.consumes = ["Plugin", "settings", "commands", "layout", "anims", "ui", "c9"];
    main.provides = ["menus", "Menu", "MenuItem", "Divider"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var settings = imports.settings;
        var commands = imports.commands;
        var anims = imports.anims;
        var layout = imports.layout;
        var ui = imports.ui;
        var c9 = imports.c9;
        var assert = require("c9/assert");
        
        /***** Initialization *****/
        
        var plugin = new Plugin("Ajax.org", main.consumes);
        var emit = plugin.getEmitter();
        
        var items = {};
        var menus = {};
        var queue = [];
        var count = 0;
        var debug = location.href.indexOf('menus=1') > -1;
        var minimized;
        var inited, height = 31, minimizedHeight = 12;
        
        var menubar, logobar; // UI elements
        
        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;
            
            draw();
            
            commands.addCommand({
                name: "toggleMenubar",
                group: "General",
                exec: function(e) {
                    if (!minimized)
                        minimize();
                    else
                        restore();
                }
            }, plugin);
            
            // Hook deep into APF to make hotkeys work as we want
            
            function formatHotkey(value) {
                if (value && /^commands./.test(value))
                    value = commands.getHotkey(value.slice("commands.".length));
                if (apf.isMac && value)
                    value = apf.hotkeys.toMacNotation(value);
                return value;
            }
            
            function addHotkeyListener(amlNode, value) {
                var oldHandler = amlNode.$funcHandlers.hotkey;
                if (oldHandler && oldHandler.value != value) {
                    commands.commandManager.off("prop." + oldHandler.prop, oldHandler.handler);
                }
                if (value && /^commands./.test(value) && commands.commandManager) {
                    // this allows to remove event handler when node.destroy is called 
                    oldHandler = amlNode.$funcHandlers.hotkey = oldHandler || {
                        handler: function(e) {
                            amlNode.$propHandlers["hotkey"].call(amlNode, amlNode.hotkey);
                        }
                    };
                    oldHandler.prop = value.slice("commands.".length);
                    oldHandler.amlNode = commands.commandManager;
                    commands.commandManager.on("prop." + oldHandler.prop, oldHandler.handler);
                }
            }
            
            apf.button.prototype.$propHandlers["hotkey"] = function(value) {
                if (this.$hotkey)
                    apf.setNodeValue(this.$hotkey, formatHotkey(value));
                this.$propHandlers["tooltip"].call(this, this.tooltip);
                addHotkeyListener(this, value);
            };
            apf.button.prototype.$propHandlers["tooltip"] = function(value) {
                if (this.$ext) {
                    var hotkey = formatHotkey(this.hotkey);
                    this.$ext.setAttribute("title", (this.tooltip || "")
                        + (hotkey ? " (" + hotkey + ")" : ""));
                }
            };
    
            apf.item.prototype.$propHandlers["hotkey"] = function(value) {
                if (!this.$amlLoaded) {
                    var _self = this;
                    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
                        if (_self.$hotkey && _self.hotkey)
                            apf.setNodeValue(this.$hotkey, formatHotkey(_self.hotkey));
                    });
                }
                else if (this.$hotkey)
                    apf.setNodeValue(this.$hotkey, formatHotkey(value));
                addHotkeyListener(this, value);
            };
    
            apf.splitbutton.prototype.$propHandlers["command"] =
            apf.button.prototype.$propHandlers["command"] =
            apf.item.prototype.$propHandlers["command"] = function(value) {
                if (!value) {
                    this.removeAttribute("hotkey");
                    this.onclick = null;
                    return;
                }
                
                this.setAttribute("hotkey", value && "commands." + value || "");
    
                this.onclick = function(e) {
                    if (e && e.htmlEvent && e.htmlEvent.button) return;
                    commands.exec(value, null, { source: "click" });
                    var command = commands.commands[value];
                    if (command && command.focusContext)
                        emit("focusEditor");
                };
            };
    
            settings.on("read", function(e) {
                settings.setDefaults("state/menus", [["minimized", "false"]]);
    
                if (settings.getBool("state/menus/@minimized")) {
                    minimize(true, true);
                    minimized = true;
                }
                else {
                    minimized = false;
                }
            }, plugin);
            
            if (options.autoInit)
                init();
        }
        
        function draw() {
            // Create UI elements
            logobar = layout.findParent(plugin);
            
            menubar = logobar.insertBefore(new apf.bar({
                "id": "menubar",
                "class": "fakehbox aligncenter menubar",
                "style": "padding : 0 5px 0 3px;position:static",
            }), logobar.firstChild);
            
            menubar.insertBefore(new apf.button({
                "class": "c9-mbar-minimize",
                "skin": "c9-simple-btn",
                "onmousedown": function(e) {
                    if (!minimized)
                        minimize();
                    else
                        restore();
                }
            }), menubar.firstChild);
            
            // Mark menu bar
            menubar.menubar = true;
            
            plugin.addElement(menubar);
    
            logobar.$ext.addEventListener("mousedown", function() {
                restore();
            });
            
            emit("draw");

            if (c9.local)
                apf.popup.setMargin({ top: 50 });
        }
        
        /***** Methods *****/
        
        function splitSafe(path) {
           var pieces = [], escaped;
           path.split("/").forEach(function(n) {
               if (escaped) n = escaped + "/" + n;
               escaped = n.substr(-1) == "\\" ? n : false; //.substr(n, n.length - 1)
               if (!escaped) pieces.push(n);
           });
           if (escaped) pieces.push(escaped);
           return pieces;
        }
        
        function popSafe(path) {
            return splitSafe(path).pop().replace(/\\\//g, "/");
        }
        
        function init() {
            inited = true;
            layout.initMenus(plugin);
            
            addItemByPath("View/Menu Bar", new ui.item({
                type: "check",
                command: "toggleMenubar",
                isAvailable: function() {
                    !settings.getBool("state/menus/@minimized")
                        ? this.check() : this.uncheck();
                    return true;
                }
            }), 250, plugin);
            
            if (queue) {
                queue.forEach(function(args) {
                    addItemByPath.apply(null, args);
                });
                
                queue = null;
            }
            
            emit.sticky("ready");
        }
        
        function checkItems(e) {
            if (e.value) {
                var editor = emit("getEditor");
    
                var nodes = this.childNodes;
                for (var n, prev, i = nodes.length - 1; i >= 0; i--) {
                    var cmd = (n = nodes[i]).command;

                    if (!n.visible) continue;
                    
                    // prevent dividers two consecutive dividers and dividers
                    // at bottom and top
                    if (n.localName == "divider") {
                        if (!prev || prev.localName == "divider")
                            n.hide();
                        else
                            n.show();
                        
                        prev = n;
                        continue;
                    }
                    prev = n;
                    
                    var c = cmd && commands.commands[cmd];
                    var fn = c && c.isAvailable;
                    var a = (!n.isAvailable || n.isAvailable(editor))
                      && (!fn || fn(editor));
                     
                    if (!cmd) 
                        cmd = n.command;
                    if (!cmd && !n.isAvailable)
                        continue;

                    n[a ? "enable" : "disable"]();
                }
                if (prev && prev.localName == "divider") {
                    prev.hide();
                }
            }
            
            toggleIframes(e);
        }
        
        function toggleIframes(e) {
            var frames = document.getElementsByTagName("iframe");
            for (var i = 0; i < frames.length; i++)
                frames[i].style.pointerEvents = e.value ? "none" : "";
        }
        
        function decorate(menu) {
            menu.addEventListener("prop.visible", checkItems);
        }
        
        function setRootMenu(name, index, item, menu, plugin) {
            if (item instanceof Menu) {
                menu = item.aml;
                plugin = menu;
                item = null;
            }
            else if (typeof item == "object" && !item.nodeFunc) {
                plugin = item;
                item = null;
            }
            
            if (menu instanceof Menu)
                menu = menu.aml;
            else if (!plugin && menu && !menu.nodeFunc) {
                plugin = menu;
                menu = null;
            }
            
            if (menu) {
                // if (!menu)
                //     menu.setAttribute("id", "mnuMenus" + (++count));
                menu.addEventListener("prop.visible", checkItems);
                menus[name] = menu;
            }
            else {
                menu = menus[name];
                if (!menu) {
                    menu = menus[name] = new ui.menu({
                        id: "mnuMenus" + (++count),
                        "onprop.visible": checkItems
                    });
                }
            }
            
            var drawIt = emit("root", {
                name: name,
                menu: menu,
                index: index
            }) !== false;
            
            if (item != -1) {
                if (item) {
                    item.setAttribute("submenu", menu || "mnuMenus" + count);
                    items[name] = item;
                }
                else {
                    item = items[name];
                    if (!item && drawIt) {
                        item = items[name] = new ui.button({
                            skin: "c9-menu-btn",
                            submenu: menu,
                            margin: "0 0 0 0",
                            caption: (debug ? "(" + index + ") " : "") + name
                        });
                    }
                }
        
                if (typeof index == "number" && drawIt)
                    ui.insertByIndex(menubar, item, index, plugin);
                
                item && lifeCycleHooks(item, items, name, plugin);
            }
            
            lifeCycleHooks(menu, menus, name, plugin);
    
            return menu;
        }
    
        function setSubMenu(parent, name, index, item, menu, plugin, force) {
            if (item && !item.nodeFunc) {
                plugin = item;
                item = null;
            }
            else if (menu && !menu.nodeFunc) {
                plugin = menu;
                menu = null;
            }
            
            if (menu) {
                //Switch old menu for new menu
                if (menus[name]) {
                    var nodes = menus[name].childNodes;
                    for (var i = nodes.length - 1; i >= 0; i--) {
                        ui.insertByIndex(menu, nodes[i], nodes[i].$position, plugin);
                    }
    
                    menus[name].destroy(true, true);
                }
    
                if (!menu)
                    menu.setAttribute("id", "mnuMenus" + (++count));
                menus[name] = menu;
            }
            else {
                menu = menus[name];
                if (!menu) {
                    menu = menus[name] = new ui.menu({
                        id: "mnuMenus" + (++count),
                        "onprop.visible": checkItems
                    });
                }
            }
            
            if (item) {
                item.setAttribute("submenu", menu);
                item.setAttribute("caption",
                    apf.escapeXML((debug ? "(" + index + ")" : "") 
                    + popSafe(name)));
                items[name] = item;
            }
            else {
                item = items[name];
                if (!item) {
                    item = items[name] = new apf.item({
                        submenu: menu,
                        caption: (debug ? "(" + index + ") " : "") +
                            popSafe(name)
                    });
                }
                else {
                    item.setAttribute("submenu", menu);
                }
            }
            
            emit("submenu", {
                name: name,
                parent: parent,
                menu: menu,
                item: item,
                index: index
            });
            
            if (typeof index == "number")
                ui.insertByIndex(parent, item, index, plugin);
            
            lifeCycleHooks(item, items, name, plugin);
            lifeCycleHooks(menu, menus, name, plugin);
    
            return menu;
        }
    
        function setMenuItem(parent, name, menuItem, index, item, plugin) {
            if (item && !item.nodeFunc) plugin = item, item = null;
            
            var itemName = popSafe(name);
            if (itemName == "~")
                name += index;
    
            item = items[name];
            if (!item) {
                item = items[name] = menuItem;
    
                if (itemName != "~") {
                    if (debug)
                        itemName = "(" + index + ") " + itemName;
                    item.setAttribute("caption", itemName);
                }
            }

            item.on("DOMNodeInsertedIntoDocument", function() {
                emit("menuitem", {
                    name: name,
                    parent: parent,
                    item: item,
                    index: index
                });
            });

            //index...
            if (typeof index == "number")
                ui.insertByIndex(parent, item, index, plugin);
            else
                parent.appendChild(item);
            
            lifeCycleHooks(item, items, name, plugin);
        }
        
        function lifeCycleHooks(item, items, name, plugin) {
            if (plugin)
                plugin.addElement(item);
            
            item.on("DOMNodeRemovedFromDocument", function() {
                delete items[name];
            });
        }
    
        function addItemByPath(path, menuItem, index, menu, plugin) {
            if (!inited && index < 1000) {
                queue.push([path, menuItem, index, menu, plugin]);
                return;
            }
            
            if (!plugin) 
                plugin = menu, menu = null;
            if (index && typeof index == "object") 
                plugin = index, index = null;
            else if (!plugin && menu && menu.aml)
                menu = menu.aml;
            
            if (menuItem && menuItem.aml)
                menuItem = menuItem.aml;
            
            assert(plugin !== undefined, "addItemByPath requires a plugin argument");
            
            var steps = splitSafe(path), name, p = [], isLast;
            var curpath;
    
            if (!menuItem)
                menuItem = "";
            
            for (var i = 0, l = steps.length; i < l; i++) {
                name = steps[i];
                p.push(name);
                curpath = p.join("/");
    
                //Menubar button & menu
                if (i === 0 && !menu) {
                    isLast = !steps[i + 1];
                    menu = !isLast && menus[curpath]
                      || setRootMenu(name, i == l - 1 || isLast ? index : null,
                        isLast && (!menuItem.nodeFunc && menuItem.item || menuItem.localName == "button" && menuItem),
                        isLast && (!menuItem.nodeFunc && menuItem.menu || menuItem.localName == "menu" && menuItem),
                        plugin);
                }
                //Submenu item & menu
                else if (i != l - 1 || !menuItem.nodeFunc || menuItem.localName == "menu") { // || !menuItem.nodeFunc
                    isLast = !steps[i + 1];
                    menu = !isLast && menus[curpath]
                      || setSubMenu(menu, curpath, i == l - 1 || isLast ? index : null,
                        isLast && (!menuItem.nodeFunc ? menuItem.item : menuItem.localName != "menu" && menuItem),
                        isLast && (!menuItem.nodeFunc && menuItem.menu || menuItem.localName == "menu" && menuItem),
                        plugin);
                }
                //Leaf
                else {
                    setMenuItem(menu, p.join("/"), menuItem, index, plugin);
                }
    
                if (isLast) break;
            }
    
            // return menuItem || menu;
        }
        
        function addItemToMenu(menu, menuItem, index, plugin) {
            return ui.insertByIndex(menu, menuItem, index, plugin);
        }
        
        function getMenuId(path) {
            var menu = menus[path];
            return menu && menu.id;
        }
    
        function restore(preview, noAnim) {
            if (!minimized && !preview) return;
            
            apf.setStyleClass(logobar.$ext, "", ["minimized"]);
    
            logobar.$ext.style.overflow = "hidden";
    
            anims.animateSplitBoxNode(logobar, {
                height: height + "px",
                timingFunction: "cubic-bezier(.10, .10, .25, .90)",
                duration: 0.15,
                immediate: noAnim
            }, 
            function() {
                // tabs.getPanes().forEach(function(pane) {
                //     apf.layout.forceResize(pane.aml.$ext);
                // });
                logobar.$ext.style.overflow = "";
            });
    
            if (!preview) {
                settings.set("state/menus/@minimized", false);
                minimized = false;
    
                emit("restore");
            }
        }
    
        function minimize(preview, noAnim) {
            if (minimized && !preview) return;
            
            logobar.$ext.style.overflow = "hidden";
    
            anims.animateSplitBoxNode(logobar, {
                height: minimizedHeight + "px",
                timingFunction: "cubic-bezier(.10, .10, .25, .90)",
                duration: 0.15,
                immediate: noAnim
            }, 
            function() {
                apf.setStyleClass(logobar.$ext, "minimized");
                apf.layout.forceResize();
                logobar.$ext.style.overflow = "";
            });
    
            if (!preview) {
                settings.set("state/menus/@minimized", true);
                minimized = true;
    
                emit("minimize");
            }
        }
        
        function expand(path) {
            if (!items[path])
                throw new Error("Could not find menu item " + path);
            
            var steps = splitSafe(path), p = [], item;
            var curpath;
    
            for (var name, i = 0, l = steps.length; i < l; i++) {
                name = steps[i];
                p.push(name);
                curpath = p.join("/");
                
                item = items[curpath];
                item.localName == "button"
                    ? item.showMenu()
                    : item.$submenu();
            }
            
            return menus[path] || items[path];
        }
        
        function collapse(path) {
            if (!items[path])
                throw new Error("Could not find menu item " + path);
            
            var item = items[path];
            if (item.localName == "button") {
                item.hideMenu();
                item.$setState("Out", {}, "mouseout");
            }
            else {
                item.$submenu(true);
            }
        }
        
        function click(path) {
            if (!items[path])
                throw new Error("Could not find menu item " + path);
            
            var item = items[path];
            item.localName == "button"
                ? item.dispatchEvent("click")
                : item.$up();
        }
        
        function enableItem(path) {
            if (!items[path])
                throw new Error("Could not find menu item " + path);
            
            var item = items[path];
            item.enable();
            
            var menu = menus[path];
            menu && menu.enable();
        }
    
        function disableItem(path) {
            if (!items[path])
                throw new Error("Could not find menu item " + path);
            
            var item = items[path];
            item.disable();
            
            var menu = menus[path];
            menu && menu.disable();
        }
    
        function remove(path) {
            var removed = false;
            var i = path.length;
            if (path[i - 1] == "/") i -= 1; 
            
            Object.keys(items).forEach(function(p) {
                if (p.indexOf(path) === 0 && (!p[i] || p[i] === "/")) {
                    items[p].destroy(true, true);
                    delete items[p];
                    removed = true;
                }
            });
            
            Object.keys(menus).forEach(function(p) {
                if (p.indexOf(path) === 0 && (!p[i] || p[i] === "/")) {
                    menus[p].destroy(true, true);
                    delete menus[p];
                    removed = true;
                }
            });
            
            return removed;
        }
        
        function get(path) {
            return {
                item: items[path],
                menu: menus[path]
            };
        }
        
        function escape(name) {
            return name.replace(/[/]/g, "\u2044");
        }
        
        /***** Lifecycle *****/
        
        plugin.on("load", function() {
            load();
        });
        plugin.on("enable", function() {
            
        });
        plugin.on("disable", function() {
            
        });
        plugin.on("unload", function() {
            loaded = false;
        });
        
        /***** Register and define API *****/
        
        /**
         * Draws the main application menu bar and provides an API for adding
         * and removing menus and menu items to the menu bar.
         * 
         * Example:
         * 
         *     // Add the main Edit menu button
         *     menus.setRootMenu("Edit", 200, plugin);
         * 
         *     // Add a divider to the Edit menu at position 300
         *     menus.addItemByPath("Edit/~", new Divider(), 300, plugin);
         * 
         *     // Add the cut, copy and paste menu items below the divider at positions 400 - 600
         *     menus.addItemByPath("Edit/Cut", new MenuItem({ command : "cut" }), 400, plugin);
         *     menus.addItemByPath("Edit/Copy", new MenuItem({ command : "copy" }), 500, plugin);
         *     menus.addItemByPath("Edit/Paste", new MenuItem({ command : "paste" }), 600, plugin);    
         * 
         * You can visualize the index numbers that are used by the menus by
         * starting Cloud9 with ?menus=1. This will show the index number as
         * caption of the menu items and buttons. It's always a good practice
         * to leave space between the numbers. That way other plugins can add 
         * menu items in between the ones you create. It is also good practice
         * to choose a unique range to prevent obvious collisions with other
         * plugins (e.g. 312, 315, 318).
         * 
         * @singleton
         **/
        plugin.freezePublicAPI({
            /**
             * The HTML Element for the menu bar.
             * @property {HTMLElement} container
             * @readonly
             */
            get container() { return menubar.$ext; },
            
            /**
             * 
             */
            get minimized() { return minimized; },
            
            /**
             * 
             */
            get height() { return height; },
            set height(v) {
                height = v;
                if (minimized === false) restore(true, true);
            },
            get minimizedHeight() { return minimizedHeight; },
            set minimizedHeight(v) {
                minimizedHeight = v;
                if (minimized === true) minimize(true, true);
            },
            
            _events: [
                /**
                 * Fires when a reference to the focussed editor is requested.
                 * This happens when a menu is shown, to check the availability
                 * of the commands.
                 * @event getEditor
                 * @private
                 */
                "getEditor",
                /**
                 * Fires when the menus are expanded to their original height.
                 * @event restore
                 */
                "restore",
                /**
                 * Fires when the menus are collapsed to a minimal size.
                 * @event minimize
                 */
                "minimize"
            ],
            
            /**
             * Creates a button in the menu bar and adds an empty menu that
             * is shown when a user clicks the button.
             * 
             * The {@link layout} plugin creates the default menu buttons. These
             * are File, Edit, Find, View, Goto, Run, Tools, Windows. Use this
             * method to add more buttons. If you are implementing your own
             * layout plugin, use this method to define the menus yourself.
             * 
             * @param {String}     name    The caption of the menu button.
             * @param {Number}     index   Determines the position of the button. Higher indexes are more to the right.
             * @param {Menu}       [menu]  An alternative menu to attach to the button. 
             * @param {Plugin}     plugin  The plugin responsible for creating 
             *   the root menu. This is needed for proper 
             *   {@link Plugin#cleanUp} when the plugin is unloaded.
             */
            setRootMenu: setRootMenu,
            
            /**
             * Adds sub menus and menu items to the 
             * {@link #setRootMenu root menus}. You can add items many levels
             * deep. 
             * 
             * Example:
             * 
             *     var item = new MenuItem({ command: "saveas" });
             *     menus.addItemByPath("File/Save As...", item, 400, plugin);
             * 
             * Your item will only show up if the higher elements are
             * created. In the following example, the item won't show up, 
             * because the "Extra" item is not created.
             * 
             *     var item = new MenuItem({ command: "test" });
             *     menus.addItemByPath("File/Extra/Test", item, 100, plugin);
             * 
             * Any time after executing the code above you can create the
             * "Extra" item:
             * 
             *     menus.addItemByPath("File/Extra", null, 300, plugin);
             * 
             * @param {String}        path      The captions of all elements 
             *   that lead to the item to add, separated by a "/".
             * @param {MenuItem/null} menuItem  The menu item placed at the 
             *   location specified by `path`.
             * @param {Number}        index     The position of the menu item 
             *   within in the menu. Higher means lower in the menu.
             * @param {Number}        plugin    The plugin responsible for 
             *   creating the menu item. This is needed for proper 
             *   {@link Plugin#cleanUp} when the plugin is unloaded.
             */
            addItemByPath: addItemByPath,
            
            /**
             * Adds an instance of ui.item to an instance of ui.menu.
             * @param {AMLElement} menu
             * @param {AMLElement} menuItem
             * @param {Number}     index
             * @param {Plugin}     plugin
             * @private
             */
            addItemToMenu: addItemToMenu,
            
            /**
             * Enables a menu item.
             * @param {String} path  The path pointing to the item.
             */
            enableItem: enableItem,
            
            /**
             * Disables a menu item.
             * @param {String} path  The path pointing to the item.
             */
            disableItem: disableItem,
            
            /**
             * Removes a menu item.
             * @param {String} path  The path pointing to the item.
             */
            remove: remove,
            
            /**
             * Retrieves the id of a menu item. 
             * @param {String} path  The path pointing to the item.
             * @private
             */
            getMenuId: getMenuId,
            
            /**
             * Expands a menu.
             * @param {String} path  The path pointing to the menu.
             */
            expand: expand,
            
            /**
             * Collapses a menu.
             * @param {String} path  The path pointing to the menu.
             */
            collapse: collapse,
            
            /**
             * Executes the click event handlers on a menu item.
             * @param {String} path  The path pointing to the item.
             */
            click: click,
            
            /**
             * Retrieves the AMLElements for the menu and menu item for a path
             * @param {String} path  The path pointing to the menu / item.
             * @return {Object}
             * @return {AMLElement} return.menu
             * @return {AMLElement} return.item
             * @private
             */
            get: get,
            
            /**
             * @ignore
             */
            init: init,
            
            /**
             * Expands the menu bar to it's normal size
             * @fires expand
             */
            restore: restore,
            
            /**
             * Collapses the menu bar to it's smallest size
             * @fires collapse
             */
            minimize: minimize,
            
            /**
             * @ignore
             */
            decorate: decorate,
            
            /**
             * Escapes / in menu item name.
             * @param {String} name  A name of menu item.
             */
            escape: escape
        });
        
        /***** Constructors *****/
        
        function Menu(options, forPlugin) {
            var plugin = new Plugin("Ajax.org", main.consumes);
            var emit = plugin.getEmitter();
            
            var items = [], meta = {};
            var aml, lastCoords;
            
            if (!options) options = 0;
            
            function append(item) {
                ui.insertByIndex(aml, item.aml, item.position, item.plugin || forPlugin);
                
                item.menu = plugin;
                items.push(item);
                
                item.aml.on("DOMNodeRemoveFromDocument", function() {
                    items.splice(items.indexOf(item), 1);
                });
                
                return item;
            }
            
            function remove(item) {
                aml.removeChild(item.aml);
            }
            
            function show(x, y, type) {
                if (type == "context") {
                    x += 2;
                    y += 2;
                }
                lastCoords = { x: x, y: y };
                aml.display(x, y);
            }
            
            plugin.on("load", function() {
                aml = new ui.menu({
                    id: options.id,
                    zindex: options.zindex,
                    visible: options.visible,
                    width: options.width,
                    height: options.height,
                    minWidth: options.minWidth,
                    minHeight: options.minHeight,
                    "onprop.visible": function(e) {
                        emit(e.value ? "show" : "hide", lastCoords);
                        checkItems.call(this, e);
                    },
                    "onitemclick": function(e) {
                        emit("itemclick", { value: e.value, item: e.relatedNode });
                    }
                });
                aml.cloud9menu = plugin;
                (forPlugin || plugin).addElement(aml);
                
                var items = options.items;
                if (items) {
                    for (var i = 0; i < items.length; i++)
                        append(items[i]);
                }
                
                if (typeof options.onitemclick == "function")
                    plugin.on("itemclick", options.onitemclick);
                
                aml.on("DOMNodeRemovedFromDocument", function() {
                    plugin.unload();
                });
            });
            
            /**
             * Menu Class for Cloud9. Menus can be used anywhere throughout
             * Cloud9, this includes the top menu bar as well as context menus 
             * and any other place where menus are required.
             * 
             * Menus work together with two other classes:
             * 
             * * **Menu - The container of menu items and dividers**
             *   * {@link MenuItem} - A clickable item in a menu
             *   * {@link Divider} - A break between clickable items, indicating a new logical section
             * 
             * This example shows how to create a menu with 3 menu items:
             * 
             *     var menu = new Menu({ items: [
             *         new MenuItem({ command: "open", caption: "Open..." }),
             *         new MenuItem({ command: "save", caption: "Save" }),
             *         new MenuItem({ command: "saveas", caption: "Save As..." }),
             *     ]}, plugin);
             * 
             * This example shows how to add menu items using the {@link #append} 
             * method:
             * 
             *     var menu = new Menu({}, plugin);
             *     var item = new MenuItem({ value: "unix", caption: "Unix" });
             *     menu.append(item);
             * 
             * The following code shows a menu as a context menu:
             * 
             *     document.addEventListener("mousedown", function(e) {
             *         if (e.button == 2)
             *             menu.show(e.x, e.y);
             *     }, false);
             */
            /**
             * @constructor
             * Creates a new Menu instance.
             * @param {Object} options
             * @param {Array}  options.items  The menu items to add. See {@link #items}.
             * @param {Plugin} plugin         The plugin responsible for creating this menu.
             */
            plugin.freezePublicAPI({
                /**
                 * The APF UI element that is presenting the menu in the UI.
                 * This property is here for internal reasons only. *Do not 
                 * depend on this property in your plugin.*
                 * @property {ui.menu} aml
                 * @private
                 * @readonly
                 */
                get aml() { return aml; },
                /**
                 * A meta data object that allows you to store whatever you want
                 * in relation to this menu.
                 * @property {Object} meta
                 * @readonly
                 */
                get meta() { return meta; },
                /**
                 * The HTMLElement representing the menu
                 * @property {HTMLElement} html
                 * @readonly
                 */
                get html() { return aml && aml.$ext; },
                /**
                 * The button or item responsible for displaying this menu
                 * @property {HTMLElement} opener
                 * @readonly
                 */
                get opener() { return aml.opener && aml.opener.cloud9item; },
                /**
                 * Specifies whether the menu is shown
                 * @property {Boolean} visible
                 * @readonly
                 */
                get visible() { return aml.visible; },
                /**
                 * Specifies the zindex of the menu
                 * @property {Number} zindex
                 */
                get zindex() { return aml && ui.getStyle(aml.$ext, "z-index"); },
                set zindex(value) { aml && aml.setAttribute("zindex", value); },
                /**
                 * Specifies the width of the menu
                 * @property {Number} width
                 */
                get width() { return aml && aml.getWidth(); },
                set width(value) { aml && aml.setAttribute("width", value); },
                /**
                 * Specifies the height of the menu
                 * @property {Number} height
                 */
                get height() { return aml && aml.getHeight(); },
                set height(value) { aml && aml.setAttribute("height", value); },
                /**
                 * Specifies the minimal width of the menu
                 * @property {Number} width
                 */
                get minWidth() { return aml && aml.getAttribute("minwidth"); },
                set minWidth(value) { aml && aml.setAttribute("minwidth", value); },
                /**
                 * Specifies the minimal height of the menu
                 * @property {Number} height
                 */
                get minHeight() { return aml && aml.getAttribute("minheight"); },
                set minHeight(value) { aml && aml.setAttribute("minheight", value); },
                /**
                 * The menu items appended to this menu
                 * @property {MenuItem[]} items
                 * @readonly
                 */
                get items() { return items.slice(0); },
                
                _events: [
                    /**
                     * Fires when a user clicks on one of the menu items
                     * @event itemclick
                     * @param {Object} e
                     * @param {Object} e.value  The value of the menu item that was clicked.
                     */
                    "itemclick",
                    /**
                     * Fires when the menu is shown
                     */
                    "show",
                    /**
                     * Fires when the menu is hidden
                     */
                    "hide"
                ],
                
                /**
                 * Append a menu item to this menu at the position specified
                 * by the {@link MenuItem#position item's position property}.
                 * @param {MenuItem} item  The item to add to this menu.
                 */
                append: append,
                
                /**
                 * Remove a menu item from this menu
                 * @param {MenuItem} item  The item to remove from this menu.
                 */
                remove: remove,
                
                /**
                 * Show the menu at the specified position
                 * @param {Number} x  The x coordinate specified in pixels from the left of the browser window.
                 * @param {Number} y  The y coordinate specified in pixels from the top of the browser window.
                 */
                show: show
            });
            
            plugin.load(null, "menu");
            
            return plugin;
        }
        
        function MenuItem(options, forPlugin) {
            var plugin = new Plugin("Ajax.org", main.consumes);
            var emit = plugin.getEmitter();
            
            var aml, menu, position = options.position;
            
            plugin.on("load", function() {
                var opts = {
                    "onmousedown": function(e) { emit("mousedown"); },
                    "onmousemove": function(e) { emit("mousemove"); },
                    "onmouseup": function(e) { emit("onmouseup"); },
                    "onclick": function(e) { emit("click"); }
                };
                
                for (var prop in options) {
                    if (prop.substr(0, 2) == "on")
                        plugin.on(prop.substr(2), options[prop]);
                    else if (prop == "submenu") {
                        if (options.submenu)
                            opts.submenu = options.submenu.aml || options.submenu;
                    }
                    else if (prop != "isAvailable")
                        opts[prop] = options[prop];
                }
                
                aml = new ui.item(opts);
                aml.cloud9item = plugin;
                plugin.addElement(aml);
                
                if (options.isAvailable)
                    aml.isAvailable = options.isAvailable;
                
                aml.on("DOMNodeRemoveFromDocument", function() {
                    plugin.unload();
                });
            });
            
            /**
             * MenuItem Class for Cloud9. Menu items should always be 
             * appended to {@link Menu Menus}. A menu item can offer a way for 
             * a user to execute a function, toggle a feature or select an item
             * out of several choices. See {@link #type} for more
             * information. A menu item can also be the anchor for a 
             * {@link #submenu sub menu}.
             * 
             * MenuItems work together with two other classes:
             * 
             * * {@link Menu} - The container of menu items and dividers
             *   * **MenuItem - A clickable item in a menu**
             *   * {@link Divider} - A break between clickable items, indicating a new logical section
             * 
             * This example shows how to create a menu with 3 menu items of
             * which only one can be selected at the same time:
             * 
             *     var menu = new Menu({ items: [
             *         new MenuItem({ type: "radio", value: "1", caption: "#1" }),
             *         new MenuItem({ type: "radio", value: "2", caption: "#2" }),
             *         new MenuItem({ type: "radio", value: "3", caption: "#3" }),
             *     ]}, plugin);
             * 
             * This example shows how to create a menu item with an icon and
             * a method that is called when the user clicks on the item:
             * 
             *     var item = new MenuItem({ 
             *         caption : "Alert" 
             *         icon    : "alert.png" 
             *         onclick : function(){
             *             alert("Alert!");
             *         }
             *     });
             */ 
            /** 
             * @constructor
             * Creates a new MenuItem instance.
             * @param {Object}   options
             * @param {Boolean}  options.disabled     See {@link #disabled}.
             * @param {String}   options.icon         See {@link #icon}.
             * @param {String}   options.type         See {@link #type}.
             * @param {String}   options.value        See {@link #value}.
             * @param {String}   options.caption      See {@link #caption}.
             * @param {String}   options.group        See {@link #group}.
             * @param {Boolean}  options.checked      See {@link #checked}.
             * @param {Menu}     options.submenu      See {@link #submenu}.
             * @param {String}   options.command      See {@link #command}.
             * @param {Number}   options.position     See {@link #position}.
             * @param {Plugin}   plugin               The plugin responsible for creating this menu item.
             */
            plugin.freezePublicAPI({
                /**
                 * The APF UI element that is presenting the menu item in the UI.
                 * This property is here for internal reasons only. *Do not 
                 * depend on this property in your plugin.*
                 * @property {ui.item} aml
                 * @private
                 * @readonly
                 */
                get aml() { return aml; },
                /**
                 * The HTMLElement representing the menu item
                 * @property {HTMLElement} html
                 * @readonly
                 */
                get html() { return aml && aml.$ext; },
                /**
                 * The plugin that is responsible for creating this menu item.
                 * @property {Plugin} plugin
                 * @readonly
                 */
                get plugin() { return forPlugin; },
                
                /**
                 * Sets or retrieves the menu that this menu item is appended to
                 * @property {Menu} menu
                 * @readonly
                 */
                get menu() { return menu; },
                set menu(m) { menu = m; },
                
                /**
                 * Sets or retrieves whether this item is disabled. When the 
                 * item is disabled, the caption is grayed out and the user is 
                 * unable to click the item.
                 * @property {Boolean} [disabled=false]
                 */
                get disabled() { return aml.disabled; },
                set disabled(v) { aml.setAttribute("disabled", v); },
                
                /**
                 * Sets or retrieves he url pointing to the icon that is shown 
                 * next to the caption of this menu item.
                 * @property {String} icon
                 */
                get icon() { return aml.icon; },
                set icon(v) { aml.setAttribute("icon", v); },
                
                /**
                 * Sets or retrieves the type of interaction this menu item 
                 * offers to the user. A menu item can have three different 
                 * behaviors:
                 * 
                 * <table>
                 * <tr><td>Type</td><td>    Behavior</td></tr>
                 * <tr><td>"radio"</td><td> This item shows a closed circle in 
                 *   front of the caption when selected. Only one item can be 
                 *   selected in the {@link #group} that it belongs to.</td></tr>
                 * <tr><td>"check"</td><td> This item shows a check in front of
                 *   the caption when in the {@link #checked} state. The user 
                 *   can click on the item to change the state of 
                 *   the item.</td></tr>
                 * <tr><td>null</td><td>    This item has no state and behaves
                 *   as if the type property was never set.</td></tr>
                 * </table>
                 * 
                 * This example shows two different sets of radio menu items in 
                 * the same menu:
                 * 
                 *     var menu = new Menu({ items: [
                 *         new MenuItem({ type: "radio", group: "nr", value: "1", caption: "#1" }),
                 *         new MenuItem({ type: "radio", group: "nr", value: "2", caption: "#2" }),
                 *         new MenuItem({ type: "radio", group: "nr", value: "3", caption: "#3" }),
                 *         new Divider(),
                 *         new MenuItem({ type: "radio", group: "alpha", value: "a", caption: "A" }),
                 *         new MenuItem({ type: "radio", group: "alpha", value: "b", caption: "B" }),
                 *     ]}, plugin);
                 * 
                 * This example shows a check menu item:
                 * 
                 *     var item = new MenuItem({
                 *         type    : "check"
                 *         caption : "Status Bar"
                 *         checked : true
                 *     });
                 * 
                 * This example shows a menu item that gets it's checked state
                 * from the {@link settings}.
                 * 
                 *     var item = new MenuItem({
                 *         type    : "check"
                 *         caption : "Status Bar"
                 *         checked : "user/statusbar/@show"
                 *     });
                 * 
                 * * See also {@link MenuItem#checked}
                 * * See also {@link MenuItem#group}
                 * 
                 * @property {String} type
                 */
                get type() { return aml.type; },
                set type(v) { aml.setAttribute("type", v); },
                
                /**
                 * Sets or retrieves the string that is used to uniquely 
                 * identify this item
                 * @property {String} value
                 */
                get value() { return aml.value; },
                set value(v) { aml.setAttribute("value", v); },
                
                /**
                 * Sets or retrieves the text displayed in the UI on this 
                 * menu item
                 * @property {String} caption
                 */
                get caption() { return aml.caption; },
                set caption(v) { aml.setAttribute("caption", v); },
                
                /**
                 * Sets or retrieves the group of a radio menu item set. 
                 * See also {@link #type}
                 * @property {String} group
                 */
                get group() { return aml.group; },
                set group(v) { aml.setAttribute("group", v); },
                
                /**
                 * Sets or retrieves whether this item has a checkbox or is 
                 * the selected item in a radio group.
                 * 
                 * This example shows a menu item that gets it's checked state
                 * from the {@link settings}.
                 * 
                 *     var item = new MenuItem({
                 *         type    : "check"
                 *         caption : "Status Bar"
                 *         checked : "user/statusbar/@show"
                 *     });
                 * 
                 * @property {Boolean} checked
                 */
                get checked() { return aml.checked; },
                set checked(v) { aml.setAttribute(
                    aml.type == "radio" ? "selected" : "checked", v); },
                
                /**
                 * Sets or retrieves the submenu shown when the user hovers
                 * over this menu item.
                 * @property {MenuItem} submenu
                 */
                get submenu() { return aml.submenu && aml.submenu.cloud9menu; },
                set submenu(v) { aml.setAttribute("submenu", v.aml || v); },
                
                /**
                 * Sets or retrieves the name of the command to execute when
                 * the user clicks on the menu item.
                 * @property {String} command
                 */
                get command() { return aml.command; },
                set command(v) { aml.setAttribute("command", v); },
                
                /**
                 * Retrieves the position of the menu item in the menu.
                 * @property {Number} position
                 * @readonly
                 */
                get position() { return position; },
                
                show: function() { aml.show(); },
                hide: function() { aml.hide(); }
            });
            
            plugin.load(null, "menuitem");
            
            return plugin;
        }
        
        function Divider(options, forPlugin) {
            var plugin = new Plugin("Ajax.org", main.consumes);
            // var emit = plugin.getEmitter();
            
            var aml, menu, position = options && options.position;
            
            plugin.on("load", function() {
                aml = new ui.divider();
                aml.cloud9item = plugin;
                plugin.addElement(aml);
                
                aml.on("DOMNodeRemoveFromDocument", function() {
                    plugin.unload();
                });
            });
            
            /**
             * Divider Class for Cloud9. 
             * 
             * Dividers work together with two other classes:
             * 
             * * {@link Menu} - The container of menu items and dividers
             *   * {@link MenuItem} - A clickable item in a menu
             *   * **Divider - A break between clickable items, indicating a new logical section**
             * 
             * This example shows a menu with 2 dividers.
             * 
             *     var menu = new Menu({ items: [
             *         new MenuItem({ type: "radio", group: "nr", value: "1", caption: "#1" }),
             *         new MenuItem({ type: "radio", group: "nr", value: "2", caption: "#2" }),
             *         new MenuItem({ type: "radio", group: "nr", value: "3", caption: "#3" }),
             *         new Divider(),
             *         new MenuItem({ value: "a", caption: "A" }),
             *         new MenuItem({ value: "b", caption: "B" }),
             *         new Divider(),
             *         new MenuItem({ value: "test",    caption: "Test" }),
             *         new MenuItem({ value: "example", caption: "Example" }),
             *     ]}, plugin);
             * 
             * This example shows how to append a divider to a menu
             * 
             *     var menu = new Menu({}, plugin);
             *     menu.append(new Divider({ position: 100 }, plugin));
             * 
             */
            /**
             * @constructor
             * Creates a new Divider instance.
             * @param {Object} options
             * @param {Array}  options.position     See {@link #position}.
             * @param {Plugin} plugin               The plugin responsible for creating this divider.
             */
            plugin.freezePublicAPI({
                /**
                 * The APF UI element that is presenting the menu item in the UI.
                 * This property is here for internal reasons only. *Do not 
                 * depend on this property in your plugin.*
                 * @property {ui.divider} aml
                 * @private
                 * @readonly
                 */
                get aml() { return aml; },
                /**
                 * The HTMLElement representing the menu item
                 * @property {HTMLElement} html
                 * @readonly
                 */
                get html() { return aml && aml.$ext; },
                /**
                 * The plugin that is responsible for creating this menu item.
                 * @property {Plugin} plugin
                 * @readonly
                 */
                get plugin() { return forPlugin; },
                /**
                 * Sets or retrieves the menu that this menu item is appended to
                 * @property {Menu} menu
                 * @readonly
                 */
                get menu() { return menu; },
                set menu(m) { menu = m; },
                /**
                 * Retrieves the position of the menu item in the menu.
                 * @property {Number} position
                 * @readonly
                 */
                get position() { return position; },
                
                show: function() { aml.show(); },
                hide: function() { aml.hide(); }
            });
            
            plugin.load(null, "divider");
            
            return plugin;
        }
        
        register(null, {
            menus: plugin,
            Menu: Menu,
            MenuItem: MenuItem,
            Divider: Divider,
        });
    }
});