"use strict";
const events = require('events');
const util = require('util');
/**
 * Module containing classes and fields related to metadata.
 * @module metadata
 */
const t = require('../tokenizer');
const utils = require('../utils');
const errors = require('../errors');
const types = require('../types');
const requests = require('../requests');
const schemaParserFactory = require('./schema-parser');
const TokenRange = require('../token').TokenRange;
const ExecutionOptions = require('../execution-options').ExecutionOptions;

/**
 * @const
 * @private
 */
const _selectTraceSession = "SELECT * FROM system_traces.sessions WHERE session_id=%s";
/**
 * @const
 * @private
 */
const _selectTraceEvents = "SELECT * FROM system_traces.events WHERE session_id=%s";
/**
 * @const
 * @private
 */
const _selectSchemaVersionPeers = "SELECT schema_version FROM system.peers";
/**
 * @const
 * @private
 */
const _selectSchemaVersionLocal = "SELECT schema_version FROM system.local";
/**
 * @const
 * @private
 */
const _traceMaxAttemps = 5;
/**
 * @const
 * @private
 */
const _traceAttemptDelay = 200;

/**
 * Represents cluster and schema information.
 * The metadata class acts as a internal state of the driver.
 * @param {ClientOptions} options
 * @param {ControlConnection} controlConnection Control connection used to retrieve information.
 * @constructor
 */
function Metadata (options, controlConnection) {
  if (!options) {
    throw new errors.ArgumentError('Options are not defined');
  }
  Object.defineProperty(this, 'options', { value: options, enumerable: false, writable: false});
  Object.defineProperty(this, 'controlConnection', { value: controlConnection, enumerable: false, writable: false});
  this.keyspaces = {};
  this.initialized = false;
  this._schemaParser = schemaParserFactory.getByVersion(options, controlConnection, this.getUdt.bind(this));
  const self = this;
  this._preparedQueries = new PreparedQueries(options.maxPrepared, function () {
    self.log.apply(self, arguments);
  });
}

/**
 * Sets the cassandra version
 * @internal
 * @ignore
 * @param {Array.<Number>} version
 */
Metadata.prototype.setCassandraVersion = function (version) {
  this._schemaParser = schemaParserFactory.getByVersion(
    this.options, this.controlConnection, this.getUdt.bind(this), version, this._schemaParser);
};

/**
 * @ignore
 * @param {String} partitionerName
 */
Metadata.prototype.setPartitioner = function (partitionerName) {
  if (/RandomPartitioner$/.test(partitionerName)) {
    return this.tokenizer = new t.RandomTokenizer();
  }
  if (/ByteOrderedPartitioner$/.test(partitionerName)) {
    return this.tokenizer = new t.ByteOrderedTokenizer();
  }
  return this.tokenizer = new t.Murmur3Tokenizer();
};

/**
 * Populates the information regarding primary replica per token, datacenters (+ racks) and sorted token ring.
 * @ignore
 * @param {HostMap} hosts
 */
Metadata.prototype.buildTokens = function (hosts) {
  if (!this.tokenizer) {
    return this.log('error', 'Tokenizer could not be determined');
  }
  //Get a sorted array of tokens
  const allSorted = [];
  //Get a map of <token, primaryHost>
  const primaryReplicas = {};
  //Depending on the amount of tokens, this could be an expensive operation
  const hostArray = hosts.values();
  const stringify = this.tokenizer.stringify;
  const datacenters = {};
  hostArray.forEach((h) => {
    if (!h.tokens) {
      return;
    }
    h.tokens.forEach((tokenString) => {
      const token = this.tokenizer.parse(tokenString);
      utils.insertSorted(allSorted, token, (t1, t2) => t1.compare(t2));
      primaryReplicas[stringify(token)] = h;
    });
    let dc = datacenters[h.datacenter];
    if (!dc) {
      dc = datacenters[h.datacenter] = {
        hostLength: 0,
        racks: new utils.HashSet()
      };
    }
    dc.hostLength++;
    dc.racks.add(h.rack);
  });
  //Primary replica for given token
  this.primaryReplicas = primaryReplicas;
  //All the tokens in ring order
  this.ring = allSorted;

  // Build TokenRanges.
  const tokenRanges = new Set();
  if(this.ring.length === 1) {
    // If there is only one token, return the range ]minToken, minToken]
    const min = this.tokenizer.minToken();
    tokenRanges.add(new TokenRange(min, min, this.tokenizer));
  } else {
    for (let i = 0; i < this.ring.length; i++) {
      const start = this.ring[i];
      const end = this.ring[(i + 1) % this.ring.length];
      tokenRanges.add(new TokenRange(start, end, this.tokenizer));
    }
  }
  this.tokenRanges = tokenRanges;

  //Compute string versions as it's potentially expensive and frequently reused later
  this.ringTokensAsStrings = new Array(allSorted.length);
  for (let i = 0; i < allSorted.length; i++) {
    this.ringTokensAsStrings[i] = stringify(allSorted[i]);
  }
  //Datacenter metadata (host length and racks)
  this.datacenters = datacenters;
};

/**
 * Gets the keyspace metadata information and updates the internal state of the driver.
 *

 *   If a callback is provided, the callback is invoked when the keyspaces metadata refresh completes.
 *   Otherwise, it returns a Promise.
 *

 * @param {String} name Name of the keyspace.
 * @param {Function} [callback] Optional callback.
 */
Metadata.prototype.refreshKeyspace = function (name, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._refreshKeyspaceCb(name, cb);
  });
};

/**
 * @param {String} name
 * @param {Function} callback
 * @private
 */
Metadata.prototype._refreshKeyspaceCb = function (name, callback) {
  if (!this.initialized) {
    return callback(this._uninitializedError(), null);
  }
  this.log('info', util.format('Retrieving keyspace %s metadata', name));
  const self = this;
  this._schemaParser.getKeyspace(name, function (err, ksInfo) {
    if (err) {
      self.log('error', 'There was an error while trying to retrieve keyspace information', err);
      return callback(err);
    }
    if (!ksInfo) {
      // the keyspace was dropped
      delete self.keyspaces[name];
      return callback();
    }
    // tokens are lazily init on the keyspace, once a replica from that keyspace is retrieved.
    self.keyspaces[ksInfo.name] = ksInfo;
    callback(null, ksInfo);
  });
};

/**
 * Gets the metadata information of all the keyspaces and updates the internal state of the driver.
 *

 *   If a callback is provided, the callback is invoked when the keyspace metadata refresh completes.
 *   Otherwise, it returns a Promise.
 *

 * @param {Boolean|Function} [waitReconnect] Determines if it should wait for reconnection in case the control connection is not
 * connected at the moment. Default: true.
 * @param {Function} [callback] Optional callback.
 */
Metadata.prototype.refreshKeyspaces = function (waitReconnect, callback) {
  if (typeof waitReconnect === 'function' || typeof waitReconnect === 'undefined') {
    callback = waitReconnect;
    waitReconnect = true;
  }

  return this._refreshKeyspaces(waitReconnect, false, callback);
};

/**
 * @param {Boolean} waitReconnect
 * @param {Boolean} internal Whether or not this was called by driver (i.e. control connection)
 * @param {Function} [callback]
 * @private
 */
Metadata.prototype._refreshKeyspaces = function (waitReconnect, internal, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._refreshKeyspacesCb(waitReconnect, internal, cb);
  });
};

/**
 * @param {Boolean} waitReconnect
 * @param {Boolean} internal
 * @param {Function} callback
 * @private
 */
Metadata.prototype._refreshKeyspacesCb = function (waitReconnect, internal, callback) {
  if (!internal && !this.initialized) {
    return callback(this._uninitializedError(), null);
  }
  this.log('info', 'Retrieving keyspaces metadata');
  const self = this;
  this._schemaParser.getKeyspaces(waitReconnect, function getKeyspacesCallback(err, keyspaces) {
    if (err) {
      self.log('error', 'There was an error while trying to retrieve keyspaces information', err);
      return callback(err);
    }
    self.keyspaces = keyspaces;
    callback(null, keyspaces);
  });
};

Metadata.prototype._getKeyspaceReplicas = function (keyspace) {
  if (!keyspace.replicas) {
    //Calculate replicas the first time for the keyspace
    keyspace.replicas =
      keyspace.tokenToReplica(this.tokenizer, this.ringTokensAsStrings, this.primaryReplicas, this.datacenters);
  }
  return keyspace.replicas;
};

/**
 * Gets the host list representing the replicas that contain the given partition key, token or token range.
 *

 *   It uses the pre-loaded keyspace metadata to retrieve the replicas for a token for a given keyspace.
 *   When the keyspace metadata has not been loaded, it returns null.
 *

 * @param {String} keyspaceName
 * @param {Buffer|Token|TokenRange} token Can be Buffer (serialized partition key), Token or TokenRange
 * @returns {Array}
 */
Metadata.prototype.getReplicas = function (keyspaceName, token) {
  if (!this.ring) {
    return null;
  }
  if (Buffer.isBuffer(token)) {
    token = this.tokenizer.hash(token);
  }
  if (token instanceof TokenRange) {
    token = token.end;
  }

  let keyspace;
  if (keyspaceName) {
    keyspace = this.keyspaces[keyspaceName];
    if (!keyspace) {
      // the keyspace was not found, the metadata should be loaded beforehand
      return null;
    }
  }
  let i = utils.binarySearch(this.ring, token, (t1, t2) => t1.compare(t2));
  if (i < 0) {
    i = ~i;
  }
  if (i >= this.ring.length) {
    //it circled back
    i = i % this.ring.length;
  }
  const closestToken = this.ringTokensAsStrings[i];

  if (!keyspaceName) {
    return [this.primaryReplicas[closestToken]];
  }

  const replicas = this._getKeyspaceReplicas(keyspace);
  return replicas[closestToken];
};

/**
 * Gets the token ranges that define data distribution in the ring.
 *
 * @returns {Set<TokenRange>} The ranges of the ring or empty set if schema metadata is not enabled.
 */
Metadata.prototype.getTokenRanges = function () {
  return this.tokenRanges;
};

/**
 * Gets the token ranges that are replicated on the given host, for
 * the given keyspace.
 *
 * @param {String} keyspaceName The name of the keyspace to get ranges for.
 * @param {Host} host The host.
 * @returns {Set<TokenRange>|null} Ranges for the keyspace on this host or null if keyspace isn't found or hasn't been loaded.
 */
Metadata.prototype.getTokenRangesForHost = function (keyspaceName, host) {
  if (!this.ring) {
    return null;
  }
  let keyspace;
  if (keyspaceName) {
    keyspace = this.keyspaces[keyspaceName];
    if (!keyspace) {
      // the keyspace was not found, the metadata should be loaded beforehand
      return null;
    }
  }

  // If the ring has only 1 token, just return the ranges as we should only have a single node cluster.
  if (this.ring.length === 1) {
    return this.getTokenRanges();
  }

  const replicas = this._getKeyspaceReplicas(keyspace);
  const ranges = new Set();

  // for each range, find replicas for end token, if replicas include host, add range.
  this.tokenRanges.forEach((tokenRange) => {
    const replicasForToken = replicas[this.tokenizer.stringify(tokenRange.end)];
    if (replicasForToken.indexOf(host) !== -1) {
      ranges.add(tokenRange);
    }
  });
  return ranges;
};

/**
 * Constructs a Token from the input buffer(s) or string input.  If a string is passed in
 * it is assumed this matches the token representation reported by cassandra.
 * @param {Array<Buffer>|Buffer|String} components
 * @returns {Token} constructed token from the input buffer.
 */
Metadata.prototype.newToken = function (components) {
  if (!this.tokenizer) {
    throw new Error('Partitioner not established.  This should only happen if metadata was disabled or you have not connected yet.');
  }
  if (util.isArray(components)) {
    return this.tokenizer.hash(Buffer.concat(components));
  } else if (util.isString(components)) {
    return this.tokenizer.parse(components);
  }
  return this.tokenizer.hash(components);
};

/**
 * Constructs a TokenRange from the given start and end tokens.
 * @param {Token} start
 * @param {Token} end
 * @returns TokenRange build range spanning from start (exclusive) to end (inclusive).
 */
