function sortKD(ids, coords, nodeSize, left, right, depth) {
  if (!(right - left <= nodeSize)) {
    const m = Math.floor((left + right) / 2)
    select(ids, coords, m, left, right, depth % 2)
    sortKD(ids, coords, nodeSize, left, m - 1, depth + 1)
    sortKD(ids, coords, nodeSize, m + 1, right, depth + 1)
  }
}
function select(ids, coords, k, left, right, inc) {
  for (; right > left; ) {
    if (right - left > 600) {
      const n = right - left + 1,
        m = k - left + 1,
        z = Math.log(n),
        s = 0.5 * Math.exp((2 * z) / 3),
        sd = 0.5 * Math.sqrt((z * s * (n - s)) / n) * (m - n / 2 < 0 ? -1 : 1),
        newLeft = Math.max(left, Math.floor(k - (m * s) / n + sd)),
        newRight = Math.min(right, Math.floor(k + ((n - m) * s) / n + sd))
      select(ids, coords, k, newLeft, newRight, inc)
    }
    let t = coords[2 * k + inc],
      i = left,
      j = right
    swapItem(ids, coords, left, k)
    coords[2 * right + inc] > t && swapItem(ids, coords, left, right)
    for (; i < j; ) {
      swapItem(ids, coords, i, j)
      i++
      j--
      for (; coords[2 * i + inc] < t; ) i++
      for (; coords[2 * j + inc] > t; ) j--
    }
    if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j)
    else {
      j++
      swapItem(ids, coords, j, right)
    }
    j <= k && (left = j + 1)
    k <= j && (right = j - 1)
  }
}
function swapItem(ids, coords, i, j) {
  swap(ids, i, j)
  swap(coords, 2 * i, 2 * j)
  swap(coords, 2 * i + 1, 2 * j + 1)
}
function swap(arr, i, j) {
  const tmp = arr[i]
  arr[i] = arr[j]
  arr[j] = tmp
}
export { sortKD }

function range(ids: any[], coords, minX, minY, maxX, maxY, nodeSize) {
  const result: any[] = []
  for (let x, y, stack = [0, ids.length - 1, 0]; stack.length; ) {
    const axis = stack.pop() as number,
      right = stack.pop() as number,
      left = stack.pop() as number
    if (right - left <= nodeSize)
      for (let i = left; i <= right; i++) {
        x = coords[2 * i]
        y = coords[2 * i + 1]
        x >= minX && x <= maxX && y >= minY && y <= maxY && result.push(ids[i])
      }
    else {
      const m = Math.floor((left + right) / 2)
      x = coords[2 * m]
      y = coords[2 * m + 1]
      x >= minX && x <= maxX && y >= minY && y <= maxY && result.push(ids[m])
      const nextAxis = (axis + 1) % 2
      if (0 === axis ? minX <= x : minY <= y) {
        stack.push(left)
        stack.push(m - 1)
        stack.push(nextAxis)
      }
      if (0 === axis ? maxX >= x : maxY >= y) {
        stack.push(m + 1)
        stack.push(right)
        stack.push(nextAxis)
      }
    }
  }
  return result
}
export { range }

function within(ids, coords, qx, qy, r, nodeSize) {
  const result: any[] = []
  for (let stack = [0, ids.length - 1, 0], r2 = r * r; stack.length; ) {
    const axis = stack.pop() as number,
      right = stack.pop() as number,
      left = stack.pop() as number
    if (right - left <= nodeSize)
      for (let i = left; i <= right; i++) sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2 && result.push(ids[i])
    else {
      const m = Math.floor((left + right) / 2),
        x = coords[2 * m],
        y = coords[2 * m + 1]
      sqDist(x, y, qx, qy) <= r2 && result.push(ids[m])
      const nextAxis = (axis + 1) % 2
      if (0 === axis ? qx - r <= x : qy - r <= y) {
        stack.push(left)
        stack.push(m - 1)
        stack.push(nextAxis)
      }
      if (0 === axis ? qx + r >= x : qy + r >= y) {
        stack.push(m + 1)
        stack.push(right)
        stack.push(nextAxis)
      }
    }
  }
  return result
}
function sqDist(ax, ay, bx, by) {
  const dx = ax - bx,
    dy = ay - by
  return dx * dx + dy * dy
}
export { within }

export class KDBush {
    nodeSize: number
    points: any[]
    ids: any[]
    coords: any[]
  constructor(points, nodeSize?: number, ArrayType?: any) {
    ArrayType = ArrayType || Array
    this.nodeSize = nodeSize || 64
    this.points = points
    this.ids = new ArrayType(points.length)
    this.coords = new ArrayType(2 * points.length)
    for (let i = 0; i < points.length; i++) {
      this.ids[i] = i
      this.coords[2 * i] = points[i].x
      this.coords[2 * i + 1] = points[i].y
    }
    sortKD(this.ids, this.coords, this.nodeSize, 0, this.ids.length - 1, 0)
  }

  destroy() {
    this.ids.length && (this.ids.length = 0)
    this.coords.length && (this.coords.length = 0)
  }
  range(minX, minY, maxX, maxY) {
    return range(this.ids, this.coords, minX, minY, maxX, maxY, this.nodeSize)
  }
  within(x, y, r) {
    return within(this.ids, this.coords, x, y, r, this.nodeSize)
  }
}
