"use strict";
const utils = require('./utils');
const types = require('./types');

/**
 * Creates a new instance of {@link ExecutionProfile}.
 * @classdesc
 * Represents a set configurations to be used in a statement execution to be used for a single {@link Client} instance.
 *

 *   An {@link ExecutionProfile} instance should not be shared across different {@link Client} instances.
 *

 * @param {String} name Name of the execution profile.
 *

 *   Use 'default' to specify that the new instance should be the default {@link ExecutionProfile} if no
 *   profile is specified in the execution.
 *

 * @param {Object} [options] Profile options, when any of the options is not specified the {@link Client} will the use
 * the ones defined in the default profile.
 * @param {Number} [options.consistency] The consistency level to use for this profile.
 * @param {LoadBalancingPolicy} [options.loadBalancing] The load-balancing policy to use for this profile.
 * @param {Number} [options.readTimeout] The client per-host request timeout to use for this profile.
 * @param {RetryPolicy} [options.retry] The retry policy to use for this profile.
 * @param {Number} [options.serialConsistency] The serial consistency level to use for this profile.
 * @constructor
 */
function ExecutionProfile(name, options) {
  if (typeof name !== 'string') {
    throw new TypeError('Execution profile name must be a string');
  }
  options = options || utils.emptyObject;
  /**
   * Name of the execution profile.
   * @type {String}
   */
  this.name = name;
  /**
   * Consistency level.
   * @type {Number}
   */
  this.consistency = options.consistency;
  /**
   * Load-balancing policy
   * @type {LoadBalancingPolicy}
   */
  this.loadBalancing = options.loadBalancing;
  /**
   * Client read timeout.
   * @type {Number}
   */
  this.readTimeout = options.readTimeout;
  /**
   * Retry policy.
   * @type {RetryPolicy}
   */
  this.retry = options.retry;
  /**
   * Serial consistency level.
   * @type {Number}
   */
  this.serialConsistency = options.serialConsistency;
}

/**
 * @param {ClientOptions} options
 * @constructor
 * @ignore
 */
function ProfileManager(options) {
  this._profiles = options.profiles || [];
  this._setDefault(options);
  // A array of unique load balancing policies
  this._loadBalancingPolicies = [];
  // A dictionary of name keys and profile values
  this._profilesMap = {};
  this._profiles.forEach(function (p) {
    this._profilesMap[p.name] = p;
    // Set required properties
    p.loadBalancing = p.loadBalancing || this._defaultProfile.loadBalancing;
    // Using array indexOf is not very efficient (O(n)) but the amount of profiles should be limited
    // and a handful of load-balancing policies (no hashcode for load-Balancing policies)
    if (this._loadBalancingPolicies.indexOf(p.loadBalancing) === -1) {
      this._loadBalancingPolicies.push(p.loadBalancing);
    }
    return p;
  }, this);
}

/**
 * @param {Client} client
 * @param {HostMap} hosts
 * @param {Function} callback
 */
ProfileManager.prototype.init = function (client, hosts, callback) {
  utils.eachSeries(this._loadBalancingPolicies, function (policy, next) {
    policy.init(client, hosts, next);
  }, callback);
};

/**
 * Uses the load-balancing policies to get the relative distance to the host and return the closest one.
 * @param {Host} host
 */
ProfileManager.prototype.getDistance = function (host) {
  let distance = types.distance.ignored;
  // this is performance critical: we can't use any other language features than for-loop :(
  for (let i = 0; i < this._loadBalancingPolicies.length; i++) {
    const d = this._loadBalancingPolicies[i].getDistance(host);
    if (d < distance) {
      distance = d;
      if (distance === types.distance.local) {
        break;
      }
    }
  }
  host.setDistance(distance);
  return distance;
};

/**
 * @param {String|ExecutionProfile} name
 * @returns {ExecutionProfile|undefined} It returns the execution profile by name or the default profile when name is
 * undefined. It returns undefined when the profile does not exist.
 */
ProfileManager.prototype.getProfile = function (name) {
  if (name instanceof ExecutionProfile) {
    return name;
  }
  return this._profilesMap[name || 'default'];
};

/** @returns {ExecutionProfile} */
ProfileManager.prototype.getDefault = function () {
  return this._defaultProfile;
};

/** @returns {LoadBalancingPolicy} */
ProfileManager.prototype.getDefaultLoadBalancing = function () {
  return this._defaultProfile.loadBalancing;
};

/**
 * @private
 * @param {ClientOptions} options
 */
ProfileManager.prototype._setDefault = function (options) {
  this._defaultProfile = this._profiles.filter(function (p) { return p.name === 'default'; })[0];
  if (!this._defaultProfile) {
    this._profiles.push(this._defaultProfile = new ExecutionProfile('default'));
  }
  // set the required properties
  this._defaultProfile.loadBalancing = this._defaultProfile.loadBalancing || options.policies.loadBalancing;
  this._defaultProfile.retry = this._defaultProfile.retry || options.policies.retry;
};

exports.ProfileManager = ProfileManager;
exports.ExecutionProfile = ExecutionProfile;


"use strict";

const utils = require('../utils');

/** @module types */
/**
 * Creates a new instance of InetAddress
 * @class
 * @classdesc Represents an v4 or v6 Internet Protocol (IP) address.
 * @param {Buffer} buffer
 * @constructor
 */
function InetAddress(buffer) {
  if (!(buffer instanceof Buffer) || (buffer.length !== 4 && buffer.length !== 16)) {
    throw new TypeError('The ip address must contain 4 or 16 bytes');
  }

  /**
   * Immutable buffer that represents the IP address
   * @type Array
   */
  this.buffer = buffer;

  /**
   * Returns the length of the underlying buffer
   * @type Number
   */
  this.length = buffer.length;

  /**
   * Returns the Ip version (4 or 6)
   * @type Number
   */
  this.version = buffer.length === 4 ? 4 : 6;
}

/**
 * Parses the string representation and returns an Ip address
 * @param {String} value
 */
InetAddress.fromString = function (value) {
  if (!value) {
    return new InetAddress(utils.allocBufferFromArray([0, 0, 0, 0]));
  }
  const ipv4Pattern = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
  const ipv6Pattern = /^[\da-f:.]+$/i;
  let parts;
  if (ipv4Pattern.test(value)) {
    parts = value.split('.');
    return new InetAddress(utils.allocBufferFromArray(parts));
  }
  if (!ipv6Pattern.test(value)) {
    throw new TypeError('Value could not be parsed as InetAddress: ' + value);
  }
  parts = value.split(':');
  if (parts.length < 3) {
    throw new TypeError('Value could not be parsed as InetAddress: ' + value);
  }
  const buffer = utils.allocBufferUnsafe(16);
  let filling = 8 - parts.length + 1;
  let applied = false;
  let offset = 0;
  const embeddedIp4 = ipv4Pattern.test(parts[parts.length - 1]);
  if (embeddedIp4) {
    // Its IPv6 address with an embedded IPv4 address:
    // subtract 1 from the potential empty filling as ip4 contains 4 bytes instead of 2 of a ipv6 section
    filling -= 1;
  }
  function writeItem(uIntValue) {
    buffer.writeUInt8(+uIntValue, offset++);
  }
  for (let i = 0; i < parts.length; i++) {
    const item = parts[i];
    if (item) {
      if (embeddedIp4 && i === parts.length - 1) {
        item.split('.').forEach(writeItem);
        break;
      }
      buffer.writeUInt16BE(parseInt(item, 16), offset);
      offset = offset + 2;
      continue;
    }
    //its an empty string
    if (applied) {
      //there could be 2 occurrences of empty string
      filling = 1;
    }
    applied = true;
    for (let j = 0; j < filling; j++) {
      buffer[offset++] = 0;
      buffer[offset++] = 0;
    }
  }
  if (embeddedIp4 && !isValidIPv4Mapped(buffer)) {
    throw new TypeError('Only IPv4-Mapped IPv6 addresses are allowed as IPv6 address with embedded IPv4 address');
  }
  return new InetAddress(buffer);
};