Metadata.prototype.newTokenRange = function(start, end) {
  if (!this.tokenizer) {
    throw new Error('Partitioner not established.  This should only happen if metadata was disabled or you have not connected yet.');
  }
  return new TokenRange(start, end, this.tokenizer);
};

Metadata.prototype.log = utils.log;

/**
 * Gets the metadata information already stored associated to a prepared statement
 * @param {String} keyspaceName
 * @param {String} query
 * @internal
 * @ignore
 */
Metadata.prototype.getPreparedInfo = function (keyspaceName, query) {
  //overflow protection
  return this._preparedQueries.getOrAdd(keyspaceName, query);
};

/**
 * Clears the internal state related to the prepared statements.
 * Following calls to the Client using the prepare flag will re-prepare the statements.
 */
Metadata.prototype.clearPrepared = function () {
  this._preparedQueries.clear();
};

/** @ignore */
Metadata.prototype.getPreparedById = function (id) {
  return this._preparedQueries.getById(id);
};

/** @ignore */
Metadata.prototype.setPreparedById = function (info) {
  return this._preparedQueries.setById(info);
};

/** @ignore */
Metadata.prototype.getAllPrepared = function () {
  return this._preparedQueries.getAll();
};

/** @ignore */
Metadata.prototype._uninitializedError = function () {
  return new Error('Metadata has not been initialized.  This could only happen if you have not connected yet.');
};

/**
 * Gets the definition of an user-defined type.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 *

 * When trying to retrieve the same UDT definition concurrently, it will query once and invoke all callbacks
 * with the retrieved information.
 *

 * @param {String} keyspaceName Name of the keyspace.
 * @param {String} name Name of the UDT.
 * @param {Function} [callback] The callback to invoke when retrieval completes.
 */
Metadata.prototype.getUdt = function (keyspaceName, name, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getUdtCb(keyspaceName, name, cb);
  });
};

/**
 * @param {String} keyspaceName
 * @param {String} name
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getUdtCb = function (keyspaceName, name, callback) {
  if (!this.initialized) {
    return callback(this._uninitializedError(), null);
  }
  let cache;
  if (this.options.isMetadataSyncEnabled) {
    const keyspace = this.keyspaces[keyspaceName];
    if (!keyspace) {
      return callback(null, null);
    }
    cache = keyspace.udts;
  }
  this._schemaParser.getUdt(keyspaceName, name, cache, callback);
};

/**
 * Gets the definition of a table.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 *

 * When trying to retrieve the same table definition concurrently, it will query once and invoke all callbacks
 * with the retrieved information.
 *

 * @param {String} keyspaceName Name of the keyspace.
 * @param {String} name Name of the Table.
 * @param {Function} [callback] The callback with the err as a first parameter and the {@link TableMetadata} as
 * second parameter.
 */
Metadata.prototype.getTable = function (keyspaceName, name, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getTableCb(keyspaceName, name, cb);
  });
};

/**
 * @param {String} keyspaceName
 * @param {String} name
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getTableCb = function (keyspaceName, name, callback) {
  if (!this.initialized) {
    return callback(this._uninitializedError(), null);
  }
  let cache;
  let virtual;
  if (this.options.isMetadataSyncEnabled) {
    const keyspace = this.keyspaces[keyspaceName];
    if (!keyspace) {
      return callback(null, null);
    }
    cache = keyspace.tables;
    virtual = keyspace.virtual;
  }
  this._schemaParser.getTable(keyspaceName, name, cache, virtual, callback);
};

/**
 * Gets the definition of CQL functions for a given name.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 *

 * When trying to retrieve the same function definition concurrently, it will query once and invoke all callbacks
 * with the retrieved information.
 *

 * @param {String} keyspaceName Name of the keyspace.
 * @param {String} name Name of the Function.
 * @param {Function} [callback] The callback with the err as a first parameter and the array of {@link SchemaFunction}
 * as second parameter.
 */
Metadata.prototype.getFunctions = function (keyspaceName, name, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getFunctionsCb(keyspaceName, name, cb);
  });
};

/**
 * @param {String} keyspaceName
 * @param {String} name
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getFunctionsCb = function (keyspaceName, name, callback) {
  if (typeof callback !== 'function') {
    throw new errors.ArgumentError('Callback is not a function');
  }
  if (!keyspaceName || !name) {
    return callback(
      new errors.ArgumentError('You must provide the keyspace name and cql function name to retrieve the metadata'));
  }
  this._getFunctions(keyspaceName, name, false, function (err, functionsMap) {
    if (err) {
      return callback(err, null);
    }
    callback(null, utils.objectValues(functionsMap));
  });
};

/**
 * Gets a definition of CQL function for a given name and signature.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 *

 * When trying to retrieve the same function definition concurrently, it will query once and invoke all callbacks
 * with the retrieved information.
 *

 * @param {String} keyspaceName Name of the keyspace
 * @param {String} name Name of the Function
 * @param {Array.<String>|Array.<{code, info}>} signature Array of types of the parameters.
 * @param {Function} [callback] The callback with the err as a first parameter and the {@link SchemaFunction} as second
 * parameter.
 */
Metadata.prototype.getFunction = function (keyspaceName, name, signature, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getSingleFunctionCb(keyspaceName, name, signature, false, cb);
  });
};

/**
 * Gets the definition of CQL aggregate for a given name.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 *

 * When trying to retrieve the same aggregates definition concurrently, it will query once and invoke all callbacks
 * with the retrieved information.
 *

 * @param {String} keyspaceName Name of the keyspace
 * @param {String} name Name of the Function
 * @param {Function} [callback] The callback with the err as a first parameter and the array of {@link Aggregate} as
 * second parameter.
 */
Metadata.prototype.getAggregates = function (keyspaceName, name, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getAggregatesCb(keyspaceName, name, cb);
  });
};

/**
 * @param {String} keyspaceName
 * @param {String} name
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getAggregatesCb = function (keyspaceName, name, callback) {
  if (typeof callback !== 'function') {
    throw new errors.ArgumentError('Callback is not a function');
  }
  if (!keyspaceName || !name) {
    return callback(new errors.ArgumentError('You must provide the keyspace name and cql aggregate name to retrieve the metadata'));
  }
  this._getFunctions(keyspaceName, name, true, function (err, functionsMap) {
    if (err) {
      return callback(err, null);
    }
    callback(null, utils.objectValues(functionsMap));
  });
};

/**
 * Gets a definition of CQL aggregate for a given name and signature.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 *

 * When trying to retrieve the same aggregate definition concurrently, it will query once and invoke all callbacks
 * with the retrieved information.
 *

 * @param {String} keyspaceName Name of the keyspace
 * @param {String} name Name of the aggregate
 * @param {Array.<String>|Array.<{code, info}>} signature Array of types of the parameters.
 * @param {Function} [callback] The callback with the err as a first parameter and the {@link Aggregate} as second parameter.
 */
Metadata.prototype.getAggregate = function (keyspaceName, name, signature, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getSingleFunctionCb(keyspaceName, name, signature, true, cb);
  });
};

/**
 * Gets the definition of a CQL materialized view for a given name.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 *

 *   Note that, unlike the rest of the {@link Metadata} methods, this method does not cache the result for following
 *   calls, as the current version of the Cassandra native protocol does not support schema change events for
 *   materialized views. Each call to this method will produce one or more queries to the cluster.
 *

 * @param {String} keyspaceName Name of the keyspace
 * @param {String} name Name of the materialized view
 * @param {Function} [callback] The callback with the err as a first parameter and the {@link MaterializedView} as
 * second parameter.
 */
Metadata.prototype.getMaterializedView = function (keyspaceName, name, callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getMaterializedViewCb(keyspaceName, name, cb);
  });
};

/**
 * @param {String} keyspaceName
 * @param {String} name
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getMaterializedViewCb = function (keyspaceName, name, callback) {
  if (!this.initialized) {
    return callback(this._uninitializedError(), null);
  }
  let cache;
  if (this.options.isMetadataSyncEnabled) {
    const keyspace = this.keyspaces[keyspaceName];
    if (!keyspace) {
      return callback(null, null);
    }
    cache = keyspace.views;
  }
  this._schemaParser.getMaterializedView(keyspaceName, name, cache, callback);
};

/**
 * Gets a map of cql function definitions or aggregates based on signature.
 * @param {String} keyspaceName
 * @param {String} name Name of the function or aggregate
 * @param {Boolean} aggregate
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getFunctions = function (keyspaceName, name, aggregate, callback) {
  if (!this.initialized) {
    return callback(this._uninitializedError(), null);
  }
  let cache;
  if (this.options.isMetadataSyncEnabled) {
    const keyspace = this.keyspaces[keyspaceName];
    if (!keyspace) {
      return callback(null, null);
    }
    cache = aggregate ? keyspace.aggregates : keyspace.functions;
  }
  this._schemaParser.getFunctions(keyspaceName, name, aggregate, cache, callback);
};

/**
 * Gets a single cql function or aggregate definition
 * @param {String} keyspaceName
 * @param {String} name
 * @param {Array} signature
 * @param {Boolean} aggregate
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getSingleFunctionCb = function (keyspaceName, name, signature, aggregate, callback) {
  if (typeof callback !== 'function') {
    throw new errors.ArgumentError('Callback is not a function');
  }
  if (!keyspaceName || !name) {
    return callback(
      new errors.ArgumentError('You must provide the keyspace name and cql function name to retrieve the metadata'));
  }
  if (!util.isArray(signature)) {
    return callback(new errors.ArgumentError('Signature must be an array of types'));
  }
  try {
    signature = signature.map(function (item) {
      if (typeof item === 'string') {
        return item;
      }
      return types.getDataTypeNameByCode(item);
    });
  }
  catch (err) {
    return callback(err);
  }
  this._getFunctions(keyspaceName, name, aggregate, function (err, functionsMap) {
    if (err) {
      return callback(err, null);
    }
    let f;
    if (functionsMap) {
      f = functionsMap['(' + signature.join(',') + ')'];
    }
    callback(null, f || null);
  });
};

/**
 * Gets the trace session generated by Cassandra when query tracing is enabled for the
 * query. The trace itself is stored in Cassandra in the sessions and
 * events table in the system_traces keyspace and can be
 * retrieve manually using the trace identifier.
 *

 *   If a callback is provided, the callback is invoked when the metadata retrieval completes.
 *   Otherwise, it returns a Promise.
 *

 * @param {Uuid} traceId Identifier of the trace session.
 * @param {Number} [consistency] The consistency level to obtain the trace.
 * @param {Function} [callback] The callback with the err as first parameter and the query trace as second parameter.
 */
Metadata.prototype.getTrace = function (traceId, consistency, callback) {
  if (!callback && typeof consistency === 'function') {
    // Both callback and consistency are optional parameters
    // In this case, the second parameter is the callback
    callback = consistency;
    consistency = null;
  }
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    this._getTraceCb(traceId, consistency, cb);
  });
};

/**
 * @param {Uuid} traceId
 * @param {Number} consistency
 * @param {Function} callback
 * @private
 */
Metadata.prototype._getTraceCb = function (traceId, consistency, callback) {
  if (!this.initialized) {
    return callback(this._uninitializedError(), null);
  }
  let trace;
  let attempts = 0;
  const info = ExecutionOptions.empty();
  info.getConsistency = () => consistency;
  const sessionRequest = new requests.QueryRequest(util.format(_selectTraceSession, traceId), null, info);
  const eventsRequest = new requests.QueryRequest(util.format(_selectTraceEvents, traceId), null, info);
  const self = this;

  utils.whilst(function condition() {
    return !trace && (attempts++ < _traceMaxAttemps);
  }, function iterator(next) {
    self.controlConnection.query(sessionRequest, function (err, result) {
      if (err) {
        return next(err);
      }
      const sessionRow = result.rows[0];
      if (!sessionRow || !sessionRow['duration']) {
        return setTimeout(next, _traceAttemptDelay);
      }
      trace = {
        requestType: sessionRow['request'],
        coordinator: sessionRow['coordinator'],
        parameters: sessionRow['parameters'],
        startedAt: sessionRow['started_at'],
        duration: sessionRow['duration'],
        clientAddress: sessionRow['client'],
        events: []
      };
      self.controlConnection.query(eventsRequest, function (err, result) {
        if (err) {
          return next(err);
        }
        result.rows.forEach(function (row) {
          trace.events.push({
            id: row['event_id'],
            activity: row['activity'],
            source: row['source'],
            elapsed: row['source_elapsed'],
            thread: row['thread']
          });
        });
        next();
      });
    });
  }, function getTraceEnded(err) {
    if (!err && !trace) {
      err = new Error(util.format('Trace %s could not fully retrieved after %d attempts', traceId, _traceMaxAttemps));
    }
    callback(err, trace);
  });
};

