/**
 * @fileoverview This file contains helper code used by BinaryReader
 * and BinaryWriter.
 *
 *
 * @author aappleby@google.com (Austin Appleby)
 */

goog.module('jspb.utils');
goog.module.declareLegacyNamespace();

const BinaryConstants = goog.require('jspb.BinaryConstants');
const { assert } = goog.require('goog.asserts');
const { isBigIntAvailable } = goog.require('jspb.internal_options');
const { ByteString } = goog.require('jspb.bytestring');
const { decodeStringToUint8Array } = goog.require('goog.crypt.base64');
const { unsafeUint8ArrayFromByteString } = goog.require('jspb.unsafe_bytestring');

/**
 * Flag for browser support of Uint8Array slicing.
 *
 * This isn't available in every browser that supports Uint8Array
 *
 * See https://caniuse.com/mdn-javascript_builtins_typedarray_slice and
 * go/jscompiler-flags#browser-featureset-year-options
 * @const {boolean}
 */
const SUPPORTS_UINT8ARRAY_SLICING = goog.FEATURESET_YEAR >= 2018 ||
  (typeof Uint8Array.prototype.slice === 'function');


/** @const {number} */
const MAX_SCRATCHPAD_BYTES = 8;

/**
 * Returns a copy of a slice of a Uint8Array.
 *
 * @param {!Uint8Array} arr the input array to slice.
 * @param {number} startIdx The starting index of the slice.
 * @param {number} endIdx The ending index of the slice.
 * @return {!Uint8Array} the array slice.
 */
function sliceUint8Array(arr, startIdx, endIdx) {
  // See https://jsbench.me/ysl0kb8y54/1
  // This test is significantly faster than native slice performance for an
  // empty slice, and empty slices are not uncommon.
  if (startIdx === endIdx) {
    return new Uint8Array(0);
  }
  return SUPPORTS_UINT8ARRAY_SLICING ?
    arr.slice(startIdx, endIdx) :
    new Uint8Array(arr.subarray(startIdx, endIdx));
}

/**
 * Javascript can't natively handle 64-bit data types, so to manipulate them we
 * have to split them into two 32-bit halves and do the math manually.
 *
 * Instead of instantiating and passing small structures around to do this, we
 * instead just use two global temporary values. This one stores the low 32
 * bits of a split value - for example, if the original value was a 64-bit
 * integer, this temporary value will contain the low 32 bits of that integer.
 * If the original value was a double, this temporary value will contain the
 * low 32 bits of the binary representation of that double, etcetera.
 *
 * This value may be signed or unsigned for the same bit pattern. Coerce to a
 * specific interpretation before use if needed.
 * @type {number}
 */
let split64Low = 0;


/**
 * And correspondingly, this temporary variable will contain the high 32 bits
 * of whatever value was split.
 * @type {number}
 */
let split64High = 0;

/** @type {!DataView|undefined} */
let scratchpad;

/**
 * Splits an unsigned Javascript integer into two 32-bit halves and stores it
 * in the temp values above.
 * @param {number} value The number to split.
 */
function splitUint64(value) {
  // Extract low 32 bits and high 32 bits as unsigned integers.
  const lowBits = value >>> 0;
  const highBits = ((value - lowBits) / BinaryConstants.TWO_TO_32) >>> 0;

  split64Low = lowBits;
  split64High = highBits;
}

/**
 * Splits a signed Javascript integer into two 32-bit halves and stores it in
 * the temp values above.
 * @param {number} value The number to split.
 */
function splitInt64(value) {
  // Perform two's complement conversion if the sign bit was set.
  if (value < 0) {
    // Convert to sign-magnitude representation.
    splitUint64(0 - value);
    const [negLow, negHigh] = negate(split64Low, split64High);
    split64Low = negLow >>> 0;
    split64High = negHigh >>> 0;
  } else {
    splitUint64(value);
  }
}


/**
 * Converts a signed Javascript integer into zigzag format, splits it into two
 * 32-bit halves, and stores it in the temp values above.
 * @param {number} value The number to split.
 */