/**
 * Compares 2 addresses and returns true if the underlying bytes are the same
 * @param {InetAddress} other
 * @returns {Boolean}
 */
InetAddress.prototype.equals = function (other) {
  if (!(other instanceof InetAddress)) {
    return false;
  }
  return (this.buffer.length === other.buffer.length &&
    this.buffer.toString('hex') === other.buffer.toString('hex'));
};

/**
 * Returns the underlying buffer
 * @returns {Buffer}
 */
InetAddress.prototype.getBuffer = function () {
  return this.buffer;
};

/**
 * Provide the name of the constructor and the string representation
 * @returns {string}
 */
InetAddress.prototype.inspect = function () {
  return this.constructor.name + ': ' + this.toString();
};

/**
 * Returns the string representation of the IP address.
 *
For v4 IP addresses, a string in the form of d.d.d.d is returned.

 *

 *   For v6 IP addresses, a string in the form of x:x:x:x:x:x:x:x is returned, where the 'x's are the hexadecimal
 *   values of the eight 16-bit pieces of the address, according to rfc5952.
 *   In cases where there is more than one field of only zeros, it can be shortened. For example, 2001:0db8:0:0:0:1:0:1
 *   will be expressed as 2001:0db8::1:0:1.
 *

 * @param {String} [encoding]
 * @returns {String}
 */
InetAddress.prototype.toString = function (encoding) {
  if (encoding === 'hex') {
    //backward compatibility: behave in the same way as the buffer
    return this.buffer.toString('hex');
  }
  if (this.buffer.length === 4) {
    return (
      this.buffer[0] + '.' +
      this.buffer[1] + '.' +
      this.buffer[2] + '.' +
      this.buffer[3]
    );
  }
  let start = -1;
  const longest = { length: 0, start: -1};
  function checkLongest (i) {
    if (start >= 0) {
      //close the group
      const length = i - start;
      if (length > longest.length) {
        longest.length = length;
        longest.start = start;
        start = -1;
      }
    }
  }
  //get the longest 16-bit group of zeros
  for (let i = 0; i < this.buffer.length; i = i + 2) {
    if (this.buffer[i] === 0 && this.buffer[i + 1] === 0) {
      //its a group of zeros
      if (start < 0) {
        start = i;
      }

      // at the end of the buffer, make a final call to checkLongest.
      if(i === this.buffer.length - 2) {
        checkLongest(i+2);
      }
      continue;
    }
    //its a group of non-zeros
    checkLongest(i);
  }

  let address = '';
  for (let h = 0; h < this.buffer.length; h = h + 2) {
    if (h === longest.start) {
      address += ':';
      continue;
    }
    if (h < (longest.start + longest.length) && h > longest.start) {
      //its a group of zeros
      continue;
    }
    if (address.length > 0) {
      address += ':';
    }
    address += ((this.buffer[h] << 8) | this.buffer[h+1]).toString(16);
  }
  if (address.charAt(address.length-1) === ':') {
    address += ':';
  }
  return address;
};

/**
 * Returns the string representation.
 * Method used by the native JSON.stringify() to serialize this instance.
 */
InetAddress.prototype.toJSON = function () {
  return this.toString();
};

/**
 * Validates for a IPv4-Mapped IPv6 according to https://tools.ietf.org/html/rfc4291#section-2.5.5
 * @private
 * @param {Buffer} buffer
 */
function isValidIPv4Mapped(buffer) {
  // check the form
  // |      80 bits   | 16 |   32 bits
  // +----------------+----+-------------
  // |0000........0000|FFFF| IPv4 address

  for (let i = 0; i < buffer.length - 6; i++) {
    if (buffer[i] !== 0) {
      return false;
    }
  }
  return !(buffer[10] !== 255 || buffer[11] !== 255);
}

module.exports = InetAddress;


"use strict";
const util = require('util');
const events = require('events');
/**
 * Creates a new instance of DataCollection
 * @param {String} name Name of the data object.
 * @classdesc Describes a table or a view
 * @alias module:metadata~DataCollection
 * @constructor
 * @abstract
 */
function DataCollection(name) {
  events.EventEmitter.call(this);
  this.setMaxListeners(0);
  //private
  Object.defineProperty(this, 'loading', { value: false, enumerable: false, writable: true });
  Object.defineProperty(this, 'loaded', { value: false, enumerable: false, writable: true });
  /**
   * Name of the object
   * @type {String}
   */
  this.name = name;
  /**
   * False-positive probability for SSTable Bloom filters.
   * @type {number}
   */
  this.bloomFilterFalsePositiveChance = 0;
  /**
   * Level of caching: all, keys_only, rows_only, none
   * @type {String}
   */
  this.caching = null;
  /**
   * A human readable comment describing the table.
   * @type {String}
   */
  this.comment = null;
  /**
   * Specifies the time to wait before garbage collecting tombstones (deletion markers)
   * @type {number}
   */
  this.gcGraceSeconds = 0;
  /**
   * Compaction strategy class used for the table.
   * @type {String}
   */
  this.compactionClass = null;
  /**
   * Associative-array containing the compaction options keys and values.
   * @type {Object}
   */
  this.compactionOptions = null;
  /**
   * Associative-array containing the compression options.
   * @type {Object}
   */
  this.compression = null;
  /**
   * Specifies the probability of read repairs being invoked over all replicas in the current data center.
   * @type {number}
   */
  this.localReadRepairChance = 0;
  /**
   * Specifies the probability with which read repairs should be invoked on non-quorum reads. The value must be
   * between 0 and 1.
   * @type {number}
   */
  this.readRepairChance = 0;
  /**
   * An associative Array containing extra metadata for the table.
   *

   * For Apache Cassandra versions prior to 3.0.0, this method always returns null.
   *

   * @type {Object}
   */
  this.extensions = null;
  /**
   * When compression is enabled, this option defines the probability
   * with which checksums for compressed blocks are checked during reads.
   * The default value for this options is 1.0 (always check).
   *

   *   For Apache Cassandra versions prior to 3.0.0, this method always returns null.
   *

   * @type {Number|null}
   */
  this.crcCheckChance = null;
  /**
   * Whether the populate I/O cache on flush is set on this table.
   * @type {Boolean}
   */
  this.populateCacheOnFlush = false;
  /**
   * Returns the default TTL for this table.
   * @type {Number}
   */
  this.defaultTtl = 0;
  /**
   * * Returns the speculative retry option for this table.
   * @type {String}
   */
  this.speculativeRetry = 'NONE';
  /**
   * Returns the minimum index interval option for this table.
   *

   *   Note: this option is available in Apache Cassandra 2.1 and above, and will return null for
   *   earlier versions.
   *

   * @type {Number|null}
   */
  this.minIndexInterval = 128;
  /**
   * Returns the maximum index interval option for this table.
   *

   * Note: this option is available in Apache Cassandra 2.1 and above, and will return null for
   * earlier versions.
   *

   * @type {Number|null}
   */
  this.maxIndexInterval = 2048;
  /**
   * Array describing the table columns.
   * @type {Array}
   */
  this.columns = null;
  /**
   * An associative Array of columns by name.
   * @type {Object}
   */
  this.columnsByName = null;
  /**
   * Array describing the columns that are part of the partition key.
   * @type {Array}
   */
  this.partitionKeys = [];
  /**
   * Array describing the columns that form the clustering key.
   * @type {Array}
   */
  this.clusteringKeys = [];
  /**
   * Array describing the clustering order of the columns in the same order as the clusteringKeys.
   * @type {Array}
   */
  this.clusteringOrder = [];
}