/**
 * Checks whether hosts that are currently up agree on the schema definition.
 *

 *   This method performs a one-time check only, without any form of retry; therefore
 *   protocolOptions.maxSchemaAgreementWaitSeconds setting does not apply in this case.
 *

 * @param {Function} [callback] A function that is invoked with a value
 * true when all hosts agree on the schema and false when there is no agreement or when
 * the check could not be performed (for example, if the control connection is down).
 * @returns {Promise} Returns a Promise when a callback is not provided. The promise resolves to
 * true when all hosts agree on the schema and false when there is no agreement or when
 * the check could not be performed (for example, if the control connection is down).
 */
Metadata.prototype.checkSchemaAgreement = function (callback) {
  return utils.promiseWrapper.call(this, this.options, callback, function handler(cb) {
    const connection = this.controlConnection.connection;
    if (!connection) {
      return cb(null, false);
    }

    this.compareSchemaVersions(connection, (err, agreement) => {
      // The error is never thrown
      cb(null, !err && agreement);
    });
  });
};

/**
 * Uses the metadata to fill the user provided parameter hints
 * @param {String} keyspace
 * @param {Array} hints
 * @param {Function} callback
 * @internal
 * @ignore
 */
Metadata.prototype.adaptUserHints = function (keyspace, hints, callback) {
  if (!util.isArray(hints)) {
    return callback();
  }
  const udts = [];
  //check for udts and get the metadata
  function checkUdtTypes(type) {
    if (type.code === types.dataTypes.udt) {
      const udtName = type.info.split('.');
      type.info = {
        keyspace: udtName[0],
        name: udtName[1]
      };
      if (!type.info.name) {
        if (!keyspace) {
          throw new TypeError('No keyspace specified for udt: ' + udtName.join('.'));
        }
        //use the provided keyspace
        type.info.name = type.info.keyspace;
        type.info.keyspace = keyspace;
      }
      udts.push(type);
      return;
    }
    if (!type.info) {
      return;
    }
    if (type.code === types.dataTypes.list || type.code === types.dataTypes.set) {
      return checkUdtTypes(type.info);
    }
    if (type.code === types.dataTypes.map) {
      checkUdtTypes(type.info[0]);
      checkUdtTypes(type.info[1]);
    }
  }
  for (let i = 0; i < hints.length; i++) {
    const hint = hints[i];
    if (typeof hint !== 'string') {
      continue;
    }
    try {
      const type = types.dataTypes.getByName(hint);
      checkUdtTypes(type);
      hints[i] = type;
    }
    catch (err) {
      return callback(err);
    }
  }
  const self = this;
  utils.each(udts, function (type, next) {
    self.getUdt(type.info.keyspace, type.info.name, function (err, udtInfo) {
      if (err) {
        return next(err);
      }
      if (!udtInfo) {
        return next(new TypeError('User defined type not found: ' + type.info.keyspace + '.' + type.info.name));
      }
      type.info = udtInfo;
      next();
    });
  }, callback);
};

/**
 * Uses the provided connection to query the schema versions and compare them.
 * @param {Connection} connection
 * @param {Function} callback
 * @internal
 * @ignore
 */
Metadata.prototype.compareSchemaVersions = function (connection, callback) {
  const versions = new Set();

  utils.series([
    next => {
      const request = new requests.QueryRequest(_selectSchemaVersionLocal, null, null);
      connection.sendStream(request, null, function (err, result) {
        if (!err && result && result.rows && result.rows.length === 1) {
          versions.add(result.rows[0]['schema_version'].toString());
        }
        next(err);
      });
    },
    next => {
      const request = new requests.QueryRequest(_selectSchemaVersionPeers, null, null);
      connection.sendStream(request, null, function (err, result) {
        if (!err && result && result.rows) {
          for (const row of result.rows) {
            const value = row['schema_version'];
            if (!value) {
              continue;
            }
            versions.add(value.toString());
          }
        }
        next(err);
      });
    }
  ], (err) => {
    callback(err, versions.size === 1);
  });
};

/**
 * Allows to store prepared queries and retrieval by query or query id.
 * @param {Number} maxPrepared
 * @param {Function} logger
 * @constructor
 * @ignore
 */
function PreparedQueries(maxPrepared, logger) {
  this.length = 0;
  this._maxPrepared = maxPrepared;
  this._mapByKey = {};
  this._mapById = {};
  this._logger = logger;
}

PreparedQueries.prototype._getKey = function (keyspace, query) {
  return ( keyspace || '' ) + query;
};

PreparedQueries.prototype.getOrAdd = function (keyspace, query) {
  const key = this._getKey(keyspace, query);
  let info = this._mapByKey[key];
  if (info) {
    return info;
  }
  this._validateOverflow();
  info = new events.EventEmitter();
  info.setMaxListeners(0);
  info.query = query;
  // The keyspace in which it was prepared
  info.keyspace = keyspace;
  this._mapByKey[key] = info;
  this.length++;
  return info;
};

PreparedQueries.prototype._validateOverflow = function () {
  if (this.length < this._maxPrepared) {
    return;
  }
  const toRemove = [];
  this._logger('warning',
    'Prepared statements exceeded maximum. This could be caused by preparing queries that contain parameters');
  const existingKeys = Object.keys(this._mapByKey);
  for (let i = 0; i < existingKeys.length && this.length - toRemove.length < this._maxPrepared; i++) {
    const info = this._mapByKey[existingKeys[i]];
    if (!info.queryId) {
      // Only remove queries that contain queryId
      continue;
    }
    toRemove.push(info);
  }
  toRemove.forEach(function (item) {
    delete this._mapByKey[item.query];
    delete this._mapById[item.queryId];
    this.length--;
  }, this);
};

PreparedQueries.prototype.setById = function (info) {
  this._mapById[info.queryId.toString('hex')] = info;
};

PreparedQueries.prototype.getById = function (id) {
  return this._mapById[id.toString('hex')];
};

PreparedQueries.prototype.clear = function () {
  this._mapByKey = {};
  this._mapById = {};
  this.length = 0;
};

PreparedQueries.prototype.getAll = function () {
  return utils.objectValues(this._mapByKey).filter(function (info) {
    return !!info.queryId;
  });
};

module.exports = Metadata;


"use strict";
const util = require('util');

/**
 * Contains the error classes exposed by the driver.
 * @module errors
 */

/**
 * Base Error
 * @private
 */
function DriverError (message) {
  Error.call(this, message);
  Error.captureStackTrace(this, this.constructor);
  this.name = this.constructor.name;
  this.info = 'Cassandra Driver Error';
  // Explicitly set the message property as the Error.call() doesn't set the property on v8
  this.message = message;
}

util.inherits(DriverError, Error);

/**
 * Represents an error when a query cannot be performed because no host is available or could be reached by the driver.
 * @param {Object} innerErrors An object map containing the error per host tried
 * @param {String} [message]
 * @constructor
 */
function NoHostAvailableError(innerErrors, message) {
  DriverError.call(this, message);
  this.innerErrors = innerErrors;
  this.info = 'Represents an error when a query cannot be performed because no host is available or could be reached by the driver.';
  if (!message) {
    this.message = 'All host(s) tried for query failed.';
    if (innerErrors) {
      const hostList = Object.keys(innerErrors);
      if (hostList.length > 0) {
        const host = hostList[0];
        this.message += util.format(' First host tried, %s: %s. See innerErrors.', host, innerErrors[host]);
      }
    }
  }
}

util.inherits(NoHostAvailableError, DriverError);

/**
 * Represents an error message from the server
 * @param {Number} code Cassandra exception code
 * @param {String} message
 * @constructor
 */
function ResponseError(code, message) {
  DriverError.call(this, message);
  /**
   * The error code as defined in [responseErrorCodes]{@link module:types~responseErrorCodes}.
   * @type {Number}
   */
  this.code = code;
  this.info = 'Represents an error message from the server';
}

util.inherits(ResponseError, DriverError);

/**
 * Represents a bug inside the driver or in a Cassandra host.
 * @param {String} message
 * @constructor
 */
function DriverInternalError(message) {
  DriverError.call(this, message);
  this.info = 'Represents a bug inside the driver or in a Cassandra host.';
}

util.inherits(DriverInternalError, DriverError);

/**
 * Represents an error when trying to authenticate with auth-enabled host
 * @param {String} message
 * @constructor
 */
function AuthenticationError(message) {
  DriverError.call(this, message);
  this.info = 'Represents an authentication error from the driver or from a Cassandra node.';
}

util.inherits(AuthenticationError, DriverError);

/**
 * Represents an error that is raised when one of the arguments provided to a method is not valid
 * @param {String} message
 * @constructor
 */
function ArgumentError(message) {
  DriverError.call(this, message);
  this.info = 'Represents an error that is raised when one of the arguments provided to a method is not valid.';
}

util.inherits(ArgumentError, DriverError);

/**
 * Represents a client-side error that is raised when the client didn't hear back from the server within
 * {@link ClientOptions.socketOptions.readTimeout}.
 * @param {String} message The error message.
 * @param {String} [host] Address of the server host that caused the operation to time out.
 * @constructor
 */
function OperationTimedOutError(message, host) {
  DriverError.call(this, message, this.constructor);
  this.info = 'Represents a client-side error that is raised when the client did not hear back from the server ' +
    'within socketOptions.readTimeout';

  /**
   * When defined, it gets the address of the host that caused the operation to time out.
   * @type {String|undefined}
   */
  this.host = host;
}

util.inherits(OperationTimedOutError, DriverError);

/**
 * Represents an error that is raised when a feature is not supported in the driver or in the current Cassandra version.
 * @param message
 * @constructor
 */
function NotSupportedError(message) {
  DriverError.call(this, message, this.constructor);
  this.info = 'Represents a feature that is not supported in the driver or in the Cassandra version.';
}

util.inherits(NotSupportedError, DriverError);

/**
 * Represents a client-side error indicating that all connections to a certain host have reached
 * the maximum amount of in-flight requests supported.
 * @param {String} address
 * @param {Number} maxRequestsPerConnection
 * @param {Number} connectionLength
 * @constructor
 */
function BusyConnectionError(address, maxRequestsPerConnection, connectionLength) {
  const message = util.format('All connections to host %s are busy, %d requests are in-flight on %s',
    address, maxRequestsPerConnection, connectionLength === 1 ? 'a single connection': 'each connection');
  DriverError.call(this, message, this.constructor);
  this.info = 'Represents a client-side error indicating that all connections to a certain host have reached ' +
    'the maximum amount of in-flight requests supported (pooling.maxRequestsPerConnection)';
}

util.inherits(BusyConnectionError, DriverError);

exports.ArgumentError = ArgumentError;
exports.AuthenticationError = AuthenticationError;
exports.BusyConnectionError = BusyConnectionError;
exports.DriverError = DriverError;
exports.OperationTimedOutError = OperationTimedOutError;
exports.DriverInternalError = DriverInternalError;
exports.NoHostAvailableError = NoHostAvailableError;
exports.NotSupportedError = NotSupportedError;
exports.ResponseError = ResponseError;


"use strict";
const util = require('util');

const policies = require('./policies');
const types = require('./types');
const utils = require('./utils');
const tracker = require('./tracker');
const metrics = require('./metrics');

/** Core connections per host for protocol versions 1 and 2 */
const coreConnectionsPerHostV2 = {
  [types.distance.local]: 2,
  [types.distance.remote]: 1,
  [types.distance.ignored]: 0
};

