/**
 * Terminal for the Cloud9
 *
 * @copyright 2013, Ajax.org B.V.
 */
define(function(require, exports, module) {

module.exports = function(c9, proc, installPath, shell) {
    function reconnectTimed(session, force) {
        if (session.killed) { // Session has been killed
            console.warn("Reconnecting while session has been killed");
            return;
        }
        
        setTimeout(function(){
            reconnect(session, force);
        }, session.reconnecting === 0 ? 0 : 
            (session.reconnecting < 10 ? 100 : 1000));
    }
    
    function reconnect(session, force) {
        // Make sure we have process control
        if (!c9.has(c9.PROCESS)) {
            console.warn("Reconnecting while not online");
            // We'll let the stateChange handler retry
            return;
        }
        
        if ((session.connecting || session.connected) && !force) {
            console.error("Reconnecting while already connected/connecting. Abort.");
            return false;
        }
        
        // A little counter for debugging purpose
        if (!session.reconnecting)
            session.reconnecting = 0;
        session.reconnecting++;
        
        // Make sure the pty is no longer active
        session.disregard && session.disregard(true);
        
        // Lets get our TMUX process
        getTMUX(session, function(err) {
            if (err) {
                console.error("Error creating TMUX session: ", err.message);
                
                if (err.code != "EACCESS")
                    reconnectTimed(session, true);
                else
                    console.warn("Terminal EACCESS error");
            }
        });
    }
    
    function getOutputHistory(options, cb) {
        options.id = options.id || this.id;
        options.retries = (options.retries || 0) + 1;
        var start = typeof options.start == "number" ? options.start : -32768;
        var end = typeof options.end == "number" ? options.end : 1000;
        var paneId = (options.id || this.id) + ":0.0"; // for now let's assume there is only one pane
        
        if (c9.platform == "win32") return;
        
        proc.tmux("", { 
            capturePane: {
                start: start,
                end: end,
                pane: paneId.trim(),
                joinLines: options.joinLines
            }
        }, function(err, _1, _2, meta) {
            if (err || !meta.process) return cb(err);
            var buffer = "", errBuffer = "";
            meta.process.stdout.on("data", function(data) {
                buffer += data.toString();
            });
            meta.process.stderr.on("data", function(data) {
                errBuffer += data.toString();
            });
            meta.process.on("close", function() {
                if (!buffer && !errBuffer && options.retries < 4) {
                    // tmux doesn't produce any output if two instances are invoked at the same time
                    return setTimeout(function() {
                        getOutputHistory(options, cb);
                    }, options.retries * 100 + 300);
                }
                if (buffer) {
                    var i = buffer.search(/\x1b\[1mPane is dead\x1b\[0m\s*$/);
                    if (i != -1)
                        buffer = buffer.slice(0, i).replace(/\s*$/, "\n");
                }
                cb(errBuffer, buffer);
            });
        });
    }
    
    function getStatus(options, cb) {
        if (options.id == null)
            options.id = this.id;
        proc.tmux("", {
            getStatus: options
        }, function(err, _1, _2, meta) {
            cb(err, meta.status);
        });
    }
    
    function sendTmuxCommand(command, exec, terminal) {
        if (c9.platform == "win32") return;
        if (!terminal || !terminal.send)
            terminal = this.terminal;
        terminal.send("\u0002:" + command + (!exec ? "\r\u001b" : exec == 1 ? "\r" : ""));
    }
    
    function clearHistory() {
        sendTmuxCommand("send-keys -R ; clear-history", 1, this.terminal);
    }
    
    function changeMode(){ return false; }
    
    function getTMUX(session, callback) {
        var disregarded;
        
        session.getOutputHistory = getOutputHistory;
        session.getStatus = getStatus;
        session.sendTmuxCommand = sendTmuxCommand;
        session.clearHistory = clearHistory;
        
        var cwd = session.cwd || "";
        if (!/^~(\/|$)/.test(cwd))
            cwd = session.root + cwd;
        var command = "";
        var options = {
            cwd: cwd,
            cols: session.cols || 80,
            rows: session.rows || 24,
            name: "xterm-color",
            base: installPath && installPath.replace(/^~/, c9.home || "~")
        };
        
        // Output Mode
        if (session.output) {
            options.idle = true;
            options.session = session.id;
            options.attach = true;
            options.output = true;
            options.detachOthers = !session.hasConnected;
        }
        // Terminal Mode
        else {
            command = shell || "";
            
            if (!session.id) {
                session.id = c9.workspaceId.split("/", 2).join("@") 
                    + "_" + Math.round(Math.random() * 1000);
                options.attach = false;
            }
            else {
                options.attach = true;
            }
            
            options.session = session.id;
            options.output = false;
            options.terminal = true;
            options.detachOthers = !session.hasConnected;
            options.defaultEditor = session.defaultEditor;
        }
        
        // Connect to backend and start tmux session
        session.connecting = true;
        session.connected = false;
        session.setState("connecting");
        
        session.reconnect = reconnect.bind(null, session);
        
        proc.tmux(command, options, function(err, pty) {
            if (session.pty && session.pty != pty) {
                console.warn("trying to set pty twice", session.pty);
                session.pty._events = {};
            }
            
            // Document was unloaded before connection was made
            if (!session.connecting) {
                if (!err && !session.connected) {
                    session.pty = pty;
                    session.kill();
                    console.error("Got into weird terminal state.");
                }
                return callback(new Error("Session was not connecting"));
            }
            
            // Handle a possible error
            if (err) {
                console.warn("Error connecting to the terminal: ", err);
                
                session.connecting = false;
                session.setState("error");
                
                return callback(err);
            }
            
            session.pty = pty;
            session.reconnecting = 0;
            
            session.disregard = function(keepId) {
                if (!keepId && !session.output)
                    delete session.id;
                
                console.warn("Disregard terminal: ", session.id);
                    
                disregarded = true;
                pty.kill();
                delete session.pty;
            };
            
            session.pty.on("exit", function(){
                if (!disregarded) {
                    session.connected = false;
                    session.connecting = false;
                    
                    session.terminal.off("changeMode", changeMode);
                    if (session.monitor.wasQuitSent()) {
                        session.tab.meta.$ignore = true;
                        session.tab.close();
                    }
                    else if (session.tab.isActive() && session.tab.pane.visible
                        && document.hasFocus()
                    ) {
                        reconnect(session);
                    }
                }
            });
            
            session.pty.on("data", function(data) {
                if (!disregarded) {
                    if (typeof data == "object") {
                        if (data.started)
                            return;
                        else if (data.code) {
                            if (data.type == "exception") { // Error
                                session.disregard();
                                console.error("Error creating TMUX session: ", err.message);
                                session.setState("error");
                            }
                            else
                                session.warn(data);
                            return;
                        }
                        else
                            return session.setSize(data);
                    }
                    
                    if (session.filter)
                        data = session.filter(data);
                    if (data)
                        session.write(data);
                }
            });
            
            // first message from tmux clears screen discarding 
            session.terminal.once("changeMode", changeMode);
            
            // Success
            session.connecting = false;
            session.connected = true;
            session.hasConnected = true;
            session.setState("connected");
            
            // Resize the terminal to the size of the container
            session.updatePtySize();
            
            callback();
            session.getEmitter()("connected");
        });
    }
    
    return {
        init: function(session, cb) {
            getTMUX(session, function(err) {
                if (err) {
                    // util.alert(
                    //     "Error opening Terminal",
                    //     "Error opening Terminal",
                    //     "Could not open terminal with the following reason:"
                    //         + err);
                    console.error("Error opening Terminal: " + err.message);
                    
                    session.setState("error");
                    
                    if (err.code != "EACCESS")
                        reconnectTimed(session, true);
                    else
                        console.warn("Terminal EACCESS error");
                }
                
                // Lets wait until we get process control back
                c9.on("stateChange", function(e) {
                    if (e.state & c9.PROCESS && !session.connected) {
                        console.warn("Terminal reconnect due to connection regain");
                        reconnect(session);
                    }
                }, session);
                
                cb && cb(err, session);
            });
        },
        kill: function(session) {
            console.warn("KILLING SESSION:", session.id);
            
            if (session.id) {
                proc.tmux("", {
                    session: session.id,
                    kill: true
                }, function(err) {
                    // Ignore errors for now
                    if (err)
                        return console.error(err);
                });
            }
            
            console.warn("Killed tmux session: ", session.id);
            
            // If we're still connecting disregard will not be set yet
            // The callback of the connection process will check 
            // the connecting property and terminate the connection
            // then.
            if (session && session.disregard)
                session.disregard();
            
            delete session.id;
            //doc.terminal.onResize(); ??
            
            session.killed = true;
            session.connecting = false;
            session.connected = false;
            session.setState("killed");
        }
    };
};
    
});