util.inherits(DataCollection, events.EventEmitter);

module.exports = DataCollection;


// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/** @module types */

var utils = require('../utils');

/**
 * Constructs a two's-complement integer an array containing bits of the
 * integer in 32-bit (signed) pieces, given in little-endian order (i.e.,
 * lowest-order bits in the first piece), and the sign of -1 or 0.
 *
 * See the from* functions below for other convenient ways of constructing
 * Integers.
 *
 * The internal representation of an integer is an array of 32-bit signed
 * pieces, along with a sign (0 or -1) that indicates the contents of all the
 * other 32-bit pieces out to infinity.  We use 32-bit pieces because these are
 * the size of integers on which Javascript performs bit-operations.  For
 * operations like addition and multiplication, we split each number into 16-bit
 * pieces, which can easily be multiplied within Javascript's floating-point
 * representation without overflow or change in sign.
 *
 * @constructor
 * @param {Array.<number>} bits Array containing the bits of the number.
 * @param {number} sign The sign of the number: -1 for negative and 0 positive.
 * @final
 */
function Integer (bits, sign) {
  /**
   * @type {!Array.<number>}
   * @private
   */
  this.bits_ = [];

  /**
   * @type {number}
   * @private
   */
  this.sign_ = sign;

  // Copy the 32-bit signed integer values passed in.  We prune out those at the
  // top that equal the sign since they are redundant.
  var top = true;
  for (var i = bits.length - 1; i >= 0; i--) {
    var val = bits[i] | 0;
    if (!top || val != sign) {
      this.bits_[i] = val;
      top = false;
    }
  }
}

// NOTE: Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the
// from* methods on which they depend.

/**
 * A cache of the Integer representations of small integer values.
 * @type {!Object}
 * @private
 */
Integer.IntCache_ = {};

/**
 * Returns an Integer representing the given (32-bit) integer value.
 * @param {number} value A 32-bit integer value.
 * @return {!Integer} The corresponding Integer value.
 */
Integer.fromInt = function(value) {
  if (-128 <= value && value < 128) {
    var cachedObj = Integer.IntCache_[value];
    if (cachedObj) {
      return cachedObj;
    }
  }

  var obj = new Integer([value | 0], value < 0 ? -1 : 0);
  if (-128 <= value && value < 128) {
    Integer.IntCache_[value] = obj;
  }
  return obj;
};

/**
 * Returns an Integer representing the given value, provided that it is a finite
 * number.  Otherwise, zero is returned.
 * @param {number} value The value in question.
 * @return {!Integer} The corresponding Integer value.
 */
Integer.fromNumber = function(value) {
  if (isNaN(value) || !isFinite(value)) {
    return Integer.ZERO;
  } else if (value < 0) {
    return Integer.fromNumber(-value).negate();
  } else {
    var bits = [];
    var pow = 1;
    for (var i = 0; value >= pow; i++) {
      bits[i] = (value / pow) | 0;
      pow *= Integer.TWO_PWR_32_DBL_;
    }
    return new Integer(bits, 0);
  }
};

/**
 * Returns a Integer representing the value that comes by concatenating the
 * given entries, each is assumed to be 32 signed bits, given in little-endian
 * order (lowest order bits in the lowest index), and sign-extending the highest
 * order 32-bit value.
 * @param {Array.<number>} bits The bits of the number, in 32-bit signed pieces,
 *     in little-endian order.
 * @return {!Integer} The corresponding Integer value.
 */
Integer.fromBits = function(bits) {
  var high = bits[bits.length - 1];
  //noinspection JSBitwiseOperatorUsage
  return new Integer(bits, high & (1 << 31) ? -1 : 0);
};

/**
 * Returns an Integer representation of the given string, written using the
 * given radix.
 * @param {string} str The textual representation of the Integer.
 * @param {number=} opt_radix The radix in which the text is written.
 * @return {!Integer} The corresponding Integer value.
 */
Integer.fromString = function(str, opt_radix) {
  if (str.length == 0) {
    throw TypeError('number format error: empty string');
  }

  var radix = opt_radix || 10;
  if (radix < 2 || 36 < radix) {
    throw Error('radix out of range: ' + radix);
  }

  if (str.charAt(0) == '-') {
    return Integer.fromString(str.substring(1), radix).negate();
  } else if (str.indexOf('-') >= 0) {
    throw TypeError('number format error: interior "-" character');
  }

  // Do several (8) digits each time through the loop, so as to
  // minimize the calls to the very expensive emulated div.
  var radixToPower = Integer.fromNumber(Math.pow(radix, 8));

  var result = Integer.ZERO;
  for (var i = 0; i < str.length; i += 8) {
    var size = Math.min(8, str.length - i);
    var value = parseInt(str.substring(i, i + size), radix);
    if (size < 8) {
      var power = Integer.fromNumber(Math.pow(radix, size));
      result = result.multiply(power).add(Integer.fromNumber(value));
    } else {
      result = result.multiply(radixToPower);
      result = result.add(Integer.fromNumber(value));
    }
  }
  return result;
};

/**
 * Returns an Integer representation of a given big endian Buffer.
 * The internal representation of bits contains bytes in groups of 4
 * @param {Buffer} buf
 * @returns {Integer}
 */
Integer.fromBuffer = function (buf) {
  var bits = new Array(Math.ceil(buf.length / 4));
  //noinspection JSBitwiseOperatorUsage
  var sign = buf[0] & (1 << 7) ? -1 : 0;
  for (var i = 0; i < bits.length; i++) {
    var offset = buf.length - ((i + 1) * 4);
    var value;
    if (offset < 0) {
      //The buffer length is not multiple of 4
      offset = offset + 4;
      value = 0;
      for (var j = 0; j < offset; j++) {
        var byte = buf[j];
        if (sign === -1) {
          //invert the bits
          byte = ~byte & 0xff;
        }
        value = value | (byte << (offset - j - 1) * 8);
      }
      if (sign === -1) {
        //invert all the bits
        value = ~value;
      }
    }
    else {
      value = buf.readInt32BE(offset);
    }
    bits[i] = value;
  }
  return new Integer(bits, sign);
};

/**
 * Returns a big endian buffer representation of an Integer.
 * Internally the bits are represented using 4 bytes groups (numbers),
 * in the Buffer representation there might be the case where we need less than the 4 bytes.
 * For example: 0x00000001 -> '01', 0xFFFFFFFF -> 'FF', 0xFFFFFF01 -> 'FF01'
 * @param {Integer} value
 * @returns {Buffer}
*/
Integer.toBuffer = function (value) {
  var sign = value.sign_;
  var bits = value.bits_;
  if (bits.length === 0) {
    //[0] or [0xffffffff]
    return utils.allocBufferFromArray([value.sign_]);
  }
  //the high bits might need to be represented in less than 4 bytes
  var highBits = bits[bits.length-1];
  if (sign === -1) {
    highBits = ~highBits;
  }
  var high = [];
  if (highBits >>> 24 > 0) {
    high.push((highBits >> 24) & 0xff);
  }
  if (highBits >>> 16 > 0) {
    high.push((highBits >> 16) & 0xff);
  }
  if (highBits >>> 8 > 0) {
    high.push((highBits >> 8) & 0xff);
  }
  high.push(highBits & 0xff);
  if (sign === -1) {
    //The byte containing the sign bit got removed
    if (high[0] >> 7 !== 0) {
      //it is going to be negated
      high.unshift(0);
    }
  }
  else if (high[0] >> 7 !== 0) {
    //its positive but it lost the byte containing the sign bit
    high.unshift(0);
  }
  var buf = utils.allocBufferUnsafe(high.length + ((bits.length-1) * 4));
  for (var j = 0; j < high.length; j++) {
    var b = high[j];
    if (sign === -1) {
      buf[j] = ~b;
    }
    else {
      buf[j] = b;
    }
  }
  for (var i = 0; i < bits.length - 1; i++) {
    var group = bits[bits.length - 2 - i];
    var offset = high.length + i * 4;
    buf.writeInt32BE(group, offset);
  }
  return buf;
};