function splitZigzag64(value) {
  // Convert to sign-magnitude and scale by 2 before we split the value.
  const sign = (value < 0);
  value = Math.abs(value) * 2;

  splitUint64(value);
  let lowBits = split64Low;
  let highBits = split64High;

  // If the value is negative, subtract 1 from the split representation so we
  // don't lose the sign bit due to precision issues.
  if (sign) {
    if (lowBits == 0) {
      if (highBits == 0) {
        lowBits = 0xFFFFFFFF;
        highBits = 0xFFFFFFFF;
      } else {
        highBits--;
        lowBits = 0xFFFFFFFF;
      }
    } else {
      lowBits--;
    }
  }

  split64Low = lowBits;
  split64High = highBits;
}

/**
 * Initialize the scratchpad `DataView` to the given number of bytes and
 * returns scratchpad.
 * @param {number} numBytes
 * @return {!DataView}
 */
function getScratchpad(numBytes) {
  assert(numBytes <= MAX_SCRATCHPAD_BYTES);
  return scratchpad ||
    (scratchpad = new DataView(new ArrayBuffer(MAX_SCRATCHPAD_BYTES)));
}

/**
 * Converts a floating-point number into 32-bit IEEE representation and stores
 * it in the temp values above.
 * @param {number|string} value to split. Accepts 'Infinity'/'-Infinity'/'NaN'
 *     for JSPB wire format compatibility.
 */
function splitFloat32(value) {
  const scratch = getScratchpad(/* numBytes= */ 4);
  //  See go/proto-encoding#cheat-sheet re: little endian.
  scratch.setFloat32(0, +value, /* littleEndian= */ true);
  split64High = 0;
  split64Low = scratch.getUint32(0, /* littleEndian = */ true);
}


/**
 * Converts a floating-point number into 64-bit IEEE representation and stores
 * it in the temp values above.
 * @param {number|string} value to split. Accepts 'Infinity'/'-Infinity'/'NaN'
 *     for JSPB wire format compatibility.
 */
function splitFloat64(value) {
  const scratch = getScratchpad(/* numBytes= */ 8);
  //  See go/proto-encoding#cheat-sheet re: little endian.
  scratch.setFloat64(0, +value, /* littleEndian= */ true);
  split64Low = scratch.getUint32(0, /* littleEndian = */ true);
  split64High = scratch.getUint32(4, /* littleEndian = */ true);
}


/**
 * Converts an 8-byte array into two 32-bit numbers and stores them in the temp
 * values above.
 * @param {!Array<number>} bytes
 */
function splitBytes64(bytes) {
  const [a, b, c, d, e, f, g, h] = bytes;

  split64Low = (a + (b << 8) + (c << 16) + (d << 24)) >>> 0;
  split64High = (e + (f << 8) + (g << 16) + (h << 24)) >>> 0;
}


/**
 * Joins two 32-bit values into a 64-bit unsigned integer. Value will be
 * returned as a string if it is greater than 2^52 to avoid precision loss.
 * @param {number} bitsLow
 * @param {number} bitsHigh
 * @return {number}
 */
function joinUint64(bitsLow, bitsHigh) {
  const maybeUnsafeValue =
    bitsHigh * BinaryConstants.TWO_TO_32 + (bitsLow >>> 0);
  return Number.isSafeInteger(maybeUnsafeValue) ?
    maybeUnsafeValue : /** @type {number} */
    (/** @type {*} */ (joinUnsignedDecimalString(bitsLow, bitsHigh)));
}

/**
 * Joins two 32-bit values into a 64-bit signed integer. Value will be
 * returned as a string if it outside of the safe integer range.
 * @param {number} bitsLow
 * @param {number} bitsHigh
 * @return {number}
 */
function joinInt64(bitsLow, bitsHigh) {
  // If the high bit is set, do a manual two's complement conversion.
  const sign = (bitsHigh & 0x80000000);
  if (sign) {
    bitsLow = (~bitsLow + 1) >>> 0;
    bitsHigh = ~bitsHigh >>> 0;
    if (bitsLow == 0) {
      bitsHigh = (bitsHigh + 1) >>> 0;
    }
  }

  const result = joinUint64(bitsLow, bitsHigh);
  if (typeof result === 'number') {
    return sign ? -result : result;
  }

  return sign ? /** @type {number} */ (/** @type {*} */ ('-' + result)) :
    result;
}

