// Generated by CoffeeScript 1.10.0
var Connection, HANDSHAKE_AUTHFAIL, HANDSHAKE_SUCCESS, HttpConnection, Promise, TcpConnection, ar, aropt, crypto, cursors, err, events, mkAtom, mkErr, net, pbkdf2_cache, protoProtocol, protoQueryType, protoResponseType, protoVersion, protoVersionNumber, protodef, r, tls, util, varar,
  extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
  hasProp = {}.hasOwnProperty;

net = require('net');

tls = require('tls');

events = require('events');

util = require('./util');

err = require('./errors');

cursors = require('./cursor');

protodef = require('./proto-def');

crypto = require("crypto");

protoVersion = protodef.VersionDummy.Version.V1_0;

protoVersionNumber = 0;

protoProtocol = protodef.VersionDummy.Protocol.JSON;

protoQueryType = protodef.Query.QueryType;

protoResponseType = protodef.Response.ResponseType;

r = require('./ast');

Promise = require('bluebird');

ar = util.ar;

varar = util.varar;

aropt = util.aropt;

mkAtom = util.mkAtom;

mkErr = util.mkErr;

HANDSHAKE_SUCCESS = "SUCCESS";

HANDSHAKE_AUTHFAIL = "ERROR: Incorrect authorization key.\n";