/**
 * A number used repeatedly in calculations.  This must appear before the first
 * call to the from* functions below.
 * @type {number}
 * @private
 */
Integer.TWO_PWR_32_DBL_ = (1 << 16) * (1 << 16);

/** @type {!Integer} */
Integer.ZERO = Integer.fromInt(0);

/** @type {!Integer} */
Integer.ONE = Integer.fromInt(1);

/**
 * @type {!Integer}
 * @private
 */
Integer.TWO_PWR_24_ = Integer.fromInt(1 << 24);

/**
 * Returns the value, assuming it is a 32-bit integer.
 * @return {number} The corresponding int value.
 */
Integer.prototype.toInt = function() {
  return this.bits_.length > 0 ? this.bits_[0] : this.sign_;
};

/** @return {number} The closest floating-point representation to this value. */
Integer.prototype.toNumber = function() {
  if (this.isNegative()) {
    return -this.negate().toNumber();
  } else {
    var val = 0;
    var pow = 1;
    for (var i = 0; i < this.bits_.length; i++) {
      val += this.getBitsUnsigned(i) * pow;
      pow *= Integer.TWO_PWR_32_DBL_;
    }
    return val;
  }
};

/**
 * @param {number=} opt_radix The radix in which the text should be written.
 * @return {string} The textual representation of this value.
 * @override
 */
Integer.prototype.toString = function(opt_radix) {
  var radix = opt_radix || 10;
  if (radix < 2 || 36 < radix) {
    throw Error('radix out of range: ' + radix);
  }

  if (this.isZero()) {
    return '0';
  } else if (this.isNegative()) {
    return '-' + this.negate().toString(radix);
  }

  // Do several (6) digits each time through the loop, so as to
  // minimize the calls to the very expensive emulated div.
  var radixToPower = Integer.fromNumber(Math.pow(radix, 6));

  var rem = this;
  var result = '';
  while (true) {
    var remDiv = rem.divide(radixToPower);
    var intval = rem.subtract(remDiv.multiply(radixToPower)).toInt();
    var digits = intval.toString(radix);

    rem = remDiv;
    if (rem.isZero()) {
      return digits + result;
    } else {
      while (digits.length < 6) {
        digits = '0' + digits;
      }
      result = '' + digits + result;
    }
  }
};

/**
 * Returns the index-th 32-bit (signed) piece of the Integer according to
 * little-endian order (i.e., index 0 contains the smallest bits).
 * @param {number} index The index in question.
 * @return {number} The requested 32-bits as a signed number.
 */
Integer.prototype.getBits = function(index) {
  if (index < 0) {
    return 0;  // Allowing this simplifies bit shifting operations below...
  } else if (index < this.bits_.length) {
    return this.bits_[index];
  } else {
    return this.sign_;
  }
};

/**
 * Returns the index-th 32-bit piece as an unsigned number.
 * @param {number} index The index in question.
 * @return {number} The requested 32-bits as an unsigned number.
 */
Integer.prototype.getBitsUnsigned = function(index) {
  var val = this.getBits(index);
  return val >= 0 ? val : Integer.TWO_PWR_32_DBL_ + val;
};

/** @return {number} The sign bit of this number, -1 or 0. */
Integer.prototype.getSign = function() {
  return this.sign_;
};

/** @return {boolean} Whether this value is zero. */
Integer.prototype.isZero = function() {
  if (this.sign_ != 0) {
    return false;
  }
  for (var i = 0; i < this.bits_.length; i++) {
    if (this.bits_[i] != 0) {
      return false;
    }
  }
  return true;
};

/** @return {boolean} Whether this value is negative. */
Integer.prototype.isNegative = function() {
  return this.sign_ == -1;
};

/** @return {boolean} Whether this value is odd. */
Integer.prototype.isOdd = function() {
  return (this.bits_.length == 0) && (this.sign_ == -1) ||
    (this.bits_.length > 0) && ((this.bits_[0] & 1) != 0);
};

/**
 * @param {Integer} other Integer to compare against.
 * @return {boolean} Whether this Integer equals the other.
 */
Integer.prototype.equals = function(other) {
  if (this.sign_ != other.sign_) {
    return false;
  }
  var len = Math.max(this.bits_.length, other.bits_.length);
  for (var i = 0; i < len; i++) {
    if (this.getBits(i) != other.getBits(i)) {
      return false;
    }
  }
  return true;
};

/**
 * @param {Integer} other Integer to compare against.
 * @return {boolean} Whether this Integer does not equal the other.
 */
Integer.prototype.notEquals = function(other) {
  return !this.equals(other);
};

/**
 * @param {Integer} other Integer to compare against.
 * @return {boolean} Whether this Integer is greater than the other.
 */
Integer.prototype.greaterThan = function(other) {
  return this.compare(other) > 0;
};

/**
 * @param {Integer} other Integer to compare against.
 * @return {boolean} Whether this Integer is greater than or equal to the other.
 */
Integer.prototype.greaterThanOrEqual = function(other) {
  return this.compare(other) >= 0;
};

/**
 * @param {Integer} other Integer to compare against.
 * @return {boolean} Whether this Integer is less than the other.
 */
Integer.prototype.lessThan = function(other) {
  return this.compare(other) < 0;
};

/**
 * @param {Integer} other Integer to compare against.
 * @return {boolean} Whether this Integer is less than or equal to the other.
 */
Integer.prototype.lessThanOrEqual = function(other) {
  return this.compare(other) <= 0;
};

/**
 * Compares this Integer with the given one.
 * @param {Integer} other Integer to compare against.
 * @return {number} 0 if they are the same, 1 if the this is greater, and -1
 *     if the given one is greater.
 */
Integer.prototype.compare = function(other) {
  var diff = this.subtract(other);
  if (diff.isNegative()) {
    return -1;
  } else if (diff.isZero()) {
    return 0;
  } else {
    return +1;
  }
};

/**
 * Returns an integer with only the first numBits bits of this value, sign
 * extended from the final bit.
 * @param {number} numBits The number of bits by which to shift.
 * @return {!Integer} The shorted integer value.
 */
Integer.prototype.shorten = function(numBits) {
  var arr_index = (numBits - 1) >> 5;
  var bit_index = (numBits - 1) % 32;
  var bits = [];
  for (var i = 0; i < arr_index; i++) {
    bits[i] = this.getBits(i);
  }
  var sigBits = bit_index == 31 ? 0xFFFFFFFF : (1 << (bit_index + 1)) - 1;
  var val = this.getBits(arr_index) & sigBits;
  //noinspection JSBitwiseOperatorUsage
  if (val & (1 << bit_index)) {
    val |= 0xFFFFFFFF - sigBits;
    bits[arr_index] = val;
    return new Integer(bits, -1);
  } else {
    bits[arr_index] = val;
    return new Integer(bits, 0);
  }
};

/** @return {!Integer} The negation of this value. */
Integer.prototype.negate = function() {
  return this.not().add(Integer.ONE);
};

/**
 * Returns the sum of this and the given Integer.
 * @param {Integer} other The Integer to add to this.
 * @return {!Integer} The Integer result.
 */