/**
 * Converts 32-bit values from standard two's complement encoding to zig-zag
 * encoding.
 *
 * @param {number} value
 * @return {number}
 */
function toZigzag32(value) {
  return ((value << 1) ^ (value >> 31)) >>> 0;
}

/**
 * Converts split 64-bit values from standard two's complement encoding to
 * zig-zag encoding. Invokes the provided function to produce final result.
 *
 * @param {number} bitsLow
 * @param {number} bitsHigh
 * @param {function(number, number): T} convert Conversion function to produce
 *     the result value, takes parameters (lowBits, highBits).
 * @return {T}
 * @template T
 */
function toZigzag64(bitsLow, bitsHigh, convert) {
  // See
  // https://engdoc.corp.google.com/eng/howto/protocolbuffers/developerguide/encoding.shtml?cl=head#types
  // 64-bit math is: (n << 1) ^ (n >> 63)
  //
  // To do this in 32 bits, we can get a 32-bit sign-flipping mask from the
  // high word.
  // Then we can operate on each word individually, with the addition of the
  // "carry" to get the most significant bit from the low word into the high
  // word.
  const signFlipMask = bitsHigh >> 31;
  bitsHigh = (bitsHigh << 1 | bitsLow >>> 31) ^ signFlipMask;
  bitsLow = (bitsLow << 1) ^ signFlipMask;
  return convert(bitsLow, bitsHigh);
}


/**
 * Joins two 32-bit values into a 64-bit unsigned integer and applies zigzag
 * decoding. Precision will be lost if the result is greater than 2^52.
 * @param {number} bitsLow
 * @param {number} bitsHigh
 * @return {number}
 */
function joinZigzag64(bitsLow, bitsHigh) {
  return fromZigzag64(bitsLow, bitsHigh, joinInt64);
}

/**
 * Converts 32-bit value from zigzag encoding to standard two's
 * complement encoding.
 * @param {number} zigzag
 * @return {number}
 */
function fromZigzag32(zigzag) {
  const signFlipMask = -(zigzag & 1);
  return (zigzag >>> 1) ^ signFlipMask;
}

/**
 * Converts split 64-bit values from zigzag encoding to standard two's
 * complement encoding. Invokes the provided function to produce final result.
 *
 * @param {number} bitsLow
 * @param {number} bitsHigh
 * @param {function(number, number): T} convert Conversion function to produce
 *     the result value, takes parameters (lowBits, highBits).
 * @return {T}
 * @template T
 */
function fromZigzag64(bitsLow, bitsHigh, convert) {
  // 64 bit math is:
  //   signmask = (zigzag & 1) ? -1 : 0;
  //   twosComplement = (zigzag >> 1) ^ signmask;
  //
  // To work with 32 bit, we can operate on both but "carry" the lowest bit
  // from the high word by shifting it up 31 bits to be the most significant bit
  // of the low word.
  const signFlipMask = -(bitsLow & 1);
  bitsLow = ((bitsLow >>> 1) | (bitsHigh << 31)) ^ signFlipMask;
  bitsHigh = (bitsHigh >>> 1) ^ signFlipMask;
  return convert(bitsLow, bitsHigh);
}


/**
 * Joins two 32-bit values into a 32-bit IEEE floating point number and
 * converts it back into a Javascript number.
 * @param {number} bitsLow The low 32 bits of the binary number;
 * @param {number} bitsHigh The high 32 bits of the binary number.
 * @return {number}
 */
function joinFloat32(bitsLow, bitsHigh) {
  const sign = ((bitsLow >> 31) * 2 + 1);
  const exp = (bitsLow >>> 23) & 0xFF;
  const mant = bitsLow & 0x7FFFFF;

  if (exp == 0xFF) {
    if (mant) {
      return NaN;
    } else {
      return sign * Infinity;
    }
  }

  if (exp == 0) {
    // Denormal.
    return sign * Math.pow(2, -149) * mant;
  } else {
    return sign * Math.pow(2, exp - 150) * (mant + Math.pow(2, 23));
  }
}