Connection = (function(superClass) {
  extend(Connection, superClass);

  Connection.prototype.DEFAULT_HOST = 'localhost';

  Connection.prototype.DEFAULT_PORT = 28015;

  Connection.prototype.DEFAULT_AUTH_KEY = '';

  Connection.prototype.DEFAULT_TIMEOUT = 20;

  function Connection(host, callback) {
    var conCallback, errCallback;
    if (typeof host === 'undefined') {
      host = {};
    } else if (typeof host === 'string') {
      host = {
        host: host
      };
    }
    this.host = host.host || this.DEFAULT_HOST;
    this.port = host.port || this.DEFAULT_PORT;
    this.db = host.db;
    this.authKey = host.authKey || this.DEFAULT_AUTH_KEY;
    this.timeout = host.timeout || this.DEFAULT_TIMEOUT;
    if (typeof host.ssl === 'boolean' && host.ssl) {
      this.ssl = {};
    } else if (typeof host.ssl === 'object') {
      this.ssl = host.ssl;
    } else {
      this.ssl = false;
    }
    this.outstandingCallbacks = {};
    this.nextToken = 1;
    this.open = false;
    this.closing = false;
    this.buffer = new Buffer(0);
    this._events = this._events || {};
    errCallback = (function(_this) {
      return function(e) {
        _this.removeListener('connect', conCallback);
        if (e instanceof err.ReqlError) {
          return callback(e);
        } else {
          return callback(new err.ReqlDriverError("Could not connect to " + _this.host + ":" + _this.port + ".\n" + e.message));
        }
      };
    })(this);
    this.once('error', errCallback);
    conCallback = (function(_this) {
      return function() {
        _this.removeListener('error', errCallback);
        _this.open = true;
        return callback(null, _this);
      };
    })(this);
    this.once('connect', conCallback);
    this._closePromise = null;
  }

  Connection.prototype._data = function(buf) {
    var response, responseBuffer, responseLength, results, token;
    this.buffer = Buffer.concat([this.buffer, buf]);
    results = [];
    while (this.buffer.length >= 12) {
      token = this.buffer.readUInt32LE(0) + 0x100000000 * this.buffer.readUInt32LE(4);
      responseLength = this.buffer.readUInt32LE(8);
      if (!(this.buffer.length >= (12 + responseLength))) {
        break;
      }
      responseBuffer = this.buffer.slice(12, responseLength + 12);
      response = JSON.parse(responseBuffer);
      this._processResponse(response, token);
      results.push(this.buffer = this.buffer.slice(12 + responseLength));
    }
    return results;
  };

  Connection.prototype._delQuery = function(token) {
    return delete this.outstandingCallbacks[token];
  };

  Connection.prototype._processResponse = function(response, token) {
    var cb, cursor, errType, feed, k, len1, note, opts, profile, ref, ref1, root;
    profile = response.p;
    if (this.outstandingCallbacks[token] != null) {
      ref = this.outstandingCallbacks[token], cb = ref.cb, root = ref.root, cursor = ref.cursor, opts = ref.opts, feed = ref.feed;
      if (cursor != null) {
        cursor._addResponse(response);
        if (cursor._endFlag && cursor._outstandingRequests === 0) {
          return this._delQuery(token);
        }
      } else if (cb != null) {
        switch (response.t) {
          case protoResponseType.COMPILE_ERROR:
            cb(mkErr(err.ReqlServerCompileError, response, root));
            return this._delQuery(token);
          case protoResponseType.CLIENT_ERROR:
            cb(mkErr(err.ReqlDriverError, response, root));
            return this._delQuery(token);
          case protoResponseType.RUNTIME_ERROR:
            errType = util.errorClass(response.e);
            cb(mkErr(errType, response, root));
            return this._delQuery(token);
          case protoResponseType.SUCCESS_ATOM:
            response = mkAtom(response, opts);
            if (Array.isArray(response)) {
              response = cursors.makeIterable(response);
            }
            if (profile != null) {
              response = {
                profile: profile,
                value: response
              };
            }
            cb(null, response);
            return this._delQuery(token);
          case protoResponseType.SUCCESS_PARTIAL:
            cursor = null;
            ref1 = response.n;
            for (k = 0, len1 = ref1.length; k < len1; k++) {
              note = ref1[k];
              switch (note) {
                case protodef.Response.ResponseNote.SEQUENCE_FEED:
                  if (cursor == null) {
                    cursor = new cursors.Feed(this, token, opts, root);
                  }
                  break;
                case protodef.Response.ResponseNote.UNIONED_FEED:
                  if (cursor == null) {
                    cursor = new cursors.UnionedFeed(this, token, opts, root);
                  }
                  break;
                case protodef.Response.ResponseNote.ATOM_FEED:
                  if (cursor == null) {
                    cursor = new cursors.AtomFeed(this, token, opts, root);
                  }
                  break;
                case protodef.Response.ResponseNote.ORDER_BY_LIMIT_FEED:
                  if (cursor == null) {
                    cursor = new cursors.OrderByLimitFeed(this, token, opts, root);
                  }
              }
            }
            if (cursor == null) {
              cursor = new cursors.Cursor(this, token, opts, root);
            }
            this.outstandingCallbacks[token].cursor = cursor;
            if (profile != null) {
              return cb(null, {
                profile: profile,
                value: cursor._addResponse(response)
              });
            } else {
              return cb(null, cursor._addResponse(response));
            }
            break;
          case protoResponseType.SUCCESS_SEQUENCE:
            cursor = new cursors.Cursor(this, token, opts, root);
            this._delQuery(token);
            if (profile != null) {
              return cb(null, {
                profile: profile,
                value: cursor._addResponse(response)
              });
            } else {
              return cb(null, cursor._addResponse(response));
            }
            break;
          case protoResponseType.WAIT_COMPLETE:
            this._delQuery(token);
            return cb(null, null);
          case protoResponseType.SERVER_INFO:
            this._delQuery(token);
            response = mkAtom(response, opts);
            return cb(null, response);
          default:
            return cb(new err.ReqlDriverError("Unknown response type"));
        }
      }
    } else {

    }
  };

  Connection.prototype.close = varar(0, 2, function(optsOrCallback, callback) {
    var cb, key, noreplyWait, opts;
    if (callback != null) {
      opts = optsOrCallback;
      if (Object.prototype.toString.call(opts) !== '[object Object]') {
        throw new err.ReqlDriverError("First argument to two-argument `close` must be an object.");
      }
      cb = callback;
    } else if (Object.prototype.toString.call(optsOrCallback) === '[object Object]') {
      opts = optsOrCallback;
      cb = null;
    } else if (typeof optsOrCallback === 'function') {
      opts = {};
      cb = optsOrCallback;
    } else {
      opts = optsOrCallback;
      cb = null;
    }
    for (key in opts) {
      if (!hasProp.call(opts, key)) continue;
      if (key !== 'noreplyWait') {
        throw new err.ReqlDriverError("First argument to two-argument `close` must be { noreplyWait: <bool> }.");
      }
    }
    if (this._closePromise != null) {
      return this._closePromise.nodeify(cb);
    }
    this.closing = true;
    noreplyWait = ((opts.noreplyWait == null) || opts.noreplyWait) && this.open;
    return this._closePromise = new Promise((function(_this) {
      return function(resolve, reject) {
        var wrappedCb;
        wrappedCb = function(err, result) {
          _this.open = false;
          _this.closing = false;
          _this.cancel();
          if (err != null) {
            return reject(err);
          } else {
            return resolve(result);
          }
        };
        if (noreplyWait) {
          return _this.noreplyWait(wrappedCb);
        } else {
          return wrappedCb();
        }
      };
    })(this)).nodeify(cb);
  });

  Connection.prototype.noreplyWait = varar(0, 1, function(callback) {
    var query, token;
    if (!this.open) {
      return new Promise(function(resolve, reject) {
        return reject(new err.ReqlDriverError("Connection is closed."));
      }).nodeify(callback);
    }
    token = this.nextToken++;
    query = {};
    query.type = protoQueryType.NOREPLY_WAIT;
    query.token = token;
    return new Promise((function(_this) {
      return function(resolve, reject) {
        var wrappedCb;
        wrappedCb = function(err, result) {
          if (err) {
            return reject(err);
          } else {
            return resolve(result);
          }
        };
        _this.outstandingCallbacks[token] = {
          cb: wrappedCb,
          root: null,
          opts: null
        };
        return _this._sendQuery(query);
      };
    })(this)).nodeify(callback);
  });

  Connection.prototype.server = varar(0, 1, function(callback) {
    var query, token;
    if (!this.open) {
      return new Promise(function(resolve, reject) {
        return reject(new err.ReqlDriverError("Connection is closed."));
      }).nodeify(callback);
    }
    token = this.nextToken++;
    query = {};
    query.type = protoQueryType.SERVER_INFO;
    query.token = token;
    return new Promise((function(_this) {
      return function(resolve, reject) {
        var wrappedCb;
        wrappedCb = function(err, result) {
          if (err) {
            return reject(err);
          } else {
            return resolve(result);
          }
        };
        _this.outstandingCallbacks[token] = {
          cb: wrappedCb,
          root: null,
          opts: null
        };
        return _this._sendQuery(query);
      };
    })(this)).nodeify(callback);
  });

  Connection.prototype.cancel = ar(function() {
    var key, ref, response, value;
    response = {
      t: protoResponseType.RUNTIME_ERROR,
      r: ["Connection is closed."],
      b: []
    };
    ref = this.outstandingCallbacks;
    for (key in ref) {
      if (!hasProp.call(ref, key)) continue;
      value = ref[key];
      if (value.cursor != null) {
        value.cursor._addResponse(response);
      } else if (value.cb != null) {
        value.cb(mkErr(util.errorClass(response.e), response, value.root));
      }
    }
    return this.outstandingCallbacks = {};
  });

  Connection.prototype.reconnect = varar(0, 2, function(optsOrCallback, callback) {
    var cb, opts;
    if (callback != null) {
      opts = optsOrCallback;
      cb = callback;
    } else if (typeof optsOrCallback === "function") {
      opts = {};
      cb = optsOrCallback;
    } else {
      if (optsOrCallback != null) {
        opts = optsOrCallback;
      } else {
        opts = {};
      }
      cb = callback;
    }
    return new Promise((function(_this) {
      return function(resolve, reject) {
        var closeCb;
        closeCb = function(err) {
          return _this.constructor.call(_this, {
            host: _this.host,
            port: _this.port,
            timeout: _this.timeout,
            authKey: _this.authKey
          }, function(err, conn) {
            if (err != null) {
              return reject(err);
            } else {
              return resolve(conn);
            }
          });
        };
        return _this.close(opts, closeCb);
      };
    })(this)).nodeify(cb);
  });

  Connection.prototype.use = ar(function(db) {
    return this.db = db;
  });

  Connection.prototype.isOpen = function() {
    return this.open && !this.closing;
  };

  Connection.prototype._start = function(term, cb, opts) {
    var key, query, token, value;
    if (!this.open) {
      throw new err.ReqlDriverError("Connection is closed.");
    }
    token = this.nextToken++;
    query = {};
    query.global_optargs = {};
    query.type = protoQueryType.START;
    query.query = term.build();
    query.token = token;
    for (key in opts) {
      if (!hasProp.call(opts, key)) continue;
      value = opts[key];
      query.global_optargs[util.fromCamelCase(key)] = r.expr(value).build();
    }
    if ((opts.db != null) || (this.db != null)) {
      query.global_optargs.db = r.db(opts.db || this.db).build();
    }
    if (opts.noreply != null) {
      query.global_optargs['noreply'] = r.expr(!!opts.noreply).build();
    }
    if (opts.profile != null) {
      query.global_optargs['profile'] = r.expr(!!opts.profile).build();
    }
    if ((opts.noreply == null) || !opts.noreply) {
      this.outstandingCallbacks[token] = {
        cb: cb,
        root: term,
        opts: opts
      };
    }
    this._sendQuery(query);
    if ((opts.noreply != null) && opts.noreply && typeof cb === 'function') {
      return cb(null);
    }
  };

  Connection.prototype._continueQuery = function(token) {
    var query;
    if (!this.open) {
      throw new err.ReqlDriverError("Connection is closed.");
    }
    query = {
      type: protoQueryType.CONTINUE,
      token: token
    };
    return this._sendQuery(query);
  };

  Connection.prototype._endQuery = function(token) {
    var query;
    if (!this.open) {
      throw new err.ReqlDriverError("Connection is closed.");
    }
    query = {
      type: protoQueryType.STOP,
      token: token
    };
    return this._sendQuery(query);
  };

  Connection.prototype._sendQuery = function(query) {
    var data;
    data = [query.type];
    if (!(query.query === void 0)) {
      data.push(query.query);
      if ((query.global_optargs != null) && Object.keys(query.global_optargs).length > 0) {
        data.push(query.global_optargs);
      }
    }
    return this._writeQuery(query.token, JSON.stringify(data));
  };

  return Connection;

})(events.EventEmitter);