Integer.prototype.add = function(other) {
  var len = Math.max(this.bits_.length, other.bits_.length);
  var arr = [];
  var carry = 0;

  for (var i = 0; i <= len; i++) {
    var a1 = this.getBits(i) >>> 16;
    var a0 = this.getBits(i) & 0xFFFF;

    var b1 = other.getBits(i) >>> 16;
    var b0 = other.getBits(i) & 0xFFFF;

    var c0 = carry + a0 + b0;
    var c1 = (c0 >>> 16) + a1 + b1;
    carry = c1 >>> 16;
    c0 &= 0xFFFF;
    c1 &= 0xFFFF;
    arr[i] = (c1 << 16) | c0;
  }
  return Integer.fromBits(arr);
};

/**
 * Returns the difference of this and the given Integer.
 * @param {Integer} other The Integer to subtract from this.
 * @return {!Integer} The Integer result.
 */
Integer.prototype.subtract = function(other) {
  return this.add(other.negate());
};

/**
 * Returns the product of this and the given Integer.
 * @param {Integer} other The Integer to multiply against this.
 * @return {!Integer} The product of this and the other.
 */
Integer.prototype.multiply = function(other) {
  if (this.isZero()) {
    return Integer.ZERO;
  } else if (other.isZero()) {
    return Integer.ZERO;
  }

  if (this.isNegative()) {
    if (other.isNegative()) {
      return this.negate().multiply(other.negate());
    } else {
      return this.negate().multiply(other).negate();
    }
  } else if (other.isNegative()) {
    return this.multiply(other.negate()).negate();
  }

  // If both numbers are small, use float multiplication
  if (this.lessThan(Integer.TWO_PWR_24_) &&
    other.lessThan(Integer.TWO_PWR_24_)) {
    return Integer.fromNumber(this.toNumber() * other.toNumber());
  }

  // Fill in an array of 16-bit products.
  var len = this.bits_.length + other.bits_.length;
  var arr = [];
  for (var i = 0; i < 2 * len; i++) {
    arr[i] = 0;
  }
  for (var i = 0; i < this.bits_.length; i++) {
    for (var j = 0; j < other.bits_.length; j++) {
      var a1 = this.getBits(i) >>> 16;
      var a0 = this.getBits(i) & 0xFFFF;

      var b1 = other.getBits(j) >>> 16;
      var b0 = other.getBits(j) & 0xFFFF;

      arr[2 * i + 2 * j] += a0 * b0;
      Integer.carry16_(arr, 2 * i + 2 * j);
      arr[2 * i + 2 * j + 1] += a1 * b0;
      Integer.carry16_(arr, 2 * i + 2 * j + 1);
      arr[2 * i + 2 * j + 1] += a0 * b1;
      Integer.carry16_(arr, 2 * i + 2 * j + 1);
      arr[2 * i + 2 * j + 2] += a1 * b1;
      Integer.carry16_(arr, 2 * i + 2 * j + 2);
    }
  }

  // Combine the 16-bit values into 32-bit values.
  for (var i = 0; i < len; i++) {
    arr[i] = (arr[2 * i + 1] << 16) | arr[2 * i];
  }
  for (var i = len; i < 2 * len; i++) {
    arr[i] = 0;
  }
  return new Integer(arr, 0);
};

/**
 * Carries any overflow from the given index into later entries.
 * @param {Array.<number>} bits Array of 16-bit values in little-endian order.
 * @param {number} index The index in question.
 * @private
 */
Integer.carry16_ = function(bits, index) {
  while ((bits[index] & 0xFFFF) != bits[index]) {
    bits[index + 1] += bits[index] >>> 16;
    bits[index] &= 0xFFFF;
  }
};

/**
 * Returns this Integer divided by the given one.
 * @param {Integer} other Th Integer to divide this by.
 * @return {!Integer} This value divided by the given one.
 */
Integer.prototype.divide = function(other) {
  if (other.isZero()) {
    throw Error('division by zero');
  } else if (this.isZero()) {
    return Integer.ZERO;
  }

  if (this.isNegative()) {
    if (other.isNegative()) {
      return this.negate().divide(other.negate());
    } else {
      return this.negate().divide(other).negate();
    }
  } else if (other.isNegative()) {
    return this.divide(other.negate()).negate();
  }

  // Repeat the following until the remainder is less than other:  find a
  // floating-point that approximates remainder / other *from below*, add this
  // into the result, and subtract it from the remainder.  It is critical that
  // the approximate value is less than or equal to the real value so that the
  // remainder never becomes negative.
  var res = Integer.ZERO;
  var rem = this;
  while (rem.greaterThanOrEqual(other)) {
    // Approximate the result of division. This may be a little greater or
    // smaller than the actual value.
    var approx = Math.max(1, Math.floor(rem.toNumber() / other.toNumber()));

    // We will tweak the approximate result by changing it in the 48-th digit or
    // the smallest non-fractional digit, whichever is larger.
    var log2 = Math.ceil(Math.log(approx) / Math.LN2);
    var delta = (log2 <= 48) ? 1 : Math.pow(2, log2 - 48);

    // Decrease the approximation until it is smaller than the remainder.  Note
    // that if it is too large, the product overflows and is negative.
    var approxRes = Integer.fromNumber(approx);
    var approxRem = approxRes.multiply(other);
    while (approxRem.isNegative() || approxRem.greaterThan(rem)) {
      approx -= delta;
      approxRes = Integer.fromNumber(approx);
      approxRem = approxRes.multiply(other);
    }

    // We know the answer can't be zero... and actually, zero would cause
    // infinite recursion since we would make no progress.
    if (approxRes.isZero()) {
      approxRes = Integer.ONE;
    }

    res = res.add(approxRes);
    rem = rem.subtract(approxRem);
  }
  return res;
};

/**
 * Returns this Integer modulo the given one.
 * @param {Integer} other The Integer by which to mod.
 * @return {!Integer} This value modulo the given one.
 */
Integer.prototype.modulo = function(other) {
  return this.subtract(this.divide(other).multiply(other));
};

/** @return {!Integer} The bitwise-NOT of this value. */
Integer.prototype.not = function() {
  var len = this.bits_.length;
  var arr = [];
  for (var i = 0; i < len; i++) {
    arr[i] = ~this.bits_[i];
  }
  return new Integer(arr, ~this.sign_);
};

/**
 * Returns the bitwise-AND of this Integer and the given one.
 * @param {Integer} other The Integer to AND with this.
 * @return {!Integer} The bitwise-AND of this and the other.
 */
Integer.prototype.and = function(other) {
  var len = Math.max(this.bits_.length, other.bits_.length);
  var arr = [];
  for (var i = 0; i < len; i++) {
    arr[i] = this.getBits(i) & other.getBits(i);
  }
  return new Integer(arr, this.sign_ & other.sign_);
};

/**
 * Returns the bitwise-OR of this Integer and the given one.
 * @param {Integer} other The Integer to OR with this.
 * @return {!Integer} The bitwise-OR of this and the other.
 */
Integer.prototype.or = function(other) {
  var len = Math.max(this.bits_.length, other.bits_.length);
  var arr = [];
  for (var i = 0; i < len; i++) {
    arr[i] = this.getBits(i) | other.getBits(i);
  }
  return new Integer(arr, this.sign_ | other.sign_);
};

/**
 * Returns the bitwise-XOR of this Integer and the given one.
 * @param {Integer} other The Integer to XOR with this.
 * @return {!Integer} The bitwise-XOR of this and the other.
 */
Integer.prototype.xor = function(other) {
  var len = Math.max(this.bits_.length, other.bits_.length);
  var arr = [];
  for (var i = 0; i < len; i++) {
    arr[i] = this.getBits(i) ^ other.getBits(i);
  }
  return new Integer(arr, this.sign_ ^ other.sign_);
};

/**
 * Returns this value with bits shifted to the left by the given amount.
 * @param {number} numBits The number of bits by which to shift.
 * @return {!Integer} This shifted to the left by the given amount.
 */
