import { readPixelsToArray } from '@luma.gl/webgl';
import PickLayersPass from '../passes/pick-layers-pass';
import { getClosestObject, getUniqueObjects } from './picking/query-object';
import { processPickInfo, getLayerPickingInfo, getEmptyPickingInfo } from './picking/pick-info';
export default class DeckPicker {
  constructor(device) {
    this.device = void 0;
    this.pickingFBO = void 0;
    this.depthFBO = void 0;
    this.pickLayersPass = void 0;
    this.layerFilter = void 0;
    this.lastPickedInfo = void 0;
    this._pickable = true;
    this.device = device;
    this.pickLayersPass = new PickLayersPass(device);
    this.lastPickedInfo = {
      index: -1,
      layerId: null,
      info: null
    };
  }

  setProps(props) {
    if ('layerFilter' in props) {
      this.layerFilter = props.layerFilter;
    }

    if ('_pickable' in props) {
      this._pickable = props._pickable;
    }
  }

  finalize() {
    if (this.pickingFBO) {
      this.pickingFBO.delete();
    }

    if (this.depthFBO) {
      this.depthFBO.colorAttachments[0].delete();
      this.depthFBO.delete();
    }
  }

  pickObject(opts) {
    return this._pickClosestObject(opts);
  }

  pickObjects(opts) {
    return this._pickVisibleObjects(opts);
  }

  getLastPickedObject({
    x,
    y,
    layers,
    viewports
  }, lastPickedInfo = this.lastPickedInfo.info) {
    const lastPickedLayerId = lastPickedInfo && lastPickedInfo.layer && lastPickedInfo.layer.id;
    const lastPickedViewportId = lastPickedInfo && lastPickedInfo.viewport && lastPickedInfo.viewport.id;
    const layer = lastPickedLayerId ? layers.find(l => l.id === lastPickedLayerId) : null;
    const viewport = lastPickedViewportId && viewports.find(v => v.id === lastPickedViewportId) || viewports[0];
    const coordinate = viewport && viewport.unproject([x - viewport.x, y - viewport.y]);
    const info = {
      x,
      y,
      viewport,
      coordinate,
      layer
    };
    return { ...lastPickedInfo,
      ...info
    };
  }

  _resizeBuffer() {
    var _this$pickingFBO, _this$depthFBO;

    if (!this.pickingFBO) {
      this.pickingFBO = this.device.createFramebuffer({
        colorAttachments: ['rgba8unorm']
      });

      if (this.device.isTextureFormatRenderable('rgba32float')) {
        const depthFBO = this.device.createFramebuffer({
          colorAttachments: [this.device.createTexture({
            format: this.device.info.type === 'webgl2' ? 'rgba32float' : 'rgba8unorm'
          })]
        });
        this.depthFBO = depthFBO;
      }
    }

    const gl = this.device.gl;
    (_this$pickingFBO = this.pickingFBO) === null || _this$pickingFBO === void 0 ? void 0 : _this$pickingFBO.resize({
      width: gl.canvas.width,
      height: gl.canvas.height
    });
    (_this$depthFBO = this.depthFBO) === null || _this$depthFBO === void 0 ? void 0 : _this$depthFBO.resize({
      width: gl.canvas.width,
      height: gl.canvas.height
    });
  }

  _getPickable(layers) {
    if (this._pickable === false) {
      return null;
    }

    const pickableLayers = layers.filter(layer => this.pickLayersPass.shouldDrawLayer(layer) && !layer.isComposite);
    return pickableLayers.length ? pickableLayers : null;
  }

  _pickClosestObject({
    layers,
    views,
    viewports,
    x,
    y,
    radius = 0,
    depth = 1,
    mode = 'query',
    unproject3D,
    onViewportActive,
    effects
  }) {
    const pixelRatio = this.device.canvasContext.cssToDeviceRatio();

    const pickableLayers = this._getPickable(layers);

    if (!pickableLayers) {
      return {
        result: [],
        emptyInfo: getEmptyPickingInfo({
          viewports,
          x,
          y,
          pixelRatio
        })
      };
    }

    this._resizeBuffer();

    const devicePixelRange = this.device.canvasContext.cssToDevicePixels([x, y], true);
    const devicePixel = [devicePixelRange.x + Math.floor(devicePixelRange.width / 2), devicePixelRange.y + Math.floor(devicePixelRange.height / 2)];
    const deviceRadius = Math.round(radius * pixelRatio);
    const {
      width,
      height
    } = this.pickingFBO;

    const deviceRect = this._getPickingRect({
      deviceX: devicePixel[0],
      deviceY: devicePixel[1],
      deviceRadius,
      deviceWidth: width,
      deviceHeight: height
    });

    const cullRect = {
      x: x - radius,
      y: y - radius,
      width: radius * 2 + 1,
      height: radius * 2 + 1
    };
    let infos;
    const result = [];
    const affectedLayers = new Set();

    for (let i = 0; i < depth; i++) {
      let pickInfo;

      if (deviceRect) {
        const pickedResult = this._drawAndSample({
          layers: pickableLayers,
          views,
          viewports,
          onViewportActive,
          deviceRect,
          cullRect,
          effects,
          pass: `picking:${mode}`
        });

        pickInfo = getClosestObject({ ...pickedResult,
          deviceX: devicePixel[0],
          deviceY: devicePixel[1],
          deviceRadius,
          deviceRect
        });
      } else {
        pickInfo = {
          pickedColor: null,
          pickedObjectIndex: -1
        };
      }

      let z;

      if (pickInfo.pickedLayer && unproject3D && this.depthFBO) {
        const {
          pickedColors: pickedColors2
        } = this._drawAndSample({
          layers: [pickInfo.pickedLayer],
          views,
          viewports,
          onViewportActive,
          deviceRect: {
            x: pickInfo.pickedX,
            y: pickInfo.pickedY,
            width: 1,
            height: 1
          },
          cullRect,
          effects,
          pass: `picking:${mode}:z`
        }, true);

        if (pickedColors2[3]) {
          z = pickedColors2[0];
        }
      }

      if (pickInfo.pickedLayer && i + 1 < depth) {
        affectedLayers.add(pickInfo.pickedLayer);
        pickInfo.pickedLayer.disablePickingIndex(pickInfo.pickedObjectIndex);
      }

      infos = processPickInfo({
        pickInfo,
        lastPickedInfo: this.lastPickedInfo,
        mode,
        layers: pickableLayers,
        viewports,
        x,
        y,
        z,
        pixelRatio
      });

      for (const info of infos.values()) {
        if (info.layer) {
          result.push(info);
        }
      }

      if (!pickInfo.pickedColor) {
        break;
      }
    }

    for (const layer of affectedLayers) {
      layer.restorePickingColors();
    }

    return {
      result,
      emptyInfo: infos.get(null)
    };
  }

