
/**
 * Draws a path.
 * @param {number[]|PIXI.IPointData[]|PIXI.Polygon|...number|...PIXI.IPointData} path    The polygon or points.
 * @returns {this}    This Graphics instance.
 */
PIXI.Graphics.prototype.drawPath = function(...path) {
  let closeStroke = false;
  let polygon = path[0];
  let points;
  if ( polygon.points ) {
    closeStroke = polygon.closeStroke;
    points = polygon.points;
  } else if ( Array.isArray(path[0]) ) {
    points = path[0];
  } else {
    points = path;
  }
  polygon = new PIXI.Polygon(points);
  polygon.closeStroke = closeStroke;
  return this.drawShape(polygon);
};
PIXI.LegacyGraphics.prototype.drawPath = PIXI.Graphics.prototype.drawPath;
PIXI.smooth.SmoothGraphics.prototype.drawPath = PIXI.Graphics.prototype.drawPath;

/* -------------------------------------------- */

/**
 * Draws a smoothed polygon.
 * @param {number[]|PIXI.IPointData[]|PIXI.Polygon|...number|...PIXI.IPointData} path    The polygon or points.
 * @param {number} [smoothing=0]    The smoothness in the range [0, 1]. 0: no smoothing; 1: maximum smoothing.
 * @returns {this}                  This Graphics instance.
 */
PIXI.Graphics.prototype.drawSmoothedPolygon = function(...path) {
  let closeStroke = true;
  let polygon = path[0];
  let points;
  let factor;
  if ( polygon.points ) {
    closeStroke = polygon.closeStroke;
    points = polygon.points;
    factor = path[1];
  } else if ( Array.isArray(path[0]) ) {
    points = path[0];
    factor = path[1];
  } else if ( typeof path[0] === "number" ) {
    points = path;
    factor = path.length % 2 ? path.at(-1) : 0;
  } else {
    const n = path.length - (typeof path.at(-1) !== "object" ? 1 : 0);
    points = [];
    for ( let i = 0; i < n; i++ ) points.push(path[i].x, path[i].y);
    factor = path.at(n);
  }
  factor ??= 0;
  if ( (points.length < 6) || (factor <= 0) ) {
    polygon = new PIXI.Polygon(points.slice(0, points.length - (points.length % 2)));
    polygon.closeStroke = closeStroke;
    return this.drawShape(polygon);
  }
  const dedupedPoints = [points[0], points[1]];
  for ( let i = 2; i < points.length - 1; i += 2 ) {
    const x = points[i];
    const y = points[i + 1];
    if ( (x === points[i - 2]) && (y === points[i - 1]) ) continue;
    dedupedPoints.push(x, y);
  }
  points = dedupedPoints;
  if ( closeStroke && (points[0] === points.at(-2)) && (points[1] === points.at(-1)) ) points.length -= 2;
  if ( points.length < 6 ) {
    polygon = new PIXI.Polygon(points);
    polygon.closeStroke = closeStroke;
    return this.drawShape(polygon);
  }
  const getBezierControlPoints = (fromX, fromY, toX, toY, nextX, nextY) => {
    const vectorX = nextX - fromX;
    const vectorY = nextY - fromY;
    const preDistance = Math.hypot(toX - fromX, toY - fromY);
    const postDistance = Math.hypot(nextX - toX, nextY - toY);
    const totalDistance = preDistance + postDistance;
    const cp0d = 0.5 * factor * (preDistance / totalDistance);
    const cp1d = 0.5 * factor * (postDistance / totalDistance);
    return [
      toX - (vectorX * cp0d),
      toY - (vectorY * cp0d),
      toX + (vectorX * cp1d),
      toY + (vectorY * cp1d)
    ];
  };
  let [fromX, fromY, toX, toY] = points;
  let [cpX, cpY, cpXNext, cpYNext] = getBezierControlPoints(points.at(-2), points.at(-1), fromX, fromY, toX, toY);
  this.moveTo(fromX, fromY);
  for ( let i = 2, n = points.length + (closeStroke ? 2 : 0); i < n; i += 2 ) {
    const nextX = points[(i + 2) % points.length];
    const nextY = points[(i + 3) % points.length];
    cpX = cpXNext;
    cpY = cpYNext;
    let cpX2;
    let cpY2;
    [cpX2, cpY2, cpXNext, cpYNext] = getBezierControlPoints(fromX, fromY, toX, toY, nextX, nextY);
    if ( !closeStroke && (i === 2) ) this.quadraticCurveTo(cpX2, cpY2, toX, toY);
    else if ( !closeStroke && (i === points.length - 2) ) this.quadraticCurveTo(cpX, cpY, toX, toY);
    else this.bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY);
    fromX = toX;
    fromY = toY;
    toX = nextX;
    toY = nextY;
  }
  if ( closeStroke ) this.closePath();
  this.finishPoly();
  return this;
};
PIXI.LegacyGraphics.prototype.drawSmoothedPolygon = PIXI.Graphics.prototype.drawSmoothedPolygon;
PIXI.smooth.SmoothGraphics.prototype.drawSmoothedPolygon = PIXI.Graphics.prototype.drawSmoothedPolygon;

/* -------------------------------------------- */

/**
 * Draws a smoothed path.
 * @param {number[]|PIXI.IPointData[]|PIXI.Polygon|...number|...PIXI.IPointData} path    The polygon or points.
 * @param {number} [smoothing=0]    The smoothness in the range [0, 1]. 0: no smoothing; 1: maximum smoothing.
 * @returns {this}                  This Graphics instance.
 */
PIXI.Graphics.prototype.drawSmoothedPath = function(...path) {
  let closeStroke = false;
  let polygon = path[0];
  let points;
  let factor;
  if ( polygon.points ) {
    closeStroke = polygon.closeStroke;
    points = polygon.points;
    factor = path[1];
  } else if ( Array.isArray(path[0]) ) {
    points = path[0];
    factor = path[1];
  } else if ( typeof path[0] === "number" ) {
    points = path;
    factor = path.length % 2 ? path.at(-1) : 0;
  } else {
    const n = path.length - (typeof path.at(-1) !== "object" ? 1 : 0);
    points = [];
    for ( let i = 0; i < n; i++ ) points.push(path[i].x, path[i].y);
    factor = path.at(n);
  }
  polygon = new PIXI.Polygon(points);
  polygon.closeStroke = closeStroke;
  return this.drawSmoothedPolygon(polygon, factor);
};
PIXI.LegacyGraphics.prototype.drawSmoothedPath = PIXI.Graphics.prototype.drawSmoothedPath;
PIXI.smooth.SmoothGraphics.prototype.drawSmoothedPath = PIXI.Graphics.prototype.drawSmoothedPath;