Integer.prototype.shiftLeft = function(numBits) {
  var arr_delta = numBits >> 5;
  var bit_delta = numBits % 32;
  var len = this.bits_.length + arr_delta + (bit_delta > 0 ? 1 : 0);
  var arr = [];
  for (var i = 0; i < len; i++) {
    if (bit_delta > 0) {
      arr[i] = (this.getBits(i - arr_delta) << bit_delta) |
      (this.getBits(i - arr_delta - 1) >>> (32 - bit_delta));
    } else {
      arr[i] = this.getBits(i - arr_delta);
    }
  }
  return new Integer(arr, this.sign_);
};

/**
 * Returns this value with bits shifted to the right by the given amount.
 * @param {number} numBits The number of bits by which to shift.
 * @return {!Integer} This shifted to the right by the given amount.
 */
Integer.prototype.shiftRight = function(numBits) {
  var arr_delta = numBits >> 5;
  var bit_delta = numBits % 32;
  var len = this.bits_.length - arr_delta;
  var arr = [];
  for (var i = 0; i < len; i++) {
    if (bit_delta > 0) {
      arr[i] = (this.getBits(i + arr_delta) >>> bit_delta) |
      (this.getBits(i + arr_delta + 1) << (32 - bit_delta));
    } else {
      arr[i] = this.getBits(i + arr_delta);
    }
  }
  return new Integer(arr, this.sign_);
};

/**
 * Provide the name of the constructor and the string representation
 * @returns {string}
 */
Integer.prototype.inspect = function () {
  return this.constructor.name + ': ' + this.toString();
};

/**
 * Returns a Integer whose value is the absolute value of this
 * @returns {Integer}
 */
Integer.prototype.abs = function () {
  return this.sign_ === 0 ? this : this.negate();
};

/**
 * Returns the string representation.
 * Method used by the native JSON.stringify() to serialize this instance.
 */
Integer.prototype.toJSON = function () {
  return this.toString();
};

module.exports = Integer;


'use strict';
const util = require('util');
const stream = require('stream');
const Transform = stream.Transform;
const Writable = stream.Writable;

const types = require('./types');
const utils = require('./utils');
const errors = require('./errors');
const FrameHeader = types.FrameHeader;
const FrameReader = require('./readers').FrameReader;

/**
 * Transforms chunks, emits data objects {header, chunk}
 * @param options Stream options
 * @extends Transform
 */
function Protocol (options) {
  Transform.call(this, options);
  this.header = null;
  this.bodyLength = 0;
  this.clearHeaderChunks();
  this.version = 0;
  this.headerSize = 0;
}

util.inherits(Protocol, Transform);

Protocol.prototype._transform = function (chunk, encoding, callback) {
  let error = null;
  try {
    this.readItems(chunk);
  }
  catch (err) {
    error = err;
  }
  callback(error);
};

/**
 * Parses the chunk into frames (header and body).
 * Emits (push) complete frames or frames with incomplete bodies. Following chunks containing the rest of the body will
 * be emitted using the same frame.
 * It buffers incomplete headers.
 * @param {Buffer} chunk
 */
Protocol.prototype.readItems = function (chunk) {
  if (!chunk || chunk.length === 0) {
    return;
  }
  if (this.version === 0) {
    //The server replies the first message with the max protocol version supported
    this.version = FrameHeader.getProtocolVersion(chunk);
    this.headerSize = FrameHeader.size(this.version);
  }
  let offset = 0;
  let currentHeader = this.header;
  this.header = null;
  if (this.headerChunks.byteLength !== 0) {
    //incomplete header was buffered try to read the header from the buffered chunks
    this.headerChunks.parts.push(chunk);
    if (this.headerChunks.byteLength + chunk.length < this.headerSize) {
      this.headerChunks.byteLength += chunk.length;
      return;
    }
    currentHeader = FrameHeader.fromBuffer(Buffer.concat(this.headerChunks.parts, this.headerSize));
    offset = this.headerSize - this.headerChunks.byteLength;
    this.clearHeaderChunks();
  }
  const items = [];
  while (true) {
    if (!currentHeader) {
      if (this.headerSize > chunk.length - offset) {
        if (chunk.length - offset <= 0) {
          break;
        }
        //the header is incomplete, buffer it until the next chunk
        const headerPart = chunk.slice(offset, chunk.length);
        this.headerChunks.parts.push(headerPart);
        this.headerChunks.byteLength = headerPart.length;
        break;
      }
      //read header
      currentHeader = FrameHeader.fromBuffer(chunk, offset);
      offset += this.headerSize;
    }
    //parse body
    const remaining = chunk.length - offset;
    if (currentHeader.bodyLength <= remaining + this.bodyLength) {
      items.push({ header: currentHeader, chunk: chunk, offset: offset, frameEnded: true });
      offset += currentHeader.bodyLength - this.bodyLength;
      //reset the body length
      this.bodyLength = 0;
    }
    else if (remaining >= 0) {
      //the body is not fully contained in this chunk
      //will continue later
      this.header = currentHeader;
      this.bodyLength += remaining;
      if (remaining > 0) {
        //emit if there is at least a byte to emit
        items.push({ header: currentHeader, chunk: chunk, offset: offset, frameEnded: false });
      }
      break;
    }
    currentHeader = null;
  }
  for (let i = 0; i < items.length; i++) {
    this.push(items[i]);
  }
};

Protocol.prototype.clearHeaderChunks = function () {
  this.headerChunks = { byteLength: 0, parts: [] };
};

/**
 * A stream that gets reads header + body chunks and transforms them into header + (row | error)
 * @param {Object} streamOptions Node.js Stream options
 * @param {Encoder} encoder Encoder instance for the parser to use
 * @extends Transform
 */
function Parser (streamOptions, encoder) {
  Transform.call(this, streamOptions);
  //frames that are streaming, indexed by id
  this.frames = {};
  this.encoder = encoder;
}

util.inherits(Parser, Transform);

Parser.prototype._transform = function (item, encoding, callback) {
  const frameInfo = this.frameState(item);

  let error = null;
  try {
    this.parseBody(frameInfo, item);
  }
  catch (err) {
    error = err;
  }
  callback(error);

  if (item.frameEnded) {
    if (frameInfo.cellBuffer) {
      //Frame was being streamed but an error force it to buffer the result
      this.push({
        header: frameInfo.header,
        error: new errors.DriverInternalError('There was an problem while parsing streaming frame, opcode ' +
          frameInfo.header.opcode)
      });
    }
    //all the parsing finished and it was streamed down
    //emit an item that signals it
    this.push({ header: frameInfo.header, frameEnded: true});
  }
};

/**
 * @param frameInfo
 * @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
 */
Parser.prototype.parseBody = function (frameInfo, item) {
  frameInfo.isStreaming = frameInfo.byRow && item.header.opcode === types.opcodes.result;
  if (!this.handleFrameBuffers(frameInfo, item)) {
    // Frame isn't complete and we are not streaming the frame
    return;
  }
  const reader = new FrameReader(item.header, item.chunk, item.offset);
  // Check that flags have not been parsed yet for this frame
  if (frameInfo.flagsInfo === undefined) {
    const originalOffset = reader.offset;
    try {
      frameInfo.flagsInfo = reader.readFlagsInfo();
    }
    catch (e) {
      return this.handleParsingError(e, frameInfo, reader, originalOffset);
    }
  }

  //All the body for most operations is already buffered at this stage
  //Except for RESULT
  switch (item.header.opcode) {
    case types.opcodes.result:
      return this.parseResult(frameInfo, reader);
    case types.opcodes.ready:
    case types.opcodes.authSuccess:
      return this.push({ header: frameInfo.header, ready: true });
    case types.opcodes.authChallenge:
      return this.push({ header: frameInfo.header, authChallenge: true, token: reader.readBytes()});
    case types.opcodes.authenticate:
      return this.push({ header: frameInfo.header, mustAuthenticate: true, authenticatorName: reader.readString()});
    case types.opcodes.error:
      return this.push({ header: frameInfo.header, error: reader.readError()});
    case types.opcodes.supported:
      return this.push({ header: frameInfo.header });
    case types.opcodes.event:
      return this.push({ header: frameInfo.header, event: reader.readEvent()});
    default:
      return this.push({ header: frameInfo.header, error: new Error('Received invalid opcode: ' + item.header.opcode) });
  }
};

