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

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var commands = imports.commands;
        
        var Aceterm = require("../c9.ide.terminal/aceterm/aceterm");
        
        /***** Constructors *****/
        
        /*
            plugin.on("copy", function(e) {
                if (e.native) return; // Ace handles this herself
                
                var data = aceterm.getCopyText();
                e.clipboardData.setData("text/plain", data);
            });
            plugin.on("paste", function(e) {
                if (e.native) return; // Ace handles this herself
                
                var data = e.clipboardData.getData("text/plain");
                if (data !== false)
                    aceterm.onPaste(data);
            });
        */
        
        function Terminal(options, forPlugin, baseclass) {
            if (!options) throw new Error("options are required");
            
            var plugin = new Plugin("Ajax.org", main.consumes);
            var emit = plugin.getEmitter();
            if (baseclass) plugin.baseclass();
            
            var rows = options.rows || 0;
            var cols = options.cols || 0;
            
            var aceterm, terminal;
            var redirectEvents;
            var meta = {};
            
            var excludedEvents = { 
                "draw": 1, "load":1, "unload":1, "resize":1,
                "addListener":1, "removeListener":1, "input":1
            };
            var renameEvents = {
                "select": "changeSelection",
                "scroll": "changeScrollTop"
            };
            
            /***** Methods *****/
            
            function send(data) {
                emit("input", { data: data });
            }
            
            function _resize(force){
                var ace = terminal.aceSession.ace;
                
                var size = ace.renderer.$size;
                var config = ace.renderer.layerConfig;
                
                var h = size.scrollerHeight;
                var w = size.scrollerWidth - 2 * config.padding;
                
                if (!h || config.lineHeight <= 1)
                    return false;

                // top 1px is for cursor outline
                var _rows = Math.floor((h - 1) / config.lineHeight);
                if (_rows <= 2 && !ace.renderer.scrollBarV.isVisible)
                    w -= ace.renderer.scrollBarV.width;
                var _cols = Math.floor(w / config.characterWidth);
                
                if (!_cols || !_rows)
                    return;

                // Don't do anything if the size remains the same
                if (!force && _cols == terminal.cols && _rows == terminal.rows)
                    return;
                    
                if (_cols > 1000 || _rows > 1000) {
                    console.error("invalid terminal size");
                    return;
                }
                
                // do not resize terminal to very small heights during initialization
                rows = Math.max(_rows, 2);
                cols = Math.max(_cols, 2);
                    
                terminal.resize(_cols, _rows);

                emit("resize", { cols: cols, rows: rows });
            }
            
            /***** Life Cycle *****/
            
            var drawn = false;
            function draw(htmlNode) {
                if (drawn) return;
                drawn = true;
                
                aceterm = Aceterm.createEditor(htmlNode, "ace/theme/idle_fingers");
                
                terminal = new Aceterm(cols, rows, send);
                aceterm.setSession(terminal.aceSession);
                
                aceterm.renderer.on("resize", function(){
                    _resize();
                });
                
                var cm = commands;
                // TODO find better way for terminal and ace commands to coexist
                aceterm.commands.addCommands(cm.getExceptionList());
                cm.on("update", function() {
                    aceterm.commands.addCommands(cm.getExceptionList());
                }, plugin);
                
                aceterm.commands.exec = function(command) {
                    return cm.exec(command);
                };
                
                // Set Default Theme
                // if (!options.theme)
                //     options.theme = "custom-tree ace-tree-" + (options.baseName || "list");
                
                // Set properties
                for (var prop in options) {
                    if (prop == "container") continue;
                    if (plugin.hasOwnProperty(prop)) 
                        plugin[prop] = options[prop];
                }
                
                // Configure redirected events
                redirectEvents = {
                    afterRender: aceterm.renderer,
                    title: terminal,
                    afterWrite: terminal
                };
                
                emit.sticky("draw");
            }
            
            plugin.on("load", function(){
                if (options.container)
                    plugin.attachTo(options.container);
                
                forPlugin.once("unload", function(){
                    plugin.unload();
                });
            });
            plugin.on("unload", function(){
                if (aceterm) {
                    var container = aceterm.container;
                    aceterm.destroy();
                    
                    container.innerHTML = "";
                    container.parentNode.removeChild(container);
                }
                
                aceterm = null;
                container = null;
                meta = {};
            });
            plugin.on("newListener", function(type, fn){
                if (excludedEvents[type]) return;
                
                if (renameEvents[type])
                    type = renameEvents[type];
                
                if (redirectEvents[type])
                    redirectEvents[type].on(type, fn);
                else
                    aceterm.on(type, fn);
            });
            plugin.on("removeListener", function(type, fn){
                if (excludedEvents[type]) return;
                
                if (renameEvents[type])
                    type = renameEvents[type];
                
                if (redirectEvents[type])
                    redirectEvents[type].removeListener(type, fn);
                else
                    aceterm.removeListener(type, fn);
            });
            
            /**
             * @constructor
             * Creates a new List instance.
             * @param {Object} options
             * @param {Plugin} plugin         The plugin responsible for creating this list.
             */
            plugin.freezePublicAPI({
                // Getter Properties
                /**
                 * @ignore
                 * @readonly
                 */
                get aceterm(){ return aceterm; },
                /**
                 * A meta data object that allows you to store whatever you want
                 * in relation to this menu.
                 * @property {Object} meta
                 * @readonly
                 */
                get meta(){ return meta; },
                // /**
                //  * 
                //  */
                // get scrollTop(){ return model.getScrollTop(); },
                // set scrollTop(value){ return model.setScrollTop(value); },
                /**
                 * 
                 */
                get focussed(){ return aceterm.isFocussed(); },
                /**
                 * 
                 */
                get container(){ return aceterm.container; },
                /**
                 * 
                 */
                get renderer(){ return aceterm.renderer; },
                /**
                 * 
                 */
                get selection(){ return aceterm.selection; },
                /**
                 * 
                 */
                get commands(){ return aceterm.commands; },
                /**
                 * 
                 */
                get cols(){ return cols; },
                /**
                 * 
                 */
                get rows(){ return rows; },
                
                // Getters and Setters for Properties
                /**
                 * 
                 */
                get textInput(){ return aceterm.textInput; },
                set textInput(value){ return aceterm.textInput = value; },
                /**
                 *
                 */
                get scrollMargin(){ return aceterm.renderer.scrollMargin; },
                set scrollMargin(value){ 
                    aceterm.renderer.setScrollMargin(value[0], value[1]); 
                },
                /**
                 * 
                 */
                get theme(){ return aceterm.renderer.theme.cssClass; },
                set theme(value){ aceterm.renderer.setTheme({cssClass: value}); },
                /**
                 * 
                 */
                get convertEol(){ return terminal.convertEol || false; },
                set convertEol(value){ terminal.convertEol = value; },
                
                 // Events
                _events: [
                    /**
                     * @event draw Fires 
                     */
                    "draw",
                    /**
                     * @event click Fires 
                     */
                    "click",
                    /**
                     * @event userSelect Fires 
                     */
                    "userSelect",
                    /**
                     * @event focus Fires 
                     */
                    "focus",
                    /**
                     * @event blur Fires 
                     */
                    "blur",
                    /**
                     * @event select Fires 
                     */
                    "select",
                    /**
                     * @event scroll Fires 
                     */
                    "scroll",
                    /**
                     * @event scrollbarVisibilityChanged Fires 
                     */
                    "scrollbarVisibilityChanged",
                    /**
                     * @event resize Fires 
                     */
                    "resize",
                    /**
                     * @event afterRender Fires
                     */
                    "afterRender",
                    /**
                     * @event title Fires
                     */
                    "title",
                    /**
                     * @event afterWrite Fires
                     */
                    "afterWrite"
                ],
                
                /**
                 * 
                 */
                resize: function(){
                    return aceterm.resize(true);
                },
                /**
                 * 
                 */
                focus: function(){
                    return aceterm.focus();
                },
                /**
                 * 
                 */
                blur: function(){
                    return aceterm.blur();
                },
                /**
                 * 
                 */
                execCommand: function(cmd){
                    return aceterm.execCommand(cmd);
                },
                /**
                 * 
                 */
                scrollIntoView: function(anchor, lead, offset){ 
                    return aceterm.renderer.scrollCaretIntoView(anchor, lead, offset);
                },
                /**
                 * 
                 */
                enable: function(){
                    return aceterm.enable();
                },
                /**
                 * 
                 */
                disable: function(){
                    return aceterm.enable();
                },
                /**
                 * 
                 */
                write: function(data){
                    return terminal.write(data);
                },
                /**
                 * 
                 */
                clear: function(data){
                    terminal.clear();
                },
                /**
                 * 
                 */
                attachTo: function(htmlNode, beforeNode){
                    var container;
                    if (drawn)
                        container = aceterm.container;
                    else {
                        container = document.createElement("div");
                        container.style.height = "100%";
                    }
                    
                    htmlNode.insertBefore(container, beforeNode);
                        
                    if (!drawn)
                        draw(container);
                }
            });
            
            plugin.load(null, options.baseName || "list");
            
            return plugin;
        }
        
        register(null, {
            Terminal: Terminal
        });
    }
});