/** Core connections per host for protocol version 3 and above */
const coreConnectionsPerHostV3 = {
  [types.distance.local]: 1,
  [types.distance.remote]: 1,
  [types.distance.ignored]: 0
};

/** Default maxRequestsPerConnection value for protocol v1 and v2 */
const maxRequestsPerConnectionV2 = 128;

/** Default maxRequestsPerConnection value for protocol v3+ */
const maxRequestsPerConnectionV3 = 2048;

/**
 * @returns {ClientOptions}
 */
function defaultOptions () {
  return ({
    policies: {
      addressResolution: policies.defaultAddressTranslator(),
      loadBalancing: policies.defaultLoadBalancingPolicy(),
      reconnection: policies.defaultReconnectionPolicy(),
      retry: policies.defaultRetryPolicy(),
      speculativeExecution: policies.defaultSpeculativeExecutionPolicy(),
      timestampGeneration: policies.defaultTimestampGenerator()
    },
    queryOptions: {
      consistency: types.consistencies.localOne,
      fetchSize: 5000,
      prepare: false,
      captureStackTrace: false
    },
    protocolOptions: {
      port: 9042,
      maxSchemaAgreementWaitSeconds: 10,
      maxVersion: 0,
      noCompact: false
    },
    pooling: {
      heartBeatInterval: 30000,
      warmup: true
    },
    socketOptions: {
      connectTimeout: 5000,
      defunctReadTimeoutThreshold: 64,
      keepAlive: true,
      keepAliveDelay: 0,
      readTimeout: 12000,
      tcpNoDelay: true,
      coalescingThreshold: 65536
    },
    authProvider: null,
    requestTracker: null,
    metrics: new metrics.DefaultMetrics(),
    maxPrepared: 500,
    refreshSchemaDelay: 1000,
    isMetadataSyncEnabled: true,
    prepareOnAllHosts: true,
    rePrepareOnUp: true,
    encoding: {
      copyBuffer: true,
      useUndefinedAsUnset: true
    }
  });
}

/**
 * Extends and validates the user options
 * @param {Object} [baseOptions] The source object instance that will be overridden
 * @param {Object} userOptions
 * @returns {Object}
 */
function extend(baseOptions, userOptions) {
  if (arguments.length === 1) {
    userOptions = arguments[0];
    baseOptions = {};
  }
  const options = utils.deepExtend(baseOptions, defaultOptions(), userOptions);
  if (!util.isArray(options.contactPoints) || options.contactPoints.length === 0) {
    throw new TypeError('Contacts points are not defined.');
  }
  for (let i = 0; i < options.contactPoints.length; i++) {
    const hostName = options.contactPoints[i];
    if (!hostName) {
      throw new TypeError(util.format('Contact point %s (%s) is not a valid host name, ' +
        'the following values are valid contact points: ipAddress, hostName or ipAddress:port', i, hostName));
    }
  }
  if (!options.logEmitter) {
    options.logEmitter = function () {};
  }
  if (!options.queryOptions) {
    throw new TypeError('queryOptions not defined in options');
  }

  if (options.requestTracker !== null && !(options.requestTracker instanceof tracker.RequestTracker)) {
    throw new TypeError('requestTracker must be an instance of RequestTracker');
  }

  if (!(options.metrics instanceof metrics.ClientMetrics)) {
    throw new TypeError('metrics must be an instance of ClientMetrics');
  }

  validatePoliciesOptions(options.policies);
  validateProtocolOptions(options.protocolOptions);
  validateSocketOptions(options.socketOptions);
  options.encoding = options.encoding || {};
  validateEncodingOptions(options.encoding);
  if (options.profiles && !util.isArray(options.profiles)) {
    throw new TypeError('profiles must be an Array of ExecutionProfile instances');
  }
  return options;
}

/**
 * Validates the policies from the client options.
 * @param {ClientOptions.policies} policiesOptions
 * @private
 */
function validatePoliciesOptions(policiesOptions) {
  if (!policiesOptions) {
    throw new TypeError('policies not defined in options');
  }
  if (!(policiesOptions.loadBalancing instanceof policies.loadBalancing.LoadBalancingPolicy)) {
    throw new TypeError('Load balancing policy must be an instance of LoadBalancingPolicy');
  }
  if (!(policiesOptions.reconnection instanceof policies.reconnection.ReconnectionPolicy)) {
    throw new TypeError('Reconnection policy must be an instance of ReconnectionPolicy');
  }
  if (!(policiesOptions.retry instanceof policies.retry.RetryPolicy)) {
    throw new TypeError('Retry policy must be an instance of RetryPolicy');
  }
  if (!(policiesOptions.addressResolution instanceof policies.addressResolution.AddressTranslator)) {
    throw new TypeError('Address resolution policy must be an instance of AddressTranslator');
  }
  if (policiesOptions.timestampGeneration !== null &&
    !(policiesOptions.timestampGeneration instanceof policies.timestampGeneration.TimestampGenerator)) {
    throw new TypeError('Timestamp generation policy must be an instance of TimestampGenerator');
  }
}

/**
 * Validates the protocol options.
 * @param {ClientOptions.protocolOptions} protocolOptions
 * @private
 */
function validateProtocolOptions(protocolOptions) {
  if (!protocolOptions) {
    throw new TypeError('protocolOptions not defined in options');
  }
  const version = protocolOptions.maxVersion;
  if (version && (typeof version !== 'number' || !types.protocolVersion.isSupported(version))) {
    throw new TypeError(util.format('protocolOptions.maxVersion provided (%s) is invalid', version));
  }
}

/**
 * Validates the socket options.
 * @param {ClientOptions.socketOptions} socketOptions
 * @private
 */
function validateSocketOptions(socketOptions) {
  if (!socketOptions) {
    throw new TypeError('socketOptions not defined in options');
  }
  if (typeof socketOptions.readTimeout !== 'number') {
    throw new TypeError('socketOptions.readTimeout must be a Number');
  }
  if (typeof socketOptions.coalescingThreshold !== 'number' || socketOptions.coalescingThreshold <= 0) {
    throw new TypeError('socketOptions.coalescingThreshold must be a positive Number');
  }
}

/**
 * Validates the encoding options.
 * @param {ClientOptions.encoding} encodingOptions
 * @private
 */
function validateEncodingOptions(encodingOptions) {
  if (encodingOptions.map) {
    const mapConstructor = encodingOptions.map;
    if (typeof mapConstructor !== 'function' ||
      typeof mapConstructor.prototype.forEach !== 'function' ||
      typeof mapConstructor.prototype.set !== 'function') {
      throw new TypeError('Map constructor not valid');
    }
  }

  if (encodingOptions.set) {
    const setConstructor = encodingOptions.set;
    if (typeof setConstructor !== 'function' ||
      typeof setConstructor.prototype.forEach !== 'function' ||
      typeof setConstructor.prototype.add !== 'function') {
      throw new TypeError('Set constructor not valid');
    }
  }

  if ((encodingOptions.useBigIntAsLong || encodingOptions.useBigIntAsVarint) && typeof BigInt === 'undefined') {
    throw new TypeError('BigInt is not supported by the JavaScript engine');
  }
}

/**
 * Sets the default options that depend on the protocol version.
 * @param {ClientOptions} options
 * @param {Number} version
 */
function setProtocolDependentDefaults(options, version) {
  let coreConnectionsPerHost = coreConnectionsPerHostV3;
  let maxRequestsPerConnection = maxRequestsPerConnectionV3;
  if (!types.protocolVersion.uses2BytesStreamIds(version)) {
    coreConnectionsPerHost = coreConnectionsPerHostV2;
    maxRequestsPerConnection = maxRequestsPerConnectionV2;
  }
  options.pooling = utils.deepExtend({}, { coreConnectionsPerHost, maxRequestsPerConnection }, options.pooling);
}

exports.extend = extend;
exports.defaultOptions = defaultOptions;
exports.coreConnectionsPerHostV2 = coreConnectionsPerHostV2;
exports.coreConnectionsPerHostV3 = coreConnectionsPerHostV3;
exports.maxRequestsPerConnectionV2 = maxRequestsPerConnectionV2;
exports.maxRequestsPerConnectionV3 = maxRequestsPerConnectionV3;
exports.setProtocolDependentDefaults = setProtocolDependentDefaults;


'use strict';
const util = require('util');

const errors = require('../errors');
const TimeUuid = require('./time-uuid');
const Uuid = require('./uuid');
const protocolVersion = require('./protocol-version');
const utils = require('../utils');

/** @module types */
/**
 * Long constructor, wrapper of the internal library used: {@link https://github.com/dcodeIO/long.js Long.js}.
 * @constructor
 */
const Long = require('long');

/**
 * Consistency levels
 * @type {Object}
 * @property {Number} any Writing: A write must be written to at least one node. If all replica nodes for the given row key are down, the write can still succeed after a hinted handoff has been written. If all replica nodes are down at write time, an ANY write is not readable until the replica nodes for that row have recovered.
 * @property {Number} one Returns a response from the closest replica, as determined by the snitch.
 * @property {Number} two Returns the most recent data from two of the closest replicas.
 * @property {Number} three Returns the most recent data from three of the closest replicas.
 * @property {Number} quorum Reading: Returns the record with the most recent timestamp after a quorum of replicas has responded regardless of data center. Writing: A write must be written to the commit log and memory table on a quorum of replica nodes.
 * @property {Number} all Reading: Returns the record with the most recent timestamp after all replicas have responded. The read operation will fail if a replica does not respond. Writing: A write must be written to the commit log and memory table on all replica nodes in the cluster for that row.
 * @property {Number} localQuorum Reading: Returns the record with the most recent timestamp once a quorum of replicas in the current data center as the coordinator node has reported. Writing: A write must be written to the commit log and memory table on a quorum of replica nodes in the same data center as the coordinator node. Avoids latency of inter-data center communication.
 * @property {Number} eachQuorum Reading: Returns the record once a quorum of replicas in each data center of the cluster has responded. Writing: Strong consistency. A write must be written to the commit log and memtable on a quorum of replica nodes in all data centers.
 * @property {Number} serial Achieves linearizable consistency for lightweight transactions by preventing unconditional updates.
 * @property {Number} localSerial Same as serial but confined to the data center. A write must be written conditionally to the commit log and memtable on a quorum of replica nodes in the same data center.
 * @property {Number} localOne Similar to One but only within the DC the coordinator is in.
 */
const consistencies = {
  any:          0x00,
  one:          0x01,
  two:          0x02,
  three:        0x03,
  quorum:       0x04,
  all:          0x05,
  localQuorum:  0x06,
  eachQuorum:   0x07,
  serial:       0x08,
  localSerial:  0x09,
  localOne:     0x0a
};

/**
 * Mapping of consistency level codes to their string representation.
 * @type {Object}
 */
const consistencyToString = {};
consistencyToString[consistencies.any] = 'ANY';
consistencyToString[consistencies.one] = 'ONE';
consistencyToString[consistencies.two] = 'TWO';
consistencyToString[consistencies.three] = 'THREE';
consistencyToString[consistencies.quorum] = 'QUORUM';
consistencyToString[consistencies.all] = 'ALL';
consistencyToString[consistencies.localQuorum] = 'LOCAL_QUORUM';
consistencyToString[consistencies.eachQuorum] = 'EACH_QUORUM';
consistencyToString[consistencies.serial] = 'SERIAL';
consistencyToString[consistencies.localSerial] = 'LOCAL_SERIAL';
consistencyToString[consistencies.localOne] = 'LOCAL_ONE';

