define(function(require, module, exports) {
    main.consumes = ["Plugin", "ui", "clipboard"];
    main.provides = ["Editor"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var clipboard = imports.clipboard;
        var ui = imports.ui;
        
        function Editor(developer, deps, extensions) {
            // Editor extends ext.Plugin
            var plugin = new Plugin(developer, deps);
            var emit = plugin.getEmitter();
            emit.setMaxListeners(1000);
            
            var amlTab, type, pane, activeDocument, focussed;
            var meta = {};
            
            /***** Methods *****/
            
            function unloadDocument(doc, options) {
                if (!options) options = {};
                options.doc = doc;
                emit("documentUnload", options);
                doc.getSession().unload();
            }
            
            function loadDocument(doc) {
                // Old Editor
                var lastEditor = doc.editor;
                if (lastEditor && lastEditor != plugin) {
                    lastEditor.unloadDocument(doc, { 
                        toEditor: plugin, 
                        fromEditor: lastEditor 
                    });
                }
                
                activeDocument = doc;
                
                // Initialize the Document in the Editor
                // When the Document unloads the editor should clear all it's state
                // When the Editor unloads it should clear all it's state
                if (lastEditor != plugin) {
                    var editor = plugin;
                    doc.editor = plugin;
                    
                    var session = doc.getSession();
                    var state = (doc.lastState || false)[editor.type]; // Retrieve last state
                    bufferEvent.call(plugin, "documentLoad", { 
                        doc: doc, 
                        state: state || {}
                    });
                    
                    // Check if session was unloaded from the previous editor
                    if (!session.loaded)
                        session.load(); 
                    
                    if (!doc.meta.$unloadEditor) {
                        doc.meta.$unloadEditor = true;
                        doc.on("unload", function(){
                            doc.editor.unloadDocument(doc);
                        });
                    }
                    
                    // editor.on("unload", function(){
                    //     editor.unloadDocument(doc);
                    // }, session);
                    
                    if (state)
                        plugin.setState(doc, state);
                }
                
                // Set Document Active in the Editor
                bufferEvent.call(plugin, "documentActivate", { doc : doc });
            }
            
            function bufferEvent(name, event) {
                var _self = plugin;
                
                emit(name, event);
                    
                // Add new listeners
                function listenForEvent(curName, listener) {
                    if (curName == name) 
                        listener(event);
                }
                plugin.on("newListener", listenForEvent);
                plugin.on("documentUnload", function callee(e) {
                    if (e.doc == event.doc) {
                        _self.off("newListener", listenForEvent);
                        _self.off("documentUnload", callee);
                    }
                });
            }
            
            function getState(doc, filter) {
                var state = {};
                
                emit("getState", {
                    doc: doc,
                    state: state,
                    filter: filter || false
                });
                
                return state;
            }
            
            function setState(doc, state) {
                emit("setState", {
                    doc: doc,
                    state: state || {}
                });
            }
            
            function clear(){
                emit("clear");
            }
            
            // Clipboard support
            function isClipboardAvailable(e) { 
                return true;
            }
            
            function cut(e) {
                emit("cut", e || { clipboardData: clipboard.clipboardData });
            }
            
            function copy(e) {
                emit("copy", e || { clipboardData: clipboard.clipboardData });
            }
            
            function paste(e) {
                emit("paste", e || { clipboardData: clipboard.clipboardData });
            }
            
            function focus(regain, lost) {
                if (!lost && amlTab) 
                    amlTab.focus(); //@todo this might break selenium editor
                
                emit("focus", {
                    regain: regain || false,
                    lost: lost || false
                });
                
                focussed = lost ? 0 : true;
            }
            
            function blur(){
                emit("blur");
                focussed = false;
            }
            
            function resize(e) {
                if (pane && pane.visible)
                    emit("resize", e || {});
            }
            
            function isValid(doc, info) {
                return emit("validate", {document: doc, info: info});
            }
            
            function attachTo(tb) {
                if (amlTab && !amlTab.$amlDestroyed)
                    throw new Error("Editors should only be attached once");

                //Create Tab Element
                plugin.addElement(
                    amlTab = new ui.page({
                        id: "editor::" + plugin.type,
                        mimeTypes: extensions,
                        visible: false,
                        realtime: false,
                        focussable: true,
                        $focussable: true
                    })
                );
                
                // Remember which pane we belong too
                pane = tb;
                
                // Set the reference to us on the tab element
                amlTab.editor = this;
                
                // Insert our tab into the pane element
                pane.aml.insertBefore(amlTab, pane.aml.getPage(0));
                
                // Emit draw event
                var event = { tab: amlTab, htmlNode: amlTab.$int };
                emit.sticky("draw", event);
                
                var editor = this;
                pane.on("unload", function(){
                    editor.unload();
                });
            }
            
            function drawOn(htmlNode, pg) {
                amlTab = pg;
                
                // Emit draw event
                var event = { htmlNode: htmlNode, tab: amlTab };
                emit.sticky("draw", event);
            }
            
            /***** Register and define API *****/
            
            // This is a base class
            plugin.baseclass();
            
            /**
             * Editor base class for Cloud9 Editors. Each file that is opened
             * in Cloud9 has an editor to display it's content. An editor can register 
             * itself for a set of file extensions. There are also editors that
             * don't display files but have a different purpose. For instance
             * the {@link terminal.Terminal Terminal} and 
             * {@link preferences.Preferences Preferences} panel. 
             * 
             * The editor relates to other objects as such:
             * 
             * * {@link Pane} - Represent a single pane, housing multiple tabs
             *   * {@link Tab} - A single tab (button) in a pane
             *     * **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
             * 
             * Tabs are managed by the {@link tabManager}. The default way to
             * open a new file in an editor uses the tabManager:
             * 
             *     tabManager.openFile("/file.js", true, function(err, tab) {
             *         var editor = tab.editor;
             *         editor.focus();
             *     });
             * 
             * The event flow of an editor plugin is as follows:
             * 
             * * {@link #event-documentLoad} - *A document is loaded in the editor*
             * * {@link #event-documentActivate} - *A becomes active in the editor*
             * * {@link #event-documentUnload} - *The document is unloaded in the editor*
             * 
             * This is in addition to the event flow of the {@link Plugin} base class.
             * 
             * #### User Actions:
             * 
             * * {@link #event-draw} - *The editor is drawn*
             * * {@link #event-focus} - *The editor receives focus*
             * * {@link #event-blur} - *The editor lost focus*
             * * {@link #event-clear} - *The editor's contents is cleared*
             * * {@link #event-cut} - *The editor's selection is copied and deleted*
             * * {@link #event-copy} - *The editor's selection is copied*
             * * {@link #event-paste} - *The editor's selection is deleted and new content is pasted in*
             * * {@link #event-resize} - *The editor is resized*
             * 
             * Implementing your own editor takes a new Editor() object rather
             * than a new Plugin() object. See the {@link texteditor.TextEditor TextEditor} for a
             * full implementation of an editor. Here's a short example:
             * 
             *     function TextEditor(){
             *         var plugin = new Editor("(Company) Name", main.consumes, ["txt"]);
             *         var emit = plugin.getEmitter();
             *         
             *         plugin.freezePublicAPI({
             *             example: function(){
             *      
             *             }
             *         });
             *         
             *         plugin.load(null, "texteditor");
             *         
             *         return plugin;
             *     }
             * 
             * @class Editor
             * @extends Plugin
             */
            /**
             * @constructor
             * Creates a new Editor 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 {String[]} extensions  A list of file extension that this
             * editor can handle.
             */
            plugin.freezePublicAPI({
                /**
                 * @ignore
                 */
                get aml(){ return amlTab; },
                
                /**
                 * The pane that this editor is a part of. Each editor is 
                 * initialized in the context of a single pane and the editor
                 * is destroyed when the pane is destroyed.
                 * @property {Pane} pane
                 * @readonly
                 */
                get pane(){ return pane; },
                
                /**
                 * The document that is displayed in this editor.
                 * @property {Document} activeDocument
                 * @readonly
                 */
                get activeDocument(){ return activeDocument; },
                
                /** 
                 * @property {String} type the unique identifier for this editor
                 * @readonly
                 */
                get type(){ return type; },
                set type(val) {
                    if (!type)
                        type = val;
                    else
                        throw new Error("Plugin Type Exception");
                },
                
                /**
                 * @property {Object} meta
                 */
                get meta(){ return meta; },
                
                /**
                 * @property {String[]} fileExtensions Array of file extensions supported by this editor
                 * @readonly
                 */
                get fileExtensions(){ return extensions; },
                
                _events: [
                    /** 
                     * Fires when a document is loaded into the editor.
                     * This event is also fired when this document is attached to another
                     * instance of the same editor (in a split view situation). Often you
                     * want to keep the session information partially in tact when this
                     * happens.
                     * @event documentLoad 
                     * @param {Object}   e
                     * @param {Document} e.doc    the document that is loaded into the editor
                     * @param {Object}   e.state  state that was saved in the document
                     */
                    "documentLoad",
                    /** 
                     * Fires when a document becomes the active document of an editor
                     * This event is called every time a tab becomes the active tab of
                     * a pane. Use it to show / hide whatever is necessary.
                     * 
                     * @event documentActivate
                     * @param {Object}   e
                     * @param {Document} e.doc  the document that is activate
                     */
                    "documentActivate",
                    /** 
                     * Fires when a document is unloaded from the editor.
                     * This event is also fired when this document is attached to another
                     * instance of the same editor (in a split view situation).
                     * @event documentUnload
                     * @param {Object}   e
                     * @param {Document} e.doc  the document that was loaded into the editor
                     */
                    "documentUnload",
                    /** 
                     * Fires when the state of the editor is retrieved
                     * @event getState
                     * @param {Object}   e
                     * @param {Document} e.doc    the document for which the state is retrieved
                     * @param {Object}   e.state  the state to add values to {See Editor#getState}
                     */
                    "getState",
                    /** 
                     * Fires when the state of the editor is set
                     * @event setState
                     * @param {Object}   e
                     * @param {Document} e.doc    the document for which the state is set
                     * @param {Object}   e.state  the state that is being set
                     */
                    "setState",
                    /** 
                     * Fires when the editor is cleared
                     * @event clear
                     */
                    "clear",
                    /** 
                     * Fires when the editor gets the focus. See also 
                     * {@link tabManager#focusTab}, {@link tabManager#focussedTab}
                     * @event focus
                     * @param {Object}  e
                     * @param {Boolean} e.regain whether the focus is regained. 
                     *   This means that the editor had lost the focus 
                     *   previously (the focus event with e.lost set to true 
                     *   was called.) and now the focus has been given back to 
                     *   the tabs.
                     * @param {Boolean} e.lost   whether the focus is lost, 
                     *   while the editor remains the focussed editor. This 
                     *   happens when an element outside of the editors 
                     *   (for instance the tree or a menu) gets the focus.
                     */
                    "focus",
                    /** 
                     * Fires when the editor looses focus.
                     * @event blur
                     */
                    "blur",
                    /**
                     * Fires when the cut command is dispatched to this editor,
                     * either using a keybinding or programmatically.
                     * 
                     *    plugin.on("cut", function(e) {
                     *        var data = this.yankDataFromSomewhere();
                     *        e.clipboardData.setData("text/plain", data);
                     *    });
                     * 
                     * @event cut
                     * @param {Object}                  e
                     * @param {clipboard.ClipboardData} e.clipboardData  the api to interact with the clipboard
                     * @param {Boolean}                 e.native         whether this is a native clipboard event
                     */
                    "cut",
                    /**
                     * Fires when the copy command is dispatched to this editor,
                     * either using a keybinding or programmatically.
                     * 
                     *    plugin.on("cut", function(e) {
                     *        var data = this.getDataFromSomewhere();
                     *        e.clipboardData.setData("text/plain", data);
                     *    });
                     * 
                     * @event copy
                     * @param {Object}                  e
                     * @param {clipboard.ClipboardData} e.clipboardData  the api to interact with the clipboard
                     * @param {Boolean}                 e.native         whether this is a native clipboard event
                     */
                    "copy",
                    /**
                     * Fires when the copy command is dispatched to this editor,
                     * either using a keybinding or programmatically.
                     * 
                     *    plugin.on("paste", function(e) {
                     *        e.clipboardData.getData("text/plain", function(err, data) {
                     *            // Process the data here
                     *        });
                     *    });
                     * 
                     * @event paste
                     * @param {Object}                  e
                     * @param {clipboard.ClipboardData} e.clipboardData  the api to interact with the clipboard
                     * @param {Boolean}                 e.native         whether this is a native clipboard event
                     */
                    "paste",
                    /**
                     * Fires when the editor resizes
                     * @event resize
                     * @param {Event} e  the DOMEvent related to the resize event.
                     */
                    "resize",
                    /**
                     * Fires when the editor is requested to draw itself. Cloud9
                     * is optimized by lazy loading everyhing. This means that
                     * the editor won't be loaded until absolutely necessary.
                     * This is usually the case when the tab is active for the 
                     * first time. 
                     * @event draw
                     * @param {Object}      e
                     * @param {HTMLElement} e.htmlNode  
                     * @param {Tab}         e.tab      
                     */
                    "draw",
                    /**
                     * Fires when a new document is going to be loaded in this
                     * editor. The purpose of this event is to allow a check
                     * prior to switching from one editor to the next for the
                     * same document.
                     * @event validate
                     * @param {Object}   e
                     * @param {Document} e.document  the document that will be loaded
                     * @param {Object}   e.info      extra information about the document
                     */
                    "validate",
                ],
                
                /**
                 * Unloads the document from this editor.
                 * @private
                 */
                unloadDocument: unloadDocument,
                
                /**
                 * Loads the document in this editor to be displayed.
                 * @param {Document} doc the document to display
                 */
                loadDocument: loadDocument,

                /**
                 * Retrieves the state of a document in relation to this editor
                 * @param {Document} doc the document for which to return the state
                 * @return {Object}
                 */
                getState: getState, 
                
                /**
                 * Sets the state of this editor (related to a document)
                 * @param {Document} doc the document for which to set the state
                 * @param {Object} state the state of the document for this editor
                 */
                setState: setState,
                
                /**
                 * Clears the value of the document in the editor
                 */
                clear: clear,
                
                /**
                 * Checks if clipboard action is available
                 * @param {Object} clipboard action type
                 */
                isClipboardAvailable: isClipboardAvailable,
                
                /**
                 * Cuts the current selection from the editor into the clipboard
                 */
                cut: cut,

                /**
                 * Copies the current selection from the editor into the clipboard
                 */
                copy: copy,

                /**
                 * Pastes the current clipboard buffer into this editor
                 */
                paste: paste,

                /**
                 * Sets the focus to this editor
                 */
                focus: focus,

                /**
                 * Removes the focus from this editor
                 */
                blur: blur,

                /**
                 * Resize the editor to fit it's container
                 */
                resize: resize,

                /**
                 * Checks whether the passed document would be valid in the context
                 * of this editor.
                 * @param {Document} doc  the document to check
                 * @param {Object} info object to put values on to display in a return alert (title, head, message)
                 */
                isValid: isValid,

                /**
                 * Attaches editor to pane element. This can be done only once
                 * @param pane {AmlNode.Tab} the pane element to add this editor to
                 */
                attachTo: attachTo,
                
                /**
                 * @private
                 */
                drawOn: drawOn
            });
            
            return plugin;
        }
        
        /***** Register and define API *****/
        
        register(null, {
            Editor: Editor
        })
    }
});