/**
 * Joins two 32-bit values into a 64-bit IEEE floating point number and
 * converts it back into a Javascript number.
 * @param {number} bitsLow The low 32 bits of the binary number;
 * @param {number} bitsHigh The high 32 bits of the binary number.
 * @return {number}
 */
function joinFloat64(bitsLow, bitsHigh) {
  const sign = ((bitsHigh >> 31) * 2 + 1);
  const exp = (bitsHigh >>> 20) & 0x7FF;
  const mant = BinaryConstants.TWO_TO_32 * (bitsHigh & 0xFFFFF) + bitsLow;

  if (exp == 0x7FF) {
    if (mant) {
      return NaN;
    } else {
      return sign * Infinity;
    }
  }

  if (exp == 0) {
    // Denormal.
    return sign * Math.pow(2, -1074) * mant;
  } else {
    return sign * Math.pow(2, exp - 1075) * (mant + BinaryConstants.TWO_TO_52);
  }
}

/**
 * Losslessly converts a 64-bit unsigned integer in 32:32 split representation
 * into a decimal string.
 * @param {number} bitsLow The low 32 bits of the binary number;
 * @param {number} bitsHigh The high 32 bits of the binary number.
 * @return {string} The binary number represented as a string.
 */
function joinUnsignedDecimalString(bitsLow, bitsHigh) {
  // Must ensure the values are handled as unsigned, any bitwise operations
  // on the input arguments would have turned them into signed values (e.g.
  // zigzag decoding).
  bitsHigh = bitsHigh >>> 0;
  bitsLow = bitsLow >>> 0;
  // Skip the expensive conversion if the number is small enough to use the
  // built-in conversions.
  // Number.MAX_SAFE_INTEGER = 0x001FFFFF FFFFFFFF, thus any number with
  // bitsHigh <= 0x1FFFFF can be safely expressed with a double and retain
  // integer precision.
  // Proven by: Number.isSafeInteger(0x1FFFFF * 2**32 + 0xFFFFFFFF) == true.
  // Even when BigInt is supported, it's faster to avoid the conversion to
  // bigint and back.
  if (bitsHigh <= 0x1FFFFF) {
    return '' + (BinaryConstants.TWO_TO_32 * bitsHigh + bitsLow);
  } else if (isBigIntAvailable()) {
    return '' + (BigInt(bitsHigh) << BigInt(32) | BigInt(bitsLow));
  }
  return joinUnsignedDecimalStringFallback(bitsLow, bitsHigh);
}

/**
 * Losslessly converts a 64-bit unsigned integer in 32:32 split representation
 * into a decimal string without using BigInt.
 * @param {number} bitsLow The unsigned low 32 bits of the binary number;
 * @param {number} bitsHigh The unsigned high 32 bits of the binary number.
 * @return {string} The binary number represented as a string.
 * @package For Testing only.
 */
function joinUnsignedDecimalStringFallback(bitsLow, bitsHigh) {
  // What this code is doing is essentially converting the input number from
  // base-2 to base-1e7, which allows us to represent the 64-bit range with
  // only 3 (very large) digits. Those digits are then trivial to convert to
  // a base-10 string.

  // Split 32:32 representation into 16:24:24 representation so our
  // intermediate digits don't overflow.
  const low = bitsLow & LOW_24_BITS;
  const mid = ((bitsLow >>> 24) | (bitsHigh << 8)) & LOW_24_BITS;
  const high = (bitsHigh >> 16) & LOW_16_BITS;

  // Assemble our three base-1e7 digits, ignoring carries. The maximum
  // value in a digit at this step is representable as a 48-bit integer, which
  // can be stored in a 64-bit floating point number.

  // The magic numbers used here are -
  // 2^24 = 16777216 = (1,6777216) in base-1e7.
  // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7.
  let digitA = low + (mid * 6777216) + (high * 6710656);
  let digitB = mid + (high * 8147497);
  let digitC = (high * 2);

  // Apply carries from A to B and from B to C.
  const base = 10000000;
  if (digitA >= base) {
    digitB += (digitA / base) >>> 0;
    digitA %= base;
  }

  if (digitB >= base) {
    digitC += (digitB / base) >>> 0;
    digitB %= base;
  }

  // If digitC is 0, then we should have returned in the trivial code path
  // at the top for non-safe integers. Given this, we can assume both digitB
  // and digitA need leading zeros.
  assert(digitC);
  return digitC + decimalFrom1e7WithLeadingZeros(digitB) +
    decimalFrom1e7WithLeadingZeros(digitA);
}