/**
 * CQL data types
 * @type {Object}
 * @property {Number} custom A custom type.
 * @property {Number} ascii ASCII character string.
 * @property {Number} bigint 64-bit signed long.
 * @property {Number} blob Arbitrary bytes (no validation).
 * @property {Number} boolean true or false.
 * @property {Number} counter Counter column (64-bit signed value).
 * @property {Number} decimal Variable-precision decimal.
 * @property {Number} double 64-bit IEEE-754 floating point.
 * @property {Number} float 32-bit IEEE-754 floating point.
 * @property {Number} int 32-bit signed integer.
 * @property {Number} text UTF8 encoded string.
 * @property {Number} timestamp A timestamp.
 * @property {Number} uuid Type 1 or type 4 UUID.
 * @property {Number} varchar UTF8 encoded string.
 * @property {Number} varint Arbitrary-precision integer.
 * @property {Number} timeuuid  Type 1 UUID.
 * @property {Number} inet An IP address. It can be either 4 bytes long (IPv4) or 16 bytes long (IPv6).
 * @property {Number} date A date without a time-zone in the ISO-8601 calendar system.
 * @property {Number} time A value representing the time portion of the day.
 * @property {Number} smallint 16-bit two's complement integer.
 * @property {Number} tinyint 8-bit two's complement integer.
 * @property {Number} list A collection of elements.
 * @property {Number} map Key/value pairs.
 * @property {Number} set A collection that contains no duplicate elements.
 * @property {Number} udt User-defined type.
 * @property {Number} tuple A sequence of values.
 */
const dataTypes = {
  custom:     0x0000,
  ascii:      0x0001,
  bigint:     0x0002,
  blob:       0x0003,
  boolean:    0x0004,
  counter:    0x0005,
  decimal:    0x0006,
  double:     0x0007,
  float:      0x0008,
  int:        0x0009,
  text:       0x000a,
  timestamp:  0x000b,
  uuid:       0x000c,
  varchar:    0x000d,
  varint:     0x000e,
  timeuuid:   0x000f,
  inet:       0x0010,
  date:       0x0011,
  time:       0x0012,
  smallint:   0x0013,
  tinyint:    0x0014,
  list:       0x0020,
  map:        0x0021,
  set:        0x0022,
  udt:        0x0030,
  tuple:      0x0031,
  /**
   * Returns the typeInfo of a given type name
   * @param name
   * @returns {{code: number, info: *|Object}}
   */
  getByName:  function(name) {
    name = name.toLowerCase();
    if (name.indexOf('<') > 0) {
      const listMatches = /^(list|set)<(.+)>$/.exec(name);
      if (listMatches) {
        return { code: this[listMatches[1]], info: this.getByName(listMatches[2])};
      }
      const mapMatches = /^(map)< *(.+) *, *(.+)>$/.exec(name);
      if (mapMatches) {
        return { code: this[mapMatches[1]], info: [this.getByName(mapMatches[2]), this.getByName(mapMatches[3])]};
      }
      const udtMatches = /^(udt)<(.+)>$/.exec(name);
      if (udtMatches) {
        //udt name as raw string
        return { code: this[udtMatches[1]], info: udtMatches[2]};
      }
      const tupleMatches = /^(tuple)<(.+)>$/.exec(name);
      if (tupleMatches) {
        //tuple info as an array of types
        return { code: this[tupleMatches[1]], info: tupleMatches[2].split(',').map(function (x) {
          return this.getByName(x.trim());
        }, this)};
      }
    }
    const typeInfo = { code: this[name], info: null};
    if (typeof typeInfo.code !== 'number') {
      throw new TypeError('Data type with name ' + name + ' not valid');
    }
    return typeInfo;
  }
};

/**
 * Map of Data types by code
 * @internal
 * @private
 */
const _dataTypesByCode = (function () {
  const result = {};
  for (const key in dataTypes) {
    if (!dataTypes.hasOwnProperty(key)) {
      continue;
    }
    const val = dataTypes[key];
    if (typeof val !== 'number') {
      continue;
    }
    result[val] = key;
  }
  return result;
})();

/**
 * Represents the distance of Cassandra node as assigned by a LoadBalancingPolicy relatively to the driver instance.
 * @type {Object}
 * @property {Number} local A local node.
 * @property {Number} remote A remote node.
 * @property {Number} ignored A node that is meant to be ignored.
 */
const distance = {
  local:    0,
  remote:   1,
  ignored:  2
};

/**
 * An integer byte that distinguish the actual message from and to Cassandra
 * @internal
 * @ignore
 */
const opcodes = {
  error:          0x00,
  startup:        0x01,
  ready:          0x02,
  authenticate:   0x03,
  credentials:    0x04,
  options:        0x05,
  supported:      0x06,
  query:          0x07,
  result:         0x08,
  prepare:        0x09,
  execute:        0x0a,
  register:       0x0b,
  event:          0x0c,
  batch:          0x0d,
  authChallenge:  0x0e,
  authResponse:   0x0f,
  authSuccess:    0x10,

  /**
   * Determines if the code is a valid opcode
   */
  isInRange: function (code) {
    return code > this.error && code > this.event;
  }
};

/**
 * Event types from Cassandra
 * @type {{topologyChange: string, statusChange: string, schemaChange: string}}
 * @internal
 * @ignore
 */
const protocolEvents = {
  topologyChange: 'TOPOLOGY_CHANGE',
  statusChange: 'STATUS_CHANGE',
  schemaChange: 'SCHEMA_CHANGE'
};

/**
 * Server error codes returned by Cassandra
 * @type {Object}
 * @property {Number} serverError Something unexpected happened.
 * @property {Number} protocolError Some client message triggered a protocol violation.
 * @property {Number} badCredentials Authentication was required and failed.
 * @property {Number} unavailableException Raised when coordinator knows there is not enough replicas alive to perform a query with the requested consistency level.
 * @property {Number} overloaded The request cannot be processed because the coordinator is overloaded.
 * @property {Number} isBootstrapping The request was a read request but the coordinator node is bootstrapping.
 * @property {Number} truncateError Error encountered during a truncate request.
 * @property {Number} writeTimeout Timeout encountered on write query on coordinator waiting for response(s) from replicas.
 * @property {Number} readTimeout Timeout encountered on read query on coordinator waitign for response(s) from replicas.
 * @property {Number} readFailure A non-timeout error encountered during a read request.
 * @property {Number} functionFailure A (user defined) function encountered during execution.
 * @property {Number} writeFailure A non-timeout error encountered during a write request.
 * @property {Number} syntaxError The submitted query has a syntax error.
 * @property {Number} unauthorized The logged user doesn't have the right to perform the query.
 * @property {Number} invalid The query is syntactically correct but invalid.
 * @property {Number} configError The query is invalid because of some configuration issue.
 * @property {Number} alreadyExists The query attempted to create a schema element (i.e. keyspace, table) that already exists.
 * @property {Number} unprepared Can be thrown while a prepared statement tries to be executed if the provided statement is not known by the coordinator.
 */
const responseErrorCodes = {
  serverError:            0x0000,
  protocolError:          0x000A,
  badCredentials:         0x0100,
  unavailableException:   0x1000,
  overloaded:             0x1001,
  isBootstrapping:        0x1002,
  truncateError:          0x1003,
  writeTimeout:           0x1100,
  readTimeout:            0x1200,
  readFailure:            0x1300,
  functionFailure:        0x1400,
  writeFailure:           0x1500,
  syntaxError:            0x2000,
  unauthorized:           0x2100,
  invalid:                0x2200,
  configError:            0x2300,
  alreadyExists:          0x2400,
  unprepared:             0x2500
};

/**
 * Type of result included in a response
 * @internal
 * @ignore
 */
const resultKind = {
  voidResult:      0x0001,
  rows:            0x0002,
  setKeyspace:     0x0003,
  prepared:        0x0004,
  schemaChange:    0x0005
};

/**
 * Message frame flags
 * @internal
 * @ignore
 */
const frameFlags = {
  compression:    0x01,
  tracing:        0x02,
  customPayload:  0x04,
  warning:        0x08
};

/**
 * Unset representation.
 *

 *   Use this field if you want to set a parameter to unset. Valid for Cassandra 2.2 and above.
 *

 */
const unset = Object.freeze({'unset': true});

/**
 * A long representing the value 1000
 * @const
 * @private
 */
const _longOneThousand = Long.fromInt(1000);

/**
 * Counter used to generate up to 1000 different timestamp values with the same Date
 * @private
 */
let _timestampTicks = 0;

/**
 *
Backward compatibility only, use [TimeUuid]{@link module:types~TimeUuid} instead.

 * Generates and returns a RFC4122 v1 (timestamp based) UUID in a string representation.
 * @param {{msecs, node, clockseq, nsecs}} [options]
 * @param {Buffer} [buffer]
 * @param {Number} [offset]
 * @deprecated Use [TimeUuid]{@link module:types~TimeUuid} instead
 */
function timeuuid(options, buffer, offset) {
  let date;
  let ticks;
  let nodeId;
  let clockId;
  if (options) {
    if (typeof options.msecs === 'number') {
      date = new Date(options.msecs);
    }
    if (options.msecs instanceof Date) {
      date = options.msecs;
    }
    if (util.isArray(options.node)) {
      nodeId = utils.allocBufferFromArray(options.node);
    }
    if (typeof options.clockseq === 'number') {
      clockId = utils.allocBufferUnsafe(2);
      clockId.writeUInt16BE(options.clockseq, 0);
    }
    if (typeof options.nsecs === 'number') {
      ticks = options.nsecs;
    }
  }
  const uuid = new TimeUuid(date, ticks, nodeId, clockId);
  if (buffer instanceof Buffer) {
    //copy the values into the buffer
    uuid.getBuffer().copy(buffer, offset || 0);
    return buffer;
  }
  return uuid.toString();
}

/**
 *
Backward compatibility only, use [Uuid]{@link module:types~Uuid} class instead.

 * Generate and return a RFC4122 v4 UUID in a string representation.
 * @deprecated Use [Uuid]{@link module:types~Uuid} class instead
 */
function uuid(options, buffer, offset) {
  let uuid;
  if (options) {
    if (util.isArray(options.random)) {
      uuid = new Uuid(utils.allocBufferFromArray(options.random));
    }
  }
  if (!uuid) {
    uuid = Uuid.random();
  }
  if (buffer instanceof Buffer) {
    //copy the values into the buffer
    uuid.getBuffer().copy(buffer, offset || 0);
    return buffer;
  }
  return uuid.toString();
}

/**
 * Gets the data type name for a given type definition
 * @internal
 * @ignore
 * @throws {ArgumentError}
 */
function getDataTypeNameByCode(item) {
  if (!item || typeof item.code !== 'number') {
    throw new errors.ArgumentError('Invalid signature type definition');
  }
  const typeName = _dataTypesByCode[item.code];
  if (!typeName) {
    throw new errors.ArgumentError(util.format('Type with code %d not found', item.code));
  }
  if (!item.info) {
    return typeName;
  }
  if (util.isArray(item.info)) {
    return (typeName +
      '<' +
      item.info.map(function (t) {
        return getDataTypeNameByCode(t);
      }).join(', ') +
      '>');
  }
  if (typeof item.info.code === 'number') {
    return typeName + '<' + getDataTypeNameByCode(item.info) + '>';
  }
  return typeName;
}

//classes

/**
 * Represents a frame header that could be used to read from a Buffer or to write to a Buffer
 * @ignore
 * @param {Number} version Protocol version
 * @param {Number} flags
 * @param {Number} streamId
 * @param {Number} opcode
 * @param {Number} bodyLength
 * @constructor
 */
function FrameHeader(version, flags, streamId, opcode, bodyLength) {
  this.version = version;
  this.flags = flags;
  this.streamId = streamId;
  this.opcode = opcode;
  this.bodyLength = bodyLength;
}

/**
 * The length of the header of the frame based on the protocol version
 * @returns {Number}
 */
FrameHeader.size = function (version) {
  if (protocolVersion.uses2BytesStreamIds(version)) {
    return 9;
  }
  return 8;
};

/**
 * Gets the protocol version based on the first byte of the header
 * @param {Buffer} buffer
 * @returns {Number}
 */
FrameHeader.getProtocolVersion = function (buffer) {
  return buffer[0] & 0x7F;
};

/**
 * @param {Buffer} buf
 * @param {Number} [offset]
 * @returns {FrameHeader}
 */
FrameHeader.fromBuffer = function (buf, offset) {
  let streamId = 0;
  if (!offset) {
    offset = 0;
  }
  const version = buf[offset++] & 0x7F;
  const flags = buf.readUInt8(offset++);
  if (!protocolVersion.uses2BytesStreamIds(version)) {
    streamId = buf.readInt8(offset++);
  }
  else {
    streamId = buf.readInt16BE(offset);
    offset += 2;
  }
  return new FrameHeader(version, flags, streamId, buf.readUInt8(offset++), buf.readUInt32BE(offset));
};