/**
 * Buffers if needed and returns true if it has all the necessary data to continue parsing the frame.
 * @param frameInfo
 * @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
 * @returns {Boolean}
 */
Parser.prototype.handleFrameBuffers = function (frameInfo, item) {
  if (!frameInfo.isStreaming) {
    // Handle buffering for complete frame bodies
    const currentLength = (frameInfo.bufferLength || 0) + item.chunk.length - item.offset;
    if (currentLength < item.header.bodyLength) {
      //buffer until the frame is completed
      this.addFrameBuffer(frameInfo, item);
      return false;
    }
    //We have received the full frame body
    if (frameInfo.buffers) {
      item.chunk = this.getFrameBuffer(frameInfo, item);
      item.offset = 0;
    }
    return true;
  }
  if (frameInfo.cellBuffer) {
    // Handle buffering for frame cells (row cells or metadata cells)
    if (item.offset !== 0) {
      throw new errors.DriverInternalError('Following chunks can not have an offset greater than zero');
    }
    frameInfo.cellBuffer.parts.push(item.chunk);
    if (!frameInfo.cellBuffer.expectedLength) {
      //Its a buffer outside a row cell (metadata or other)
      if (frameInfo.cellBuffer.parts.length !== 2) {
        throw new errors.DriverInternalError('Buffer for streaming frame can not contain more than 1 item');
      }
      item.chunk = Buffer.concat(frameInfo.cellBuffer.parts, frameInfo.cellBuffer.byteLength + item.chunk.length);
      frameInfo.cellBuffer = null;
      return true;
    }
    if (frameInfo.cellBuffer.expectedLength > frameInfo.cellBuffer.byteLength + item.chunk.length) {
      //We still haven't got the cell data
      frameInfo.cellBuffer.byteLength += item.chunk.length;
      return false;
    }
    item.chunk = Buffer.concat(frameInfo.cellBuffer.parts, frameInfo.cellBuffer.byteLength + item.chunk.length);
    frameInfo.cellBuffer = null;
  }
  return true;
};

/**
 * Adds this chunk to the frame buffers.
 * @param frameInfo
 * @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
 */
Parser.prototype.addFrameBuffer = function (frameInfo, item) {
  if (!frameInfo.buffers) {
    frameInfo.buffers = [ item.chunk.slice(item.offset) ];
    frameInfo.bufferLength = item.chunk.length - item.offset;
    return;
  }
  if (item.offset > 0) {
    throw new errors.DriverInternalError('Following chunks can not have an offset greater than zero');
  }
  frameInfo.buffers.push(item.chunk);
  frameInfo.bufferLength += item.chunk.length;
};

/**
 * Adds the last chunk and concatenates the frame buffers
 * @param frameInfo
 * @param {{header: FrameHeader, chunk: Buffer, offset: Number}} item
 */
Parser.prototype.getFrameBuffer = function (frameInfo, item) {
  frameInfo.buffers.push(item.chunk);
  const result = Buffer.concat(frameInfo.buffers, frameInfo.bodyLength);
  frameInfo.buffers = null;
  return result;
};

/**
 * Tries to read the result in the body of a message
 * @param frameInfo Frame information, header / metadata
 * @param {FrameReader} reader
 */
Parser.prototype.parseResult = function (frameInfo, reader) {
  let result;
  // As we might be streaming and the frame buffer might not be complete,
  // read the metadata and different types of result values in a try-catch.
  // Store the reader position
  const originalOffset = reader.offset;
  try {
    if (!frameInfo.meta) {
      frameInfo.kind = reader.readInt();
      // Spec 4.2.5
      switch (frameInfo.kind) {
        case types.resultKind.voidResult:
          result = { header: frameInfo.header, flags: frameInfo.flagsInfo };
          break;
        case types.resultKind.rows:
          // Parse the rows metadata, the rest of the response is going to be parsed afterwards
          frameInfo.meta = reader.readMetadata(frameInfo.kind);
          break;
        case types.resultKind.setKeyspace:
          result = { header: frameInfo.header, keyspaceSet: reader.readString(), flags: frameInfo.flagsInfo };
          break;
        case types.resultKind.prepared:
        {
          const preparedId = utils.copyBuffer(reader.readShortBytes());
          frameInfo.meta = reader.readMetadata(frameInfo.kind);
          result = { header: frameInfo.header, id: preparedId, meta: frameInfo.meta, flags: frameInfo.flagsInfo };
          break;
        }
        case types.resultKind.schemaChange:
          result = { header: frameInfo.header, schemaChange: reader.parseSchemaChange(), flags: frameInfo.flagsInfo };
          break;
        default:
          throw errors.DriverInternalError('Unexpected result kind: ' + frameInfo.kind);
      }
    }
  }
  catch (e) {
    return this.handleParsingError(e, frameInfo, reader, originalOffset);
  }
  if (result) {
    if (frameInfo.emitted) {
      // It may contain additional metadata and info that it's not being parsed
      return;
    }
    frameInfo.emitted = true;
    return this.push(result);
  }
  // Its a `Rows` result
  if (reader.remainingLength() > 0) {
    this.parseRows(frameInfo, reader);
  }
};

/**
 * @param frameInfo
 * @param {FrameReader} reader
 */
Parser.prototype.parseRows = function (frameInfo, reader) {
  if (frameInfo.parsingError) {
    //No more processing on this frame
    return;
  }
  if (frameInfo.rowLength === undefined) {
    try {
      frameInfo.rowLength = reader.readInt();
    }
    catch (e) {
      return this.handleParsingError(e, frameInfo, reader);
    }
  }
  if (frameInfo.rowLength === 0) {
    return this.push({
      header: frameInfo.header,
      result: { rows: utils.emptyArray, meta: frameInfo.meta, flags: frameInfo.flagsInfo }
    });
  }
  const meta = frameInfo.meta;
  frameInfo.rowIndex = frameInfo.rowIndex || 0;
  for (let i = frameInfo.rowIndex; i < frameInfo.rowLength; i++) {
    const rowOffset = reader.offset;
    const row = new types.Row(meta.columns);
    let cellBuffer;
    for (let j = 0; j < meta.columns.length; j++ ) {
      const c = meta.columns[j];
      try {
        cellBuffer = reader.readBytes();
      }
      catch (e) {
        return this.handleParsingError(e, frameInfo, reader, rowOffset, i);
      }
      try {
        row[c.name] = this.encoder.decode(cellBuffer, c.type);
      }
      catch (e) {
        //Something went wrong while decoding, we are not going to be able to recover
        return this.handleParsingError(e, frameInfo, null);
      }
    }
    this.push({
      header: frameInfo.header,
      row: row,
      meta: frameInfo.meta,
      byRow: frameInfo.byRow,
      length: frameInfo.rowLength,
      flags: frameInfo.flagsInfo
    });
  }
};

/**
 * Sets parser options (ie: how to yield the results as they are parsed)
 * @param {Number} id Id of the stream
 * @param options
 */
Parser.prototype.setOptions = function (id, options) {
  if (this.frames[id.toString()]) {
    throw new types.DriverError('There was already state for this frame');
  }
  this.frames[id.toString()] = options;
};