/**
 * @param {number} digit1e7 Number < 1e7
 * @return {string} Decimal representation of digit1e7 with leading zeros.
 */
function decimalFrom1e7WithLeadingZeros(digit1e7) {
  const partial = String(digit1e7);
  return '0000000'.slice(partial.length) + partial;
}

/**
 * Losslessly converts a 64-bit signed integer in 32:32 split representation
 * into a decimal string.
 * @param {number} bitsLow The low 32 bits of the binary number;
 * @param {number} bitsHigh The high 32 bits of the binary number.
 * @return {string} The binary number represented as a string.
 */
function joinSignedDecimalString(bitsLow, bitsHigh) {
  const negative = (bitsHigh & 0x80000000);
  if (negative) {
    if (isBigIntAvailable()) {
      return '' +
        ((BigInt(bitsHigh | 0) << BigInt(32)) | BigInt(bitsLow >>> 0));
    }
    return joinNegativeDecimalStringFallback(bitsLow, bitsHigh);
  } else {
    return joinUnsignedDecimalString(bitsLow, bitsHigh);
  }
}

/**
 * Losslessly converts a 64-bit signed integer in 32:32 split representation
 * into a number or decimal string, using a number when the value is between
 * [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], or decimal string
 * otherwise.
 *
 * @param {number} bitsLow The low 32 bits of the binary number;
 * @param {number} bitsHigh The high 32 bits of the binary number.
 * @return {number|string} The number represented as a number or string.
 */
function joinSignedNumberOrDecimalString(bitsLow, bitsHigh) {
  const possiblyUnsafe = joinInt64(bitsLow, bitsHigh);
  if (Number.isSafeInteger(possiblyUnsafe)) {
    return possiblyUnsafe;
  }
  return joinSignedDecimalString(bitsLow, bitsHigh);
}

/**
 * Losslessly converts a 64-bit unsigned integer in 32:32 split representation
 * into a number or decimal string, using a number when the value is between
 * [0, Number.MAX_SAFE_INTEGER], or decimal string otherwise.
 *
 * @param {number} bitsLow The low 32 bits of the binary number;
 * @param {number} bitsHigh The high 32 bits of the binary number.
 * @return {number|string} The number represented as a number or string.
 */
function joinUnsignedNumberOrDecimalString(bitsLow, bitsHigh) {
  bitsHigh >>>= 0;
  const possiblyUnsafe = joinUint64(bitsLow, bitsHigh);
  if (Number.isSafeInteger(possiblyUnsafe)) {
    return possiblyUnsafe;
  }
  return joinUnsignedDecimalString(bitsLow, bitsHigh);
}

/**
 * Losslessly converts a 64-bit signed integer in 32:32 split representation
 * into a decimal string.
 * @param {number} bitsLow The low 32 bits of the binary number;
 * @param {number} bitsHigh The high 32 bits of the binary number.
 * @return {string} The binary number represented as a string.
 * @package For Testing only.
 */
function joinNegativeDecimalStringFallback(bitsLow, bitsHigh) {
  // Do a manual two's complement conversion before the decimal conversion.
  const [negLow, negHigh] = negate(bitsLow, bitsHigh);
  bitsLow = negLow;
  bitsHigh = negHigh;

  return '-' + joinUnsignedDecimalString(bitsLow, bitsHigh);
}

/**
 * Converts a signed or unsigned decimal string into two 32-bit halves, and
 * stores them in the temp variables listed above. Only the lower 64 bits of the
 * value are kept.
 * @param {string} value The decimal string to convert.
 */