/** @returns {Buffer} */
FrameHeader.prototype.toBuffer = function () {
  const buf = utils.allocBufferUnsafe(FrameHeader.size(this.version));
  buf.writeUInt8(this.version, 0);
  buf.writeUInt8(this.flags, 1);
  let offset = 3;
  if (!protocolVersion.uses2BytesStreamIds(this.version)) {
    buf.writeInt8(this.streamId, 2);
  }
  else {
    buf.writeInt16BE(this.streamId, 2);
    offset = 4;
  }
  buf.writeUInt8(this.opcode, offset++);
  buf.writeUInt32BE(this.bodyLength, offset);
  return buf;
};
/**
 * Returns a long representation.
 * Used internally for deserialization
 */
Long.fromBuffer = function (value) {
  if (!(value instanceof Buffer)) {
    throw new TypeError('Expected Buffer, obtained ' + util.inspect(value));
  }
  return new Long(value.readInt32BE(4), value.readInt32BE(0));
};

/**
 * Returns a big-endian buffer representation of the Long instance
 * @param {Long} value
 */
Long.toBuffer = function (value) {
  if (!(value instanceof Long)) {
    throw new TypeError('Expected Long, obtained ' + util.inspect(value));
  }
  const buffer = utils.allocBufferUnsafe(8);
  buffer.writeUInt32BE(value.getHighBitsUnsigned(), 0);
  buffer.writeUInt32BE(value.getLowBitsUnsigned(), 4);
  return buffer;
};

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

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

/**
 * Generates a value representing the timestamp for the query in microseconds based on the date and the microseconds provided
 * @param {Date} [date] The date to generate the value, if not provided it will use the current date.
 * @param {Number} [microseconds] A number from 0 to 999 used to build the microseconds part of the date.
 * @returns {Long}
 */
function generateTimestamp(date, microseconds) {
  if (!date) {
    date = new Date();
  }
  let longMicro = Long.ZERO;
  if (typeof microseconds === 'number' && microseconds >= 0 && microseconds < 1000) {
    longMicro = Long.fromInt(microseconds);
  }
  else {
    if (_timestampTicks > 999) {
      _timestampTicks = 0;
    }
    longMicro = Long.fromInt(_timestampTicks);
    _timestampTicks++;
  }
  return Long
    .fromNumber(date.getTime())
    .multiply(_longOneThousand)
    .add(longMicro);
}

//error classes

/** @private */
function QueryParserError(e) {
  QueryParserError.super_.call(this, e.message, this.constructor);
  this.internalError = e;
}
util.inherits(QueryParserError, errors.DriverError);

/** @private */
function TimeoutError (message) {
  TimeoutError.super_.call(this, message, this.constructor);
  this.info = 'Represents an error that happens when the maximum amount of time for an operation passed.';
}
util.inherits(TimeoutError, errors.DriverError);

exports.opcodes = opcodes;
exports.consistencies = consistencies;
exports.consistencyToString = consistencyToString;
exports.dataTypes = dataTypes;
exports.getDataTypeNameByCode = getDataTypeNameByCode;
exports.distance = distance;
exports.frameFlags = frameFlags;
exports.protocolEvents = protocolEvents;
exports.protocolVersion = protocolVersion;
exports.responseErrorCodes = responseErrorCodes;
exports.resultKind = resultKind;
exports.timeuuid = timeuuid;
exports.uuid = uuid;
exports.BigDecimal = require('./big-decimal');
exports.Duration = require('./duration');
exports.FrameHeader = FrameHeader;
exports.InetAddress = require('./inet-address');
exports.Integer = require('./integer');
exports.LocalDate = require('./local-date');
exports.LocalTime = require('./local-time');
exports.Long = Long;
exports.ResultSet = require('./result-set');
exports.ResultStream = require('./result-stream');
exports.Row = require('./row');
//export DriverError for backward-compatibility
exports.DriverError = errors.DriverError;
exports.TimeoutError = TimeoutError;
exports.TimeUuid = TimeUuid;
exports.Tuple = require('./tuple');
exports.Uuid = Uuid;
exports.unset = unset;
exports.generateTimestamp = generateTimestamp;


"use strict";

const Long = require('long');

const TWO_PWR_16_DBL = 1 << 16;
const TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL;
const one = new MutableLong(1, 0, 0, 0);

/**
 * Constructs a signed int64 representation.
 * @constructor
 * @ignore
 */
function MutableLong(b00, b16, b32, b48) {
  // Use an array of uint16
  this._arr = [ b00 & 0xffff, b16 & 0xffff, b32 & 0xffff, b48 & 0xffff ];
}

MutableLong.fromNumber = function fromNumber(value) {
  if (isNaN(value) || !isFinite(value)) {
    return new MutableLong();
  }
  if (value < 0) {
    return MutableLong.fromNumber(-value).negate();
  }
  const low32Bits = value % TWO_PWR_32_DBL;
  const high32Bits = value / TWO_PWR_32_DBL;
  return MutableLong.fromBits(low32Bits, high32Bits);
};

MutableLong.fromBits = function fromBits(low32Bits, high32Bits) {
  return new MutableLong(low32Bits, low32Bits >>> 16, high32Bits, high32Bits >>> 16);
};

/**
 * Returns a Long representation of the given string, written using the specified radix.
 * @param {String} str
 * @param {Number} [radix]
 * @return {MutableLong}
 */
MutableLong.fromString = function fromString(str, radix) {
  if (typeof str !== 'string') {
    throw new Error('String format is not valid: ' + str);
  }
  if (str.length === 0) {
    throw Error('number format error: empty string');
  }
  if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") {
    return new MutableLong();
  }
  radix = radix || 10;
  if (radix < 2 || radix > 36) {
    throw Error('radix out of range: ' + radix);
  }

  let p;
  if ((p = str.indexOf('-')) > 0) {
    throw Error('number format error: interior "-" character: ' + str);
  }
  if (p === 0) {
    return MutableLong.fromString(str.substring(1), radix).negate();
  }

  // Do several (8) digits each time through the loop
  const radixToPower = MutableLong.fromNumber(Math.pow(radix, 8));

  const result = new MutableLong();
  for (let i = 0; i < str.length; i += 8) {
    const size = Math.min(8, str.length - i);
    const value = parseInt(str.substring(i, i + size), radix);
    if (size < 8) {
      const power = MutableLong.fromNumber(Math.pow(radix, size));
      result.multiply(power).add(MutableLong.fromNumber(value));
      break;
    }
    result.multiply(radixToPower);
    result.add(MutableLong.fromNumber(value));
  }
  return result;
};

MutableLong.prototype.toString = function toString() {
  return this.toImmutable().toString();
};

/**
 * Compares this value with the provided value.
 * @param {MutableLong} other
 * @return {number}
 */
MutableLong.prototype.compare = function (other) {
  const thisNeg = this.isNegative();
  const otherNeg = other.isNegative();
  if (thisNeg && !otherNeg) {
    return -1;
  }
  if (!thisNeg && otherNeg) {
    return 1;
  }
  // At this point the sign bits are the same
  return this._compareBits(other);
};

MutableLong.prototype._compareBits = function(other) {
  for (let i = 3; i >= 0; i--) {
    if (this._arr[i] > other._arr[i]) {
      return 1;
    }
    if (this._arr[i] < other._arr[i]) {
      return -1;
    }
  }
  return 0;
};

MutableLong.prototype.getUint16 = function (index) {
  return this._arr[index];
};

MutableLong.prototype.getLowBitsUnsigned = function () {
  return (this._arr[0] | ((this._arr[1] & 0xffff) << 16)) >>> 0;
};

MutableLong.prototype.getHighBitsUnsigned = function () {
  return (this._arr[2] | (this._arr[3] << 16)) >>> 0;
};

MutableLong.prototype.toNumber = function () {
  return (this._arr[3] << 16 | this._arr[2]) * TWO_PWR_32_DBL + ((this._arr[1] << 16 | this._arr[0]) >>> 0);
};

/**
 * Performs the bitwise NOT of this value.
 * @return {MutableLong}
 */
MutableLong.prototype.not = function () {
  this._arr[0] = ~this._arr[0] & 0xffff;
  this._arr[1] = ~this._arr[1] & 0xffff;
  this._arr[2] = ~this._arr[2] & 0xffff;
  this._arr[3] = ~this._arr[3] & 0xffff;
  return this;
};

MutableLong.prototype.add = function (addend) {
  let c48 = 0, c32 = 0, c16 = 0, c00 = 0;
  c00 += this._arr[0] + addend._arr[0];
  this._arr[0] = c00 & 0xffff;

  c16 += c00 >>> 16;
  c16 += this._arr[1] + addend._arr[1];
  this._arr[1] = c16 & 0xffff;

  c32 += c16 >>> 16;
  c32 += this._arr[2] + addend._arr[2];
  this._arr[2] = c32 & 0xffff;

  c48 += c32 >>> 16;
  c48 += this._arr[3] + addend._arr[3];
  this._arr[3] = c48 & 0xffff;
  return this;
};

MutableLong.prototype.shiftLeft = function (numBits) {
  if (numBits === 0) {
    return this;
  }
  if (numBits >= 64) {
    return this.toZero();
  }
  const remainingBits = numBits % 16;
  const pos = Math.floor(numBits / 16);
  if (pos > 0) {
    this._arr[3] = this._arr[3 - pos];
    this._arr[2] = pos > 2 ? 0 : this._arr[2 - pos];
    this._arr[1] = pos > 1 ? 0 : this._arr[0];
    this._arr[0] = 0;
  }
  if (remainingBits > 0) {
    // shift left within the int16 and the next one
    this._arr[3] = ((this._arr[3] << remainingBits) | (this._arr[2] >>> (16 - remainingBits))) & 0xffff;
    this._arr[2] = ((this._arr[2] << remainingBits) | (this._arr[1] >>> (16 - remainingBits))) & 0xffff;
    this._arr[1] = ((this._arr[1] << remainingBits) | (this._arr[0] >>> (16 - remainingBits))) & 0xffff;
    this._arr[0] = (this._arr[0] << remainingBits) & 0xffff;
  }
  return this;
};

MutableLong.prototype.shiftRightUnsigned = function (numBits) {
  if (numBits === 0) {
    return this;
  }
  if (numBits >= 64) {
    return this.toZero();
  }
  const remainingBits = numBits % 16;
  const pos = Math.floor(numBits / 16);
  if (pos > 0) {
    this._arr[0] = this._arr[pos];
    this._arr[1] = pos > 2 ? 0 : this._arr[1 + pos];
    this._arr[2] = pos > 1 ? 0 : this._arr[3];
    this._arr[3] = 0;
  }
  if (remainingBits > 0) {
    this._arr[0] = (this._arr[0] >>> remainingBits) | ((this._arr[1] << (16 - remainingBits)) & 0xffff);
    this._arr[1] = (this._arr[1] >>> remainingBits) | ((this._arr[2] << (16 - remainingBits)) & 0xffff);
    this._arr[2] = (this._arr[2] >>> remainingBits) | ((this._arr[3] << (16 - remainingBits)) & 0xffff);
    this._arr[3] = this._arr[3] >>> remainingBits;
  }
  return this;
};

MutableLong.prototype.or = function (other) {
  this._arr[0] |= other._arr[0];
  this._arr[1] |= other._arr[1];
  this._arr[2] |= other._arr[2];
  this._arr[3] |= other._arr[3];
  return this;
};

/**
 * Returns the bitwise XOR of this Long and the given one.
 * @param {MutableLong} other
 * @returns {MutableLong} this instance.
 */
MutableLong.prototype.xor = function (other) {
  this._arr[0] ^= other._arr[0];
  this._arr[1] ^= other._arr[1];
  this._arr[2] ^= other._arr[2];
  this._arr[3] ^= other._arr[3];
  return this;
};

MutableLong.prototype.clone = function () {
  return new MutableLong(this._arr[0], this._arr[1], this._arr[2], this._arr[3]);
};

/**
 * Performs the product of this and the specified Long.
 * @param {MutableLong} multiplier
 * @returns {MutableLong} this instance.
 */
