import { Point, Geometry, Buffer, TYPES, Texture, SHAPES, WRAP_MODES, Matrix, Color } from '@pixi/core';
import { Bounds } from '@pixi/display';
import { matrixEquals, BatchDrawCall } from './core/BatchDrawCall.mjs';
import { BatchPart } from './core/BatchPart.mjs';
import { BuildData } from './core/BuildData.mjs';
import { SegmentPacker } from './core/SegmentPacker.mjs';
import { SmoothGraphicsData } from './core/SmoothGraphicsData.mjs';
import { FILL_COMMANDS } from './shapes/index.mjs';

const BATCH_POOL = [];
const DRAW_CALL_POOL = [];
const tmpPoint = new Point();
const tmpBounds = new Bounds();
class SmoothGraphicsGeometry extends Geometry {
  constructor() {
    super();
    this.indicesUint16 = null;
    this.initAttributes(false);
    this.buildData = new BuildData();
    this.graphicsData = [];
    this.dirty = 0;
    this.batchDirty = -1;
    this.cacheDirty = -1;
    this.clearDirty = 0;
    this.drawCalls = [];
    this.batches = [];
    this.shapeBuildIndex = 0;
    this.shapeBatchIndex = 0;
    this._bounds = new Bounds();
    this.boundsDirty = -1;
    this.boundsPadding = 0;
    this.batchable = false;
    this.indicesUint16 = null;
    this.packer = null;
    this.packSize = 0;
    this.pack32index = null;
  }
  get points() {
    return this.buildData.verts;
  }
  get closePointEps() {
    return this.buildData.closePointEps;
  }
  initAttributes(_static) {
    this._buffer = new Buffer(null, _static, false);
    this._bufferFloats = new Float32Array();
    this._bufferUint = new Uint32Array();
    this._indexBuffer = new Buffer(null, _static, true);
    this.addAttribute("aPrev", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aPoint1", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aPoint2", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aNext", this._buffer, 2, false, TYPES.FLOAT).addAttribute("aTravel", this._buffer, 1, false, TYPES.FLOAT).addAttribute("aVertexJoint", this._buffer, 1, false, TYPES.FLOAT).addAttribute("aStyleId", this._buffer, 1, false, TYPES.FLOAT).addAttribute("aColor", this._buffer, 4, true, TYPES.UNSIGNED_BYTE).addIndex(this._indexBuffer);
    this.strideFloats = 12;
  }
  checkInstancing(instanced, allow32Indices) {
    if (this.packer) {
      return;
    }
    this.packer = new SegmentPacker();
    this.pack32index = allow32Indices;
  }
  /**
   * Get the current bounds of the graphic geometry.
   *
   * @member {PIXI.Bounds}
   * @readonly
   */
  get bounds() {
    if (this.boundsDirty !== this.dirty) {
      this.boundsDirty = this.dirty;
      this.calculateBounds();
    }
    return this._bounds;
  }
  /**
   * Call if you changed graphicsData manually.
   * Empties all batch buffers.
   */
  invalidate() {
    this.boundsDirty = -1;
    this.dirty++;
    this.batchDirty++;
    this.shapeBuildIndex = 0;
    this.shapeBatchIndex = 0;
    this.packSize = 0;
    this.buildData.clear();
    for (let i = 0; i < this.drawCalls.length; i++) {
      this.drawCalls[i].clear();
      DRAW_CALL_POOL.push(this.drawCalls[i]);
    }
    this.drawCalls.length = 0;
    for (let i = 0; i < this.batches.length; i++) {
      const batchPart = this.batches[i];
      batchPart.reset();
      BATCH_POOL.push(batchPart);
    }
    this.batches.length = 0;
  }
  clear() {
    if (this.graphicsData.length > 0) {
      this.invalidate();
      this.clearDirty++;
      this.graphicsData.length = 0;
    }
    return this;
  }
  drawShape(shape, fillStyle = null, lineStyle = null, matrix = null) {
    const data = new SmoothGraphicsData(shape, fillStyle, lineStyle, matrix);
    this.graphicsData.push(data);
    this.dirty++;
    return this;
  }
  drawHole(shape, matrix = null) {
    if (!this.graphicsData.length) {
      return null;
    }
    const data = new SmoothGraphicsData(shape, null, null, matrix);
    const lastShape = this.graphicsData[this.graphicsData.length - 1];
    data.lineStyle = lastShape.lineStyle;
    lastShape.holes.push(data);
    this.dirty++;
    return this;
  }
  destroy() {
    super.destroy();
    for (let i = 0; i < this.graphicsData.length; ++i) {
      this.graphicsData[i].destroy();
    }
    this.buildData.destroy();
    this.buildData = null;
    this.indexBuffer.destroy();
    this.indexBuffer = null;
    this.graphicsData.length = 0;
    this.graphicsData = null;
    this.drawCalls.length = 0;
    this.drawCalls = null;
    this.batches.length = 0;
    this.batches = null;
    this._bounds = null;
  }
  /**
   * Check to see if a point is contained within this geometry.
   *
   * @param {PIXI.IPointData} point - Point to check if it's contained.
   * @return {Boolean} `true` if the point is contained within geometry.
   */
  containsPoint(point) {
    const graphicsData = this.graphicsData;
    for (let i = 0; i < graphicsData.length; ++i) {
      const data = graphicsData[i];
      if (!data.fillStyle.visible) {
        continue;
      }
      if (data.shape) {
        if (data.matrix) {
          data.matrix.applyInverse(point, tmpPoint);
        } else {
          tmpPoint.copyFrom(point);
        }
        if (data.shape.contains(tmpPoint.x, tmpPoint.y)) {
          let hitHole = false;
          if (data.holes) {
            for (let i2 = 0; i2 < data.holes.length; i2++) {
              const hole = data.holes[i2];
              if (hole.shape.contains(tmpPoint.x, tmpPoint.y)) {
                hitHole = true;
                break;
              }
            }
          }
          if (!hitHole) {
            return true;
          }
        }
      }
    }
    return false;
  }
  updatePoints() {
  }
  updateBufferSize() {
    this._buffer.update(new Float32Array());
  }
  updateBuild() {
    const { graphicsData, buildData } = this;
    const len = graphicsData.length;
    for (let i = this.shapeBuildIndex; i < len; i++) {
      const data = graphicsData[i];
      data.strokeStart = 0;
      data.strokeLen = 0;
      data.fillStart = 0;
      data.fillLen = 0;
      const { fillStyle, lineStyle, holes } = data;
      if (!fillStyle.visible && !lineStyle.visible) {
        continue;
      }
      const command = FILL_COMMANDS[data.type];
      data.clearPath();
      command.path(data, buildData);
      if (data.matrix) {
        this.transformPoints(data.points, data.matrix);
      }
      data.clearBuild();
      if (data.points.length <= 2) {
        continue;
      }
      if (fillStyle.visible || lineStyle.visible) {
        this.processHoles(holes);
      }
      if (fillStyle.visible) {
        data.fillAA = data.fillStyle.smooth && data.fillStyle.texture === Texture.WHITE && holes.length === 0 && !(data.closeStroke && data.lineStyle.visible && !data.lineStyle.shader && data.lineStyle.alpha >= 0.99 && data.lineStyle.width * Math.min(data.lineStyle.alignment, 1 - data.lineStyle.alignment) >= 0.495);
        data.fillStart = buildData.joints.length;
        if (holes.length) {
          FILL_COMMANDS[SHAPES.POLY].fill(data, buildData);
        } else {
          command.fill(data, buildData);
        }
        data.fillLen = buildData.joints.length - data.fillStart;
      }
      if (lineStyle.visible) {
        data.strokeStart = buildData.joints.length;
        command.line(data, buildData);
        for (let i2 = 0; i2 < holes.length; i2++) {
          const hole = holes[i2];
          FILL_COMMANDS[hole.type].line(hole, buildData);
        }
        data.strokeLen = buildData.joints.length - data.strokeStart;
      }
    }
    this.shapeBuildIndex = len;
  }
  updateBatches(shaderSettings) {
    if (!this.graphicsData.length) {
      this.batchable = true;
      return;
    }
    this.updateBuild();
    if (!this.validateBatching()) {
      return;
    }
    const { buildData, graphicsData } = this;
    const len = graphicsData.length;
    this.cacheDirty = this.dirty;
    let batchPart = null;
    let currentStyle = null;
    if (this.batches.length > 0) {
      batchPart = this.batches[this.batches.length - 1];
      currentStyle = batchPart.style;
    }
    for (let i = this.shapeBatchIndex; i < len; i++) {
      const data = graphicsData[i];
      const fillStyle = data.fillStyle;
      const lineStyle = data.lineStyle;
      if (data.matrix) {
        this.transformPoints(data.points, data.matrix);
      }
      if (!fillStyle.visible && !lineStyle.visible) {
        continue;
      }
      for (let j = 0; j < 2; j++) {
        const style = j === 0 ? fillStyle : lineStyle;
        if (!style.visible)
          continue;
        const nextTexture = style.texture.baseTexture;
        const attribOld = buildData.vertexSize;
        const indexOld = buildData.indexSize;
        nextTexture.wrapMode = WRAP_MODES.REPEAT;
        if (j === 0) {
          this.packer.updateBufferSize(data.fillStart, data.fillLen, data.triangles.length, buildData);
        } else {
          this.packer.updateBufferSize(data.strokeStart, data.strokeLen, data.triangles.length, buildData);
        }
        const attribSize = buildData.vertexSize;
        if (attribSize === attribOld)
          continue;
        if (batchPart && !this._compareStyles(currentStyle, style)) {
          batchPart.end(indexOld, attribOld);
          batchPart = null;
        }
        if (!batchPart) {
          batchPart = BATCH_POOL.pop() || new BatchPart();
          batchPart.begin(style, indexOld, attribOld);
          this.batches.push(batchPart);
          currentStyle = style;
        }
        if (j === 0) {
          batchPart.jointEnd = data.fillStart + data.fillLen;
        } else {
          batchPart.jointEnd = data.strokeStart + data.strokeLen;
        }
      }
    }
    this.shapeBatchIndex = len;
    if (batchPart) {
      batchPart.end(buildData.indexSize, buildData.vertexSize);
    }
    if (this.batches.length === 0) {
      this.batchable = true;
      return;
    }
    this.batchable = this.isBatchable();
    if (this.batchable) {
      this.packBatches();
    } else {
      this.buildDrawCalls(shaderSettings);
      this.updatePack();
    }
  }
  updatePack() {
    const { vertexSize, indexSize } = this.buildData;
    if (this.packSize === vertexSize) {
      return;
    }
    const { strideFloats, packer, buildData, batches } = this;
    const buffer = this._buffer;
    const index = this._indexBuffer;
    const floatsSize = vertexSize * strideFloats;
    if (buffer.data.length !== floatsSize) {
      const arrBuf = new ArrayBuffer(floatsSize * 4);
      this._bufferFloats = new Float32Array(arrBuf);
      this._bufferUint = new Uint32Array(arrBuf);
      buffer.data = this._bufferFloats;
    }
    if (index.data.length !== indexSize) {
      if (vertexSize > 65535 && this.pack32index) {
        index.data = new Uint32Array(indexSize);
      } else {
        index.data = new Uint16Array(indexSize);
      }
    }
    packer.beginPack(buildData, this._bufferFloats, this._bufferUint, index.data);
    let j = 0;
    for (let i = 0; i < this.graphicsData.length; i++) {
      const data = this.graphicsData[i];
      if (data.fillLen) {
        while (batches[j].jointEnd <= data.fillStart) {
          j++;
        }
        packer.packInterleavedGeometry(
          data.fillStart,
          data.fillLen,
          data.triangles,
          batches[j].styleId,
          batches[j].rgba
        );
      }
      if (data.strokeLen) {
        while (batches[j].jointEnd <= data.strokeStart) {
          j++;
        }
        packer.packInterleavedGeometry(
          data.strokeStart,
          data.strokeLen,
          data.triangles,
          batches[j].styleId,
          batches[j].rgba
        );
      }
    }
    buffer.update();
    index.update();
    this.packSize = vertexSize;
  }
  /**
   * Affinity check
   *
   * @param {PIXI.FillStyle | PIXI.LineStyle} styleA
   * @param {PIXI.FillStyle | PIXI.LineStyle} styleB
   */
  _compareStyles(styleA, styleB) {
    if (!styleA || !styleB) {
      return false;
    }
    if (styleA.texture.baseTexture !== styleB.texture.baseTexture) {
      return false;
    }
    if (styleA.color + styleA.alpha !== styleB.color + styleB.alpha) {
      return false;
    }
    if (styleA.shader !== styleB.shader) {
      return false;
    }
    if (styleA.width !== styleB.width) {
      return false;
    }
    if (styleA.scaleMode !== styleB.scaleMode) {
      return false;
    }
    if (styleA.alignment !== styleB.alignment) {
      return false;
    }
    const mat1 = styleA.matrix || Matrix.IDENTITY;
    const mat2 = styleB.matrix || Matrix.IDENTITY;
    return matrixEquals(mat1, mat2);
  }
  /**
   * Test geometry for batching process.
   *
   * @protected
   */
  validateBatching() {
    if (this.dirty === this.cacheDirty || !this.graphicsData.length) {
      return false;
    }
    for (let i = 0, l = this.graphicsData.length; i < l; i++) {
      const data = this.graphicsData[i];
      const fill = data.fillStyle;
      const line = data.lineStyle;
      if (fill && !fill.texture.baseTexture.valid)
        return false;
      if (line && !line.texture.baseTexture.valid)
        return false;
    }
    return true;
  }
  /**
   * Offset the indices so that it works with the batcher.
   *
   * @protected
   */
  packBatches() {
    this.batchDirty++;
    const batches = this.batches;
    for (let i = 0, l = batches.length; i < l; i++) {
      const batch = batches[i];
      for (let j = 0; j < batch.size; j++) {
        const index = batch.start + j;
        this.indicesUint16[index] = this.indicesUint16[index] - batch.attribStart;
      }
    }
  }
  isBatchable() {
    return false;
  }
  /**
   * Converts intermediate batches data to drawCalls.
   *
   * @protected
   */
  buildDrawCalls(shaderSettings) {
    for (let i = 0; i < this.drawCalls.length; i++) {
      this.drawCalls[i].clear();
      DRAW_CALL_POOL.push(this.drawCalls[i]);
    }
    this.drawCalls.length = 0;
    let currentGroup = DRAW_CALL_POOL.pop() || new BatchDrawCall();
    currentGroup.begin(shaderSettings, null);
    let index = 0;
    this.drawCalls.push(currentGroup);
    for (let i = 0; i < this.batches.length; i++) {
      const batchData = this.batches[i];
      const style = batchData.style;
      if (batchData.attribSize === 0) {
        continue;
      }
      let styleId = -1;
      const mat = style.getTextureMatrix();
      if (currentGroup.check(style.shader)) {
        styleId = currentGroup.add(
          style.texture,
          mat,
          style.width,
          style.alignment || 0,
          style.packLineScale()
        );
      }
      if (styleId < 0) {
        currentGroup = DRAW_CALL_POOL.pop() || new BatchDrawCall();
        this.drawCalls.push(currentGroup);
        currentGroup.begin(shaderSettings, style.shader);
        currentGroup.start = index;
        styleId = currentGroup.add(
          style.texture,
          mat,
          style.width,
          style.alignment || 0,
          style.packLineScale()
        );
      }
      currentGroup.size += batchData.size;
      index += batchData.size;
      const { color, alpha } = style;
      const bgr = Color.shared.setValue(color).toLittleEndianNumber();
      batchData.rgba = Color.shared.setValue(bgr).toPremultiplied(alpha);
      batchData.styleId = styleId;
    }
  }
  processHoles(holes) {
    for (let i = 0; i < holes.length; i++) {
      const hole = holes[i];
      const command = FILL_COMMANDS[hole.type];
      hole.clearPath();
      command.path(hole, this.buildData);
      if (hole.matrix) {
        this.transformPoints(hole.points, hole.matrix);
      }
    }
  }
  /**
   * Update the local bounds of the object. Expensive to use performance-wise.
   *
   * @protected
   */
  calculateBounds() {
    const bounds = this._bounds;
    const sequenceBounds = tmpBounds;
    let curMatrix = Matrix.IDENTITY;
    this._bounds.clear();
    sequenceBounds.clear();
    for (let i = 0; i < this.graphicsData.length; i++) {
      const data = this.graphicsData[i];
      const shape = data.shape;
      const type = data.type;
      const lineStyle = data.lineStyle;
      const nextMatrix = data.matrix || Matrix.IDENTITY;
      let lineWidth = 0;
      if (lineStyle && lineStyle.visible) {
        lineWidth = lineStyle.width;
        if (type !== SHAPES.POLY || data.fillStyle.visible) {
          lineWidth *= Math.max(0, lineStyle.alignment);
        } else {
          lineWidth *= Math.max(lineStyle.alignment, 1 - lineStyle.alignment);
        }
      }
      if (curMatrix !== nextMatrix) {
        if (!sequenceBounds.isEmpty()) {
          bounds.addBoundsMatrix(sequenceBounds, curMatrix);
          sequenceBounds.clear();
        }
        curMatrix = nextMatrix;
      }
      if (type === SHAPES.RECT || type === SHAPES.RREC) {
        const rect = shape;
        sequenceBounds.addFramePad(
          rect.x,
          rect.y,
          rect.x + rect.width,
          rect.y + rect.height,
          lineWidth,
          lineWidth
        );
      } else if (type === SHAPES.CIRC) {
        const circle = shape;
        sequenceBounds.addFramePad(
          circle.x,
          circle.y,
          circle.x,
          circle.y,
          circle.radius + lineWidth,
          circle.radius + lineWidth
        );
      } else if (type === SHAPES.ELIP) {
        const ellipse = shape;
        sequenceBounds.addFramePad(
          ellipse.x,
          ellipse.y,
          ellipse.x,
          ellipse.y,
          ellipse.width + lineWidth,
          ellipse.height + lineWidth
        );
      } else {
        const poly = shape;
        bounds.addVerticesMatrix(curMatrix, poly.points, 0, poly.points.length, lineWidth, lineWidth);
      }
    }
    if (!sequenceBounds.isEmpty()) {
      bounds.addBoundsMatrix(sequenceBounds, curMatrix);
    }
    bounds.pad(this.boundsPadding, this.boundsPadding);
  }
  /**
   * Transform points using matrix.
   *
   * @protected
   * @param {number[]} points - Points to transform
   * @param {PIXI.Matrix} matrix - Transform matrix
   */
  transformPoints(points, matrix) {
    for (let i = 0; i < points.length / 2; i++) {
      const x = points[i * 2];
      const y = points[i * 2 + 1];
      points[i * 2] = matrix.a * x + matrix.c * y + matrix.tx;
      points[i * 2 + 1] = matrix.b * x + matrix.d * y + matrix.ty;
    }
  }
}
SmoothGraphicsGeometry.BATCHABLE_SIZE = 100;

export { BATCH_POOL, DRAW_CALL_POOL, SmoothGraphicsGeometry };
//# sourceMappingURL=SmoothGraphicsGeometry.mjs.map