function splitDecimalString(value) {
  assert(value.length > 0);

  // Strings that are shorter than MAX_SAFE_INTEGER are sure to be safe
  // to parse directly to a double for conversion with Int64.fromNumber.
  if (value.length < MAX_SAFE_INTEGER_DECIMAL_LENGTH) {
    splitInt64(Number(value));
    return;
  }

  if (isBigIntAvailable()) {
    const bigInt = BigInt(value);
    split64Low = Number(bigInt & BigInt(ALL_32_BITS)) >>> 0;
    split64High = Number((bigInt >> BigInt(32)) & BigInt(ALL_32_BITS));
  } else {
    splitDecimalStringFallback(value);
  }
}

/**
 * Converts a signed or unsigned decimal string into two 32-bit halves, and
 * stores them in the temp variables listed above. Only the lower 64 bits of the
 * value are kept.
 * @param {string} value The decimal string to convert.
 * @package For Testing only.
 */
function splitDecimalStringFallback(value) {
  assert(value.length > 0);
  // Check for minus sign.
  const firstDigitIndex = +(value[0] === '-');
  split64Low = 0;
  split64High = 0;
  const end = value.length;
  // Work 6 decimal digits at a time, acting like we're converting base 1e6
  // digits to binary. This is safe to do with floating point math because
  // Number.isSafeInteger(ALL_32_BITS * 1e6) == true.
  const base = 1e6;
  for (let sliceStart = 0 + firstDigitIndex,
    sliceEnd = (end - firstDigitIndex) % 6 + firstDigitIndex;
    sliceEnd <= end; sliceStart = sliceEnd, sliceEnd += 6) {
    const digit1e6 = Number(value.slice(sliceStart, sliceEnd));
    split64High *= base;
    split64Low = split64Low * base + digit1e6;
    // Carry bits from split64Low to
    if (split64Low >= BinaryConstants.TWO_TO_32) {
      split64High += Math.trunc(split64Low / BinaryConstants.TWO_TO_32);
      // Drop any bits higher than 64 as we accumulate
      split64High = split64High >>> 0;
      split64Low = split64Low >>> 0;
    }
  }

  if (firstDigitIndex /* != 0 */) {
    const [negLow, negHigh] = negate(split64Low, split64High);
    split64Low = negLow;
    split64High = negHigh;
  }
}

/**
 * @param {number} lowBits
 * @param {number} highBits
 * @return {!Array<number>} [low, high] words of the result.
 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Signed_32-bit_integers
 */
function negate(lowBits, highBits) {
  highBits = ~highBits;
  if (lowBits) {
    lowBits = ~lowBits + 1;
  } else {
    // If lowBits is 0, then bitwise-not is 0xFFFFFFFF,
    // adding 1 to that, results in 0x100000000, which leaves
    // the low bits 0x0 and simply adds one to the high bits.
    highBits += 1;
  }
  return [lowBits, highBits];
}


/**
 * Counts the number of contiguous varints in a buffer.
 * @param {!Uint8Array} buffer The buffer to scan.
 * @param {number} start The starting point in the buffer to scan.
 * @param {number} end The end point in the buffer to scan.
 * @return {number} The number of varints in the buffer.
 */
function countVarints(buffer, start, end) {
  // Count how many high bits of each byte were set in the buffer.
  let count = 0;
  for (let i = start; i < end; i++) {
    count += buffer[i] >> 7;
  }

  // The number of varints in the buffer equals the size of the buffer minus
  // the number of non-terminal bytes in the buffer (those with the high bit
  // set).
  return (end - start) - count;
}


/**
 * Counts the number of contiguous varint fields with the given field number in
 * the buffer.
 * @param {!Uint8Array} buffer The buffer to scan.
 * @param {number} start The starting point in the buffer to scan.
 * @param {number} end The end point in the buffer to scan.
 * @param {number} field The field number to count.
 * @return {number} The number of matching fields in the buffer.
 */