MutableLong.prototype.multiply = function multiply(multiplier) {
  if (this.isZero() || multiplier.isZero()) {
    return this.toZero();
  }
  if (this.isNegative()) {
    if (multiplier.isNegative()) {
      return this.negate().multiply(multiplier.clone().negate());
    }
    return this.negate().multiply(multiplier).negate();
  }
  else if (multiplier.isNegative()) {
    return this.multiply(multiplier.clone().negate()).negate();
  }
  // We can skip products that would overflow.
  let c48 = 0, c32 = 0, c16 = 0, c00 = 0;
  c00 += this._arr[0] * multiplier._arr[0];
  c16 += c00 >>> 16;

  c16 += this._arr[1] * multiplier._arr[0];
  c32 += c16 >>> 16;
  c16 &= 0xFFFF;
  c16 += this._arr[0] * multiplier._arr[1];
  c32 += c16 >>> 16;

  c32 += this._arr[2] * multiplier._arr[0];
  c48 += c32 >>> 16;
  c32 &= 0xFFFF;
  c32 += this._arr[1] * multiplier._arr[1];
  c48 += c32 >>> 16;
  c32 &= 0xFFFF;
  c32 += this._arr[0] * multiplier._arr[2];
  c48 += c32 >>> 16;
  c48 += this._arr[3] * multiplier._arr[0] + this._arr[2] * multiplier._arr[1] +
    this._arr[1] * multiplier._arr[2] + this._arr[0] * multiplier._arr[3];

  this._arr[0] = c00 & 0xffff;
  this._arr[1] = c16 & 0xffff;
  this._arr[2] = c32 & 0xffff;
  this._arr[3] = c48 & 0xffff;
  return this;
};

MutableLong.prototype.toZero = function () {
  this._arr[3] = this._arr[2] = this._arr[1] =this._arr[0] = 0;
  return this;
};

MutableLong.prototype.isZero = function () {
  return (this._arr[3] === 0 && this._arr[2] === 0 && this._arr[1] === 0 && this._arr[0] === 0);
};

MutableLong.prototype.isNegative = function () {
  // most significant bit turned on
  return (this._arr[3] & 0x8000) > 0;
};

/**
 * Negates this value.
 * @return {MutableLong}
 */
MutableLong.prototype.negate = function () {
  return this.not().add(one);
};

MutableLong.prototype.equals = function (other) {
  if (!(other instanceof MutableLong)) {
    return false;
  }
  return (
    this._arr[0] === other._arr[0] && this._arr[1] === other._arr[1] &&
    this._arr[2] === other._arr[2] && this._arr[3] === other._arr[3]);
};

MutableLong.prototype.toImmutable = function () {
  return Long.fromBits(this.getLowBitsUnsigned(), this.getHighBitsUnsigned(), false);
};

module.exports = MutableLong;


"use strict";

/**
 * Creates a new Aggregate.
 * @classdesc Describes a CQL aggregate.
 * @alias module:metadata~Aggregate
 * @constructor
 */
function Aggregate() {
  /**
   * Name of the aggregate.
   * @type {String}
   */
  this.name = null;
  /**
   * Name of the keyspace where the aggregate is declared.
   */
  this.keyspaceName = null;
  /**
   * Signature of the aggregate.
   * @type {Array.<String>}
   */
  this.signature = null;
  /**
   * List of the CQL aggregate argument types.
   * @type {Array.<{code, info}>}
   */
  this.argumentTypes = null;
  /**
   * State Function.
   * @type {String}
   */
  this.stateFunction = null;
  /**
   * State Type.
   * @type {{code, info}}
   */
  this.stateType = null;
  /**
   * Final Function.
   * @type {String}
   */
  this.finalFunction = null;
  this.initConditionRaw = null;
  /**
   * Initial state value of this aggregate.
   * @type {String}
   */
  this.initCondition = null;
  /**
   * Type of the return value.
   * @type {{code: number, info: (Object|Array|null)}}
   */
  this.returnType = null;
}

module.exports = Aggregate;


"use strict";
const util = require('util');
const events = require('events');

const utils = require('./utils');
const types = require('./types');
const HostConnectionPool = require('./host-connection-pool');
const PrepareHandler = require('./prepare-handler');

const healthResponseCountInterval = 200;

/**
 * Creates a new Host instance.
 * @classdesc
 * Represents a Cassandra node.
 * @extends EventEmitter
 * @constructor
 */
function Host(address, protocolVersion, options, metadata) {
  events.EventEmitter.call(this);
  /**
   * Gets ip address and port number of the node separated by `:`.
   * @type {String}
   */
  this.address = address;
  this.setDownAt = 0;

  /**
   * Gets the timestamp of the moment when the Host was marked as UP.
   * @type {Number|null}
   * @ignore
   * @internal
   */
  this.isUpSince = null;

  Object.defineProperty(this, 'options', { value: options, enumerable: false, writable: false});

  /**
   * The host pool.
   * @internal
   * @ignore
   * @type {HostConnectionPool}
   */
  Object.defineProperty(this, 'pool', { value: new HostConnectionPool(this, protocolVersion), enumerable: false});

  const self = this;
  this.pool.on('open', this._onNewConnectionOpen.bind(this));
  this.pool.on('remove', function onConnectionRemovedFromPool() {
    self._checkPoolState();
  });
  /**
   * Gets string containing the Cassandra version.
   * @type {String}
   */
  this.cassandraVersion = null;
  /**
   * Gets data center name of the node.
   * @type {String}
   */
  this.datacenter = null;
  /**
   * Gets rack name of the node.
   * @type {String}
   */
  this.rack = null;
  /**
   * Gets the tokens assigned to the node.
   * @type {Array}
   */
  this.tokens = null;

  /**
   * Gets the id of the host.
   *
This identifier is used by the server for internal communication / gossip.

   * @type {Uuid}
   */
  this.hostId = null;

  // the distance as last set using the load balancing policy
  this._distance = types.distance.ignored;
  this._healthResponseCounter = 0;

  // Make some of the private instance variables not enumerable to prevent from showing when inspecting
  Object.defineProperty(this, '_metadata', { value: metadata, enumerable: false });
  Object.defineProperty(this, '_healthResponseCountTimer', { value: null, enumerable: false, writable: true });

  this.reconnectionSchedule = this.options.policies.reconnection.newSchedule();
  this.reconnectionDelay = 0;
}

util.inherits(Host, events.EventEmitter);

/**
 * Marks this host as not available for query coordination, when the host was previously marked as UP, otherwise its
 * a no-op.
 * @internal
 * @ignore
 */
Host.prototype.setDown = function() {
  // Multiple events signaling that a host is failing could cause multiple calls to this method
  if (this.setDownAt !== 0) {
    // the host is already marked as Down
    return;
  }

  if (this.pool.isClosing()) {
    // the pool is being closed/shutdown, don't mind
    return;
  }

  this.setDownAt = Date.now();

  if (this.pool.coreConnectionsLength > 0) {
    // According to the distance, there should be connections open to it => issue a warning
    this.log('warning', `Host ${this.address} considered as DOWN. Reconnection delay ${this.reconnectionDelay}ms.`);
  } else {
    this.log('info', `Host ${this.address} considered as DOWN.`);
  }

  this.emit('down');
  this._checkPoolState();
};

/**
 * Marks this host as available for querying.
 * @param {Boolean} [clearReconnection]
 * @internal
 * @ignore
 */
Host.prototype.setUp = function (clearReconnection) {
  if (!this.setDownAt) {
    //The host is already marked as UP
    return;
  }
  this.log('info', util.format('Setting host %s as UP', this.address));
  this.setDownAt = 0;
  this.isUpSince = Date.now();
  //if it was unhealthy and now it is not, lets reset the reconnection schedule.
  this.reconnectionSchedule = this.options.policies.reconnection.newSchedule();
  if (clearReconnection) {
    this.pool.clearNewConnectionAttempt();
  }
  this.emit('up');
};

/**
 * Resets the reconnectionSchedule and tries to issue a reconnection immediately.
 * @internal
 * @ignore
 */
Host.prototype.checkIsUp = function () {
  if (this.isUp()) {
    return;
  }
  this.reconnectionSchedule = this.options.policies.reconnection.newSchedule();
  this.reconnectionDelay = 0;
  this.pool.attemptNewConnectionImmediate();
};

/**
 * @param {Boolean} waitForPending When true, it waits for in-flight operations to be finish before closing the
 * connections.
 * @param {Function} [callback]
 * @internal
 * @ignore
 */
Host.prototype.shutdown = function (waitForPending, callback) {
  callback = callback || utils.noop;

  if (this._healthResponseCountTimer) {
    clearInterval(this._healthResponseCountTimer);
  }

  if (waitForPending) {
    this.pool.drainAndShutdown();
    // Gracefully draining and shutting down the pool is being done in the background, it's not required
    // for the shutting down to be over to callback
    return callback();
  }

  this.pool.shutdown(callback);
};

/**
 * Determines if the node is UP now (seen as UP by the driver).
 * @returns {boolean}
 */
Host.prototype.isUp = function () {
  return !this.setDownAt;
};

/**
 * Determines if the host can be considered as UP
 * @returns {boolean}
 */
Host.prototype.canBeConsideredAsUp = function () {
  const self = this;
  function hasTimePassed() {
    return new Date().getTime() - self.setDownAt >= self.reconnectionDelay;
  }
  return !this.setDownAt || hasTimePassed();
};

/**
 * Sets the distance of the host relative to the client using the load balancing policy.
 * @param {Number} distance
 * @internal
 * @ignore
 */
Host.prototype.setDistance = function (distance) {
  const previousDistance = this._distance;
  this._distance = distance || types.distance.local;
  if (this.options.pooling.coreConnectionsPerHost) {
    this.pool.coreConnectionsLength = this.options.pooling.coreConnectionsPerHost[this._distance] || 0;
  }
  else {
    this.pool.coreConnectionsLength = 1;
  }
  if (this._distance === previousDistance) {
    return this._distance;
  }

  if (this._healthResponseCountTimer) {
    clearInterval(this._healthResponseCountTimer);
  }

  if (this._distance === types.distance.ignored) {
    // this host was local/remote and now must be ignored
    this.emit('ignore');
    this.pool.drainAndShutdown();
  }
  else {
    if (!this.isUp()) {
      this.checkIsUp();
    }

    // Reset the health check timer
    this._healthResponseCountTimer = setInterval(() => {
      this._healthResponseCounter = this.pool.getAndResetResponseCounter();
    }, healthResponseCountInterval);
  }

  return this._distance;
};

/**
 * Changes the protocol version of a given host
 * @param {Number} value
 * @internal
 * @ignore
 */
Host.prototype.setProtocolVersion = function (value) {
  this.pool.protocolVersion = value;
};

/**
 * It gets an open connection to the host.
 * If there isn't an available connections, it will open a new one according to the pooling options.
 * @param {String} keyspace The keyspace that the connection must be using. When the keyspace provided is null, no
 * keyspace check is performed.
 * @param {Connection} previousConnection The previous connection. When provided, the pool should try to provide a
 * different connection.
 * @param {Function} callback
 * @internal
 * @ignore
 */
Host.prototype.borrowConnection = function (keyspace, previousConnection, callback) {
  if (previousConnection) {
    // Obtain one of the existing connections
    return this.pool.borrowConnection(keyspace, previousConnection, callback);
  }

  this.pool.createAndBorrowConnection(keyspace, callback);
};

/**
 * Creates all the connection in the pool.
 * @param {Function} callback
 * @internal
 * @ignore
 */
Host.prototype.warmupPool = function (callback) {
  this.pool.create(true, callback);
};

/**
 * Gets any connection that is already opened or null if not found.
 * @returns {Connection}
 * @internal
 * @ignore
 */
Host.prototype.getActiveConnection = function () {
  if (!this.isUp() || !this.pool.connections.length) {
    return null;
  }
  return this.pool.connections[0];
};

/**
 * Internal method to get the amount of responses dequeued in the last interval (between 200ms and 400ms) on all
 * connections to the host.
 * @returns {Number}
 * @internal
 * @ignore
 */
