import { createIterable, getAccessorFromBuffer } from './iterable-utils';
import defaultTypedArrayManager from './typed-array-manager';
import assert from './assert';
import { Buffer } from '@luma.gl/core';
export default class Tesselator {
  constructor(opts) {
    this.opts = void 0;
    this.typedArrayManager = void 0;
    this.indexStarts = [0];
    this.vertexStarts = [0];
    this.vertexCount = 0;
    this.instanceCount = 0;
    this.attributes = void 0;
    this._attributeDefs = void 0;
    this.data = void 0;
    this.getGeometry = void 0;
    this.geometryBuffer = void 0;
    this.buffers = void 0;
    this.positionSize = void 0;
    this.normalize = void 0;
    const {
      attributes = {}
    } = opts;
    this.typedArrayManager = defaultTypedArrayManager;
    this.attributes = {};
    this._attributeDefs = attributes;
    this.opts = opts;
    this.updateGeometry(opts);
  }

  updateGeometry(opts) {
    Object.assign(this.opts, opts);
    const {
      data,
      buffers = {},
      getGeometry,
      geometryBuffer,
      positionFormat,
      dataChanged,
      normalize = true
    } = this.opts;
    this.data = data;
    this.getGeometry = getGeometry;
    this.positionSize = geometryBuffer && geometryBuffer.size || (positionFormat === 'XY' ? 2 : 3);
    this.buffers = buffers;
    this.normalize = normalize;

    if (geometryBuffer) {
      assert(data.startIndices);
      this.getGeometry = this.getGeometryFromBuffer(geometryBuffer);

      if (!normalize) {
        buffers.vertexPositions = geometryBuffer;
      }
    }

    this.geometryBuffer = buffers.vertexPositions;

    if (Array.isArray(dataChanged)) {
      for (const dataRange of dataChanged) {
        this._rebuildGeometry(dataRange);
      }
    } else {
      this._rebuildGeometry();
    }
  }

  updatePartialGeometry({
    startRow,
    endRow
  }) {
    this._rebuildGeometry({
      startRow,
      endRow
    });
  }

  getGeometryFromBuffer(geometryBuffer) {
    const value = geometryBuffer.value || geometryBuffer;

    if (!ArrayBuffer.isView(value)) {
      return null;
    }

    return getAccessorFromBuffer(value, {
      size: this.positionSize,
      offset: geometryBuffer.offset,
      stride: geometryBuffer.stride,
      startIndices: this.data.startIndices
    });
  }

  _allocate(instanceCount, copy) {
    const {
      attributes,
      buffers,
      _attributeDefs,
      typedArrayManager
    } = this;

    for (const name in _attributeDefs) {
      if (name in buffers) {
        typedArrayManager.release(attributes[name]);
        attributes[name] = null;
      } else {
        const def = _attributeDefs[name];
        def.copy = copy;
        attributes[name] = typedArrayManager.allocate(attributes[name], instanceCount, def);
      }
    }
  }

  _forEachGeometry(visitor, startRow, endRow) {
    const {
      data,
      getGeometry
    } = this;
    const {
      iterable,
      objectInfo
    } = createIterable(data, startRow, endRow);

    for (const object of iterable) {
      objectInfo.index++;
      const geometry = getGeometry ? getGeometry(object, objectInfo) : null;
      visitor(geometry, objectInfo.index);
    }
  }

  _rebuildGeometry(dataRange) {
    if (!this.data) {
      return;
    }

    let {
      indexStarts,
      vertexStarts,
      instanceCount
    } = this;
    const {
      data,
      geometryBuffer
    } = this;
    const {
      startRow = 0,
      endRow = Infinity
    } = dataRange || {};
    const normalizedData = {};

    if (!dataRange) {
      indexStarts = [0];
      vertexStarts = [0];
    }

    if (this.normalize || !geometryBuffer) {
      this._forEachGeometry((geometry, dataIndex) => {
        const normalizedGeometry = geometry && this.normalizeGeometry(geometry);
        normalizedData[dataIndex] = normalizedGeometry;
        vertexStarts[dataIndex + 1] = vertexStarts[dataIndex] + (normalizedGeometry ? this.getGeometrySize(normalizedGeometry) : 0);
      }, startRow, endRow);

      instanceCount = vertexStarts[vertexStarts.length - 1];
    } else {
      vertexStarts = data.startIndices;
      instanceCount = vertexStarts[data.length] || 0;

      if (ArrayBuffer.isView(geometryBuffer)) {
        instanceCount = instanceCount || geometryBuffer.length / this.positionSize;
      } else if (geometryBuffer instanceof Buffer) {
        const byteStride = this.positionSize * 4;
        instanceCount = instanceCount || geometryBuffer.byteLength / byteStride;
      } else if (geometryBuffer.buffer) {
        const byteStride = geometryBuffer.stride || this.positionSize * 4;
        instanceCount = instanceCount || geometryBuffer.buffer.byteLength / byteStride;
      } else if (geometryBuffer.value) {
        const bufferValue = geometryBuffer.value;
        const elementStride = geometryBuffer.stride / bufferValue.BYTES_PER_ELEMENT || this.positionSize;
        instanceCount = instanceCount || bufferValue.length / elementStride;
      }
    }

    this._allocate(instanceCount, Boolean(dataRange));

    this.indexStarts = indexStarts;
    this.vertexStarts = vertexStarts;
    this.instanceCount = instanceCount;
    const context = {};

    this._forEachGeometry((geometry, dataIndex) => {
      const normalizedGeometry = normalizedData[dataIndex] || geometry;
      context.vertexStart = vertexStarts[dataIndex];
      context.indexStart = indexStarts[dataIndex];
      const vertexEnd = dataIndex < vertexStarts.length - 1 ? vertexStarts[dataIndex + 1] : instanceCount;
      context.geometrySize = vertexEnd - vertexStarts[dataIndex];
      context.geometryIndex = dataIndex;
      this.updateGeometryAttributes(normalizedGeometry, context);
    }, startRow, endRow);

    this.vertexCount = indexStarts[indexStarts.length - 1];
  }

}
//# sourceMappingURL=tesselator.js.map