text: 'use strict';
  
  var Class    = require('../util/class'),
      Cookie   = require('../util/cookies').Cookie,
      Promise  = require('../util/promise'),
      array    = require('../util/array'),
      assign   = require('../util/assign'),
      Logging  = require('../mixins/logging'),
      Timeouts = require('../mixins/timeouts'),
      Channel  = require('../protocol/channel');
  
  var Transport = assign(Class({ className: 'Transport',
    DEFAULT_PORTS: { 'http:': 80, 'https:': 443, 'ws:': 80, 'wss:': 443 },
    MAX_DELAY:     0,
  
    batching:  true,
  
    initialize: function(dispatcher, endpoint) {
      this._dispatcher = dispatcher;
      this.endpoint    = endpoint;
      this._outbox     = [];
      this._proxy      = assign({}, this._dispatcher.proxy);
  
      if (!this._proxy.origin)
        this._proxy.origin = this._findProxy();
    },
  
    close: function() {},
  
    encode: function(messages) {
      return '';
    },
  
    sendMessage: function(message) {
      this.debug('Client ? sending message to ?: ?',
                 this._dispatcher.clientId, this.endpoint.href, message);
  
      if (!this.batching) return Promise.resolve(this.request([message]));
  
      this._outbox.push(message);
      this._flushLargeBatch();
  
      if (message.channel === Channel.HANDSHAKE)
        return this._publish(0.01);
  
      if (message.channel === Channel.CONNECT)
        this._connectMessage = message;
  
      return this._publish(this.MAX_DELAY);
    },
  
    _makePromise: function() {
      var self = this;
  
      this._requestPromise = this._requestPromise || new Promise(function(resolve) {
        self._resolvePromise = resolve;
      });
    },
  
    _publish: function(delay) {
      this._makePromise();
  
      this.addTimeout('publish', delay, function() {
        this._flush();
        delete this._requestPromise;
      }, this);
  
      return this._requestPromise;
    },
  
    _flush: function() {
      this.removeTimeout('publish');
  
      if (this._outbox.length > 1 && this._connectMessage)
        this._connectMessage.advice = { timeout: 0 };
  
      this._resolvePromise(this.request(this._outbox));
  
      this._connectMessage = null;
      this._outbox = [];
    },
  
    _flushLargeBatch: function() {
      var string = this.encode(this._outbox);
      if (string.length < this._dispatcher.maxRequestSize) return;
      var last = this._outbox.pop();
  
      this._makePromise();
      this._flush();
  
      if (last) this._outbox.push(last);
    },
  
    _receive: function(replies) {
      if (!replies) return;
      replies = [].concat(replies);
  
      this.debug('Client ? received from ? via ?: ?',
                 this._dispatcher.clientId, this.endpoint.href, this.connectionType, replies);
  
      for (var i = 0, n = replies.length; i < n; i++)
        this._dispatcher.handleResponse(replies[i]);
    },
  
    _handleError: function(messages, immediate) {
      messages = [].concat(messages);
  
      this.debug('Client ? failed to send to ? via ?: ?',
                 this._dispatcher.clientId, this.endpoint.href, this.connectionType, messages);
  
      for (var i = 0, n = messages.length; i < n; i++)
        this._dispatcher.handleError(messages[i]);
    },
  
    _getCookies: function() {
      var cookies = this._dispatcher.cookies,
          url     = this.endpoint.href;
  
      if (!cookies) return '';
  
      return array.map(cookies.getCookiesSync(url, { allPaths: true }), function(cookie) {
        return cookie.cookieString();
      }).join('; ');
    },
  
    _storeCookies: function(setCookie) {
      var cookies = this._dispatcher.cookies,
          url     = this.endpoint.href,
          cookie;
  
      if (!setCookie || !cookies) return;
      setCookie = [].concat(setCookie);
  
      for (var i = 0, n = setCookie.length; i < n; i++) {
        cookie = Cookie.parse(setCookie[i]);
        cookies.setCookieSync(cookie, url);
      }
    },
  
    _findProxy: function() {
      if (typeof process === 'undefined') return undefined;
  
      var protocol = this.endpoint.protocol;
      if (!protocol) return undefined;
  
      var name   = protocol.replace(/:$/, '').toLowerCase() + '_proxy',
          upcase = name.toUpperCase(),
          env    = process.env,
          keys, proxy;
  
      if (name === 'http_proxy' && env.REQUEST_METHOD) {
        keys = Object.keys(env).filter(function(k) { return /^http_proxy$/i.test(k) });
        if (keys.length === 1) {
          if (keys[0] === name && env[upcase] === undefined)
            proxy = env[name];
        } else if (keys.length > 1) {
          proxy = env[name];
        }
        proxy = proxy || env['CGI_' + upcase];
      } else {
        proxy = env[name] || env[upcase];
        if (proxy && !env[name])
          console.warn('The environment variable ' + upcase +
                       ' is discouraged. Use ' + name + '.');
      }
      return proxy;
    }
  
  }), {
    get: function(dispatcher, allowed, disabled, callback, context) {
      var endpoint = dispatcher.endpoint;
  
      array.asyncEach(this._transports, function(pair, resume) {
        var connType     = pair[0], klass = pair[1],
            connEndpoint = dispatcher.endpointFor(connType);
  
        if (array.indexOf(disabled, connType) >= 0)
          return resume();
  
        if (array.indexOf(allowed, connType) < 0) {
          klass.isUsable(dispatcher, connEndpoint, function() {});
          return resume();
        }
  
        klass.isUsable(dispatcher, connEndpoint, function(isUsable) {
          if (!isUsable) return resume();
          var transport = klass.hasOwnProperty('create') ? klass.create(dispatcher, connEndpoint) : new klass(dispatcher, connEndpoint);
          callback.call(context, transport);
        });
      }, function() {
        throw new Error('Could not find a usable connection type for ' + endpoint.href);
      });
    },
  
    register: function(type, klass) {
      this._transports.push([type, klass]);
      klass.prototype.connectionType = type;
    },
  
    getConnectionTypes: function() {
      return array.map(this._transports, function(t) { return t[0] });
    },
  
    disable: function(feature) {
      if (feature !== 'autodisconnect') return;
  
      for (var i = 0; i < this._transports.length; i++)
        this._transports[i][1]._unloaded = false;
    },
  
    _transports: []
  });
  
  assign(Transport.prototype, Logging);
  assign(Transport.prototype, Timeouts);
  
  module.exports = Transport;
  