Host.prototype.getResponseCount = function () {
  // Last interval plus the current count
  return this._healthResponseCounter + this.pool.responseCounter;
};

/**
 * Checks the health of a connection in the pool
 * @param {Connection} connection
 * @internal
 * @ignore
 */
Host.prototype.checkHealth = function (connection) {
  if (connection.timedOutOperations <= this.options.socketOptions.defunctReadTimeoutThreshold) {
    return;
  }
  this.removeFromPool(connection);
};

/**
 * @param {Connection} connection
 * @internal
 * @ignore
 */
Host.prototype.removeFromPool = function (connection) {
  this.pool.remove(connection);
  this._checkPoolState();
};

/**
 * Internal method that gets the amount of in-flight requests on all connections to the host.
 * @internal
 * @ignore
 */
Host.prototype.getInFlight = function () {
  return this.pool.getInFlight();
};

/**
 * Validates that the internal state of the connection pool.
 * If the pool size is smaller than expected, schedule a new connection attempt.
 * If the amount of connections is 0 for not ignored hosts, the host must be down.
 * @private
 */
Host.prototype._checkPoolState = function () {
  if (this.pool.isClosing()) {
    return;
  }

  if (this.pool.connections.length < this.pool.coreConnectionsLength) {
    // the pool needs to grow / reconnect
    if (!this.pool.hasScheduledNewConnection()) {
      this.reconnectionDelay = this.reconnectionSchedule.next().value;
      this.pool.scheduleNewConnectionAttempt(this.reconnectionDelay);
    }
  }

  const shouldHaveConnections = this._distance !== types.distance.ignored && this.pool.coreConnectionsLength > 0;

  if (shouldHaveConnections && this.pool.connections.length === 0) {
    // Mark as DOWN, if its UP
    this.setDown();
  }
};

/**
 * Executed after an scheduled new connection attempt finished
 * @private
 */
Host.prototype._onNewConnectionOpen = function (err) {
  if (err) {
    this._checkPoolState();
    return;
  }
  const self = this;
  function setUpAndContinue(err) {
    if (err) {
      self.log('warning', util.format('Failed re-preparing on host %s: %s', self.address, err), err);
    }
    self.setUp();
    self.pool.increaseSize();
  }
  if (this.isUp() || !this.options.rePrepareOnUp) {
    return setUpAndContinue();
  }
  this.log('info', util.format('Re-preparing all queries on host %s before setting it as UP', this.address));
  const allPrepared = this._metadata.getAllPrepared();
  PrepareHandler.prepareAllQueries(this, allPrepared, setUpAndContinue);
};

/**
 * Returns an array containing the Cassandra Version as an Array of Numbers having the major version in the first
 * position.
 * @returns {Array.<Number>}
 */
Host.prototype.getCassandraVersion = function () {
  if (!this.cassandraVersion) {
    return utils.emptyArray;
  }
  return this.cassandraVersion.split('-')[0].split('.').map(x => parseInt(x, 10));
};

Host.prototype.log = utils.log;

/**
 * Represents an associative-array of {@link Host hosts} that can be iterated.
 * It creates an internal copy when adding or removing, making it safe to iterate using the values() method within async operations.
 * @extends events.EventEmitter
 * @constructor
 */
function HostMap() {
  events.EventEmitter.call(this);
  this._items = {};
  this._values = null;
  Object.defineProperty(this, 'length', { get: function () { return this.values().length; }, enumerable: true });
}

util.inherits(HostMap, events.EventEmitter);

/**
 * Emitted when a host is added to the map
 * @event HostMap#add
 */
/**
 * Emitted when a host is removed from the map
 * @event HostMap#remove
 */

/**
 * Executes a provided function once per map element.
 * @param callback
 */
HostMap.prototype.forEach = function (callback) {
  //Use a new reference, allowing the map to be modified.
  const items = this._items;
  for (const key in items) {
    if (!items.hasOwnProperty(key)) {
      continue;
    }
    callback(items[key], key);
  }
};

/**
 * Gets a {@link Host host} by key or undefined if not found.
 * @param {String} key
 * @returns {Host}
 */
HostMap.prototype.get = function (key) {
  return this._items[key];
};

/**
 * Returns an array of host addresses.
 * @returns {Array.<String>}
 */
HostMap.prototype.keys = function () {
  return Object.keys(this._items);
};

/**
 * Removes an item from the map.
 * @param {String} key The key of the host
 * @fires HostMap#remove
 */
HostMap.prototype.remove = function (key) {
  if (!this._items.hasOwnProperty(key)) {
    //it's not part of it, do nothing
    return;
  }
  //clear cache
  this._values = null;
  //copy the values
  const copy = utils.extend({}, this._items);
  const h = copy[key];
  delete copy[key];
  this._items = copy;
  this.emit('remove', h);
};

/**
 * Removes multiple hosts from the map.
 * @param {Array.<String>} keys
 * @fires HostMap#remove
 */
HostMap.prototype.removeMultiple = function (keys) {
  //clear value cache
  this._values = null;
  //copy the values
  const copy = utils.extend({}, this._items);
  const removedHosts = [];
  for (let i = 0; i < keys.length; i++) {
    const h = copy[keys[i]];
    if (!h) {
      continue;
    }
    removedHosts.push(h);
    delete copy[keys[i]];
  }
  this._items = copy;
  removedHosts.forEach(function (h) {
    this.emit('remove', h);
  }, this);
};

/**
 * Adds a new item to the map.
 * @param {String} key The key of the host
 * @param {Host} value The host to be added
 * @fires HostMap#remove
 * @fires HostMap#add
 */
HostMap.prototype.set = function (key, value) {
  //clear values cache
  this._values = null;
  const originalValue = this._items[key];
  if (originalValue) {
    //The internal structure does not change
    this._items[key] = value;
    //emit a remove followed by a add
    this.emit('remove', originalValue);
    this.emit('add', value);
    return;
  }
  //copy the values
  const copy = utils.extend({}, this._items);
  copy[key] = value;
  this._items = copy;
  this.emit('add', value);
  return value;
};

/**
 * Returns a shallow copy of a portion of the items into a new array object.
 * Backward-compatibility.
 * @param {Number} [begin]
 * @param {Number} [end]
 * @returns {Array}
 * @ignore
 */
HostMap.prototype.slice = function (begin, end) {
  if (!begin && !end) {
    //avoid making a copy of the copy
    return this.values();
  }
  begin = begin || 0;
  return this.values().slice(begin, end);
};
//Backward-compatibility
HostMap.prototype.push = HostMap.prototype.set;

/**
 * Returns a shallow copy of the values of the map.
 * @returns {Array.<Host>}
 */
HostMap.prototype.values = function () {
  if (!this._values) {
    //cache the values
    const values = [];
    for (const key in this._items) {
      if (!this._items.hasOwnProperty(key)) {
        continue;
      }
      values.push(this._items[key]);
    }
    this._values = Object.freeze(values);
  }
  return this._values;
};

/**
 * Removes all items from the map.
 * @returns {Array.<Host>} The previous items
 */
HostMap.prototype.clear = function () {
  const previousItems = this.values();
  // Clear cache
  this._values = null;
  // Clear items
  this._items = {};
  for (let i = 0; i < previousItems.length; i++) {
    this.emit('remove', previousItems[i]);
  }
  return previousItems;
};

HostMap.prototype.inspect = function() {
  return this._items;
};

HostMap.prototype.toJSON = function() {
  return this._items;
};

exports.Host = Host;
exports.HostMap = HostMap;


'use strict';

const util = require('util');
const errors = require('../errors');

/** @module policies/speculativeExecution */

/**
 * @classdesc
 * The policy that decides if the driver will send speculative queries to the next hosts when the current host takes too
 * long to respond.
 *
Note that only idempotent statements will be speculatively retried.

 * @constructor
 * @abstract
 */
function SpeculativeExecutionPolicy() {

}

/**
 * Initialization method that gets invoked on Client startup.
 * @param {Client} client
 * @abstract
 */
SpeculativeExecutionPolicy.prototype.init = function (client) {

};

/**
 * Gets invoked at client shutdown, giving the opportunity to the implementor to perform cleanup.
 * @abstract
 */
SpeculativeExecutionPolicy.prototype.shutdown = function () {

};

/**
 * Gets the plan to use for a new query.
 * Returns an object with a nextExecution() method, which returns a positive number representing the
 * amount of milliseconds to delay the next execution or a non-negative number to avoid further executions.
 * @param {String} keyspace The currently logged keyspace.
 * @param {String|Array<String>} queryInfo The query, or queries in the case of batches, for which to build a plan.
 * @return {{nextExecution: function}}
 * @abstract
 */
SpeculativeExecutionPolicy.prototype.newPlan = function (keyspace, queryInfo) {
  throw new Error('You must implement newPlan() method in the SpeculativeExecutionPolicy');
};

/**
 * Gets an associative array containing the policy options.
 */
SpeculativeExecutionPolicy.prototype.getOptions = function () {
  return new Map();
};

/**
 * Creates a new instance of NoSpeculativeExecutionPolicy.
 * @classdesc
 * A {@link SpeculativeExecutionPolicy} that never schedules speculative executions.
 * @constructor
 * @extends {SpeculativeExecutionPolicy}
 */
function NoSpeculativeExecutionPolicy() {
  this._plan = {
    nextExecution: function () {
      return -1;
    }
  };
}

util.inherits(NoSpeculativeExecutionPolicy, SpeculativeExecutionPolicy);

NoSpeculativeExecutionPolicy.prototype.newPlan = function () {
  return this._plan;
};

/**
 * Creates a new instance of ConstantSpeculativeExecutionPolicy.
 * @classdesc
 * A {@link SpeculativeExecutionPolicy} that schedules a given number of speculative executions,
 * separated by a fixed delay.
 * @constructor
 * @param {Number} delay The delay between each speculative execution.
 * @param {Number} maxSpeculativeExecutions The amount of speculative executions that should be scheduled after the
 * initial execution. Must be strictly positive.
 * @extends {SpeculativeExecutionPolicy}
 */
function ConstantSpeculativeExecutionPolicy(delay, maxSpeculativeExecutions) {
  if (!(delay >= 0)) {
    throw new errors.ArgumentError('delay must be a positive number or zero');
  }
  if (!(maxSpeculativeExecutions > 0)) {
    throw new errors.ArgumentError('maxSpeculativeExecutions must be a positive number');
  }
  this._delay = delay;
  this._maxSpeculativeExecutions = maxSpeculativeExecutions;
}

util.inherits(ConstantSpeculativeExecutionPolicy, SpeculativeExecutionPolicy);

ConstantSpeculativeExecutionPolicy.prototype.newPlan = function () {
  let executions = 0;
  const self = this;
  return {
    nextExecution: function () {
      if (executions++ < self._maxSpeculativeExecutions) {
        return self._delay;
      }
      return -1;
    }
  };
};

/**
 * Gets an associative array containing the policy options.
 */
ConstantSpeculativeExecutionPolicy.prototype.getOptions = function () {
  return new Map([
    ['delay', this._delay ],
    ['maxSpeculativeExecutions', this._maxSpeculativeExecutions ]
  ]);
};

exports.NoSpeculativeExecutionPolicy = NoSpeculativeExecutionPolicy;
exports.SpeculativeExecutionPolicy = SpeculativeExecutionPolicy;
exports.ConstantSpeculativeExecutionPolicy = ConstantSpeculativeExecutionPolicy;


"use strict";
const util = require('util');
const DataCollection = require('./data-collection');
/**
 * Creates a new MaterializedView.
 * @param {String} name Name of the View.
 * @classdesc Describes a CQL materialized view.
 * @alias module:metadata~MaterializedView
 * @augments {module:metadata~DataCollection}
 * @constructor
 */
function MaterializedView(name) {
  DataCollection.call(this, name);
  /**
   * Name of the table.
   * @type {String}
   */
  this.tableName = null;
  /**
   * View where clause.
   * @type {String}
   */
  this.whereClause = null;
  /**
   * Determines if all the table columns where are included in the view.
   * @type {boolean}
   */
  this.includeAllColumns = false;
}

util.inherits(MaterializedView, DataCollection);

module.exports = MaterializedView;