pbkdf2_cache = {};

TcpConnection = (function(superClass) {
  extend(TcpConnection, superClass);

  TcpConnection.isAvailable = function() {
    return !process.browser;
  };

  function TcpConnection(host, callback) {
    var timeout;
    if (!TcpConnection.isAvailable()) {
      throw new err.ReqlDriverError("TCP sockets are not available in this environment");
    }
    TcpConnection.__super__.constructor.call(this, host, callback);
    if (this.ssl) {
      this.ssl.host = this.host;
      this.ssl.port = this.port;
      this.rawSocket = tls.connect(this.ssl);
    } else {
      this.rawSocket = net.connect(this.port, this.host);
    }
    this.rawSocket.setNoDelay();
    this.rawSocket.setKeepAlive(true);
    timeout = setTimeout(((function(_this) {
      return function() {
        _this.rawSocket.destroy();
        return _this.emit('error', new err.ReqlTimeoutError("Could not connect to " + _this.host + ":" + _this.port + ", operation timed out."));
      };
    })(this)), this.timeout * 1000);
    this.rawSocket.once('error', (function(_this) {
      return function() {
        return clearTimeout(timeout);
      };
    })(this));
    this.rawSocket.once('connect', (function(_this) {
      return function() {
        var auth_i, auth_r, auth_salt, client_first_message_bare, compare_digest, handshake_callback, handshake_error, max, message, min, nullbyte, pbkdf2_hmac, protocol, r_string, server_first_message, server_signature, state, version, xor_bytes;
        version = new Buffer(4);
        version.writeUInt32LE(protoVersion, 0);
        protocol = new Buffer(4);
        protocol.writeUInt32LE(protoProtocol, 0);
        r_string = new Buffer(crypto.randomBytes(18)).toString('base64');
        _this.rawSocket.user = host["user"];
        _this.rawSocket.password = host["password"];
        if (_this.rawSocket.user === void 0) {
          _this.rawSocket.user = "admin";
        }
        if (_this.rawSocket.password === void 0) {
          _this.rawSocket.password = "";
        }
        client_first_message_bare = "n=" + _this.rawSocket.user + ",r=" + r_string;
        message = JSON.stringify({
          protocol_version: protoVersionNumber,
          authentication_method: "SCRAM-SHA-256",
          authentication: "n,," + client_first_message_bare
        });
        nullbyte = new Buffer('\0', "binary");
        _this.rawSocket.write(Buffer.concat([version, Buffer(message.toString()), nullbyte]));
        state = 1;
        min = 0;
        max = 0;
        server_first_message = "";
        server_signature = "";
        auth_r = "";
        auth_salt = "";
        auth_i = 0;
        xor_bytes = function(a, b) {
          var i, k, len, ref, res;
          res = [];
          len = Math.min(a.length, b.length);
          for (i = k = 0, ref = len; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
            res.push(a[i] ^ b[i]);
          }
          return new Buffer(res);
        };
        pbkdf2_hmac = function(password, salt, iterations) {
          var c, cache_string, k, mac, ref, t, u;
          cache_string = password.toString("base64") + "," + salt.toString("base64") + "," + iterations.toString();
          if (pbkdf2_cache[cache_string]) {
            return pbkdf2_cache[cache_string];
          }
          mac = crypto.createHmac("sha256", password);
          mac.update(salt);
          mac.update("\x00\x00\x00\x01");
          u = mac.digest();
          t = u;
          for (c = k = 0, ref = iterations - 1; 0 <= ref ? k < ref : k > ref; c = 0 <= ref ? ++k : --k) {
            mac = crypto.createHmac("sha256", password);
            mac.update(t);
            t = mac.digest();
            u = xor_bytes(u, t);
          }
          pbkdf2_cache[cache_string] = u;
          return u;
        };
        compare_digest = function(a, b) {
          var i, k, left, len, ref, result, right;
          left = void 0;
          right = b;
          result = void 0;
          if (a.length === b.length) {
            left = a;
            result = 0;
          }
          if (a.length !== b.length) {
            left = b;
            result = 1;
          }
          len = Math.min(left.length, right.length);
          for (i = k = 0, ref = len; 0 <= ref ? k < ref : k > ref; i = 0 <= ref ? ++k : --k) {
            result |= left[i] ^ right[i];
          }
          return result === 0;
        };
        handshake_error = function(code, message) {
          if ((10 <= code && code <= 20)) {
            return _this.emit('error', new err.ReqlAuthError(message));
          } else {
            return _this.emit('error', new err.ReqlDriverError(message));
          }
        };
        handshake_callback = function(buf) {
          var auth_message, authentication, b, client_final_message_without_proof, client_key, client_proof, client_signature, error1, first_equals, i, item, j, json_error, k, l, len1, len2, ref, ref1, salted_password, server_key, server_reply, status_buf, status_str, stored_key, v;
          _this.buffer = Buffer.concat([_this.buffer, buf]);
          j = 0;
          ref = _this.buffer;
          for (i = k = 0, len1 = ref.length; k < len1; i = ++k) {
            b = ref[i];
            if (b === 0) {
              status_buf = _this.buffer.slice(j, i);
              j = i + 1;
              status_str = status_buf.toString();
              try {
                server_reply = JSON.parse(status_str);
              } catch (error1) {
                json_error = error1;
                throw new err.ReqlDriverError(status_str);
              }
              if (state === 1) {
                if (!server_reply.success) {
                  handshake_error(server_reply.error_code, server_reply.error);
                  return;
                }
                min = server_reply.min_protocol_version;
                max = server_reply.max_protocol_version;
                if (min > protoVersionNumber || max < protoVersionNumber) {
                  throw new err.ReqlDriverError("Unsupported protocol version " + protoVersionNumber + ", expected between " + min + " and " + max + ".");
                }
                state = 2;
              } else if (state === 2) {
                if (!server_reply.success) {
                  handshake_error(server_reply.error_code, server_reply.error);
                  return;
                }
                authentication = {};
                server_first_message = server_reply.authentication;
                ref1 = server_first_message.split(",");
                for (l = 0, len2 = ref1.length; l < len2; l++) {
                  item = ref1[l];
                  i = item.indexOf("=");
                  authentication[item.slice(0, i)] = item.slice(i + 1);
                }
                auth_r = authentication.r;
                auth_salt = new Buffer(authentication.s, 'base64');
                auth_i = parseInt(authentication.i);
                if (!(auth_r.substr(0, r_string.length) === r_string)) {
                  throw new err.ReqlAuthError("Invalid nonce from server");
                }
                client_final_message_without_proof = "c=biws,r=" + auth_r;
                salted_password = pbkdf2_hmac(_this.rawSocket.password, auth_salt, auth_i);
                client_key = crypto.createHmac("sha256", salted_password).update("Client Key").digest();
                stored_key = crypto.createHash("sha256").update(client_key).digest();
                auth_message = client_first_message_bare + "," + server_first_message + "," + client_final_message_without_proof;
                client_signature = crypto.createHmac("sha256", stored_key).update(auth_message).digest();
                client_proof = xor_bytes(client_key, client_signature);
                server_key = crypto.createHmac("sha256", salted_password).update("Server Key").digest();
                server_signature = crypto.createHmac("sha256", server_key).update(auth_message).digest();
                state = 3;
                message = JSON.stringify({
                  authentication: client_final_message_without_proof + ",p=" + client_proof.toString("base64")
                });
                nullbyte = new Buffer('\0', "binary");
                _this.rawSocket.write(Buffer.concat([Buffer(message.toString()), nullbyte]));
              } else if (state === 3) {
                if (!server_reply.success) {
                  handshake_error(server_reply.error_code, server_reply.error);
                  return;
                }
                first_equals = server_reply.authentication.indexOf('=');
                v = server_reply.authentication.slice(first_equals + 1);
                if (!compare_digest(v, server_signature.toString("base64"))) {
                  throw new err.ReqlAuthError("Invalid server signature");
                }
                state = 4;
                _this.rawSocket.removeListener('data', handshake_callback);
                _this.rawSocket.on('data', function(buf) {
                  return _this._data(buf);
                });
                clearTimeout(timeout);
                _this.emit('connect');
              } else {
                throw new err.ReqlDriverError("Unexpected handshake state");
              }
            }
          }
          return _this.buffer = _this.buffer.slice(j + 1);
        };
        return _this.rawSocket.on('data', handshake_callback);
      };
    })(this));
    this.rawSocket.on('error', (function(_this) {
      return function(err) {
        return _this.emit('error', err);
      };
    })(this));
    this.rawSocket.on('close', (function(_this) {
      return function() {
        if (_this.isOpen()) {
          _this.close({
            noreplyWait: false
          });
        }
        return _this.emit('close');
      };
    })(this));
    this.rawSocket.on('timeout', (function(_this) {
      return function() {
        _this.open = false;
        return _this.emit('timeout');
      };
    })(this));
  }

  TcpConnection.prototype.clientPort = function() {
    return this.rawSocket.localPort;
  };

  TcpConnection.prototype.clientAddress = function() {
    return this.rawSocket.localAddress;
  };

  TcpConnection.prototype.close = varar(0, 2, function(optsOrCallback, callback) {
    var cb, opts;
    if (callback != null) {
      opts = optsOrCallback;
      cb = callback;
    } else if (Object.prototype.toString.call(optsOrCallback) === '[object Object]') {
      opts = optsOrCallback;
      cb = null;
    } else if (typeof optsOrCallback === "function") {
      opts = {};
      cb = optsOrCallback;
    } else {
      opts = {};
    }
    return new Promise((function(_this) {
      return function(resolve, reject) {
        var wrappedCb;
        wrappedCb = function(error, result) {
          var cleanupSocket, closeCb, ref;
          closeCb = function() {
            if (error != null) {
              return reject(error);
            } else {
              return resolve(result);
            }
          };
          cleanupSocket = function() {
            var ref;
            closeCb();
            if ((ref = _this.rawSocket) != null) {
              ref.removeAllListeners();
            }
            _this.rawSocket = null;
            return _this.emit("close");
          };
          if (_this.rawSocket != null) {
            if (_this.rawSocket.readyState === 'closed') {
              return cleanupSocket();
            } else {
              if ((ref = _this.rawSocket) != null) {
                ref.once("close", cleanupSocket);
              }
              return _this.rawSocket.end();
            }
          } else {
            return process.nextTick(closeCb);
          }
        };
        return TcpConnection.__super__.close.call(_this, opts, wrappedCb);
      };
    })(this)).nodeify(cb);
  });

  TcpConnection.prototype.cancel = function() {
    this.rawSocket.destroy();
    return TcpConnection.__super__.cancel.call(this);
  };

  TcpConnection.prototype._writeQuery = function(token, data) {
    var tokenBuf;
    tokenBuf = new Buffer(8);
    tokenBuf.writeUInt32LE(token & 0xFFFFFFFF, 0);
    tokenBuf.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4);
    this.rawSocket.write(tokenBuf);
    return this.write(new Buffer(data));
  };

  TcpConnection.prototype.write = function(chunk) {
    var lengthBuffer;
    lengthBuffer = new Buffer(4);
    lengthBuffer.writeUInt32LE(chunk.length, 0);
    this.rawSocket.write(lengthBuffer);
    return this.rawSocket.write(chunk);
  };

  return TcpConnection;

})(Connection);

