text: /*!
   * Expose `RemoteObjects`.
   */
  
  module.exports = RemoteObjects;
  
  /*!
   * Module dependencies.
   */
  
  var EventEmitter = require('eventemitter2').EventEmitter2;
  var debug = require('debug')('strong-remoting:remotes');
  var util = require('util');
  var urlUtil = require('url');
  var inherits = util.inherits;
  var assert = require('assert');
  var Dynamic = require('./dynamic');
  var SharedClass = require('./shared-class');
  var ExportsHelper = require('./exports-helper');
  
  // require the rest adapter for browserification
  // TODO(ritch) remove this somehow...?
  require('./rest-adapter');
  
  /**
   * Create a new `RemoteObjects` with the given `options`.
   *
   * ```js
   * var remoteObjects = require('strong-remoting').create();
   * ```
   *
   * @param {Object} options
   * @return {RemoteObjects}
   * @class
   * @property {Object} auth Authentication options used by underlying adapters
   * to set authorization metadata. **The `rest` adapter supports:**
   *
   *  - **basic** - `username` and `password` are required.
   *  - **digest** - `username` and `password` required and `sendImmediately` must
   * be false.
   *  - **bearer** - `bearer` must be set as the bearer token
   *
   * @property {String} auth.username
   * @property {String} auth.password
   * @property {String} auth.bearer The **bearer token**.
   * @property {Boolean} auth.sendImmediately Defaults to `false`.
   */
  
  function RemoteObjects(options) {
    EventEmitter.call(this, {wildcard: true});
    // Avoid warning: possible EventEmitter memory leak detected
    this.setMaxListeners(16);
    this.options = options || {};
    this.exports = this.options.exports || {};
    this._classes = {};
  }
  
  /*!
   * Inherit from `EventEmitter`.
   */
  
  inherits(RemoteObjects, EventEmitter);
  
  /*!
   * Simplified APIs
   */
  
  RemoteObjects.create = function(options) {
    return new RemoteObjects(options);
  };
  
  RemoteObjects.extend = function(exports) {
    return new ExportsHelper(exports);
  };
  
  /**
   * Create a handler from the given adapter.
   *
   * @param {String} name Adapter name
   * @param {Object} options Adapter options
   * @return {Function}
   */
  
  RemoteObjects.prototype.handler = function(name, options) {
    var Adapter = this.adapter(name);
    var adapter = new Adapter(this, options);
    var handler = adapter.createHandler();
  
    if (handler) {
      // allow adapter reference from handler
      handler.adapter = adapter;
    }
  
    return handler;
  };
  
  /**
   * Create a connection to a remoting server.
   *
   * @param {String} url Server root
   * @param {String} name Name of the adapter (eg. "rest")
   */
  
  RemoteObjects.prototype.connect = function(url, name) {
    // parse URL for auth
    var urlWithoutAuth = url;
    var auth;
  
    var parsedUrl = urlUtil.parse(url);
    // If base parsedUrl contains auth, extract it so we can set it separately
    if (parsedUrl.auth) {
      auth = this.auth = {};
      auth.username = parsedUrl.auth.split(':')[0];
      auth.password = parsedUrl.auth.split(':')[1];
      // set base without auth so request honours our auth options
      delete parsedUrl.auth;
      urlWithoutAuth = urlUtil.format(parsedUrl);
      // ensure a "/" hasn't been appended where there wasn't one before
      if (url[url.length - 1] !== urlWithoutAuth[urlWithoutAuth.length - 1]) {
        urlWithoutAuth = urlWithoutAuth.slice(0, -1);
      }
    }
  
    var Adapter = this.adapter(name);
    var adapter = new Adapter(this);
    this.serverAdapter = adapter;
    return adapter.connect(urlWithoutAuth);
  };
  
  /**
   * Invoke a method on a remote server using the connected adapter.
   *
   * @param {String} method The remote method string
   * @param {String} [ctorArgs] Constructor arguments (for prototype methods)
   * @param {String} [args] Method arguments
   * @callback {Function} [callback] callback
   * @param {Error} err
   * @param {Any} arg...
   * @end
   */
  
  RemoteObjects.prototype.invoke = function(method, ctorArgs, args, callback) {
    assert(this.serverAdapter,
      'Cannot invoke method without an adapter. See RemoteObjects#connect().');
    return this.serverAdapter.invoke.apply(this.serverAdapter, arguments, callback);
  };
  
  /**
   * Get an adapter by name.
   * @param {String} name The adapter name
   * @return {Adapter}
   */
  
  RemoteObjects.prototype.adapter = function(name) {
    return require('./' + name + '-adapter');
  };
  
  /**
   * Get all classes.
   */
  
  RemoteObjects.prototype.classes = function(options) {
    options = options || {};
    var exports = this.exports;
    var result = [];
    var sharedClasses = this._classes;
  
    Object
      .keys(exports)
      .forEach(function(name) {
        result.push(new SharedClass(name, exports[name], options));
      });
  
    Object
      .keys(sharedClasses)
      .forEach(function(name) {
        result.push(sharedClasses[name]);
      });
  
    return result;
  };
  
  /**
   * Add a shared class.
   *
   * @param {SharedClass} sharedClass
   */
  
  RemoteObjects.prototype.addClass = function(sharedClass) {
    assert(sharedClass instanceof SharedClass);
    this._classes[sharedClass.name] = sharedClass;
  };
  
  /**
   * Find a method by its string name.
   *
   * Example Method Strings:
   *
   *  - `MyClass.prototype.myMethod`
   *  - `MyClass.staticMethod`
   *  - `obj.method`
   *
   * @param {String} methodString
   */
  
  RemoteObjects.prototype.findMethod = function(methodString) {
    var methods = this.methods();
  
    for (var i = 0; i < methods.length; i++) {
      if (methods[i].stringName === methodString) return methods[i];
    }
  };
  
  /**
   * List all methods.
   */
  
  RemoteObjects.prototype.methods = function() {
    var methods = [];
  
    this
      .classes()
      .forEach(function(sc) {
        methods = sc.methods().concat(methods);
      });
  
    return methods;
  };
  
  /**
   * Get as JSON.
   */
  
  RemoteObjects.prototype.toJSON = function() {
    var result = {};
    var methods = this.methods();
  
    methods.forEach(function(sharedMethod) {
      result[sharedMethod.stringName] = {
        http: sharedMethod.fn && sharedMethod.fn.http,
        accepts: sharedMethod.accepts,
        returns: sharedMethod.returns,
        errors: sharedMethod.errors
      };
    });
  
    return result;
  };
  
  /**
   * Execute the given function before the matched method string.
   *
   * **Examples:**
   *
   * ```js
   * // Do something before our `user.greet` example, earlier.
   * remotes.before('user.greet', function(ctx, next) {
   *   if ((ctx.req.param('password') || '').toString() !== '1234') {
   *     next(new Error('Bad password!'));
   *   } else {
   *     next();
   *   }
   * });
   *
   * // Do something before any `user` method.
   * remotes.before('user.*', function(ctx, next) {
   *   console.log('Calling a user method.');
   *   next();
   * });
   *
   * // Do something before a `dog` instance method.
   * remotes.before('dog.prototype.*', function(ctx, next) {
   *   var dog = this;
   *   console.log('Calling a method on "%s".', dog.name);
   *   next();
   * });
   * ```
   *
   * @param {String} methodMatch The glob to match a method string
   * @callback {Function} hook
   * @param {Context} ctx The adapter specific context
   * @param {Function} next Call with an optional error object
   * @param {SharedMethod} method The SharedMethod object
   */
  
  RemoteObjects.prototype.before = function(methodMatch, fn) {
    this.on('before.' + methodMatch, fn);
  };
  
  /**
   * Execute the given `hook` function after the matched method string.
   *
   * **Examples:**
   *
   * ```js
   * // Do something after the `speak` instance method.
   * // NOTE: you cannot cancel a method after it has been called.
   * remotes.after('dog.prototype.speak', function(ctx, next) {
   *   console.log('After speak!');
   *   next();
   * });
   *
   * // Do something before all methods.
   * remotes.before('**', function(ctx, next, method) {
   *   console.log('Calling:', method.name);
   *   next();
   * });
   *
   * // Modify all returned values named `result`.
   * remotes.after('**', function(ctx, next) {
   *   ctx.result += '!!!';
   *   next();
   * });
   * ```
   *
   * @param {String} methodMatch The glob to match a method string
   * @callback {Function} hook
   * @param {Context} ctx The adapter specific context
   * @param {Function} next Call with an optional error object
   * @param {SharedMethod} method The SharedMethod object
   */
  
  RemoteObjects.prototype.after = function(methodMatch, fn) {
    this.on('after.' + methodMatch, fn);
  };
  
  /*!
   * Create a middleware style emit that supports wildcards.
   */
  
  RemoteObjects.prototype.execHooks = function(when, method, scope, ctx, next) {
    var stack = [];
    var ee = this;
    var isStatic = method.isStatic ||
      method.sharedMethod && method.sharedMethod.isStatic;
    var type;
  
    // Commented-out by bajtos: init is not defined.
    // this._events || init.call(this);
  
    var handler;
  
    // context
    this.objectName = method.sharedClass && method.sharedClass.name ||
      method.restClass && method.restClass.name;
  
    this.methodName = method.name;
  
    if (method.fullName) {
      type = when + '.' + method.fullName;
    } else {
      type = when + '.' + this.objectName +
        (isStatic ? '.' : '.prototype.') + this.methodName;
    }
  
    if (this.wildcard) {
      handler = [];
      var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice();
      searchListenerTree.call(this, handler, ns, this.listenerTree, 0);
    } else {
      handler = this._events[type];
    }
  
    if (typeof handler === 'function') {
      this.event = type;
  
      addToStack(handler);
  
      return execStack();
    } else if (handler) {
      var l = arguments.length;
      var i;
      var args = new Array(l - 1);
      for (i = 1; i < l; i++) {
        args[i - 1] = arguments[i];
      }
  
      var listeners = handler.slice();
      for (i = 0, l = listeners.length; i < l; i++) {
        addToStack(listeners[i]);
      }
    }
  
    function addToStack(fn) {
      stack.push(fn);
    }
  
    function execStack(err) {
      if (err) return next(err);
  
      var cur = stack.shift();
  
      if (cur) {
        try {
          var result = cur.call(scope, ctx, execStack, method);
          if (result && typeof result.then === 'function') {
            result.then(function() { next(); }, next);
          }
        } catch (err) {
          next(err);
        }
      } else {
        next();
      }
    }
  
    return execStack();
  };
  
  // from EventEmitter2
  function searchListenerTree(handlers, type, tree, i) {
    if (!tree) {
      return [];
    }
  
    var listeners = [];
    var leaf;
    var len;
    var branch;
    var xTree;
    var xxTree;
    var isolatedBranch;
    var endReached;
    var typeLength = type.length;
    var currentType = type[i];
    var nextType = type[i + 1];
  
    if (i === typeLength && tree._listeners) {
      //
      // If at the end of the event(s) list and the tree has listeners
      // invoke those listeners.
      //
      if (typeof tree._listeners === 'function') {
        if (handlers) handlers.push(tree._listeners);
        return [tree];
      } else {
        for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) {
          if (handlers) handlers.push(tree._listeners[leaf]);
        }
        return [tree];
      }
    }
  
    if ((currentType === '*' || currentType === '**') || tree[currentType]) {
      //
      // If the event emitted is '*' at this part
      // or there is a concrete match at this patch
      //
      if (currentType === '*') {
        for (branch in tree) {
          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
            listeners = listeners.concat(
              searchListenerTree(handlers, type, tree[branch], i + 1));
          }
        }
        return listeners;
      } else if (currentType === '**') {
        endReached = (i + 1 === typeLength || (i + 2 === typeLength && nextType === '*'));
        if (endReached && tree._listeners) {
          // The next element has a _listeners, add it to the handlers.
          listeners = listeners.concat(
            searchListenerTree(handlers, type, tree, typeLength));
        }
  
        for (branch in tree) {
          if (branch !== '_listeners' && tree.hasOwnProperty(branch)) {
            if (branch === '*' || branch === '**') {
              if (tree[branch]._listeners && !endReached) {
                listeners = listeners.concat(
                  searchListenerTree(handlers, type, tree[branch], typeLength));
              }
              listeners = listeners.concat(
                searchListenerTree(handlers, type, tree[branch], i));
            } else if (branch === nextType) {
              listeners = listeners.concat(
                searchListenerTree(handlers, type, tree[branch], i + 2));
            } else {
              // No match on this one, shift into the tree but not in the type array.
              listeners = listeners.concat(
                searchListenerTree(handlers, type, tree[branch], i));
            }
          }
        }
        return listeners;
      }
  
      listeners = listeners.concat(
        searchListenerTree(handlers, type, tree[currentType], i + 1));
    }
  
    xTree = tree['*'];
    if (xTree) {
      //
      // If the listener tree will allow any match for this part,
      // then recursively explore all branches of the tree
      //
      searchListenerTree(handlers, type, xTree, i + 1);
    }
  
    xxTree = tree['**'];
    if (xxTree) {
      if (i < typeLength) {
        if (xxTree._listeners) {
          // If we have a listener on a '**', it will catch all, so add its handler.
          searchListenerTree(handlers, type, xxTree, typeLength);
        }
  
        // Build arrays of matching next branches and others.
        for (branch in xxTree) {
          if (branch !== '_listeners' && xxTree.hasOwnProperty(branch)) {
            if (branch === nextType) {
              // We know the next element will match, so jump twice.
              searchListenerTree(handlers, type, xxTree[branch], i + 2);
            } else if (branch === currentType) {
              // Current node matches, move into the tree.
              searchListenerTree(handlers, type, xxTree[branch], i + 1);
            } else {
              isolatedBranch = {};
              isolatedBranch[branch] = xxTree[branch];
              searchListenerTree(handlers, type, { '**': isolatedBranch }, i + 1);
            }
          }
        }
      } else if (xxTree._listeners) {
        // We have reached the end and still on a '**'
        searchListenerTree(handlers, type, xxTree, typeLength);
      } else if (xxTree['*'] && xxTree['*']._listeners) {
        searchListenerTree(handlers, type, xxTree['*'], typeLength);
      }
    }
  
    return listeners;
  }
  
  /**
   * Invoke the given shared method using the supplied context.
   * Execute registered before/after hooks.
   * @param {Object} ctx
   * @param {Object} method
   * @param {function(Error=)} cb
   */
  RemoteObjects.prototype.invokeMethodInContext = function(ctx, method, cb) {
    var self = this;
  
    var scope = this.getScope(ctx, method);
  
    self.execHooks('before', method, scope, ctx, function(err) {
      if (err) return cb(err);
  
      ctx.invoke(scope, method, function(err, result) {
        if (err) return cb(err);
        ctx.result = result;
        self.execHooks('after', method, scope, ctx, function(err) {
          if (err) return cb(err);
          cb();
        });
      });
    });
  };
  
  /**
   * Determine what scope object to use when invoking the given remote method in
   * the given context.
   * @private
   */
  
  RemoteObjects.prototype.getScope = function(ctx, method) {
    // Static methods are invoked on the constructor (this = constructor fn)
    // Prototype methods are invoked on the instance (this = instance)
    return ctx.instance ||
      method.ctor ||
      method.sharedMethod && method.sharedMethod.ctor;
  };
  
  /**
   * Define a named type conversion. The conversion is used when a
   * `SharedMethod` argument defines a type with the given `name`.
   *
   * ```js
   * remotes.defineType('MyType', function(val, ctx) {
   *   // use the val and ctx objects to return the concrete value
   *   return new MyType(val);
   * });
   * ```
   *
   * **Note: the alias `remotes.convert()` is deprecated.**
   *
   * @param {String} name The type name
   * @param {Function} converter
   */
  
  RemoteObjects.defineType =
  RemoteObjects.convert =
  RemoteObjects.prototype.defineType =
  RemoteObjects.prototype.convert = function(name, fn) {
    Dynamic.define(name, fn);
  };
  