var net = require('net');
var EventEmitter = require('events').EventEmitter;
var sys = require('sys');
var ltx = require('ltx');
var StreamParser = require('./stream_parser');

var NS_XMPP_TLS = exports.NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls';
var NS_STREAM = exports.NS_STREAM = 'http://etherx.jabber.org/streams';
var NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams';

/** A note on events: this base class will emit 'rawStanza' and leaves
    'stanza' to Client & Component. Therefore we won't confuse the
    user with stanzas before authentication has finished.
*/
function Connection(socket) {
    var self = this;
    EventEmitter.call(this);

    this.charset = 'UTF-8';
    this.xmlns = { stream: NS_STREAM };

    this.socket = socket || new net.Stream();
    this.socket.addListener('data', function(data) {
        self.onData(data);
    });
    this.socket.addListener('end', function() {
        self.onEnd();
    });
    var proxyEvent = function(event) {
        self.socket.addListener(event, function() {
            self.emit.apply(self, Array.prototype.splice.call(arguments, 0, 0, event));
        });
    };
    proxyEvent('data');  // let them sniff
    proxyEvent('drain');
    proxyEvent('close');
}

sys.inherits(Connection, EventEmitter);
exports.Connection = Connection;

// Defaults
Connection.prototype.charset = 'UTF-8';
Connection.prototype.allowTLS = true;


/** Climbs the stanza up if a child was passed,
    but you can send strings and buffers too.
*/
Connection.prototype.send = function(stanza) {
    if (!this.socket.writable) {
        this.socket.end();
        return;
    }

    if (stanza.root) {
        var el = this.rmStreamNs(stanza.root());
        var socket = this.socket;
        el.write(function(s) { socket.write(s); });
        return el;
    } else {
        this.socket.write(stanza);
        return stanza;
    }
};

Connection.prototype.startParser = function() {
    var self = this;
    this.parser = new StreamParser.StreamParser(this.charset, this.maxStanzaSize);

    this.parser.addListener('start', function(attrs) {
        self.streamAttrs = attrs;
        /* We need those xmlns often, store them extra */
        self.streamNsAttrs = {};
        for(var k in attrs) {
        if (k == 'xmlns' ||
            k.substr(0, 6) == 'xmlns:')
                self.streamNsAttrs[k] = attrs[k];
        }

        /* Notify in case we don't wait for <stream:features/>
           (Component or non-1.0 streams)
         */
        self.emit('streamStart', attrs);
    });
    this.parser.addListener('stanza', function(stanza) {
        self.onStanza(self.addStreamNs(stanza));
    });
    this.parser.addListener('error', function(e) {
        self.error(e.condition || 'internal-server-error', e.message);
    });
    this.parser.addListener('end', function() {
        self.stopParser();
        self.end();
    });
};

Connection.prototype.stopParser = function() {
    delete this.parser;
};

Connection.prototype.startStream = function() {
    var attrs = {};
    for(var k in this.xmlns) {
        if (this.xmlns.hasOwnProperty(k)) {
            if (!k)
                attrs.xmlns = this.xmlns[k];
            else
                attrs['xmlns:' + k] = this.xmlns[k];
        }
    }
    if (this.xmppVersion)
        attrs.version = this.xmppVersion;
    if (this.streamTo)
        attrs.to = this.streamTo;
    if (this.streamId)
        attrs.id = this.streamId;

    var el = new ltx.Element('stream:stream', attrs);
    // make it non-empty to cut the closing tag
    el.t(' ');
    var s = el.toString();
    this.send(s.substr(0, s.indexOf(' </stream:stream>')));

    this.streamOpened = true;
};

Connection.prototype.onData = function(data) {
    if (this.parser)
        this.parser.write(data);
};

Connection.prototype.setSecure = function(credentials) {
    var self = this;
    this.stopParser();
    this.socket.setSecure(credentials || this.credentials);
    this.socket.addListener('secure', function() {
        self.startParser();
    });
};

/**
 * This is not an event listener, but takes care of the TLS handshake
 * before 'rawStanza' events are emitted to the derived classes.
 */
Connection.prototype.onStanza = function(stanza) {
    if (stanza.is('error', NS_STREAM)) {
        /* TODO: extract error text */
        this.emit('error', stanza);
    } else if (stanza.is('features', NS_STREAM) &&
               this.allowTLS &&
               stanza.getChild('starttls', NS_XMPP_TLS)) {
        /* Signal willingness to perform TLS handshake */
        this.send(new ltx.Element('starttls', { xmlns: NS_XMPP_TLS }));
    } else if (this.allowTLS &&
               stanza.is('proceed', NS_XMPP_TLS)) {
        /* Server is waiting for TLS handshake */
        this.setSecure();
    } else {
        this.emit('rawStanza', stanza);
    }
};

/**
 *  Add stream xmlns to a stanza, so the user can check for
 * 'jabber:client' etc.
 */
Connection.prototype.addStreamNs = function(stanza) {
    for(var k in this.streamNsAttrs) {
        if (!stanza.attrs[k])
            stanza.attrs[k] = this.streamNsAttrs[k];
    }
    return stanza;
};

/**
 * Remove superfluous xmlns that were aleady declared in
 * our <stream:stream>
 */
Connection.prototype.rmStreamNs = function(stanza) {
    for(var k in this.xmlns) {
        var attr = k ? 'xmlns:' + k : 'xmlns';
        if (stanza.attrs[attr] == this.xmlns[k])
            delete stanza.attrs[attr];
    }
    return stanza;
};


/**
 * Connection has been ended by remote, we will not get any incoming
 * 'data' events. Alternatively, used for 'error' event.
 */
Connection.prototype.onEnd = function() {
    this.stopParser();
    this.socket.end();
};

/**
 * XMPP-style end connection for user
 */
Connection.prototype.end = function() {
    if (this.socket.writable) {
        if (this.streamOpened) {
            this.socket.write('</stream:stream>');
            delete this.streamOpened;
        } else {
            this.socket.end();
        }
    }
};

/**
 * End connection with stream error.
 * Emits 'error' event too.
 *
 * @param {String} condition XMPP error condition, see RFC3920 4.7.3. Defined Conditions
 * @param {String} text Optional error message
 */
Connection.prototype.error = function(condition, message) {
    this.emit('error', new Error(message));

    if (!this.socket.writable)
        return;

    var e = new ltx.Element('stream:error');
    e.c(condition, { xmlns: NS_XMPP_STREAMS });
    if (message)
        e.c('text', { xmlns: NS_XMPP_STREAMS,
                      'xml:lang': 'en' }).
        t(message);

    this.send(e);
    this.end();
};

@@ -1035,41 +1035,23 @@
 ) %7B%0A
-            self.emit.apply(self,
+%09    var args =
  Arr
@@ -1064,17 +1064,16 @@
 totype.s
-p
 lice.cal
@@ -1087,22 +1087,68 @@
 ents
-, 0, 0, event)
+);%0A%09    args.unshift(event);%0A%09    self.emit.apply(self, args
 );%0A 

fix events proxying