define(function(require, module, exports) {
    main.consumes = [
        "Plugin", "menus", "settings", "layout", "ui", "commands", "fs", 
        "Tab", "editors", "Pane", "watcher", "c9", "dialog.alert", 
        "focusManager", "util"
    ];
    main.provides = ["tabManager"];
    return main;
    
    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var Tab = imports.Tab;
        var Pane = imports.Pane;
        var ui = imports.ui;
        var fs = imports.fs;
        var c9 = imports.c9;
        var settings = imports.settings;
        var util = imports.util;
        var menus = imports.menus;
        var editors = imports.editors;
        var commands = imports.commands;
        var layout = imports.layout;
        var watcher = imports.watcher;
        var focusManager = imports.focusManager;
        var alert = imports["dialog.alert"].show;
        
        var basename = require("path").basename;
        
        /***** Initialization *****/
        
        var plugin = new Plugin("Ajax.org", main.consumes);
        var emit = plugin.getEmitter();
        emit.setMaxListeners(100);
        
        var loadFilesAtInit = options.loadFilesAtInit;
        var ideProviderName = options.ideProviderName || "Cloud9";
        
        var PREFIX = "/////";
        var XPREVIEW = /\.(gz|tar|tgz|zip|rar|jar|exe|pyc|pdf)$/;
        
        var unfocussed = true;
        var showTabs = true;
        var panes = []; // aml pane elements
        var tabs = {}; // Tab objects (non-aml)
        var containers = [];
        var counter = 1;
        
        var focussedTab, previewTab, previewTimeout;
        var container, mnuEditors, collapsedMenu, isReady;
        
        // Ref to focusManager - this will be changed later
        focusManager.tabManager = plugin;
        
        var loaded = false, changed = false;
        function load(){
            if (loaded) return false;
            loaded = true;
            
            draw();
            
            // Commands & Menus
            
            commands.addCommand({
                name: "toggleButtons",
                group: "Tabs",
                exec: function(e) {
                    toggleButtons();
                }
            }, plugin);
            
            // commands.addCommand({
            //     name    : "newfile",
            //     group   : "Tabs",
            //     exec    : function(e) {
            //         plusNewFile({}, true);
            //     }
            // }, plugin);
            
            function getEditor(){
                var editor = focussedTab && focussedTab.editor;
                var subeditor = editor && editor.focussedWidget;
                return subeditor && subeditor.editor || editor;
            }
            commands.on("getEditor", getEditor, plugin);
            menus.on("getEditor", getEditor, plugin);
            
            menus.addItemByPath("View/Tab Buttons", new apf.item({
                type: "check",
                checked: "user/tabs/@show",
                command: "toggleButtons"
            }), 300, plugin);
            
            menus.on("focusEditor", function(){
                focussedTab && focussedTab.editor.focus();
            }, plugin);

            // Filesystem hooks

            fs.on("beforeRename", function(e) {
                var oldPath = e.args[0];
                var newPath = e.args[1];
                
                var changed = [];
                Object.keys(tabs).forEach(function(id) {
                    var tab = tabs[id];
                    if (tab.path && tab.path.indexOf(oldPath) === 0) {
                        var char = tab.path.charAt(oldPath.length);
                        // Make sure that a path like /Untitled1 is not matched
                        // by a path like /Untitled, which are clearly different
                        // files with no relation to each other
                        if (!char || char == "/") {
                            tab.path = tab.path.replace(oldPath, newPath);
                            changed.push(tab);
                        }
                    }
                });
                
                if (changed.length) {
                    e.revert = function(){
                        changed.forEach(function(tab) {
                            // TODO store id instead of tab
                            // var tab = pages[id];
                            tab.path = tab.path.replace(newPath, oldPath);
                        });
                    };
                }
            });
            fs.on("afterRename", function(e) {
                if (e.error && e.revert) 
                    e.revert();
            });
            function removeTab(e) {
                if (!e.error) { 
                    var tab = findTab(e.path);
                    if (tab)
                        tab.unload();
                }
            }
            fs.on("afterUnlink", removeTab);
            fs.on("afterRmfile", removeTab);
            fs.on("afterRmdir", function(e) {
                var path = e.path;
                Object.keys(tabs).forEach(function(id) {
                    var tab = tabs[id];
                    if (tab.path && tab.path.indexOf(path) === 0)
                        tab.unload();
                });
            });
            // Close a pane when it doesn't open
            // @todo this should probably be interactive (unless during init)
            // @todo move this to tabbehaviours?
            fs.on("error", function(e) {
                if (e.name == "readFile") {
                    var tab = findTab(e.path);
                    if (tab)
                        tab.unload();
                }
            }, plugin);
            
            // Disconnect handler
            var disconnected;
            c9.on("disconnect", function(e) {
                disconnected = true;
            });
            c9.on("connect", function(e) {
                if (disconnected)
                    checkAllTabs();
                disconnected = false;
            });
            
            // Editors menu handling
            
            editors.on("menuClick", function(e) {
                if (!editors.findEditor(e.value).fileExtensions.length)
                    openEditor(e.value, true, function(){});
                else if (focussedTab)
                    switchEditor(focussedTab, e.value, function(){});
            });
            editors.on("menuShow", function(e) {
                var group, editor = focussedTab && focussedTab.editor;
                e.menu.childNodes.forEach(function(node) {
                    group = node.group;
                    
                    var path = focussedTab && focussedTab.path || "";
                    var type = node.value;
                    var isAvailable = editors.editorSupportsFile(type, path);
        
                    node.setAttribute("disabled", !isAvailable);
                });

                if (editor && group)
                    group.setValue(editor.type);
            });
            
            // Settings
            settings.on("read", function(e) {
                // Defaults
                settings.setDefaults("user/tabs", [
                    ["show", "true"], 
                    ["title", "false"],
                    ["asterisk", "false"]
                ]);
                settings.setDefaults("state/tabs", []);
                
                // Corner Handling
                collapsedMenu = settings.getBool("state/menus/@minimized");
                
                if (e.reset) {
                    setCornerPadding();
                    return;
                }
                
                // Create new tabs
                var state = settings.getJson("state/tabs");
                
                setTimeout(function() {
                    // Only set the state if we're not testing something else
                    if (options.testing != 2 && !isReady) {
                        setState(state, !isReady, function(){
                            emit.sticky("ready");
                        });
                    }
                    isReady = true;
                    
                    showTabs = settings.getBool("user/tabs/@show");
                    toggleButtons(showTabs);
                }, 0);
                
                if (settings.getBool("user/tabs/@asterisk"))
                    ui.setStyleClass(container, "asterisk");
                
                changed = false;
                
            }, plugin);
            
            settings.on("write", function(e) {
                if (!changed && !e.unload)
                    return;
                
                // When testing another component, don't save state
                if (options.testing != 2) {
                    var state = getState(null, true);
                    settings.setJson("state/tabs", state);
                }

                changed = false;
            }, plugin);
            
            settings.on("user/tabs/@asterisk", function(value) {
                containers.forEach(function(container) {
                    if (value)
                        ui.setStyleClass(container, "asterisk");
                    else
                        ui.setStyleClass(container, "", ["asterisk"]);
                });
            });

            // Top Right Corner Handling
            var CSS1 = ".editor_tab.morepadding .btnsesssioncontainer";
            var CSS2 = ".editor_tab .btnsesssioncontainer";
            var clearAnim = function(prop){
                setTimeout(function(){
                    ui.setStyleRule(prop, "transition-duration", "0");
                }, 200);
            };
            
            menus.on("minimize", function(){
                collapsedMenu = true;
                
                ui.setStyleRule(CSS1, "transition-duration", ".2s");
                clearAnim(CSS1);
                
                setCornerPadding();
            });
            menus.on("restore", function(){
                collapsedMenu = false;
                if (lastCorner) {
                    ui.setStyleRule(CSS2, "transition-duration", ".2s");
                    clearAnim(CSS2);
                    
                    ui.setStyleClass(lastCorner.$ext, "", ["morepadding"]);
                    lastCorner = null;
                }
            });
            
            // Focus Handling
            apf.addEventListener("movefocus", function(e) {
                var iter = e.toElement, list = [], c = 0;
                
                if (iter && iter.localName == "menu")
                    return;
                
                while (iter && containers.indexOf(iter) == -1) 
                    iter = (list[c++] = iter).parentNode;
                if (!iter) {
                    // Editor can be unset during unload of tabs
                    if (!unfocussed && focussedTab && focussedTab.editor) {
                        focussedTab.editor.focus(false, true);
                        unfocussed = true;
                    }
                    return;
                }
                
                for (var pane, i = list.length - 1; i >= 0; i--) {
                    if ((pane = list[i]).localName == "tab") {
                        if (pane.getPages().length < 1 
                          || !pane.cloud9pane.visible) 
                            continue;
                        
                        var newFocus = pane.getPage().cloud9tab;
                        if (newFocus != focussedTab || unfocussed) {
                            var soft = e.toElement != newFocus.aml ? 2 : 0;
                            focusTab(newFocus, soft);
                        }
                        unfocussed = false;
                        
                        iter.$activePaneName = pane.cloud9pane.name;

                        return;
                    }
                }
                
                focusTab(); // Blur
            });
            
            // Title
            if (!c9.local) {
                plugin.on("focusSync", function(e){ updateTitle(e.tab) });
                plugin.on("tabDestroy", function(e){ if (e.last) updateTitle(); });
                settings.on("user/tabs", function(){ updateTitle(focussedTab); });
            }
            
            emit("ready");
        }
        
        var drawn = false;
        function draw(){
            if (drawn) return;
            drawn = true;
            
            // Don't draw Tabs if we're testing something that just depends 
            // on this plugin
            if (options.testing != 2) {
                container = layout.findParent(plugin).appendChild(new ui.bar({
                    "class"  : "codeditorHolder",
                    "style"  : "height:100%"
                    //style    : "position:absolute;"
                }));
                containers.push(container);
                plugin.addElement(container);
            }
            
            mnuEditors = apf.document.documentElement.appendChild(new ui.menu({
                id: "mnuEditors",
                style: "margin: 0px 0 0 4px",
                "onprop.visible" : function(e) {
                    if (e.value)
                        mnuEditors.pane = this.opener.parentNode.cloud9pane;
                }
            }));
            plugin.addElement(mnuEditors);
            
            menus.addItemToMenu(mnuEditors,
                new ui.item({
                    caption: "New File",
                    hotkey: "{commands.commandManager.newfile}",
                    onclick: function(e) {
                        e.pane = this.parentNode.pane;
                        plusNewFile(e, true);
                    },
                    enabled: !c9.readonly
                }), 100, plugin);
            
            emit("draw");
        }
        
        function plusNewFile(e, force) {
            if (force || emit("plusClick", e) !== false) {
                var name;
                do{ name = "/Untitled" + counter++; } while (findTab(name));
                
                open({
                    path: name,
                    focus: true,
                    active: true,
                    pane: e.pane,
                    value: "",
                    document: {
                        meta: {
                            newfile: true
                        }
                    }
                }, function(){});
            }
        }
        
        function createPane(state) {
            state.createPane = createPane;
            state.container = container;
            
            var pane = new Pane(state);
            
            pane.on("beforeSwitch", onbeforeswitch);
            pane.on("afterSwitch", onafterswitch);
            
            var btnPlus = pane.getElement("btnPlus");
            btnPlus.setAttribute("submenu", mnuEditors);
            
            pane.on("beforeClose", function(e) {
                return emit("tabBeforeClose", e);
            });
            pane.on("afterClose", function(e) {
                return emit("tabAfterClose", e);
            });
            
            pane.on("tabOrder", function(e) {
                changed = true;
                settings.save();

                return emit("tabOrder", e);
            });
            
            pane.on("unload", function(){
                panes.remove(pane);
                
                if (focussedTab && !focussedTab.isActive()) {
                    if (!panes.length) {
                        focussedTab = null;
                        unfocussed = true;
                    }
                    else
                        focusTab(panes[0].getTab(), true);
                }

                emit("paneDestroy", { pane: pane });
                
                setCornerPadding();
                
                changed = true;
                settings.save();
            });
            
            panes.push(pane);
            
            pane.aml.once("DOMNodeInsertedIntoDocument", function(){
                emit.sticky("paneCreate", { pane: pane }, pane);
            });
            
            if (!settings.getBool("user/tabs/@show"))
                ui.setStyleClass(pane.aml.$ext, "notabs", ["notabs"]);
            
            changed = true;
            settings.save();
        
            return pane;
        }
        
        function updateTitle(tab) {
            document.title = tab && settings.getBool("user/tabs/@title") && tab.title
                ? tab.title + " - "  + ideProviderName
                : c9.projectName + " - "  + ideProviderName;
        }
        
        var lastCorner;
        function setCornerPadding(){
            if (!collapsedMenu || c9.local) return;
            
            var tab = (function recur(node){
                if (!node) 
                    return false;
                else if (node.localName == "tab")
                    return node;
                else if (node.$vbox)
                    return recur(node.getFirstChild());
                else
                    return recur(node.getSecondChild() || node.getFirstChild());
            })(container.childNodes[0]);
            
            if (!tab || tab == lastCorner)
                return;
            
            if (lastCorner)
                ui.setStyleClass(lastCorner.$ext, "", ["morepadding"]);
            
            ui.setStyleClass(tab.$ext, "morepadding");
            
            lastCorner = tab;
        }
        
        function createTab(state){
            if (!state.pane) {
                if (focussedTab && ui.isChildOf(container, focussedTab.aml)) {
                    state.pane = focussedTab.pane;
                }
                if (!state.pane) {
                    var i = 0;
                    while (panes[i] && !ui.isChildOf(container, panes[i].aml)) { i++ }
                    state.pane = panes[i];
                }
                if (!state.pane) {
                    throw new Error("Called open too early. Please wait until "
                        + "a pane is available. Use the ready event.");
                }
            }
            
            var tab = new Tab(state);
            
            var id = !tab.document.meta.cloned && tab.path 
                ? tab.path 
                : PREFIX + tab.name;
            tabs[id] = tab;
            
            tab.on("close", function(e) {
                var id = !tab.document.meta.cloned && tab.path 
                    ? tab.path 
                    : PREFIX + tab.name;
                delete tabs[id];
                
                if (tab.path)
                    watcher.unwatch(tab.path);
                
                if (!(function(){for (var p in tabs){return true;}})()) {
                    focusTab();
                    focussedTab = null;
                    unfocussed = true;
                }
                else if (!tab.pane.getTabs().length) {
                    if (panes.every(function(pane) {
                        if (pane.activeTab && pane.visible) {
                            focusTab(pane.activeTab);
                            return false;
                        }
                        return true;
                    })) focusTab(); // blur
                }

                if (tab.document.canUnload() !== false)
                    tab.document.unload();
                
                emit("tabDestroy", {
                    tab: tab,
                    last: e.last,
                    htmlEvent: e.htmlEvent
                });
                
                changed = true;
                settings.save();
            });
            
            tab.on("setPath", function(e) {
                var meta = tab.document.meta;
                if (!meta.preview && !meta.ignoreState && !meta.cloned) {
                    watcher.unwatch(e.oldpath);
                    watcher.watch(e.path);
                }
                
                if (!meta.cloned) {
                    if (tabs[PREFIX + tab.name])
                        delete tabs[PREFIX + tab.name];
                    delete tabs[e.oldpath];
                    tabs[e.path] = tab;
                }
                
                updateTitle(tab);
                
                changed = true;
                settings.save();
            });
            
            tab.on("beforeReparent", function(e) {
                e.tab = tab;
                return emit("tabBeforeReparent", e);
            });
            tab.on("afterReparent", function(e) {
                e.tab = tab;
                emit("tabAfterReparent", e);
            });
            
            // Only watch pages with paths. Newfiles first need to be saved
            if (tab.path) {
                var meta = tab.document.meta;
                if (meta.newfile) {
                    fs.on("afterWriteFile", function startWatch(e) {
                        if (e.path == tab.path) {
                            watcher.watch(tab.path);
                            fs.off("afterWriteFile", startWatch);
                        }
                    }, tab); // Only exists for the lifetime of the tab
                }
                else {
                    // Previewed files shouldn't be watched
                    if (!meta.preview && !meta.ignoreState && !meta.nofs && !meta.cloned)
                        watcher.watch(tab.path);
                    else
                        console.warn("[watchers] ignoring preview/ignoreState tab " + tab.path);
                }
            }
                
            emit.sticky("tabCreate", { tab: tab }, tab);
            
            changed = true;
            settings.save();
            
            return tab;
        }
        
        /* Event Handlers */
        
        function onbeforeswitch(e) {
            var amlPane = e.pane.aml;
            var tab = e.tab;
            
            var editorTab = amlPane.getPage(tab.aml.type);
            if (!editorTab) return;
            
            if (emit("tabBeforeActivate", {
                tab: tab
            }) === false)
                return false;
        }
    
        function onafterswitch(e) {
            var tab = e.tab;
            
            if (!loaded || tab.document.meta.preview) 
                return;
            
            var lastTab = focussedTab;
            
            if (!focussedTab || focussedTab.pane == tab.pane && focussedTab != tab)
                focusTab(tab, true, true);
            
            // Undocumented
            emit("tabAfterActivateSync", {
                lastTab: lastTab,
                tab: tab
            });
                
            setTimeout(function(){
                emit("tabAfterActivate", {
                    lastTab: lastTab,
                    tab: tab
                });
    
                changed = true;
                settings.save();
            }, 500);
        }
        
        /***** Methods *****/
        
        function getState(subcontainer, filter) {
            var state = (function recur(node) {
                if (!node) return;
                
                var name = node.localName;
                var isPane = name == "tab";
                if (isPane) name = "pane";
                
                var state = {
                    type: name,
                    name: isPane ? node.cloud9pane.name : undefined,
                    nodes: node.childNodes
                        .filter(function(child) {
                            if (isPane
                              && (child.localName != "page" || !child.visible)
                              || child.localName == "splitter")
                                return false;
                            
                            // Exclude previewing pages;
                            var tab = child.cloud9tab;
                            return !tab || !tab.document.meta.preview
                                && !tab.document.meta.ignoreState;
                        })
                        .map(function(child) {
                            return isPane 
                                ? child.cloud9tab.getState(filter) 
                                : recur(child);
                        })
                };
                
                if (node.width)   state.width = node.width;
                if (node.height)  state.height = node.height;
                if (node.skin)    state.skin = node.skin;
                if (node.skinset) state.skinset = node.skinset;
                
                if (isPane && node.cloud9pane.meta) {
                    var meta = node.cloud9pane.meta;
                    for (var prop in meta) {
                        if (prop.charAt(0) != "$") {
                            if (!state.meta) 
                                state.meta = {};
                            state.meta[prop] = meta[prop] && meta[prop].toJson
                                ? meta[prop].toJson() : meta[prop];
                        }
                    }
                }
                
                return state;
            })(subcontainer || container.firstChild);
            
            if (state)
                state.focus = focussedTab 
                    && (focussedTab.path || focussedTab.name);
            
            return state;
        }
        
        function setState(state, init, callback) {
            if (typeof init == "function") {
                callback = init;
                init = false;
            }
            
            if (!state) {
                state = {
                    type: "pane", 
                    nodes: []
                };
            }
            
            // Remove all existing tabs
            if (!init && !options.testing)
                clear(false, false, true);
            
            var count = 0;
            
            // Load State
            (function recur(parent, list) {
                list.forEach(function(state) {
                    var p;
                    
                    if (state.type == "pane") {
                        state.preventAutoActivate = state.nodes.length > 0;
                        p = createPane(state).aml;
                        parent.appendChild(p);
                    }
                    else if (state.type == "hsplitbox" || state.type == "vsplitbox") {
                        p = parent.appendChild(new ui[state.type]({
                            splitter: true,
                            padding: 1,
                            width: state.width,
                            height: state.height
                        }));
                    }
                    else if (state.type == "tab") {
                        var tab = findTab(state.id);
                        if (!tab) {
                            state.pane = parent.cloud9pane;
                            state.init = init;
                            
                            count++;
                            open(state, function(err, tab) {
                                if (!--count)
                                    callback();
                            });
                        }
                        else {
                            tab.attachTo(parent);
                        }
                        return;
                    }
                    recur(p, state.nodes);
                    
                    // If somehow we didn't record an active tab, 
                    // set the first tab as active.
                    if (state.type == "pane") {
                        var pane = p.cloud9pane;
                        if (!pane.activeTab) {
                            var next = pane.getTabs()[0];
                            next && next.activate();
                        }
                    }
                });
            })(state.container || container, [state]);
            
            // Set Focus
            if (state.focus)
                focusTab(findTab(state.focus));
            
            setCornerPadding();
            
            if (!count)
                callback();
        }
        
        function clear(soft, clearTabs /* For testing only */, silent){
            var list = getPanes(container);
            
            for (var i = list.length - 1; i >= 0; i--) {
                var pane = list[i], nodes = pane.getTabs();
                for (var j = nodes.length - 1; j >= 0; j--) {
                    var tab = nodes[j];
                    if (!soft) {
                        if (silent)
                            tab.document.meta.$ignoreSave = true;
                        tab.meta.$closeSync = true;
                        tab.unload();
                    }
                    else {
                        tab.aml.parentNode.removeChild(tab.aml);
                        tab.pane = null;
                    }
                }
                // if (i > 0)
                    pane.unload();
            }
            if (!soft || clearTabs) tabs = [];
            panes = [];
        }
        
        function toggleButtons(to) {
            showTabs = to !== undefined ? to : !showTabs;
            
            settings.set("user/tabs/@show", showTabs);
            emit("visible", {value: showTabs});
            
            getPanes(container).forEach(function(pane) {
                ui.setStyleClass(pane.aml.$ext, showTabs ? "" : "notabs", ["notabs"]);
            });
        }
        
        var closeTimer;
        function resizePanes(cancel) {
            clearTimeout(closeTimer);
    
            if (cancel)
                return;
    
            closeTimer = setTimeout(function(){
                panes.forEach(function(pane) {
                    pane.aml.$waitForMouseOut = false;
                    pane.aml.$scaleinit(null, "sync");
                });
            }, 500);
        }
        
        function activateTab(tab) {
            if (typeof tab == "string")
                tab = findTab(tab);
            
            tab.activate();
            
            return tab;
        }
        
        function focusTab(tab, soft, async) {
            if (typeof tab == "string")
                tab = findTab(tab);
            
            if (previewTab && tab != previewTab)
                preview({ cancel: true });
            
            // do not focus a tab during close animation
            if (tab && tab.meta && tab.meta.$closing) {
                // Focus
                if (focussedTab && (!soft || soft !== 2 && !unfocussed) && focussedTab.editor) {
                    unfocussed = false;
                    focussedTab.editor.focus();
                }
                
                return tab;
            }
            
            if (focussedTab != tab) {
                // Blur
                if (focussedTab) {
                    var blurTab = focussedTab;
                    var blur = function (){
                        emit("blur", { tab: blurTab });
                        
                        // During destroy of the pane the editor can 
                        // not exist for a tab
                        if (blurTab.editor && focussedTab
                          && blurTab.editor != focussedTab.editor)
                            blurTab.editor.blur();
                    };
                    
                    blurTab.classList.remove("focus");
                    
                    blur();
                }
                
                if (!tab) {
                    focussedTab = null;
                    unfocussed = true;
                    return;
                }
                
                // Change focussedTab
                focussedTab = tab;
                
                tab.activate();
                
                // Focus
                if ((!soft || soft !== 2 && !unfocussed) && focussedTab.editor) {
                    unfocussed = false;
                    focussedTab.editor.focus();
                }
                
                focussedTab && focussedTab.classList.add("focus");
            }
            else {
                if (!focussedTab)
                    return;
                
                if ((!soft || soft !== 2 && !unfocussed) && focussedTab.editor) {
                    unfocussed = false;
                    focussedTab.editor.focus(true);
                }
                
                return tab;
            }
            
            emit("focusSync", { tab : tab });
            
            if (async) {
                setTimeout(function(){
                    emit("focus", { tab : tab });
                }, 500);
            }
            else
                emit("focus", { tab : tab });
            
            return tab;
        }
        
        function findTab(path) {
            return tabs[PREFIX + path] || tabs[util.normalizePath(path)];
        }
        
        function getTabs(container) {
            var result = Object.keys(tabs).map(function(path) {
                return tabs[PREFIX + path] || tabs[path];
            });
            
            if (!container)
                return result;
            
            return result.filter(function(tab) {
                return ui.isChildOf(container, tab.aml);
            });
        }
        
        function findPane(name) {
            for (var i = 0; i < panes.length; i++) {
                if (panes[i].name == name)
                    return panes[i];
            }
        }
        
        function getPanes(container) {
            return !container 
                ? panes.slice()
                : panes.filter(function(pane) {
                    return ui.isChildOf(container, pane.aml);
                });
        }
        
        /**** Main entry point for opening tabs ****/
        
        // Handler to show loading indicator
        function setLoading(tab) {
            tab.classList.add("loading");
            
            // TODO this gets called for things that have nothing to do
            // with filesystem :(
            if (!tab.path)
                return;
                
            var loadStartT = Date.now();
            fs.on("downloadProgress", function progress(e) {
                if (e.path == tab.path) {
                    e.dt = Date.now() - loadStartT;
                    tab.document.progress(e);
                    if (e.complete)
                        fs.off("downloadProgress", progress);
                }
            });
            // TODO move to fs
            // tab.document.progress({dt: 0, loaded: 0, total: 1});
        }
        
        function openFile(path, active, callback) {
            if (typeof active == "function")
                callback = active, active = false;
            
            return open({path: path, active: active}, callback);
        }
        
        function openEditor(type, active, callback) {
            if (typeof active == "function")
                callback = active, active = false;
            
            return open({editorType: type, active: active}, callback);
        }
        
        function open(options, callback) {
            callback = callback || function() {};
            
            var path = options.path = util.normalizePath(options.path);
            var type = options.editorType;
            var editor;
            
            // If a tab for this file already exist, activate it
            var id = !options.forceNew && !options.init && path || options.name;
            var tab = options.tab || (id
                ? findTab(id)
                : options.demandExisting
                  && getTabs().filter(function(t) {
                         return t.editorType === options.editorType
                             && (!options.title || (t.document 
                             && t.document.title === options.title));
                     })[0]);
            
            // prevent opening of same tab twice in non cloned mode
            // TODO move cloning into ace?
            if (!tab)
                tab = findTab(path);
            
            // Clone Tab
            if (((options.document || 0).meta || 0).cloned) {
                if (!tab) {
                    if (options.init) {
                        plugin.on("open", function wait(e){
                            if (e.tab.path == path) {
                                plugin.off("open", wait);
                                options.tab = e.tab;
                                open(options, callback);
                            }
                        }, plugin);
                        return;
                    }
                    else {
                        throw new Error("Missing tab when cloning");
                    }
                }
                
                options.path = path = tab.path;
                options.editorType = tab.editorType;
                options.document = tab.document.clone();
                
                // Tab variable will be overwritten below
                tab.on("unload", function(){ tab.unload(); }, tab.document);
            }
            // Process Existing Tab
            else if (tab) {
                if (tab.document.meta.preview)
                    keepPreview();

                if (options.active)
                    tab.activate();
                if (options.focus)
                    focusTab(tab, options.focus !== true);
                if (options.document)
                    tab.document.setState(options.document);
                callback && callback(null, tab, null, true);
                return tab;
            }

            // Find editor
            if (!type) {
                editor = path && editors.findEditorByFilename(path)
                    || editors.findEditor(editors.defaultEditor);
                type = editor && editor.type || editors.defaultEditor;
            }
            else {
                editor = editors.findEditor(type);
            }
            
            // Set sensible defaults
            if (typeof path != "string")
                options.path = null;
            if (!options.document)
                options.document = {};
            if (path && !options.document.title) {
                options.document.title = basename(path);
                options.document.tooltip = path;
            }
            if (typeof options.value == "string" && !options.newOnError)
                options.document.value = options.value;
            // if (options.document.filter === undefined)
            //     options.document.filter = true;
            options.editorType = type;
            
            // Don't proceed if findEditorByFilename returned "none"
            if (editor === "none") {
                alert("Can't open " + basename(path) + ": file format unsupported");
                return callback(new Error("File not supported"));
            }
            
            // Create the tab
            tab = createTab(options);
            
            // Focus
            if (options.focus)
                focusTab(tab, options.focus !== true);
            
            var doc = options.document;
            var loadFromDisk = path 
              && (!doc || doc.value === undefined) 
              && (options.value === undefined || options.newOnError)
              // autoload to false prevents loading data, used by image editor
              && (!editor || editor.autoload !== false);
            
            // Handler to be used after content and state is loaded
            function done(err, value) {
                tab.classList.remove("loading");
                
                if (err && options.newOnError) {
                    tab.document.meta.newfile = true;
                    value = options.value || "";
                }
                else if (err) {
                    tab.classList.add("error");
                    tab.document.meta.error = true;
                    
                    if (tab.document.meta.closeOnError) {
                        tab.close();
                        return callback && callback(err);
                    }
                    
                    alert("Error opening file", 
                        "Could not open file: " + tab.path,
                        err.code == "ENOENT"
                            ? "The file could not be found on the file system."
                            : "Unknown error: " + (err.message || err.code || err),
                        function(){
                            tab.close();
                            callback && callback(err);
                        });
                    
                    return;
                }
                
                var doc = tab.document;
                if (typeof value == "string")
                    doc.value = value;
                    
                // Set timestamp to now() to indicate that the file has been
                // loaded from disk without any changes
                if (!doc.meta.timestamp)
                    doc.meta.timestamp = Date.now() - settings.timeOffset;
                
                doc.ready = true;
                emit("open", { tab: tab, options: options });
                callback && callback(null, tab);
            }

            if (loadFilesAtInit === false && options.init 
              && emit.listeners("beforeOpen").length === 0)
                return tab;

            // Hooks for plugins that want to override value and state loading
            var event = { 
                options: options,
                tab: tab, 
                loadFromDisk: loadFromDisk,
                setLoading: setLoading,
                callback: done
            };
            if (emit("beforeOpen", event) === false)
                return tab;
            
            // todo why metadata does this?
            loadFromDisk = event.options.loadFromDisk = loadFromDisk 
                    && !tab.document.meta.newfile
                    && !tab.document.meta.nofs;
            
            // If no value is specified, lets load it from storage
            // todo metadata doesn't check for this! should we?
            if (options.value === -1) {
                setLoading(tab);
                callback(null, tab, function(){
                    tab.classList.remove("loading");
                    emit("open", { tab: tab, options: options });
                });
            }
            else if (loadFromDisk) {
                setLoading(tab);
                var xhr = fs.readFile(path, "utf8", done);

                // Cancel file opening when tab is closed
                var abort = function(){
                    xhr && xhr.abort();
                };
                tab.on("close", abort);
                plugin.on("open", function(e) {
                    if (e.tab == tab) 
                        tab.off("close", abort); 
                });
            }
            else {
                // done has to be called asynchronously
                setTimeout(function() {
                    done(null, null);
                });
            }
            
            return tab;
        }
        
        function clone(tab, pane, callback){
            open({
                pane: pane,
                tab: tab,
                focus: true,
                document: {
                    meta: { cloned: true }
                }
            }, function(err, tab){
                if (tab)
                    tab.document.progress({ complete: true });
                callback(err, tab);
            });
        }

        function reload(tab, callback) {
            if (!tab) tab = focussedTab;
            if (!tab || !tab.path) 
                return callback();
            
            if (tab.editor.autoload === false) {
                tab.document.setBookmarkedValue(tab.document.value);
                
                delete tab.document.meta.error;
                tab.classList.remove("error");
                tab.classList.remove("loading");
                emit("reload", { tab: tab });
                
                return callback();
            }
            
            setLoading(tab);
            
            fs.readFile(tab.path, "utf8", function(err, data) {
                if (err) {
                    tab.classList.remove("loading");
                    tab.classList.add("error");
                    tab.document.meta.error = true;
                    callback(err);
                    return;
                }
                
                // Update val
                tab.document.setBookmarkedValue(data);
                tab.document.meta.timestamp = Date.now() - settings.timeOffset;
                
                // Make sure the newfile flag is gone
                delete tab.document.meta.newfile;
                
                // Remove the error flag
                delete tab.document.meta.error;
                
                tab.classList.remove("error");
                tab.classList.remove("loading");
                emit("reload", { tab: tab });
                callback();
            });
        }
        
        function checkAllTabs() {
            getTabs().forEach(function(tab) {
                var meta = tab.document.meta;
                if (tab.path && !meta.newfile && !meta.preview && !meta.ignoreState && !meta.nofs)
                    watcher.check(tab.path, meta.timestamp);
            });
        }
        
        var lastPreviewTab;
        function preview(options, callback) {
            var pane;

            // Cancel previewing and show focussedTab again
            if (options.cancel)
                return cancelPreview(options.keep);
            
            // Exclude certain extensions from being previewed
            if (XPREVIEW.test(options.path))
                return;
            
            if (!options.editorType && previewTab && previewTab.path === options.path) {
                // keepPreview();
                return previewTab;
            }
            // Remove existing preview pane if it's there
            else if (previewTab) {
                pane = previewTab.pane;
                if (previewTab.document.meta.existing) {
                    delete previewTab.document.meta.preview;
                    delete previewTab.document.meta.existing;
                    
                    previewTab.classList.remove("preview");
                }
                
                // Or keep tab until the new one is loaded
                else {
                    previewTab.unload({ animate: false });
                }
            }

            if (!options.path && !options.editorType)
                throw new Error("No path or editorType specified for preview");
            
            return createPreview(options, pane, callback);
        }
        
        function createPreview(options, pane, callback) {
            var path = options.path;
            // Check if pane is already loaded
            var tab = findTab(path);
            if (tab) {
                // tab.document.meta.preview = true;
                // tab.document.meta.existing = true;
                tab.activate();
                clearTimeout(previewTimeout);
                callback(null, null);
            }
            // Else create preview pane
            else if (!previewTimeout || options.immediate) {
                var doc = options.document || {};
                doc.meta = {
                    readonly: true,
                    preview: true
                };
                
                previewTab = open({ 
                    path: path, 
                    editorType: options.editorType,
                    active: true, 
                    pane: pane,
                    noanim: true,
                    document: doc
                }, function(err, tab) {
                    // Previewing has already been cancelled
                    if (err || !tab.loaded)
                        return callback(new Error("Preview was cancelled"));
                    
                    // Activate previewTab showing content
                    tab.activate();
                    
                    // Open actual tab when the user starts editing
                    // @TODO cleanup
                    tab.document.undoManager.on("change", function(){
                        if (tab.document.undoManager.position > -1)
                            keepPreview();
                    });
                    
                    callback(null, tab);
                });
                previewTab.classList.add("preview");
            } else {
                clearTimeout(previewTimeout);
                previewTimeout = setTimeout(function() {
                    previewTimeout = null;
                    createPreview({
                        path: options.path,
                        immediate: true
                    }, pane, callback);
                }, 200);
            }
            
            return previewTab;
        }

        function cancelPreview(keep) {
            var lastFocussedTab = focussedTab;
            
            // Unload last preview tab
            if (lastPreviewTab) {
                lastPreviewTab.unload();
                lastPreviewTab = null;
            }
            
            if (!previewTab)
                return false;
            
            previewTab.classList.remove("preview");
            
            if (keep)
                return keepPreview();

            if (previewTab.document.meta.existing) {
                delete previewTab.document.meta.preview;
                delete previewTab.document.meta.existing;
            }
            else {
                var tab = previewTab;
                previewTab = null;
                tab.unload(); // TODO this focusses the last tab. If there is a speed concern, fix this.
            }
            
            previewTab = null;
            lastFocussedTab && lastFocussedTab.activate();
            return false;
        }
        
        // Open actual tab
        function keepPreview(){
            if (!previewTab) return;
            
            if (previewTimeout) {
                previewTimeout = null;
                clearTimeout(previewTimeout);
            }
            
            if (!previewTab.loaded) {
                previewTab.unload();
                previewTab = null;
                return false;
            }
            
            previewTab.classList.remove("preview");
            
            if (previewTab.document.meta.existing) {
                delete previewTab.document.meta.existing;
                delete previewTab.document.meta.preview;
            }
            else {
                delete previewTab.document.meta.readonly;
                delete previewTab.document.meta.preview;
                previewTab.document.undoManager.off("change", keepPreview);
                emit("open", { tab: previewTab });
            }

            previewTab = null;
            return true;
        }
        
        function switchEditor(tab, type, callback) {
            tab.switchEditor(type, function(){
                emit("switchEditor", { tab: tab });
                callback.apply(this, arguments);
            });
        }
        
        /**** Support for state preservation ****/
    
        function pauseTabResize(){
            panes.forEach(function(pane) {
                pane.setAttribute("buttons", "close,order");
            });
        }
    
        function continueTabResize(){
            setTimeout(function(){
                panes.forEach(function(pane) {
                    pane.setAttribute("buttons", "close,scale,order");
                    pane.$waitForMouseOut = false;
                    pane.$scaleinit(null, "sync");
                });
            }, 300);
        }
    
        /***** Lifecycle *****/
        
        plugin.on("load", function(){
            load();
        });
        plugin.on("enable", function(){
            
        });
        plugin.on("disable", function(){
            
        });
        plugin.on("unload", function(){
            panes = [];
            tabs = [];
            loaded = false;
            drawn = false;
            lastPreviewTab = null;
        });
        
        /***** Register and define API *****/
        
        /**
         * Manages all tabs in all panes of Cloud9. This includes the panes
         * in the console. Use this plugin to open files and editors, to change
         * the focus of a tab, or to fetch panes and tabs.
         * 
         * The tabManager manages the whole of panes and tabs that are organized
         * as follows:
         * 
         * * {@link Pane} - Represent a single pane, housing multiple tabs
         *   * {@link Tab} - A single tab (button) in a pane
         *     * {@link Editor} - The editor responsible for displaying the file in the tab
         *     * {@link Document} - The representation of a file in the tab
         *       * {@link Session} - The session information of the editor
         *       * {@link UndoManager} - The object that manages the undo stack for this document
         * 
         * Panes can live in certain areas of Cloud9. By default these areas are:
         * 
         * * {@link panes}      - The main area where editor panes are displayed
         * * {@link console}    - The console in the bottom of the screen
         * 
         * This is the default way to open a new file in an editor:
         * 
         *     tabManager.openFile("/file.js", true, function(err, tab) {
         *         var doc = tab.document;
         *         console.log("The value is: ", doc.value);
         *     });
         * 
         * @class tabManager
         * @alternateClassName panes
         * @singleton
         **/
        plugin.freezePublicAPI({
            /**
             * The tab that currently has the focus. Multiple tabs can be 
             * active at the same time. However there can only be one tab
             * that has the focus at one time. If there are no tabs this 
             * property is null.
             * 
             * See also {@link tabManager#focusTab}
             * @property {Tab} focussedTab
             * @readonly
             */
            get focussedTab(){ return focussedTab || null; },
            /**
             * Specifies whether the tabManager has focus. When a component 
             * outside of the editors has focus (such as the tree or a menu 
             * item), then this property is set to false. Otherwise it is set
             * to true.
             * @property {Boolean} focussed
             * @readonly
             */
            get focussed(){ return !unfocussed; },
            /**
             * The tab that is shown as a preview. This property is set to null
             * if there is currently no preview being shown.
             * @property {Tab} previewTab
             * @readonly
             */
            get previewTab(){ return previewTab; },
            /**
             * An Array of all the HTML Elements that act as containers for
             * panes. Generally this is one for the main panes area and one for
             * the console.
             * @property {HTMLElement[]} containers
             * @readonly
             */
            get containers(){ return containers; },
            /**
             * The AMLElement for the main panes area.
             * @property {AMLElement} container
             * @readonly
             */
            get container(){ return container; },
            
            _events: [
                /**
                 * Fires after a tab received focus. This event is fired
                 * when the focussedTab property is set. There is about 500ms
                 * delay between the user action and the firing of this event.
                 * This delay prevents the UI from getting stuck while plugins
                 * update their UIs. If you MUST have a sync event, check out
                 * {@link tabManager#focusSync}.
                 * 
                 * @event focus
                 * @param {Object} e
                 * @param {Tab}    e.tab the tab that received focus
                 */
                "focus",
                /**
                 * Fires after a tab received focus. This event is fired
                 * when the focussedTab property is set. Do not use this event
                 * unless you ABSOLUTELY have to. Use {@link tabManager#focus}
                 * instead.
                 * 
                 * @event focusSync
                 * @param {Object} e
                 * @param {Tab}    e.tab the tab that received focus
                 */
                "focusSync",
                /**
                 * Fires after a tab lost focus. This event is fired
                 * when the focussedTab property is changed to another tab.
                 * 
                 * @event blur
                 * @param {Object} e
                 * @param {Tab}    e.tab the tab that lost focus
                 * @private
                 */
                "blur",
                /** 
                 * Fires before a tab is activated. A tab becomes active when
                 * it is the selected tab (button) of the pane it belongs to. 
                 * This is different from receiving the focus. A tab that has
                 * focus is always the active tab of it's parent pane. If a pane
                 * has at least one tab, it always has an active tab. You can
                 * cancel the activation of a tab by returning false in the 
                 * event listener. This will prevent the tab from becoming
                 * active.
                 * 
                 * See also {@link Pane#activeTab}.
                 * 
                 * @event tabBeforeActivate
                 * @cancellable
                 * @param {Object} e
                 * @param {Tab}    e.tab  the tab that will become active
                 */
                "tabBeforeActivate",
                /** 
                 * Fires after a tab is activated asynchronously. See 
                 * {@link #tabAfterActivateSync} for the synchronous event.
                 * A tab becomes active when
                 * it is the selected tab (button) of the pane it belongs to. 
                 * This is different from receiving the focus. A tab that has
                 * focus is always the active tab of it's parent pane. If a pane
                 * has at least one tab, it always has an active tab. You can
                 * cancel the activation of a tab by returning false in the 
                 * event listener. This will prevent the tab from becoming
                 * active.
                 * 
                 * See also {@link Pane#activeTab}.
                 * 
                 * @event tabAfterActivate
                 * @param {Object} e
                 * @param {Tab}    e.tab      the tab that has become active
                 * @param {Tab}    e.lastTab  the tab that is no longer active
                 */
                "tabAfterActivate",
                /**
                 * Fires after a tab is activated synchronously. See 
                 * {@link #tabAfterActivate} for the asynchronous event.
                 * A tab becomes active when
                 * it is the selected tab (button) of the pane it belongs to. 
                 * This is different from receiving the focus. A tab that has
                 * focus is always the active tab of it's parent pane. If a pane
                 * has at least one tab, it always has an active tab. You can
                 * cancel the activation of a tab by returning false in the 
                 * event listener. This will prevent the tab from becoming
                 * active.
                 * 
                 * See also {@link Pane#activeTab}.
                 * 
                 * @event tabAfterActivateSync
                 * @param {Object} e
                 * @param {Tab}    e.tab      the tab that has become active
                 * @param {Tab}    e.lastTab  the tab that is no longer active
                 */
                "tabAfterActivateSync",
                /** 
                 * Fires prior to closing a tab. The default action of closing
                 * the tab can be cancelled by returning false.
                 * 
                 * @event tabBeforeClose
                 * @cancellable
                 * @param {Object} e
                 * @param {Object} e.tab  the tab that is to be closed
                 */
                "tabBeforeClose",
                /** 
                 * Fires after a tab is closed, but not yet destroyed. Generally
                 * you want to use {@link tabManager#tabDestroy}.
                 * 
                 * @event tabAfterClose
                 * @param {Object} e
                 * @param {Tab}    e.tab  the tab that is to be closed
                 */
                "tabAfterClose",
                /** 
                 * Fires after a tab is newly created.
                 * @event tabCreate
                 * @param {Object} e
                 * @param {Tab}    e.tab  the tab that is created
                 */
                "tabCreate",
                /** 
                 * Fires after a tab is destroyed
                 * @event tabDestroy
                 * @param {Object} e
                 * @param {Tab}    e.tab    the tab that has been destroyed
                 * @param {Tab}    e.last   whether this was the last tab of a pane that was destroyed
                 */
                "tabDestroy",
                /** 
                 * Fires when a tab is moved within a pane.
                 * @event tabOrder
                 * @param {Object}  e
                 * @param {Tab}     e.tab   the tab that has been moved
                 * @param {Tab}     e.next  the tab on the right of e.tab.
                 */
                "tabOrder",
                /** 
                 * Fires before a tab is moved to a different pane. This usually
                 * happens when a pane is split, or when a tab is dragged to a
                 * different pane.
                 * 
                 * @event tabBeforeReparent
                 * @cancelable
                 * @param {Object} e
                 * @param {Tab}    e.tab      the tab that is moved.
                 * @param {Tab}    e.lastPane the previous pane that this tab was part of.
                 * @param {Pane}   e.pane     the pane that the tab is moved to.
                 */
                "tabBeforeReparent",
                /** 
                 * Fires after a tab is moved to a different pane. This usually
                 * happens when a pane is split, or when a tab is dragged to a
                 * different pane.
                 * 
                 * @event tabAfterReparent
                 * @param {Object} e
                 * @param {Tab}    e.tab      the tab that is moved.
                 * @param {Tab}    e.lastPane the previous pane that this tab was part of.
                 * @param {Pane}   e.pane     the pane that the tab is moved to.
                 */
                "tabAfterReparent",
                /** 
                 * Fires after a pane is newly created
                 * @event paneCreate
                 * @param {Object} e
                 * @param {Pane}   e.pane     the pane that is created
                 */
                "paneCreate",
                /** 
                 * Fires after a pane is destroyed
                 * @event paneDestroy
                 * @param {Object} e
                 * @param {Pane}   e.pane     the pane that is destroyed
                 */
                "paneDestroy",
                /** 
                 * Fires after a tab is opened and before it's contents is 
                 * loaded. This event can be cancelled by returning false. 
                 * Cancelling this event will prevent the data from being 
                 * loaded. It then becomes your reponsibility to load the
                 * contents and call e.callback(err, value) when done.
                 * 
                 * This event is primarily used by the {@link metadata} plugin.
                 * 
                 * @event beforeOpen
                 * @cancellable
                 * @param {Object}   e
                 * @param {Object}   e.options          The options object that was passed to the {@link tabManager#method-open} method.
                 * @param {Tab}      e.tab              The created tab that will present the file contents to the user.
                 * @param {Boolean}  e.loadFromDisk     Specifies whether the file contents will be loaded from disk if the event is not cancelled.
                 * @param {Function} e.setLoading       Call this function to set the tab into a loading state.
                 * @param {Tab}      e.setLoading.tab   Specify the tab to set in a loading state.
                 * @param {Function} e.callback         Call this function when the content is loaded.
                 * @param {Error}    e.callback.err     Specify an error if loading the contents has failed.
                 * @param {String}   e.callback.value   Specify the value of the content that is loaded.
                 */
                "beforeOpen",
                /** 
                 * Fires after a tab is opened and it's contents loaded
                 * @event open
                 * @param {Object} e
                 * @param {Tab}    e.tab      The tab that has been opened
                 * @param {Object} e.options  The options object that was passed to the {@link tabManager#method-open} method.
                 */
                "open",
                /** 
                 * Fires after the contents of a tab is reloaded
                 * @event reload
                 * @param {Object} e
                 * @param {Tab}    e.tab  The tab for which the contents is reloaded
                 */
                "reload",
                /** 
                 * Fires when the tab manager is ready to open tabs
                 * @event ready
                 */
                "ready",
                /** 
                 * Fires when the user clicked on the + button in pane.
                 * @event plusClick
                 * @cancellable
                 * @param {Object} e
                 * @param {Tab}    e.pane  The pane to which the + button belongs to
                 */
                "plusClick",
                /** 
                 * Fires after the panels container is drawn
                 * @event draw
                 */
                "draw"
            ],
            
            /**
             * Prevent all pane buttons from resizing for all panes.
             */
            pauseTabResize: pauseTabResize,
            
            /**
             * Re-enable pane button resizing for all panes.
             */
            continueTabResize: continueTabResize,
            
            /**
             * Sets the tab as the active tab of it's pane element
             * @param {Tab} tab  The tab to activate
             */
            activateTab: activateTab,
            
            /**
             * Gives the focus to the tab and it's editor and sets the tab as
             * the the active tab of it's pane.
             * @param {Tab} tab  The tab to activate
             * @fires focus
             * @fires focusSync
             * @fires blur
             */
            focusTab: focusTab,
            
            /**
             * Retrieves the state of all panes, tabs and documents 
             * as a single serializable object.
             * @param {HTMLElement} container  One of the items from the 
             *   {@link tabManager#containers} set, specifying which area it 
             *   should serialize. Defaults to the container from the panels.
             * @returns {Object}
             */
            getState: getState,
            
            /**
             * Loads the state of all panes panes, tabs anddocuments from a 
             * simple object.
             * @param {Object}  state      The state describing the pane layout.
             * @param {Boolean} [init]     When set to true, optimizes the state 
             *   loading for initialization of Cloud9.
             * @param {Function} callback  Called when the state loading is completed.
             */
            setState: setState,
            
            /**
             * Removes all panes, except one, and destroys all tabs, documents 
             * and editors. 
             * 
             * @param {Boolean} [soft=false] When set to true clear
             *   will not unload tabs. This can be useful when loading a new
             *   state with exactly the same tabs. WARNING: this can lead to
             *   leaking tabs/documents, etc. Use with caution! getTabs() will
             *   continue to return all the tabs. Even though they are no longer
             *   attached to a pane.
             */
            clear: clear,
            
            /**
             * Toggle the tab buttons between visible and hidden state.
             * @param {Number} [force] When set to 1 the buttons become visible, 
             *     when set to 2 they become hidden.
             */
            toggleButtons: toggleButtons,
            
            /**
             * Force a resize of all the tab buttons in all the panes.
             */
            resizePanes: resizePanes,
            
            /**
             * Retrieves a tab based on a path or it's unique identifier.
             * @param {String} path  The path or id of the tab to retrieve.
             */
            findTab: findTab,
            
            /**
             * Returns an array containing all the tabs.
             */
            getTabs: getTabs,
            
            /**
             * @ignore
             */
            checkAllTabs: checkAllTabs,
            
            /**
             * Retrieves a pane based on it's unique identifier.
             * @param {String} name  The name of the pane to retrieve.
             */
            findPane: findPane,
            
            /**
             * Returns an array containing all the panes (in a container).
             * @param {AMLElement} [container]  The container in which the panes 
             *   reside. If no container is specified all panes in all 
             *   containers are returned.
             */
            getPanes: getPanes,
            
            /**
             * Opens a new tab with an editor. If the tab with a specified
             * path already exists, that tab is activated.
             * (If no path was specified and option demandExisting is used,
             * we look for a tab with the same editor type and title.)
             * If state is given for a document, then that state is set prior
             * to loading the tab. If a path is specified the file contents is
             * loaded into the document. If no editorType is specified, the
             * editor is determined based on the extension of the filename.
             * 
             * @param options
             * @param {String}   [options.path]          The path of the file to open
             * @param {Pane}     [options.pane]          The pane to attach the new tab to
             * @param {String}   [options.editorType]    The type of the editor for this tab
             * @param {Boolean}  [options.active=false]  Whether this tab is set as active
             * @param {Boolean}  [options.focus=false]   Whether this tab is set as focussed
             * @param {Boolean}  [options.forceNew=false] Always create a tab
             * @param {Boolean}  [options.demandExisting=false] Whether to try opening an
             *   existing tab even for tabs without a path.
             * @param {Object}   [options.document]      Object describing the 
             *   state of the document (see {@link Document#method-getState}) 
             * @param {String}   [options.value]         The contents of the file
             * @param {String}   [options.title]         The title of the tab
             * @param {String}   [options.tooltip]       The tooltip at the button of the tab
             * @param {Function} [callback]
             * @param {Error}    callback.err            An error that might 
             *   occur during the load of the file contents.
             * @param {Tab}      callback.tab            The created tab.
             * @param {Function} callback.done           Call this function 
             *   when done retrieving the value. This is only relevant if 
             *   -1 is passed to `value`. You are responsible for settings the 
             *   document value yourself, like so: `tab.document.value = "value";`
             * @returns {Tab}                            The created tab.
             * @fires open
             * @fires beforeOpen
             */
            open: open,
            
            /**
             * Opens a new pane tab with the default editor and loads the file
             * contents into the document. This is a convenience method. For
             * the full method see {@link tabManager#method-open}.
             * 
             * @param {String}   path          The path of the file to open.
             * @param {Boolean}  [active]      When set to true the new tab will become active in it's pane.
             * @param {Function} callback      Called when the file contents is loaded.
             * @param {Error}    callback.err  An error that might 
             *   occur during the load of the file contents.
             * @param {Tab}      callback.tab  The created tab.
             * @returns {Tab} The created tab.
             */
            openFile: openFile,
            
            /**
             * Opens a new tab with a specific editor
             * @param {String}   editorType    The type of the editor for this tab.
             * @param {Boolean}  [active]      When set to true the new tab will become active in it's pane.
             * @param {Function} callback      Called when the editor is loaded.
             * @param {Error}    callback.err  An error that might 
             *   occur during the load of the editor.
             * @param {Tab}      callback.tab  The created tab.
             * @returns {Tab} The created tab.
             */
            openEditor: openEditor,
            
            /**
             * Reloads the file contents of a tab. The tab needs to have a 
             * path property set.
             * @param {Tab} tab  The tab to reload.
             */
            reload: reload,
            
            /**
             * Opens a tab in preview mode, displaying the contents of a file with
             * it's editor. The options passed can be the same as for the 
             * {@link tabManager#method-open}
             * 
             * @param options
             * @param {String}   [options.path]          The path of the file to open
             * @param {Pane}     [options.pane]          The pane to attach the new tab to
             * @param {String}   [options.editorType]    The type of the editor for this tab
             * @param {Boolean}  [options.active=false]  Whether this tab is set as active
             * @param {Object}   [options.document]      Object describing the 
             *   state of the document (see {@link Document#method-getState}) 
             * @param {String}   [options.value]         The contents of the file
             * @param {String}   [options.title]         The title of the tab
             * @param {String}   [options.tooltip]       The tooltip at the button of the tab
             * @param {Function} callback 
             * @param {Error}    callback.err            An error that might 
             *   occur during the load of the file contents.
             * @param {Tab}      callback.tab            The created tab.
             * @param {Function} callback.done           Call this function 
             *   when done retrieving the value. This is only relevant if 
             *   -1 is passed to `value`. You are responsible for settings the 
             *   document value yourself, like so: `tab.document.value = "value";`
             * @returns {Tab} The created tab.
             * @fires open
             * @fires beforeOpen
             */
            preview: preview,
            
            /**
             * 
             */
            clone: clone,
            
            /**
             * 
             */
            switchEditor: switchEditor,
            
            /**
             * @ignore
             */
            get isReady(){ return isReady; },
        });
        
        register(null, {
            tabManager: plugin
            // panes      : plugin
        });
    }
});