function countVarintFields(buffer, start, end, field) {
  let count = 0;
  let cursor = start;
  const tag = field * 8 + BinaryConstants.WireType.VARINT;

  if (tag < 128) {
    // Single-byte field tag, we can use a slightly quicker count.
    while (cursor < end) {
      // Skip the field tag, or exit if we find a non-matching tag.
      if (buffer[cursor++] != tag) return count;

      // Field tag matches, we've found a valid field.
      count++;

      // Skip the varint.
      while (1) {
        const x = buffer[cursor++];
        if ((x & 0x80) == 0) break;
      }
    }
  } else {
    while (cursor < end) {
      // Skip the field tag, or exit if we find a non-matching tag.
      let temp = tag;
      while (temp > 128) {
        if (buffer[cursor] != ((temp & 0x7F) | 0x80)) return count;
        cursor++;
        temp >>= 7;
      }
      if (buffer[cursor++] != temp) return count;

      // Field tag matches, we've found a valid field.
      count++;

      // Skip the varint.
      while (1) {
        const x = buffer[cursor++];
        if ((x & 0x80) == 0) break;
      }
    }
  }
  return count;
}


/**
 * Counts the number of contiguous fixed32 fields with the given tag in the
 * buffer.
 * @param {!Uint8Array} buffer The buffer to scan.
 * @param {number} start The starting point in the buffer to scan.
 * @param {number} end The end point in the buffer to scan.
 * @param {number} tag The tag value to count.
 * @param {number} stride The number of bytes to skip per field.
 * @return {number} The number of fields with a matching tag in the buffer.
 * @private
 */
function countFixedFields_(buffer, start, end, tag, stride) {
  let count = 0;
  let cursor = start;

  if (tag < 128) {
    // Single-byte field tag, we can use a slightly quicker count.
    while (cursor < end) {
      // Skip the field tag, or exit if we find a non-matching tag.
      if (buffer[cursor++] != tag) return count;

      // Field tag matches, we've found a valid field.
      count++;

      // Skip the value.
      cursor += stride;
    }
  } else {
    while (cursor < end) {
      // Skip the field tag, or exit if we find a non-matching tag.
      let temp = tag;
      while (temp > 128) {
        if (buffer[cursor++] != ((temp & 0x7F) | 0x80)) return count;
        temp >>= 7;
      }
      if (buffer[cursor++] != temp) return count;

      // Field tag matches, we've found a valid field.
      count++;

      // Skip the value.
      cursor += stride;
    }
  }
  return count;
}


/**
 * Counts the number of contiguous fixed32 fields with the given field number
 * in the buffer.
 * @param {!Uint8Array} buffer The buffer to scan.
 * @param {number} start The starting point in the buffer to scan.
 * @param {number} end The end point in the buffer to scan.
 * @param {number} field The field number to count.
 * @return {number} The number of matching fields in the buffer.
 */
function countFixed32Fields(buffer, start, end, field) {
  const tag = field * 8 + BinaryConstants.WireType.FIXED32;
  return countFixedFields_(buffer, start, end, tag, 4);
}


/**
 * Counts the number of contiguous fixed64 fields with the given field number
 * in the buffer.
 * @param {!Uint8Array} buffer The buffer to scan.
 * @param {number} start The starting point in the buffer to scan.
 * @param {number} end The end point in the buffer to scan.
 * @param {number} field The field number to count
 * @return {number} The number of matching fields in the buffer.
 */
function countFixed64Fields(buffer, start, end, field) {
  const tag = field * 8 + BinaryConstants.WireType.FIXED64;
  return countFixedFields_(buffer, start, end, tag, 8);
}


/**
 * Counts the number of contiguous delimited fields with the given field number
 * in the buffer.
 * @param {!Uint8Array} buffer The buffer to scan.
 * @param {number} start The starting point in the buffer to scan.
 * @param {number} end The end point in the buffer to scan.
 * @param {number} field The field number to count.
 * @return {number} The number of matching fields in the buffer.
 */
