class FlatWaveletTree {
  constructor(data) {
    this.nodeCount = data.nodeCount;
    this.alphabetMin = data.alphabetMin;
    this.alphabetMax = data.alphabetMax;
    this.lengths = data.lengths;
    this.isLeaf = data.isLeaf;
    this.leftIndex = data.leftIndex;
    this.rightIndex = data.rightIndex;
    this.bitvectorWordOffset = data.bitvectorWordOffset;
    this.bitvectorWordCount = data.bitvectorWordCount;
    this.bitvectorLength = data.bitvectorLength;
    this.blockSize = data.blockSize;
    this.blockRankOffset = data.blockRankOffset;
    this.blockRankLength = data.blockRankLength;
    this.bitvectorWords = data.bitvectorWords;
    this.blockRankWords = data.blockRankWords;
    this.length = this.lengths[0] || 0;
  }

  access(i) {
    if (i < 0 || i >= this.length) {
      throw new Error(`Index ${i} out of bounds [0, ${this.length})`);
    }

    let node = 0;
    let pos = i;
    while (node !== -1 && this.isLeaf[node] === 0) {
      const current = node;
      const bit = this._bitvectorAccess(current, pos);
      if (bit === 0) {
        pos = this._bitvectorRank0(current, pos) - 1;
        if (pos < 0) return this.alphabetMin[current];
        node = this.leftIndex[current];
        if (node === -1) return this.alphabetMin[current];
      } else {
        pos = this._bitvectorRank1(current, pos) - 1;
        if (pos < 0) return this.alphabetMax[current];
        node = this.rightIndex[current];
        if (node === -1) return this.alphabetMax[current];
      }
    }
    if (node === -1) return -1;
    return this.alphabetMin[node];
  }

  rank(c, i) {
    if (i < 0) return 0;
    if (i >= this.length) i = this.length - 1;
    let node = 0;
    let pos = i;

    while (node !== -1) {
      if (this.isLeaf[node] === 1) {
        return c === this.alphabetMin[node] ? pos + 1 : 0;
      }

      const mid = (this.alphabetMin[node] + this.alphabetMax[node]) >> 1;
      if (c <= mid) {
        pos = this._bitvectorRank0(node, pos) - 1;
        if (pos < 0) return 0;
        node = this.leftIndex[node];
      } else {
        pos = this._bitvectorRank1(node, pos) - 1;
        if (pos < 0) return 0;
        node = this.rightIndex[node];
      }
    }

    return 0;
  }

  select(c, k) {
    if (k < 1) return -1;
    const selectNode = (nodeIndex, value, count) => {
      if (nodeIndex === -1) return -1;
      if (this.isLeaf[nodeIndex] === 1) {
        return value === this.alphabetMin[nodeIndex] && count <= this.lengths[nodeIndex]
          ? count - 1
          : -1;
      }
      const mid = (this.alphabetMin[nodeIndex] + this.alphabetMax[nodeIndex]) >> 1;
      if (value <= mid) {
        const child = this.leftIndex[nodeIndex];
        const pos = selectNode(child, value, count);
        if (pos === -1) return -1;
        return this._bitvectorSelect0(nodeIndex, pos + 1);
      }
      const child = this.rightIndex[nodeIndex];
      const pos = selectNode(child, value, count);
      if (pos === -1) return -1;
      return this._bitvectorSelect1(nodeIndex, pos + 1);
    };

    return selectNode(0, c, k);
  }

  getMemoryUsage() {
    let total = 0;
    total += this.alphabetMin.byteLength;
    total += this.alphabetMax.byteLength;
    total += this.lengths.byteLength;
    total += this.isLeaf.byteLength;
    total += this.leftIndex.byteLength;
    total += this.rightIndex.byteLength;
    total += this.bitvectorWordOffset.byteLength;
    total += this.bitvectorWordCount.byteLength;
    total += this.bitvectorLength.byteLength;
    total += this.blockSize.byteLength;
    total += this.blockRankOffset.byteLength;
    total += this.blockRankLength.byteLength;
    total += this.bitvectorWords.byteLength;
    total += this.blockRankWords.byteLength;
    return total;
  }