HttpConnection = (function(superClass) {
  extend(HttpConnection, superClass);

  HttpConnection.prototype.DEFAULT_PROTOCOL = 'http';

  HttpConnection.isAvailable = function() {
    return typeof XMLHttpRequest !== "undefined";
  };

  function HttpConnection(host, callback) {
    var protocol, url, xhr;
    if (!HttpConnection.isAvailable()) {
      throw new err.ReqlDriverError("XMLHttpRequest is not available in this environment");
    }
    HttpConnection.__super__.constructor.call(this, host, callback);
    protocol = host.protocol === 'https' ? 'https' : this.DEFAULT_PROTOCOL;
    url = protocol + "://" + this.host + ":" + this.port + host.pathname + "ajax/reql/";
    xhr = new XMLHttpRequest;
    xhr.open("POST", url + ("open-new-connection?cacheBuster=" + (Math.random())), true);
    xhr.responseType = "text";
    xhr.onreadystatechange = (function(_this) {
      return function(e) {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            _this._url = url;
            _this._connId = xhr.response;
            return _this.emit('connect');
          } else {
            return _this.emit('error', new err.ReqlDriverError("XHR error, http status " + xhr.status + "."));
          }
        }
      };
    })(this);
    xhr.send();
    this.xhr = xhr;
  }

  HttpConnection.prototype.cancel = function() {
    var xhr;
    if (this._connId != null) {
      this.xhr.abort();
      xhr = new XMLHttpRequest;
      xhr.open("POST", this._url + "close-connection?conn_id=" + this._connId, true);
      xhr.responseType = "arraybuffer";
      xhr.send();
      this._url = null;
      this._connId = null;
      return HttpConnection.__super__.cancel.call(this);
    }
  };

  HttpConnection.prototype.close = varar(0, 2, function(optsOrCallback, callback) {
    var cb, opts;
    if (callback != null) {
      opts = optsOrCallback;
      cb = callback;
    } else if (Object.prototype.toString.call(optsOrCallback) === '[object Object]') {
      opts = optsOrCallback;
      cb = null;
    } else {
      opts = {};
      cb = optsOrCallback;
    }
    if (!((cb == null) || typeof cb === 'function')) {
      throw new err.ReqlDriverError("Final argument to `close` must be a callback function or object.");
    }
    return HttpConnection.__super__.close.call(this, opts, cb);
  });

  HttpConnection.prototype._writeQuery = function(token, data) {
    var buf;
    buf = new Buffer(encodeURI(data).split(/%..|./).length - 1 + 8);
    buf.writeUInt32LE(token & 0xFFFFFFFF, 0);
    buf.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4);
    buf.write(data, 8);
    return this.write(buf, token);
  };

  HttpConnection.prototype.write = function(chunk, token) {
    var i, view, xhr;
    xhr = new XMLHttpRequest;
    xhr.open("POST", this._url + "?conn_id=" + this._connId, true);
    xhr.responseType = "arraybuffer";
    xhr.onreadystatechange = (function(_this) {
      return function(e) {
        var b, buf;
        if (xhr.readyState === 4 && xhr.status === 200) {
          buf = new Buffer((function() {
            var k, len1, ref, results;
            ref = new Uint8Array(xhr.response);
            results = [];
            for (k = 0, len1 = ref.length; k < len1; k++) {
              b = ref[k];
              results.push(b);
            }
            return results;
          })());
          return _this._data(buf);
        }
      };
    })(this);
    xhr.onerror = (function(_this) {
      return function(e) {
        return _this.outstandingCallbacks[token].cb(new Error("This HTTP connection is not open"));
      };
    })(this);
    view = new Uint8Array(chunk.length);
    i = 0;
    while (i < chunk.length) {
      view[i] = chunk[i];
      i++;
    }
    xhr.send(view);
    return this.xhr = xhr;
  };

  return HttpConnection;

})(Connection);