function countDelimitedFields(buffer, start, end, field) {
  let count = 0;
  let cursor = start;
  const tag = field * 8 + BinaryConstants.WireType.DELIMITED;

  while (cursor < end) {
    // Skip the field tag, or exit if we find a non-matching tag.
    let temp = tag;
    while (temp > 128) {
      if (buffer[cursor++] != ((temp & 0x7F) | 0x80)) return count;
      temp >>= 7;
    }
    if (buffer[cursor++] != temp) return count;

    // Field tag matches, we've found a valid field.
    count++;

    // Decode the length prefix.
    let length = 0;
    let shift = 1;
    while (1) {
      temp = buffer[cursor++];
      length += (temp & 0x7f) * shift;
      shift *= 128;
      if ((temp & 0x80) == 0) break;
    }

    // Advance the cursor past the blob.
    cursor += length;
  }
  return count;
}

/**
 * Converts any type defined in ByteSource into a Uint8Array.
 * @param {!ByteSource|!ByteString} data
 * @param {boolean=} copyByteString whether to make a copy of ByteString
 *     internal data
 * @return {!Uint8Array}
 * @suppress {invalidCasts}
 */
function byteSourceToUint8Array(data, copyByteString) {
  // Comparing a property is much faster than the instanceof test below, so
  // prefer this.
  if (data.constructor === Uint8Array) {
    return /** @type {!Uint8Array} */ (data);
  }

  if (data.constructor === ArrayBuffer) {
    data = /** @type {!ArrayBuffer} */ (data);
    return /** @type {!Uint8Array} */ (new Uint8Array(data));
  }

  if (data.constructor === Array) {
    data = /** @type {!Array<number>} */ (data);
    return /** @type {!Uint8Array} */ (new Uint8Array(data));
  }

  if (data.constructor === String) {
    data = /** @type {string} */ (data);
    return decodeStringToUint8Array(data);
  }

  if (data.constructor === ByteString) {
    data = /** @type {!ByteString} */ (data);
    if (!copyByteString) {
      return unsafeUint8ArrayFromByteString(data);
    }
    return data.asUint8Array();
  }

  if (data instanceof Uint8Array) {
    // Support types like nodejs Buffer (a subclass of Uint8Array).
    data = /** @type {!Uint8Array} */ (data);
    // Make a shallow copy to ensure jspb code only ever deals with Uint8Array
    // exactly to ensure monomorphism.
    return /** @type {!Uint8Array} */ (
      new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
  }
  throw new Error(
    'Type not convertible to a Uint8Array, expected a Uint8Array, an ' +
    'ArrayBuffer, a base64 encoded string, or Array of numbers');
}

/** @return {number} */
function getSplit64Low() {
  return split64Low;
}
/** @return {number} */
function getSplit64High() {
  return split64High;
}

/**
 * Makes a wire tag
 * @return {number}
 */
function makeTag(
    /** number */ fieldNumber,
    /** !BinaryConstants.WireType */ wireType) {
  // N.B. can't use << 3 because that enforces 2s complement 32 bit math and we
  // want unsigned math. Consider that `(2**29 -1) << 3 === -8` but
  // `(2**29 -1) * 8 === 4294967288`
  return fieldNumber * 8 + wireType;
}

/** @const {number} */
const LOW_16_BITS = 0xFFFF;

/** @const {number} */
const LOW_24_BITS = 0xFFFFFF;

/** @const {number} */
const ALL_32_BITS = 0xFFFFFFFF;

/** @const {number} String(Number.MAX_SAFE_INTEGER).length */
const MAX_SAFE_INTEGER_DECIMAL_LENGTH = 16;

exports = {
  byteSourceToUint8Array,
  countDelimitedFields,
  countFixed32Fields,
  countFixed64Fields,
  countVarintFields,
  countVarints,
  fromZigzag32,
  fromZigzag64,
  getSplit64High,
  getSplit64Low,
  joinFloat32,
  joinFloat64,
  joinInt64,
  joinNegativeDecimalStringFallback,
  joinSignedDecimalString,
  joinSignedNumberOrDecimalString,
  joinUint64,
  joinUnsignedDecimalString,
  joinUnsignedDecimalStringFallback,
  joinUnsignedNumberOrDecimalString,
  joinZigzag64,
  makeTag,
  sliceUint8Array,
  splitDecimalString,
  splitDecimalStringFallback,
  splitFloat32,
  splitFloat64,
  splitInt64,
  splitUint64,
  splitZigzag64,
  toZigzag32,
  toZigzag64,
};