  _bitvectorAccess(nodeIndex, pos) {
    const wordOffset = this.bitvectorWordOffset[nodeIndex];
    const wordIndex = pos >>> 5;
    const bitIndex = pos & 31;
    return (this.bitvectorWords[wordOffset + wordIndex] >>> bitIndex) & 1;
  }

  _bitvectorRank1(nodeIndex, pos) {
    if (pos < 0) return 0;
    const length = this.bitvectorLength[nodeIndex];
    if (!length) return 0;
    if (pos >= length) pos = length - 1;
    const blockSize = this.blockSize[nodeIndex];
    if (!blockSize) return 0;
    const block = (pos / blockSize) | 0;
    const blockRankOffset = this.blockRankOffset[nodeIndex];
    let rank = this.blockRankWords[blockRankOffset + block];
    const start = block * blockSize;
    rank += this._popcountRange(nodeIndex, start, pos);
    return rank;
  }

  _bitvectorRank0(nodeIndex, pos) {
    if (pos < 0) return 0;
    if (pos >= this.bitvectorLength[nodeIndex]) pos = this.bitvectorLength[nodeIndex] - 1;
    return (pos + 1) - this._bitvectorRank1(nodeIndex, pos);
  }

  _bitvectorSelect1(nodeIndex, k) {
    if (k < 1) return -1;
    const length = this.bitvectorLength[nodeIndex];
    if (!length) return -1;
    const blockRankLen = this.blockRankLength[nodeIndex];
    if (!blockRankLen) return -1;
    const total = this.blockRankWords[this.blockRankOffset[nodeIndex] + blockRankLen - 1];
    if (k > total) return -1;
    let left = 0;
    let right = length - 1;
    while (left < right) {
      const mid = (left + right) >> 1;
      if (this._bitvectorRank1(nodeIndex, mid) < k) {
        left = mid + 1;
      } else {
        right = mid;
      }
    }
    return left;
  }

  _bitvectorSelect0(nodeIndex, k) {
    if (k < 1) return -1;
    const length = this.bitvectorLength[nodeIndex];
    if (!length) return -1;
    const blockRankLen = this.blockRankLength[nodeIndex];
    if (!blockRankLen) return -1;
    const totalOnes = this.blockRankWords[this.blockRankOffset[nodeIndex] + blockRankLen - 1];
    const totalZeros = length - totalOnes;
    if (k > totalZeros) return -1;
    let left = 0;
    let right = length - 1;
    while (left < right) {
      const mid = (left + right) >> 1;
      if (this._bitvectorRank0(nodeIndex, mid) < k) {
        left = mid + 1;
      } else {
        right = mid;
      }
    }
    return left;
  }

  _popcount32(value) {
    value -= (value >>> 1) & 0x55555555;
    value = (value & 0x33333333) + ((value >>> 2) & 0x33333333);
    return (((value + (value >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
  }

  _popcountRange(nodeIndex, start, end) {
    let count = 0;
    let i = start;
    const wordOffset = this.bitvectorWordOffset[nodeIndex];
    while (i <= end) {
      const wordIndex = i >>> 5;
      const bitIndex = i & 31;
      const remaining = end - i + 1;
      if (bitIndex === 0 && remaining >= 32) {
        count += this._popcount32(this.bitvectorWords[wordOffset + wordIndex]);
        i += 32;
        continue;
      }

      const take = Math.min(32 - bitIndex, remaining);
      let mask = 0;
      if (take === 32) {
        mask = 0xFFFFFFFF;
      } else {
        mask = ((1 << take) - 1) << bitIndex;
      }
      count += this._popcount32(this.bitvectorWords[wordOffset + wordIndex] & mask);
      i += take;
    }
    return count;
  }
}

export { FlatWaveletTree };
