define(function(require, module, exports) {
    main.consumes = [
        "Plugin", "panels", "settings", "menus", "commands", "ui", "tabManager"
    ];
    main.provides = ["Panel"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var panels = imports.panels;
        var menus = imports.menus;
        var ui = imports.ui;
        var commands = imports.commands;
        var settings = imports.settings;
        var tabs = imports.tabManager;
        
        var uCaseFirst = require("c9/string").uCaseFirst;
        
        function Panel(developer, deps, options) {
            var plugin = new Plugin(developer, deps);
            var emit = plugin.getEmitter();
            
            var autohide = options.autohide || false;
            var index = options.index || 100;
            var buttonCSSClass = options.buttonCSSClass;
            var panelCSSClass = options.panelCSSClass;
            var caption = options.caption;
            var width = options.width;
            var minWidth = options.minWidth;
            
            var mnuItem, button, area, lastPanel, xpath, where, aml;
            
            plugin.on("load", function() {
                xpath = "state/panels/" + plugin.name;
                where = settings.get(xpath + "/@where") || options.where || "left";
                
                panels.register(plugin);
    
                mnuItem = new ui.item({
                    type: "check",
                    value: plugin.name,
                    onclick: function() {
                        var area = panels.areas[panels.panels[plugin.name].where];

                        if (this.checked)
                            area.enablePanel(plugin.name);
                        else
                            area.disablePanel(plugin.name);
                    },
                });
                
                mnuItem.panel = plugin;
                
                menus.addItemByPath("Window/" + caption, mnuItem, index, plugin);
                
                panels.on("showPanel" + uCaseFirst(plugin.name), function(e) {
                    lastPanel = e.lastPanel;
                    if (lastPanel && panels.panels[lastPanel].autohide)
                        lastPanel = panels.panels[lastPanel].lastPanel;
                    
                    if (button)
                        button.setValue(true);
                    
                    emit("show", e);
                }, plugin);
                panels.on("hidePanel" + uCaseFirst(plugin.name), function(e) {
                    if (button)
                        button.setValue(false);
                    
                    emit("hide");
                }, plugin);
                
                settings.on("read", function() {
                    settings.setDefaults(xpath, [
                        ["name", plugin.name],
                        ["enabled", true]
                    ]);
                    
                    // Start disabled
                    plugin.disable();
    
                    // Attach to area
                    var areas = panels.areas;
                    attachTo(panels.areas[where]);
    
                    // Enable panel
                    if (settings.getBool(xpath + "/@enabled")) {
                        areas[where].enablePanel(plugin.name);
                        mnuItem.setAttribute("checked", true);
                    }
                    
                    // Activate Panel
                    ["left", "right"].forEach(function(where) {
                        var active = settings.get("state/panels/@active-" + where);
                        if ((!active && areas[where].defaultActive == plugin.name
                          || active == plugin.name))
                            areas[where].activate(plugin.name, true, "restoreSettings");
                    });
                });
            });
            
            plugin.on("unload", function() {
                panels.deactivate(plugin.name);
                
                button = null;
                mnuItem = null;
                button = null;
                area = null;
                lastPanel = null;
                xpath = null;
                where = null;
                drawn = false;
    
                if (button)
                    button.destroy(true, true);
                if (mnuItem)
                    mnuItem.destroy(true, true);
    
                menus.remove("Window/" + caption);
                
                panels.unregister(plugin);
            });
            
            /***** Methods *****/
            
            function setCommand(options) {
                var command = commands.addCommand({
                    name: options.name,
                    group: "Panels",
                    hint: options.hint,
                    bindKey: options.bindKey,
                    exec: options.exec ||
                        function() { 
                            if (autohide)
                                panels.activate(plugin.name); 
                            else {
                                if (panels.isActive(plugin.name))
                                    panels.deactivate(plugin.name);
                                else
                                    panels.activate(plugin.name);
                            }
                            
                            if (options.extra)
                                options.extra.apply(this, arguments);
                        }
                }, plugin);
                if (mnuItem)
                    mnuItem.setAttribute("hotkey", "commands." + options.name);
                if (button) {
                    button.setAttribute("hotkey", "commands." + options.name);
                    button.setAttribute("tooltip", options.name);
                }
                return command;
            }
            
            var drawn;
            function draw() {
                if (drawn) return false;
                drawn = true;
                
                aml = area.aml.appendChild(new ui.bar({
                    "skin": "panel-bar",
                    "class": panelCSSClass || "",
                    "visible": false
                }));
                plugin.addElement(aml);
                
                emit.sticky("draw", { 
                    html: aml.$int, 
                    aml: aml 
                });
                
                aml.$ext.style.zIndex = 100;
                aml.$ext.style.minWidth = ""; //Needed for the anims
                aml.$ext.style.position = "absolute";
                aml.$ext.style.left = where == "left" ? area.width + "px" : 0;
                aml.$ext.style.top = 0;
                aml.$ext.style.right = where == "right" ? area.width + "px" : 0;
                aml.$ext.style.bottom = 0;
                
                aml.$display = apf.CSSPREFIX + "Flex";
                
                return true;
            }
            
            function attachTo(toArea) {
                area = toArea;
                
                try {
                    if (aml)
                        area.aml.appendChild(aml);
                } catch (e) {}
                
                if (plugin.enabled)
                    enable(); // Move the button
            }
            
            function detach() {
                disable();
                area = null;
            }
            
            function enable() {
                // Draw button container
                var container = area.draw();
                
                if (!button) {
                    var hotkey = mnuItem && mnuItem.getAttribute("hotkey") || "";
                    // Insert button
                    button = new ui.button({
                        skinset: "panels",
                        state: true,
                        caption: caption,
                        auto: false,
                        class: buttonCSSClass || "",
                        hotkey: hotkey,
                        tooltip: hotkey.slice(9),
                        onmousedown: function(e) {
                            if (e.htmlEvent && e.htmlEvent.button) return;
                            panels.areas[where].toggle(plugin.name, autohide, true);
                        }
                    });
                    plugin.addElement(button);
                }
                
                ui.insertByIndex(container, button, index, false);
                
                button.show();
                
                if (area.activePanel == plugin.name)
                    button.setValue(true);
                
                mnuItem.setAttribute("checked", true);
            }
            
            function disable() {
                button && button.hide();
                mnuItem.setAttribute("checked", false);
            }
            
            function show() {
                panels.activate(plugin.name);
            }
            
            function hide() {
                if (panels.isActive(plugin.name)) {
                    if (autohide && lastPanel)
                        panels.activate(lastPanel);
                    else
                        panels.deactivate(plugin.name);
                    
                    if (tabs.focussedTab && tabs.focussedTab.editor && !panels.showing)
                        tabs.focussedTab.editor.focus();
                }
            }
            
            /***** LifeCycle *****/
            
            plugin.on("enable", function() {
                enable();
            });
            plugin.on("disable", function() {
                disable();
            });
            
            /***** Register and define API *****/
            
            plugin.freezePublicAPI.baseclass();
            
            /**
             * Panel base class for the Cloud9 UI. Panels can
             * be hidden and shown. Users can also show/hide the panel button via 
             * the Window menu.
             * 
             * Panels are located in different {@link panels.Area areas}. By 
             * default these areas are on the left and right of the screen.
             * 
             * * {@link panels} - Manages all areas and panels
             *   * {@link panels.Area Area} - Manages a single area of panels. 
             *   By default there is a panel area on the left and on the right side 
             *   of the UI.
             *     * **Panel - A single panel that lives in an area.**
             * 
             * Implementing your own panel takes a new Panel() object rather
             * than a new Plugin() object. Here's a short example:
             * 
             *     var plugin = new Panel("(Company) Name", main.consumes, {
             *         index    : 100,
             *         width    : 250,
             *         caption  : "Cool Caption",
             *         minWidth : 130,
             *         where    : "right"
             *     });
             *     var emit = plugin.getEmitter();
             * 
             *     plugin.on("load", function(){
             *         plugin.setCommand({
             *             name    : "coolpanel",
             *             hint    : "being cool",
             *             bindKey : { mac: "Command-H", win: "Ctrl-H" }
             *         });
             *     });
             * 
             *     plugin.on("draw", function(e) {
             *         e.html.innerHTML = "Hello World!";
             *     });
             *     
             *     plugin.freezePublicAPI({
             *         example: function(){
             *             
             *         }
             *     });
             */
            /**
             * @constructor
             * Creates a new Panel instance.
             * @param {String}   developer             The name of the developer of the plugin
             * @param {String[]} deps                  A list of dependencies for this 
             *   plugin. In most cases it's a reference to main.consumes.
             * @param {Object}   options               The options for this panel
             * @param {Number}   options.index         Specifies the position in the order of the panels
             * @param {String}   options.caption       Specifies the text displayed on the button
             * @param {Boolean}  [options.autohide]    Specifies whether this is an
             *   autohiding panel. The developer is responsible for hiding
             *   the panel. This behavior will animate the panel during
             * @param hide and show over other panels, if there are any.
             * @param {String}   [options.buttonCSSClass]   Specifies the name of the css class that is applied to the button
             * @param {String}   [options.panelCSSClass]   Specifies the name of the css class that is applied to the panel
             * @param {Number}   [options.width]       Specifies the default width of the panel
             * @param {Number}   [options.minWidth]    Specifies the minimal width of the panel
             * @param {String}   [options.where]       Accepts "left" or "right" to determine where the panel is added
             */
            plugin.freezePublicAPI({
                /**
                 * @ignore
                 */
                get lastPanel() { return lastPanel; },
                /**
                 * Retrieves whether this is an auto-hiding panel. i.e. if
                 * this panel hides when it looses focus.
                 * @property {Boolean} autohide
                 */
                get autohide() { return autohide; },
                set autohide(value) { autohide = value; },
                /**
                 * Retrieves the width in pixels of this panel
                 * @property {Number} width
                 * @readonly
                 */
                get width() { return width; },
                /**
                 * Retrieves the minimal width in pixels of this panel
                 * @property {Number} minWidth
                 * @readonly
                 */
                get minWidth() { return minWidth; },
                /**
                 * The APF UI element that is presenting the panel in the UI.
                 * This property is here for internal reasons only. *Do not 
                 * depend on this property in your plugin.*
                 * @property {AMLElement} aml
                 * @private
                 * @readonly
                 */
                get aml() { return aml; },
                /**
                 * @property {HTMLElement} container
                 */
                get container() { return aml && aml.$ext; },
                
                /**
                 * The area that this panel is a part of.
                 * @property {panels.Area} area
                 * @readonly
                 */
                get area() { return area; },
                
                /**
                 * Retrieves the position of the panel. This can be "left" 
                 * or "right".
                 * @property {String} where
                 * @readonly
                 */
                get where() { return where; },
                
                /**
                 * @ignore for testing purpose
                 */
                get button() { return button; },
                
                /**
                 * 
                 */
                get active() { return panels.isActive(plugin.name); },
                
                _events: [
                    /**
                     * Fires when the panel is drawn.
                     * @event draw
                     * @param {Object}      e
                     * @param {AMLElement}  e.aml
                     * @param {HTMLElement} e.html
                     */
                    "draw",
                    /**
                     * Fires when the panel is shown
                     * @event show
                     */
                    "show",
                    /**
                     * Fires when the panel is shown
                     * @event hide
                     */
                    "hide"
                ],
                
                /**
                 * Adds a command that toggles this panel to be active or not.
                 * @param {Object}   options
                 * @param {String}   options.name        Specifies the name of a new command to create, which will show the panel
                 * @param {String}   [options.hint]      Specifies the description of the command
                 * @param {Object}   [options.bindKey]   Specifies the key combination for the command. See `commands.addCommand`
                 * @param {Function} [options.exec]      Overrides the default action of the command
                 */
                setCommand: setCommand,
                
                /**
                 * Attaches this panel to an {@link panels.Area area}.
                 * @param {panels.Area} area  The area to attach this panel to.
                 */
                attachTo: attachTo,
                
                /**
                 * Detaches this panel from it's area.
                 */
                detach: detach,
                
                /**
                 * Draws the panel
                 * @private
                 */
                draw: draw,
                
                /**
                 * Shows the panel
                 */
                show: show,
                
                /**
                 * Hides the panel
                 */
                hide: hide
            });
            
            return plugin;
        }
        
        /***** Register and define API *****/
        
        register(null, {
            Panel: Panel
        });
    }
});