  _pickVisibleObjects({
    layers,
    views,
    viewports,
    x,
    y,
    width = 1,
    height = 1,
    mode = 'query',
    maxObjects = null,
    onViewportActive,
    effects
  }) {
    const pickableLayers = this._getPickable(layers);

    if (!pickableLayers) {
      return [];
    }

    this._resizeBuffer();

    const pixelRatio = this.device.canvasContext.cssToDeviceRatio();
    const leftTop = this.device.canvasContext.cssToDevicePixels([x, y], true);
    const deviceLeft = leftTop.x;
    const deviceTop = leftTop.y + leftTop.height;
    const rightBottom = this.device.canvasContext.cssToDevicePixels([x + width, y + height], true);
    const deviceRight = rightBottom.x + rightBottom.width;
    const deviceBottom = rightBottom.y;
    const deviceRect = {
      x: deviceLeft,
      y: deviceBottom,
      width: deviceRight - deviceLeft,
      height: deviceTop - deviceBottom
    };

    const pickedResult = this._drawAndSample({
      layers: pickableLayers,
      views,
      viewports,
      onViewportActive,
      deviceRect,
      cullRect: {
        x,
        y,
        width,
        height
      },
      effects,
      pass: `picking:${mode}`
    });

    const pickInfos = getUniqueObjects(pickedResult);
    const uniquePickedObjects = new Map();
    const uniqueInfos = [];
    const limitMaxObjects = Number.isFinite(maxObjects);

    for (let i = 0; i < pickInfos.length; i++) {
      var _info$object;

      if (limitMaxObjects && uniqueInfos.length >= maxObjects) {
        break;
      }

      const pickInfo = pickInfos[i];
      let info = {
        color: pickInfo.pickedColor,
        layer: null,
        index: pickInfo.pickedObjectIndex,
        picked: true,
        x,
        y,
        pixelRatio
      };
      info = getLayerPickingInfo({
        layer: pickInfo.pickedLayer,
        info,
        mode
      });
      const pickedLayerId = info.layer.id;

      if (!uniquePickedObjects.has(pickedLayerId)) {
        uniquePickedObjects.set(pickedLayerId, new Set());
      }

      const uniqueObjectsInLayer = uniquePickedObjects.get(pickedLayerId);
      const pickedObjectKey = (_info$object = info.object) !== null && _info$object !== void 0 ? _info$object : info.index;

      if (!uniqueObjectsInLayer.has(pickedObjectKey)) {
        uniqueObjectsInLayer.add(pickedObjectKey);
        uniqueInfos.push(info);
      }
    }

    return uniqueInfos;
  }

  _drawAndSample({
    layers,
    views,
    viewports,
    onViewportActive,
    deviceRect,
    cullRect,
    effects,
    pass
  }, pickZ = false) {
    const pickingFBO = pickZ ? this.depthFBO : this.pickingFBO;
    const opts = {
      layers,
      layerFilter: this.layerFilter,
      views,
      viewports,
      onViewportActive,
      pickingFBO,
      deviceRect,
      cullRect,
      effects,
      pass,
      pickZ,
      preRenderStats: {}
    };

    for (const effect of effects) {
      if (effect.useInPicking) {
        opts.preRenderStats[effect.id] = effect.preRender(this.device, opts);
      }
    }

    const {
      decodePickingColor
    } = this.pickLayersPass.render(opts);
    const {
      x,
      y,
      width,
      height
    } = deviceRect;
    const pickedColors = new (pickZ ? Float32Array : Uint8Array)(width * height * 4);
    readPixelsToArray(pickingFBO, {
      sourceX: x,
      sourceY: y,
      sourceWidth: width,
      sourceHeight: height,
      target: pickedColors
    });
    return {
      pickedColors,
      decodePickingColor
    };
  }

  _getPickingRect({
    deviceX,
    deviceY,
    deviceRadius,
    deviceWidth,
    deviceHeight
  }) {
    const x = Math.max(0, deviceX - deviceRadius);
    const y = Math.max(0, deviceY - deviceRadius);
    const width = Math.min(deviceWidth, deviceX + deviceRadius + 1) - x;
    const height = Math.min(deviceHeight, deviceY + deviceRadius + 1) - y;

    if (width <= 0 || height <= 0) {
      return null;
    }

    return {
      x,
      y,
      width,
      height
    };
  }

}
//# sourceMappingURL=deck-picker.js.map