var xmpp = require('node-xmpp');
var uuid = require('node-uuid');
var util = require('util');
var assert = require('assert');
var EventEmitter = require('events').EventEmitter;
var NullStream = require('./nullstream');

function Client(options) {
  options = options || {}
  // WORKAROUND: Disable socket streams in underlying node-xmpp, useful in cases
  //             where one is not necessary.
  this._disableStream = options.disableStream || false;
  
  xmpp.Client.call(this, options);
  this._stack = [];
  this._filters = [];
  this.addListener('stanza', this.handle);
}

util.inherits(Client, xmpp.Client);

Client.prototype.use = function(ns, handle) {
  // TODO: Allow ns to be set as an XPath expression, in order to limit the
  //       scope of the handler.
  // TODO: Alternatively, let the qualifier be the JID to which the stanza is
  //       addressed.  This would be beneficial for components that handle a
  //       domain, but want to be addressed using node@domain.  I'm leaning in
  //       favor of this approach, rather than the XPath one.
  if ('string' != typeof ns) {
    handle = ns;
    ns = null;
  }
    
  this._stack.push({ ns: ns, handle: handle });
  return this;
}

Client.prototype.filter = function(handle) {
  this._filters.push({ handle: handle });
  return this;
}

Client.prototype.handle = function(stanza) {
  var self = this;
  var stack = self._stack;
  var idx = 0;
  var res = self._prepResponse(stanza);
  
  // stanza is an instance of ltx.Element.  As such, it has the following
  // properties: name, parent, attrs, children.  Additional properties can
  // be assigned, as the stanza is parsed, including the inital set attached
  // here.
  stanza.connection = this;
  stanza.id = stanza.attrs.id;
  stanza.from = stanza.attrs.from;
  stanza.to = stanza.attrs.to;
  stanza.type = stanza.attrs.type;
  
  function next(err) {
    var layer = stack[idx++];
    
    // all done
    if (!layer) { return; }
    
    try {
      var arity = layer.handle.length;
      if (err) {
        if (arity == 4) {
          layer.handle(err, stanza, res, next);
        } else {
          next(err);
        }
      } else {
        if (arity == 2) {
          layer.handle(stanza, next);
        } else if (arity == 3 && res) {
          layer.handle(stanza, res, next);
        } else {
          next();
        }
      }
    } catch (e) {
      if (e instanceof assert.AssertionError) {
        console.error(e.stack + '\n');
        next(e);
      } else {
        next(e);
      }
    }
  }
  next();
}

Client.prototype.send = function(stanza) {
  if (stanza.toXML) {
    stanza = stanza.toXML();
  }
  
  if (stanza.root) {
      console.log('SEND: ' + stanza.root().toString() + '\n');
  } else {
      console.log('SEND: ' + stanza + '\n');
  }
  
  var filters = this._filters;
  var idx = 0;
  function next() {
    var filter = filters[idx++];
    
    // all done
    if (!filter) { return; }
    
    // TODO: Implement error handling
    try {
      filter.handle(stanza, next);
    } catch (e) {
      if (e instanceof assert.AssertionError) {
        console.error(e.stack + '\n');
        next();
      } else {
        console.error(e.stack + '\n');
        next();
      }
    }
  }
  if (stanza instanceof xmpp.Element) { next(); }
  
  xmpp.Client.prototype.send.call(this, stanza);
}

Client.prototype.setupStream = function() {
  if (this._disableStream) {
    this.socket = new NullStream();
    return;
  }
  xmpp.Client.prototype.setupStream.call(this);
}

Client.prototype.generateID = function() {
  return uuid();
}

Client.prototype._prepResponse = function(stanza) {
  var self = this;
  
  var res;
  
  if (stanza.is('iq') && (stanza.attrs.type == 'get' || stanza.attrs.type == 'set')) {
    // @todo: When connected as a component, the from field needs to be set
    //        eplicitly (response.attrs.from = stanza.attrs.to).
    res = new xmpp.Element('iq', { id: stanza.attrs.id,
                                   to: stanza.attrs.from,
                                   type: 'result' })
  }
  
  if (res) {
    // Hook for for sending the response.
    res.send = function() {
      self.send(this);
    }
  }
  return res;
}


module.exports = Client;

@@ -1127,24 +1127,359 @@
 null;%0A  %7D%0A  
+%0A  // wrap sub-apps%0A  // TODO: Implement test cases for this functionality%0A  if ('function' == typeof handle.handle) %7B%0A    var connection = handle;%0A    connection.send = this.send.bind(this);%0A    connection.generateID = this.generateID.bind(this);%0A    handle = function(stanza, next)%7B%0A      connection.handle(stanza, next);%0A    %7D;%0A  %7D%0A
   %0A  this._s
@@ -1673,32 +1673,37 @@
  function(stanza
+, out
 ) %7B%0A  var self =
@@ -2281,32 +2281,110 @@
 f (!layer) %7B
+%0A      // but wait! we have a parent%0A      if (out) %7B return out(err); %7D%0A     
  return;
  %7D%0A    %0A    
@@ -2363,32 +2363,36 @@
  %7D%0A      return;
+%0A   
  %7D%0A    %0A    try 

Allow for wrapping sub-apps.