module.exports.isConnection = function(connection) {
  return connection instanceof Connection;
};

module.exports.connect = varar(0, 2, function(hostOrCallback, callback) {
  var host;
  if (typeof hostOrCallback === 'function') {
    host = {};
    callback = hostOrCallback;
  } else {
    host = hostOrCallback || {};
  }
  return new Promise(function(resolve, reject) {
    var create_connection, wrappedCb;
    if ((host.authKey != null) && ((host.password != null) || (host.user != null) || (host.username != null))) {
      throw new err.ReqlDriverError("Cannot use both authKey and password");
    }
    if (host.user && host.username) {
      throw new err.ReqlDriverError("Cannot use both user and username");
    } else if (host.authKey) {
      host.user = "admin";
      host.password = host.authKey;
    } else {
      if (host.username != null) {
        host.user = host.username;
      }
    }
    create_connection = (function(_this) {
      return function(host, callback) {
        if (TcpConnection.isAvailable()) {
          return new TcpConnection(host, callback);
        } else if (HttpConnection.isAvailable()) {
          return new HttpConnection(host, callback);
        } else {
          throw new err.ReqlDriverError("Neither TCP nor HTTP avaiable in this environment");
        }
      };
    })(this);
    wrappedCb = function(err, result) {
      if (err) {
        return reject(err);
      } else {
        return resolve(result);
      }
    };
    return create_connection(host, wrappedCb);
  }).nodeify(callback);
});

module.exports.Connection = Connection;

module.exports.HttpConnection = HttpConnection;

module.exports.TcpConnection = TcpConnection;