/**
 * Gets the frame info from the internal state.
 * In case it is not there, it creates it.
 * In case the frame ended
 */
Parser.prototype.frameState = function (item) {
  let frameInfo = this.frames[item.header.streamId];
  if (!frameInfo) {
    frameInfo = {};
    if (!item.frameEnded) {
      //store it in the frames
      this.frames[item.header.streamId] = frameInfo;
    }
  }
  else if (item.frameEnded) {
    //if it was already stored, remove it
    delete this.frames[item.header.streamId];
  }
  frameInfo.header = item.header;
  return frameInfo;
};

/**
 * Handles parsing error: pushing an error if its unexpected or buffer the cell if its streaming
 * @param {Error} e
 * @param frameInfo
 * @param {FrameReader} reader
 * @param {Number} [originalOffset]
 * @param {Number} [rowIndex]
 */
Parser.prototype.handleParsingError = function (e, frameInfo, reader, originalOffset, rowIndex) {
  if (reader && frameInfo.isStreaming && (e instanceof RangeError)) {
    //A controlled error, buffer from offset and move on
    return this.bufferResultCell(frameInfo, reader, originalOffset, rowIndex, e.expectedLength);
  }
  frameInfo.parsingError = true;
  frameInfo.cellBuffer = null;
  this.push({ header: frameInfo.header, error: e });
};

/**
 * When streaming, it buffers data since originalOffset.
 * @param frameInfo
 * @param {FrameReader} reader
 * @param {Number} [originalOffset]
 * @param {Number} [rowIndex]
 * @param {Number} [expectedLength]
 */
Parser.prototype.bufferResultCell = function (frameInfo, reader, originalOffset, rowIndex, expectedLength) {
  if (!originalOffset && originalOffset !== 0) {
    originalOffset = reader.offset;
  }
  frameInfo.rowIndex = rowIndex;
  const buffer = reader.slice(originalOffset);
  frameInfo.cellBuffer = {
    parts: [ buffer ],
    byteLength: buffer.length,
    expectedLength: expectedLength
  };
};

/**
 * Represents a writable streams that emits results
 */
function ResultEmitter(options) {
  Writable.call(this, options);
  /**
   * Stores the rows for frames that needs to be yielded as one result with many rows
   */
  this.rowBuffer = {};
}

util.inherits(ResultEmitter, Writable);

ResultEmitter.prototype._write = function (item, encoding, callback) {
  let error = null;
  try {
    this.each(item);
  }
  catch (err) {
    error = err;
  }
  callback(error);
};

/**
 * Analyzes the item and emit the corresponding event
 */
ResultEmitter.prototype.each = function (item) {
  if (item.error || item.result) {
    //Its either an error or an empty array rows
    //no transformation needs to be made
    return this.emit('result', item.header, item.error, item.result);
  }
  if (item.frameEnded) {
    return this.emit('frameEnded', item.header);
  }
  if (item.byRow) {
    //it should be yielded by row
    return this.emit('row', item.header, item.row, item.meta, item.length, item.flags);
  }
  if (item.row) {
    //it should be yielded as a result
    //it needs to be buffered to an array of rows
    return this.bufferAndEmit(item);
  }
  if (item.event) {
    //its an event from Cassandra
    return this.emit('nodeEvent', item.header, item.event);
  }
  //its a raw response (object with flags)
  return this.emit('result', item.header, null, item);
};

/**
 * Buffers the rows until the result set is completed and emits the result event.
 */
ResultEmitter.prototype.bufferAndEmit = function (item) {
  let rows = this.rowBuffer[item.header.streamId];
  if (!rows) {
    rows = this.rowBuffer[item.header.streamId] = [];
  }
  rows.push(item.row);
  if (rows.length === item.length) {
    this.emit('result', item.header, null, { rows: rows, meta: item.meta, flags: item.flags});
    delete this.rowBuffer[item.header.streamId];
  }
};

exports.Protocol = Protocol;
exports.Parser = Parser;
exports.ResultEmitter = ResultEmitter;


"use strict";
const cassandra = require('cassandra-driver');
const Uuid = cassandra.types.Uuid;

const client = new cassandra.Client({ contactPoints: ['127.0.0.1'], localDataCenter: 'datacenter1' });

/**
 * Inserts multiple rows in a table limiting the amount of parallel requests.
 *
 * Note that here is a built-in method in the driver executeConcurrent() that allows you to execute
 * multiple simultaneous requests using an Array or a Stream. Check out execute-concurrent-array.js for more
 * information.
 *
 * You can also use existing libraries to limit concurrency in a loop, for example:
 *  - https://caolan.github.io/async/docs.html#eachLimit
 *  - http://bluebirdjs.com/docs/api/promise.map.html
 */
async function example() {
  await client.connect();
  await client.execute(`CREATE KEYSPACE IF NOT EXISTS examples
                        WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1' }`);
  await client.execute(`USE examples`);
  await client.execute(`CREATE TABLE IF NOT EXISTS tbl_sample_kv (id uuid, value text, PRIMARY KEY (id))`);

  // The maximum amount of async executions that are going to be launched in parallel
  // at any given time
  const concurrencyLevel = 32;
  const promises = new Array(concurrencyLevel);

  const info = {
    totalLength: 10000,
    counter: 0
  };

  // Launch in parallel n async operations (n being the concurrency level)
  for (let i = 0; i < concurrencyLevel; i++) {
    promises[i] = executeOneAtATime(info);
  }

  try {
    // The n promises are going to be resolved when all the executions are completed.
    await Promise.all(promises);

    console.log(`Finished executing ${info.totalLength} queries with a concurrency level of ${concurrencyLevel}.`);

  } finally {
    client.shutdown();
  }
}

async function executeOneAtATime(info) {
  const query = 'INSERT INTO tbl_sample_kv (id, value) VALUES (?, ?)';
  const options = { prepare: true, isIdempotent: true };

  while (info.counter++ < info.totalLength) {
    const params = [ Uuid.random(), `Value for ${info.counter}`];
    await client.execute(query, params, options);
  }
}

example();


"use strict";

const util = require('util');
const DataCollection = require('./data-collection');

/**
 * Creates a new instance of TableMetadata
 * @classdesc Describes a table
 * @param {String} name Name of the Table
 * @augments {module:metadata~DataCollection}
 * @alias module:metadata~TableMetadata
 * @constructor
 */
function TableMetadata(name) {
  DataCollection.call(this, name);
  /**
   * Applies only to counter tables.
   * When set to true, replicates writes to all affected replicas regardless of the consistency level specified by
   * the client for a write request. For counter tables, this should always be set to true.
   * @type {Boolean}
   */
  this.replicateOnWrite = true;
  /**
   * Returns the memtable flush period (in milliseconds) option for this table.
   * @type {Number}
   */
  this.memtableFlushPeriod = 0;
  /**
   * Returns the index interval option for this table.
   *

   * Note: this option is only available in Apache Cassandra 2.0. It is deprecated in Apache Cassandra 2.1 and
   * above, and will therefore return null for 2.1 nodes.
   *

   * @type {Number|null}
   */
  this.indexInterval = null;
  /**
   * Determines  whether the table uses the COMPACT STORAGE option.
   * @type {Boolean}
   */
  this.isCompact = false;
  /**
   *
   * @type {Array.<Index>}
   */
  this.indexes = null;

  /**
   * Determines whether the Change Data Capture (CDC) flag is set for the table.
   * @type {Boolean|null}
   */
  this.cdc = null;

  /**
   * Determines whether the table is a virtual table or not.
   * @type {Boolean}
   */
  this.virtual = false;
}

util.inherits(TableMetadata, DataCollection);

module.exports = TableMetadata;