

import GraphicsData from './GraphicsData';
import RenderTexture from "../texture/RenderTexture";
import { Matrix, Point, Rectangle } from '../math';
import { RoundedRectangle, Ellipse, Polygon, Circle } from "./shapes"
import { sign, string2hex, hex2rgb } from '../utils';
import { SHAPES, PI_2, SCALE_MODES, WRAP_MODES, BLEND_MODES } from '../const';
import { DisplayObject } from '../display/DisplayObject';
import Texture from '../texture/Texture';
import CanvasRenderer from '../renderers/CanvasRenderer';
import { Event } from "../events/Event"
import { WebglRenderer } from '../renderers/WebglRenderer';
import buildPoly from './geomBuild/buildPoly';
import buildCircle from './geomBuild/buildCircle';
import buildRectangle from './geomBuild/buildRectangle';
import buildRoundedRectangle from './geomBuild/buildRoundedRectangle';
import buildLine from './geomBuild/buildLine';
import FillStyle from './styles/FillStyle';
import LineStyle from './styles/LineStyle';
import { GRAPHICS_CURVES, quadraticCurveLength, bezierCurveLength, bezierCurveTo } from './utils';
import Container from '../display/Container';
let canvasRenderer: CanvasRenderer;
const tempMatrix = new Matrix();
const tempPoint = new Point();
const tempColor1 = new Float32Array(4);
const tempColor2 = new Float32Array(4);

//公用颜色rgb
const temp = new Float32Array(3);
//geoBatchPart对象池
const GEOBATCH_POOL: geoBatchPart[] = [];

//分割三角指令托管
const fillCommands = {};
fillCommands[SHAPES.POLY] = buildPoly;
fillCommands[SHAPES.CIRC] = buildCircle;
fillCommands[SHAPES.ELIP] = buildCircle;
fillCommands[SHAPES.RECT] = buildRectangle;
fillCommands[SHAPES.RREC] = buildRoundedRectangle;

/**
 * @class Graphics
 * 不继承container，不然缓存时太麻烦了
 * 自身matrix无效，暂时不用
 * style的纹理matrix无效，暂时不用
 */
export default class Graphics extends Container {
    /**
     * 混色模式
     * @default BLEND_MODES.NORMAL
     */
    _blendMode: BLEND_MODES = BLEND_MODES.NORMAL;
    //以后修改，估计需要标记，用来修改batch，否则可能无效
    get blendMode(): BLEND_MODES {
        return this._blendMode
    }
    /**
     * 很多效果暂时无效，再查原因，先不能设置吧
     */
    set blendMode(value: BLEND_MODES) {
        // if (value != this._blendMode) this._blendMode = value
    }
    /**
     * 当前的填充样式
     * @member {FillStyle}
     */
    private _fillStyle: FillStyle;
    /**
     * 当前的画线样式
     * @member {LineStyle}
     */
    private _lineStyle: LineStyle;
    /**
     * @member {FillStyle}
     * @readonly
     */
    get fill() {
        return this._fillStyle;
    }

    /**
     * @member {LineStyle}
     * @readonly
     */
    get line() {
        return this._lineStyle;
    }
    /**
     * 图形默认色调，暂时就webgl有效
     * 默认白色，不会有任何影响
     * @member {number}
     * @default 0xFFFFFF
     */
    private _tint: number
    get tint() {
        return this._tint;
    }
    set tint(value) {
        this._tint = value;
    }
    /**
     * 判断是否更新色值
     */
    private batchTint = -1;

    /**
     * 当前图形应用的矩阵，一般用不着
     *
     * @member {Matrix}
     */
    private _matrix;
    get matrix() {
        return this._matrix;
    }
    set matrix(matrix: Matrix) {
        //暂时不用
        // this._matrix = matrix;
    }

    public hitTestByPixel: boolean = true;
    /**
     * 当前是否是画洞
     * @member {boolean}
     * @default false
     * @protected
     */
    private _holeMode: boolean;

    /**
     * Current path
     * 只用于画多边形时用
     * @member {Polygon}
     */
    private _currentPath: Polygon;

    /**
     * 图形数据，为了一个Graphics里能绘制多个
     * @member {GraphicsData[]}
     */
    graphicsData: GraphicsData[];

    /**
     * 是否用作mask
     * @member {boolean}
     */
    isUsedToMask: boolean;
    /**
     * The bounds' padding used for bounds calculation.
     * @member {number}
     */
    boundsPadding: number;
    /**
     * Used to detect if the graphics object has changed. If this is set to true then the graphics
     * object will be recalculated.
     * 通过比对确定是否该刷新
     * @member {boolean}
     * @private
     */
    private dirty: number;
    /**
     * 与dirty对比是否重算bounds
     * Used to detect if we we need to recalculate local bounds
     * @type {Number}
     */
    private boundsDirty: number;

    /**
     * canvas必用缓存
     * renderCanvas默认用缓存,也就canvas上使用，如果经常需要重绘，设置为false
     * webgl上用贴图占用GPU空间太大，用其他方法实现,贴图是白图就一张，用几何方法实现
     * @name cacheAsBitmap
     * @member {boolean}
     * @memberof Graphics#
     * @default false
     */
    cacheAsBitmap: boolean = true;
    /**
     * 需与dirty一致
     */
    private cacheDirty: number;
    private _canvasBuffer: RenderTexture;
    //缓存的贴图
    _texture: Texture;
    //如果用缓存绘图，必须考虑偏移量；
    offsetX: number;
    offsetY: number;

    //webgl专用数据
    verts;
    indices;
    private batches;
    private geoBatches: geoBatchPart[];
    private batchDirty;
    private uvs;
    private vertexData;
    private shapeIndex;//为了在不clear情况下继续画，不用重复计算前面的batch
    private _transformID;
    constructor() {
        super();
        this._instanceType = "Graphics"
        this._fillStyle = new FillStyle();
        this._lineStyle = new LineStyle();
        this.tint = 0xFFFFFF;
        this.batchTint = -1;
        this._matrix = null;
        this._holeMode = false;
        this._currentPath = null;

        this.graphicsData = [];
        this.isUsedToMask = false;
        this.boundsPadding = 0;
        this.dirty = 0;
        this.boundsDirty = -1;
        this.cacheDirty = -1;


        this.verts = [];
        this.indices = []
        this.batches = [];
        this.geoBatches = [];
        this.uvs = [];
        this.batchDirty = -1;
        this.shapeIndex = 0;

        this.vertexData = null;
        this._transformID = -1;
    }

    /**
     * 克隆该Graphics的几何绘制，不包括它自身的transform
     * @return {Graphics} A clone of the graphics object
     */
    clone(): Graphics {
        const clone = new Graphics();

        clone.renderable = this.renderable;
        clone._fillStyle = this._fillStyle;
        clone._lineStyle = this._lineStyle;

        clone.isUsedToMask = this.isUsedToMask;
        clone.boundsPadding = this.boundsPadding;
        clone.dirty = 0;

        // copy graphics data
        for (let i = 0; i < this.graphicsData.length; ++i) {
            clone.graphicsData.push(this.graphicsData[i].clone());
        }

        //考虑当前路径是否置空
        clone._currentPath = null;
        // clone._currentPath = clone.graphicsData[clone.graphicsData.length - 1];

        clone.updateLocalBoundsSelf();

        return clone;
    }

    /**
     * line属于附属属性，暂不写beginStroke，只要没调用过beginFill就是纯stroke
     * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo()
     * method or the drawCircle() method.
     * @param {number} [lineWidth=0] - width of the line to draw, will update the objects stored style
     * @param {number} [color=0] - color of the line to draw, will update the objects stored style
     * @param {number} [alpha=1] - alpha of the line to draw, will update the objects stored style
     * @param {number} [alignment=0.5] - alignment of the line to draw, (0 = inner, 0.5 = middle, 1 = outter)
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    lineStyle(
        lineWidth: number = 0,
        color: number = 0,
        alpha: number = 1,
        alignment: number = 0.5,
        native: boolean = false
    ): Graphics {

        this.lineTextureStyle(lineWidth, Texture.WHITE, color, alpha, null, alignment, native);

        return this;
    }
    lineTextureStyle(
        width = 0,
        texture = Texture.WHITE,
        color = 0xFFFFFF,
        alpha = 1,
        matrix = null,
        alignment = 0.5,
        native = false
    ) {
        if (this._currentPath) this.startPoly();

        const visible = width > 0 && alpha > 0;

        if (!visible) {
            this._lineStyle.reset();
        }
        else {
            if (matrix) {
                matrix = matrix.clone();
                matrix.invert();
            }
            this._lineStyle.color = color;
            this._lineStyle.width = width;
            this._lineStyle.alpha = alpha;
            this._lineStyle.matrix = matrix;
            this._lineStyle.texture = texture;
            this._lineStyle.alignment = alignment;
            this._lineStyle.native = native;
            this._lineStyle.visible = visible;
            // Object.assign(this._lineStyle, {
            //     color,
            //     width,
            //     alpha,
            //     matrix,
            //     texture,
            //     alignment,
            //     native,
            //     visible,
            // });
        }
        return this;
    }
    private startPoly() {
        if (this._currentPath) {
            const points = this._currentPath.points;
            const len = this._currentPath.points.length;
            if (len > 2) {
                this.drawShape(this._currentPath);
                this._currentPath = new Polygon();
                this._currentPath.closed = false;
                this._currentPath.points.push(points[len - 2], points[len - 1]);
            }
        }
        else {
            this._currentPath = new Polygon();
            this._currentPath.closed = false;
        }
    }
    private finishPoly() {
        if (this._currentPath) {
            if (this._currentPath.points.length > 2) {
                this.drawShape(this._currentPath);
                this._currentPath = null;
            }
            else {
                this._currentPath.points.length = 0;
            }
        }
    }

    moveTo(x: number, y: number): Graphics {
        this.startPoly();
        this._currentPath.points[0] = x;
        this._currentPath.points[1] = y;
        return this;
    }


    lineTo(x: number, y: number): Graphics {
        if (!this._currentPath) this.moveTo(0, 0);

        // remove duplicates..
        const points = this._currentPath.points;
        const fromX = points[points.length - 2];
        const fromY = points[points.length - 1];

        if (fromX !== x || fromY !== y) {
            points.push(x, y);
        }
        return this;
    }
    /**
     * 初始化曲线
     * @param x 
     * @param y 
     */
    private _initCurve(x = 0, y = 0) {
        if (this._currentPath) {
            if (this._currentPath.points.length === 0) {
                this._currentPath.points = [x, y];
            }
        }
        else {
            this.moveTo(x, y);
        }
    }

    /**
     * Calculate the points for a quadratic bezier curve and then draws it.
     * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
     *
     * @param {number} cpX - Control point x
     * @param {number} cpY - Control point y
     * @param {number} toX - Destination point x
     * @param {number} toY - Destination point y
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    quadraticCurveTo(cpX: number, cpY: number, toX: number, toY: number): Graphics {
        this._initCurve();

        const points = this._currentPath.points;

        if (points.length === 0) {
            this.moveTo(0, 0);
        }
        let xa = 0;
        let ya = 0;

        const fromX = points[points.length - 2];
        const fromY = points[points.length - 1];
        const n = GRAPHICS_CURVES.segmentsCount(quadraticCurveLength(fromX, fromY, cpX, cpY, toX, toY))


        for (let i = 1; i <= n; ++i) {
            const j = i / n;

            xa = fromX + ((cpX - fromX) * j);
            ya = fromY + ((cpY - fromY) * j);

            points.push(xa + (((cpX + ((toX - cpX) * j)) - xa) * j),
                ya + (((cpY + ((toY - cpY) * j)) - ya) * j));
        }

        return this;
    }

    /**
     * Calculate the points for a bezier curve and then draws it.
     *
     * @param {number} cpX - Control point x
     * @param {number} cpY - Control point y
     * @param {number} cpX2 - Second Control point x
     * @param {number} cpY2 - Second Control point y
     * @param {number} toX - Destination point x
     * @param {number} toY - Destination point y
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    bezierCurveTo(cpX: number, cpY: number, cpX2: number, cpY2: number, toX: number, toY: number): Graphics {
        this._initCurve();

        const points = this._currentPath.points;

        const fromX = points[points.length - 2];
        const fromY = points[points.length - 1];

        points.length -= 2;

        const n = GRAPHICS_CURVES.segmentsCount(bezierCurveLength(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY))


        bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, n, points);

        return this;
    }

    /**
     * The arcTo() method creates an arc/curve between two tangents on the canvas.
     *
     * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
     *
     * @param {number} x1 - The x-coordinate of the beginning of the arc
     * @param {number} y1 - The y-coordinate of the beginning of the arc
     * @param {number} x2 - The x-coordinate of the end of the arc
     * @param {number} y2 - The y-coordinate of the end of the arc
     * @param {number} radius - The radius of the arc
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): Graphics {
        this._initCurve(x1, y1);

        const points = this._currentPath.points
        const fromX = points[points.length - 2];
        const fromY = points[points.length - 1];
        const a1 = fromY - y1;
        const b1 = fromX - x1;
        const a2 = y2 - y1;
        const b2 = x2 - x1;
        const mm = Math.abs((a1 * b2) - (b1 * a2));

        if (mm < 1.0e-8 || radius === 0) {
            if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1) {
                points.push(x1, y1);
            }
        }
        else {
            const dd = (a1 * a1) + (b1 * b1);
            const cc = (a2 * a2) + (b2 * b2);
            const tt = (a1 * a2) + (b1 * b2);
            const k1 = radius * Math.sqrt(dd) / mm;
            const k2 = radius * Math.sqrt(cc) / mm;
            const j1 = k1 * tt / dd;
            const j2 = k2 * tt / cc;
            const cx = (k1 * b2) + (k2 * b1);
            const cy = (k1 * a2) + (k2 * a1);
            const px = b1 * (k2 + j1);
            const py = a1 * (k2 + j1);
            const qx = b2 * (k1 + j2);
            const qy = a2 * (k1 + j2);
            const startAngle = Math.atan2(py - cy, px - cx);
            const endAngle = Math.atan2(qy - cy, qx - cx);

            this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1);
        }
        return this;
    }

    /**
     * The arc method creates an arc/curve (used to create circles, or parts of circles).
     *
     * @param {number} cx - The x-coordinate of the center of the circle
     * @param {number} cy - The y-coordinate of the center of the circle
     * @param {number} radius - The radius of the circle
     * @param {number} startAngle - The starting angle, in radians (0 is at the 3 o'clock position
     *  of the arc's circle)
     * @param {number} endAngle - The ending angle, in radians
     * @param {boolean} [anticlockwise=false] - Specifies whether the drawing should be
     *  counter-clockwise or clockwise. False is default, and indicates clockwise, while true
     *  indicates counter-clockwise.
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    arc(cx: number, cy: number, radius: number, startAngle: number, endAngle: number, anticlockwise: boolean = false): Graphics {
        if (startAngle === endAngle) {
            return this;
        }

        if (!anticlockwise && endAngle <= startAngle) {
            endAngle += PI_2;
        }
        else if (anticlockwise && startAngle <= endAngle) {
            startAngle += PI_2;
        }

        const sweep = endAngle - startAngle;

        if (sweep === 0) {
            return this;
        }

        const startX = cx + (Math.cos(startAngle) * radius);
        const startY = cy + (Math.sin(startAngle) * radius);

        // If the _currentPath exists, take its points. Otherwise call `moveTo` to start a path.
        let points = this._currentPath ? this._currentPath.points : null;

        if (points) {
            // We check how far our start is from the last existing point
            const xDiff = Math.abs(points[points.length - 2] - startX);
            const yDiff = Math.abs(points[points.length - 1] - startY);

            if (xDiff < 0.001 && yDiff < 0.001) {
                // If the point is very close, we don't add it, since this would lead to artifacts
                // during tesselation due to floating point imprecision.
            }
            else {
                points.push(startX, startY);
            }
        }
        else {
            this.moveTo(startX, startY);
            points = this._currentPath.points;
        }

        const segs = GRAPHICS_CURVES.segmentsCount(
            Math.abs(sweep) * radius,
            Math.ceil(Math.abs(sweep) / PI_2) * 40
        );


        const theta = sweep / (segs * 2);
        const theta2 = theta * 2;

        const cTheta = Math.cos(theta);
        const sTheta = Math.sin(theta);

        const segMinus = segs - 1;

        const remainder = (segMinus % 1) / segMinus;

        for (let i = 0; i <= segMinus; ++i) {
            const real = i + (remainder * i);

            const angle = ((theta) + startAngle + (theta2 * real));

            const c = Math.cos(angle);
            const s = -Math.sin(angle);

            points.push(
                (((cTheta * c) + (sTheta * s)) * radius) + cx,
                (((cTheta * -s) + (sTheta * c)) * radius) + cy
            );
        }

        return this;
    }

    /**
     * Specifies a simple one-color fill that subsequent calls to other Graphics methods
     * (such as lineTo() or drawCircle()) use when drawing.
     *
     * @param {number} [color=0] - the color of the fill 十六进制颜色0xffffff
     * @param {number} [alpha=1] - the alpha of the fill
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    beginFill(color: number | string = 0, alpha: number = 1): Graphics {
        //颜色做个兼容吧
        if (typeof (color) == "string") color = string2hex(color)

        return this.beginTextureFill(Texture.WHITE, color, alpha);
    }

    beginTextureFill(texture = Texture.WHITE, color = 0xFFFFFF, alpha = 1, matrix: Matrix = null) {
        if (this._currentPath) this.startPoly();

        const visible = alpha > 0;
        if (!visible) {
            this._fillStyle.reset();
            //需要额外加参数
            this._fillStyle.alphaBlock = true;
        }
        else {
            if (matrix) {
                //暂时不用
                // matrix = matrix.clone();
                // matrix.invert();
            }
            this._fillStyle.color = color;
            this._fillStyle.alpha = alpha;
            this._fillStyle.texture = texture;
            this._fillStyle.matrix = matrix;
            this._fillStyle.visible = visible;
            // Object.assign(this._fillStyle, {
            //     color,
            //     alpha,
            //     texture,
            //     matrix,
            //     visible,
            // });
        }
        return this;
    }

    /**
     * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method.
     *
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    endFill(): Graphics {
        this.finishPoly();
        this._fillStyle.reset();
        return this;
    }

    /**
     * 先加上吧
     */
    endStroke(): Graphics {
        this.finishPoly();
        this._lineStyle.reset();
        return this;
    }

    /**
     * Closes the current path.
     * 只用在多边形的路径里
     * @return {Graphics} Returns itself.
     */
    closePath(): Graphics {
        //如果下个可能要画洞，先闭合之前的路径
        const _currentPath = this._currentPath;
        if (this._currentPath) _currentPath.close();
        return this;
    }
    /**
     * 开始画洞
     */
    beginHole() {
        this.finishPoly();
        this._holeMode = true;
        return this;
    }
    /**
     * 结束画洞
     */
    endHole() {
        this.finishPoly();
        this._holeMode = false;
        return this;
    }

    /**
     * 画矩形
     * @param {number} x - The X coord of the top-left of the rectangle
     * @param {number} y - The Y coord of the top-left of the rectangle
     * @param {number} width - The width of the rectangle
     * @param {number} height - The height of the rectangle
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    drawRect(x: number, y: number, width: number, height: number): Graphics {
        return this.drawShape(new Rectangle(x, y, width, height));
    }

    /**
     * 画圆角矩形
     * @param {number} x - The X coord of the top-left of the rectangle
     * @param {number} y - The Y coord of the top-left of the rectangle
     * @param {number} width - The width of the rectangle
     * @param {number} height - The height of the rectangle
     * @param {number} radius - Radius of the rectangle corners
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    drawRoundedRect(x: number, y: number, width: number, height: number, radius: number): Graphics {
        return this.drawShape(new RoundedRectangle(x, y, width, height, radius));
    }

    /**
     * 画圆形
     * @param {number} x - The X coordinate of the center of the circle
     * @param {number} y - The Y coordinate of the center of the circle
     * @param {number} radius - The radius of the circle
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    drawCircle(x: number, y: number, radius: number): Graphics {
        return this.drawShape(new Circle(x, y, radius));
    }

    /**
     * 画椭圆
     * @param {number} x - The X coordinate of the center of the ellipse
     * @param {number} y - The Y coordinate of the center of the ellipse
     * @param {number} width - The half width of the ellipse
     * @param {number} height - The half height of the ellipse
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    drawEllipse(x: number, y: number, width: number, height: number): Graphics {
        return this.drawShape(new Ellipse(x, y, width, height));
    }

    /**
     * 画多边形
     * @param {number[]|Point[]|Polygon} path - The path data used to construct the polygon.
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    drawPolygon(path: number[] | Point[] | Polygon): Graphics {
        // prevents an argument assignment deopt
        // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
        let points = path;

        let closed = true;

        if (points instanceof Polygon) {
            closed = points.closed;
            points = points.points;
        }

        if (!Array.isArray(points)) {
            // prevents an argument leak deopt
            // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
            points = new Array(arguments.length);

            for (let i = 0; i < points.length; ++i) {
                points[i] = arguments[i]; // eslint-disable-line prefer-rest-params
            }
        }

        const shape = new Polygon(points);

        shape.closed = closed;

        this.drawShape(shape);

        return this;
    }

    /**
     * 画任意多角形。points为数量
     * @param {number} x - Center X position of the star
     * @param {number} y - Center Y position of the star
     * @param {number} points - The number of points of the star, must be > 1
     * @param {number} radius - The outer radius of the star
     * @param {number} [innerRadius] - The inner radius between points, default half `radius`
     * @param {number} [rotation=0] - The rotation of the star in radians, where 0 is vertical
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    drawStar(
        x: number,
        y: number,
        points: number,
        radius: number,
        innerRadius: number,
        rotation: number = 0
    ): Graphics {
        innerRadius = innerRadius || radius / 2;

        const startAngle = (-1 * Math.PI / 2) + rotation;
        const len = points * 2;
        const delta = PI_2 / len;
        const polygon = [];

        for (let i = 0; i < len; i++) {
            const r = i % 2 ? innerRadius : radius;
            const angle = (i * delta) + startAngle;

            polygon.push(
                x + (r * Math.cos(angle)),
                y + (r * Math.sin(angle))
            );
        }

        return this.drawPolygon(polygon);
    }

    /**
     * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon.
     *
     * @param {Circle|Ellipse|Polygon|Rectangle|RoundedRectangle} shape - The shape object to draw.
     * @return {Graphics} 
     */
    private drawShape(shape: Circle | Ellipse | Polygon | Rectangle | RoundedRectangle): Graphics {
        if (!this._holeMode) {
            const data = new GraphicsData(
                shape,
                this._fillStyle.clone(),
                this._lineStyle.clone(),
                this._matrix
            );
            this.graphicsData.push(data);
            this.dirty++;
        }
        else {
            if (!this.graphicsData.length) return this;
            const data = new GraphicsData(shape, null, null, this._matrix);
            const lastShape = this.graphicsData[this.graphicsData.length - 1];
            lastShape.holes.push(data);
            this.dirty++;
        }
        return this;
    }

    /**
     * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings.
     *
     * @return {Graphics} This Graphics object. Good for chaining method calls
     */
    clear(): Graphics {
        if (this.graphicsData.length > 0) {
            this.boundsDirty = -1;
            this.dirty++;
            this.graphicsData.length = 0;

            this.verts.length = 0;
            this.uvs.length = 0;
            this.indices.length = 0;
            this.shapeIndex = 0;
            for (let i = 0; i < this.geoBatches.length; i++) {
                const batch = this.geoBatches[i];
                batch.start = 0;
                batch.attribStart = 0;
                batch.style = null;
                GEOBATCH_POOL.push(batch);
            }
            this.geoBatches.length = 0;

            //清空
            this.batches.length = 0;
        }

        this._currentPath = null;
        this._matrix = null;
        this._holeMode = false;
        this._currentPath = null;

        return this;
    }

    /**
     * 暂时不用
     * @returns {boolean} True if only 1 rect.
     */
    isFastRect(): boolean {
        // return this.graphicsData.length === 1
        //     && this.graphicsData[0].shape.type === SHAPES.RECT
        //     && !this.graphicsData[0].lineWidth;
        return false
    }

    _renderWebGL(renderer: WebglRenderer) {
        //以防没有end，
        this.finishPoly();
        //更新批
        this.updateBatch()
        renderer.batchManager.setObjectRenderer(renderer.plugins["batch"]);
        //有才计算
        if (this.batches.length) {
            this.calculateVertices();
            this.calculateTints();
            for (let i = 0; i < this.batches.length; i++) {
                const batch = this.batches[i];
                //_worldAlpha可能会变
                batch._worldAlpha = this._worldAlpha * batch.graAlpha;
                renderer.plugins["batch"].render(batch);
            }
        }
    }

    /**
     * 根据GraphicsData分出batch，主要根据透明度和颜色
     * _texture  里面有.BaseTexture
     * _vertexData  
     * _indices 
     * graAlpha 图形透明度自身计算需要
     * _worldAlpha 
     * _batchRGB  图形颜色自身计算需要
     * _tintRGB
     * _uvs
     */
    private updateBatch() {
        if (this.batchDirty === this.dirty) return
        if (this.graphicsData.length === 0) return;
        this.batchDirty = this.dirty;

        const uvs = this.uvs;
        //取最后一个
        let batchPart: geoBatchPart;
        batchPart = this.geoBatches.pop();
        if (!batchPart) {
            batchPart = GEOBATCH_POOL.pop() || new geoBatchPart();
            batchPart.style = batchPart.style ||
                this.graphicsData[0].fillStyle ||
                this.graphicsData[0].lineStyle;
        }
        let currentTexture = batchPart.style.texture.baseTexture;
        let currentColor = batchPart.style.color + batchPart.style.alpha;
        this.geoBatches.push(batchPart);
        //先更新geoBatches
        //基本可能只有一个，多个时加
        for (var i = this.shapeIndex; i < this.graphicsData.length; i++) {
            this.shapeIndex++
            const data = this.graphicsData[i];
            const command = fillCommands[data.type];

            const fillStyle = data.fillStyle;
            const lineStyle = data.lineStyle;
            //初始化 data里的points,fill和stroke一样的
            command.build(data);
            //有矩阵先转换点
            if (data.matrix) this.transformPoints(data.points, data.matrix);

            for (let j = 0; j < 2; j++) {
                const style = (j === 0) ? fillStyle : lineStyle;
                if (!style.visible) continue;
                const nextTexture = style.texture.baseTexture;

                if (currentTexture !== nextTexture || (style.color + style.alpha) !== currentColor) {
                    nextTexture.wrapMode = WRAP_MODES.REPEAT;
                    currentTexture = nextTexture;
                    currentColor = style.color + style.alpha;

                    const index = this.indices.length;
                    const attribIndex = this.verts.length / 2;
                    batchPart.size = index - batchPart.start;
                    batchPart.attribSize = attribIndex - batchPart.attribStart;

                    if (batchPart.size > 0) {
                        batchPart = GEOBATCH_POOL.pop() || new geoBatchPart();
                        this.geoBatches.push(batchPart);
                    }
                    batchPart.style = style;
                    batchPart.start = index;
                    batchPart.attribStart = attribIndex;
                }
                const start = this.verts.length / 2;
                if (j === 0) {
                    //处理verts和indices
                    if (data.holes.length) {
                        this.proccessHoles(data.holes);
                        buildPoly.triangulate(data, this);
                    }
                    else {
                        command.triangulate(data, this);
                    }
                } else {
                    buildLine(data, this);
                }

                const size = (this.verts.length / 2) - start;
                this.addUvs(this.verts, uvs, style.texture, start, size, style.matrix);
            }
        }
        const index = this.indices.length;
        const attrib = this.verts.length / 2;

        batchPart.size = index - batchPart.start;
        batchPart.attribSize = attrib - batchPart.attribStart;
        let indicesUint16 = new Uint16Array(this.indices);
        let uvsFloat32 = new Float32Array(this.uvs);
        // offset the indices so that it works with the batcher...
        for (let i = 0; i < this.geoBatches.length; i++) {
            const batch = this.geoBatches[i];
            for (let j = 0; j < batch.size; j++) {
                const index = batch.start + j;
                indicesUint16[index] = indicesUint16[index] - batch.attribStart;
            }
        }

        //设置_transformID，确保计算顶点数据
        this._transformID = -1;
        //确保颜色值要更新
        this.batchTint = -1;
        //更新batches
        this.batches = [];
        this.vertexData = new Float32Array(this.verts);

        const blendMode = this.blendMode;

        for (let i = 0; i < this.geoBatches.length; i++) {
            const gI = this.geoBatches[i];
            const color = gI.style.color;
            const vertexData = new Float32Array(this.vertexData.buffer,
                gI.attribStart * 4 * 2,
                gI.attribSize * 2);
            const uvs = new Float32Array(uvsFloat32.buffer,
                gI.attribStart * 4 * 2,
                gI.attribSize * 2);
            const indices = new Uint16Array(indicesUint16.buffer,
                gI.start * 2,
                gI.size);

            const batch = {
                _vertexData: vertexData,
                _indices: indices,
                _uvs: uvs,
                _tintRGB: color,//还未计算
                _texture: gI.style.texture,
                _worldAlpha: 1, //还未计算
                _blendMode: blendMode,
                graAlpha: gI.style.alpha,//用于计算透明度
                _batchRGB: hex2rgb(color),//用于计算_tintRGB
            };
            this.batches[i] = batch;
        }
    }

    /**
     * 对每个batch计算颜色
     * @protected
     */
    private calculateTints() {
        if (this.batchTint !== this.tint) {
            this.batchTint = this.tint;
            const tintRGB = hex2rgb(this.tint, temp);
            for (let i = 0; i < this.batches.length; i++) {
                const batch = this.batches[i];
                const batchTint = batch._batchRGB;
                const r = (tintRGB[0] * batchTint[0]) * 255;
                const g = (tintRGB[1] * batchTint[1]) * 255;
                const b = (tintRGB[2] * batchTint[2]) * 255;
                // TODO Ivan, can this be done in one go?
                const color = (r << 16) + (g << 8) + (b | 0);

                batch._tintRGB = (color >> 16)
                    + (color & 0xff00)
                    + ((color & 0xff) << 16);
            }
        }
    }
    /**
     * If there's a transform update or a change to the shape of the
     * geometry, recaculate the vertices.
     * @protected
     */
    private calculateVertices() {
        if (this._transformID === this.transform._worldID) {
            return;
        }

        this._transformID = this.transform._worldID;

        const wt = this.transform.worldMatrix;
        const a = wt.a;
        const b = wt.b;
        const c = wt.c;
        const d = wt.d;
        const tx = wt.tx;
        const ty = wt.ty;

        const data = this.verts;// batch.vertexDataOriginal;
        const vertexData = this.vertexData;

        let count = 0;

        for (let i = 0; i < data.length; i += 2) {
            const x = data[i];
            const y = data[i + 1];
            vertexData[count++] = (a * x) + (c * y) + tx;
            vertexData[count++] = (d * y) + (b * x) + ty;
        }
    }

    private 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;
        }
    }

    /**
     * canvas渲染
     * @private
     * @param {CanvasRenderer} renderer - The renderer
     */
    _renderCanvas(renderer: CanvasRenderer) {
        if (this.cacheAsBitmap) {
            if (this.cacheDirty != this.dirty) {
                this.cacheDirty = this.dirty;
                //如果在生成贴图时需要去掉遮罩和缓存属性
                var tempMask = this.mask;
                this.mask = null;
                this.cacheAsBitmap = false;
                var tempAlpha = this.alpha;
                this.alpha = 1;
                this.generateCanvasTexture();
                //恢复
                this.mask = tempMask;
                this.alpha = tempAlpha;
                this.cacheAsBitmap = true;
                this.updateTransform()
            }
            //普通画图的渲染，提供图片和图形的插件，先判断_anchorTexture，offsetX,0
            renderer.plugins.sprite.render(this);
        } else {
            renderer.plugins.graphics.render(this);
        }
    }


    /**
     * _boundsId不知道怎么改，暂时不管，少用
     * 计算全局bounds，_localBoundsSelf做个全局转换就行
     * @private
     */
    _calculateBounds() {
        this.updateLocalBoundsSelf();
        const rect = this._localBoundsSelf;
        var matrix = this.transform.worldMatrix;
        matrix.transformPoint(rect.x, rect.y, DisplayObject._p1);
        matrix.transformPoint(rect.x + rect.width, rect.y, DisplayObject._p2);
        matrix.transformPoint(rect.x + rect.width, rect.y + rect.height, DisplayObject._p3);
        matrix.transformPoint(rect.x, rect.y + rect.height, DisplayObject._p4);
        Rectangle.createFromPoints(this._bounds, DisplayObject._p1, DisplayObject._p2, DisplayObject._p3, DisplayObject._p4);
    }

    /**
     * 碰撞检测方法重写
     * @param {Point} point - the point to test
     */
    hitTestPoint(point: Point, isMouseEvent: boolean = false): DisplayObject {
        //不可见，直接返回
        if (!this.visible) return null;
        let hitDisplayObject;
        //先检查子级，因为子级层级更高
        hitDisplayObject = super.hitTestPoint(point, isMouseEvent);
        //子级已有，返回
        if (hitDisplayObject) return hitDisplayObject;
        //子级没有的话，为了可以不updateLocalBoundsSelf，判断一下
        if (isMouseEvent && !this.mouseEnable) return null;
        //再粗略检查自己，先计算自身盒子
        this.updateLocalBoundsSelf();
        hitDisplayObject = this.displayObjectHitTestPoint(point, isMouseEvent);
        //如果
        if (hitDisplayObject) {
            //不需要像素级检测直接返回
            if (!this.hitTestByPixel) return hitDisplayObject;
            //精确检测
            return this.hitTestPointAccuratly(point, isMouseEvent)
        }
        return null
    }

    /**
     * 精确检测
     * 用几何方法
     * 不像shape里用的像素值
     * @param point 
     */
    private hitTestPointAccuratly(point: Point, isMouseEvent) {
        let s = this;
        if (isMouseEvent) {
            this.worldMatrix.transformPointInverse(point.x, point.y, tempPoint);
        } else {
            tempPoint.set(point.x, point.y);
        }
        const graphicsData = this.graphicsData;
        for (let i = 0; i < graphicsData.length; ++i) {
            const data = graphicsData[i];
            if (!data.fillStyle.visible && !data.fillStyle.alphaBlock) continue;
            // only deal with fills..
            if (data.shape) {
                if (data.shape.isPointIn(tempPoint)) {
                    if (data.holes) {
                        for (let i = 0; i < data.holes.length; i++) {
                            const hole = data.holes[i].shape;
                            if (hole.isPointIn(tempPoint)) {
                                return null;
                            }
                        }
                    }
                    return s;
                }
            }
        }
        return null;
    }

    /**
     * 更新自身包围盒
     */
    updateLocalBoundsSelf() {
        if (this.boundsDirty == this.dirty) return;
        this.boundsDirty = this.dirty;
        let minX = Infinity;
        let maxX = -Infinity;

        let minY = Infinity;
        let maxY = -Infinity;

        if (this.graphicsData.length) {
            let shape;
            let x = 0;
            let y = 0;
            let w = 0;
            let h = 0;

            for (let i = 0; i < this.graphicsData.length; i++) {
                const data = this.graphicsData[i];
                const type = data.type;
                const lineStyle = data.lineStyle;
                //矩阵的应该暂时没写，暂时不用
                const nextMatrix = data.matrix || Matrix.IDENTITY;
                let lineWidth = 0.0;
                if (lineStyle && lineStyle.visible) {
                    const alignment = lineStyle.alignment;
                    lineWidth = lineStyle.width;
                    if (type === SHAPES.POLY) {
                        lineWidth = lineWidth * (0.5 + Math.abs(0.5 - alignment));
                    }
                    else {
                        lineWidth = lineWidth * Math.max(0, alignment);
                    }
                }

                shape = data.shape;

                if (type === SHAPES.RECT || type === SHAPES.RREC) {
                    x = shape.x - (lineWidth / 2);
                    y = shape.y - (lineWidth / 2);
                    w = shape.width + lineWidth;
                    h = shape.height + lineWidth;

                    minX = x < minX ? x : minX;
                    maxX = x + w > maxX ? x + w : maxX;

                    minY = y < minY ? y : minY;
                    maxY = y + h > maxY ? y + h : maxY;
                }
                else if (type === SHAPES.CIRC) {
                    x = shape.x;
                    y = shape.y;
                    w = shape.radius + (lineWidth / 2);
                    h = shape.radius + (lineWidth / 2);

                    minX = x - w < minX ? x - w : minX;
                    maxX = x + w > maxX ? x + w : maxX;

                    minY = y - h < minY ? y - h : minY;
                    maxY = y + h > maxY ? y + h : maxY;
                }
                else if (type === SHAPES.ELIP) {
                    x = shape.x;
                    y = shape.y;
                    w = shape.width + (lineWidth / 2);
                    h = shape.height + (lineWidth / 2);

                    minX = x - w < minX ? x - w : minX;
                    maxX = x + w > maxX ? x + w : maxX;

                    minY = y - h < minY ? y - h : minY;
                    maxY = y + h > maxY ? y + h : maxY;
                }
                else {
                    // POLY
                    const points = shape.points;
                    let x2 = 0;
                    let y2 = 0;
                    let dx = 0;
                    let dy = 0;
                    let rw = 0;
                    let rh = 0;
                    let cx = 0;
                    let cy = 0;

                    for (let j = 0; j + 2 < points.length; j += 2) {
                        x = points[j];
                        y = points[j + 1];
                        x2 = points[j + 2];
                        y2 = points[j + 3];
                        dx = Math.abs(x2 - x);
                        dy = Math.abs(y2 - y);
                        h = lineWidth;
                        w = Math.sqrt((dx * dx) + (dy * dy));

                        if (w < 1e-9) {
                            continue;
                        }

                        rw = ((h / w * dy) + dx) / 2;
                        rh = ((h / w * dx) + dy) / 2;
                        cx = (x2 + x) / 2;
                        cy = (y2 + y) / 2;

                        minX = cx - rw < minX ? cx - rw : minX;
                        maxX = cx + rw > maxX ? cx + rw : maxX;

                        minY = cy - rh < minY ? cy - rh : minY;
                        maxY = cy + rh > maxY ? cy + rh : maxY;
                    }
                }
            }
        }
        else {
            minX = 0;
            maxX = 0;
            minY = 0;
            maxY = 0;
        }

        const padding = this.boundsPadding;

        this._localBoundsSelf.x = minX - padding;
        this._localBoundsSelf.y = minY - padding;
        this._localBoundsSelf.width = maxX - minX + padding * 2;
        this._localBoundsSelf.height = maxY - minY + padding * 2;
    }

    /**
     * Generates a canvas texture.
     * 不包括变形的，只根据图形数据产生的原生贴图，经过变形的，考虑，是否新建方法，这个暂时只为canvas缓存
     * 也不考虑遮罩
     * @param {number} scaleMode - The scale mode of the texture.
     * @param {number} resolution - The resolution of the texture.
     * @return {Texture} The new texture.
     */
    private generateCanvasTexture(scaleMode: number = SCALE_MODES.LINEAR): Texture {
        this.updateLocalBoundsSelf();

        const bounds = this._localBoundsSelf;
        if (!this._canvasBuffer) {
            this._canvasBuffer = RenderTexture.create(bounds.width, bounds.height, scaleMode);
        } else {
            this._canvasBuffer.resize(bounds.width, bounds.height)
        }


        if (!canvasRenderer) {
            canvasRenderer = new CanvasRenderer({});
        }

        this.transform.updateLocalMatrix();
        tempMatrix.copy(this.transform.localMatrix);

        tempMatrix.invert();
        tempMatrix.tx -= bounds.x;
        tempMatrix.ty -= bounds.y;

        canvasRenderer.render(this, this._canvasBuffer, tempMatrix);
        // document.body.appendChild(this._canvasBuffer.baseTexture["_canvasRenderTarget"].canvas)
        if (!this._texture) {
            this._texture = Texture.fromCanvas(this._canvasBuffer.baseTexture["_canvasRenderTarget"].canvas, scaleMode, 'graphics');
            this._texture.baseTexture.update();
        } else {
            this._texture.baseTexture.update();
        }
        //可能需要更改_texture，this._texture.baseTexture尺寸

        this.offsetX = bounds.x;
        this.offsetY = bounds.y;
        return this._texture;
    }

    /**
     * 
     */
    destroy() {
        super.destroy();

        // destroy each of the GraphicsData objects
        for (let i = 0; i < this.graphicsData.length; ++i) {
            this.graphicsData[i].destroy();
        }
        this._matrix = null;
        this._currentPath = null;
        this._lineStyle.destroy();
        this._lineStyle = null;
        this._fillStyle.destroy();
        this._fillStyle = null;
        this.vertexData = null;
        this.geoBatches.length = 0;
        this.geoBatches = null;
        this.batches.length = 0;
        this.batches = null;
    }


    /**
     * The width of the sprite, setting this will actually modify the scale to achieve the value set
     * @member {number}
     */
    get width() {
        this.updateLocalBoundsSelf()
        return Math.abs(this.scale.x) * this._localBoundsSelf.width;
    }

    set width(value) {
        this.updateLocalBoundsSelf();
        const width = this._localBoundsSelf.width;
        if (width !== 0) {
            const s = sign(this.scale.x) || 1;
            this.scale.x = s * value / width;
        } else {
            this.scale.x = 1;
        }
        this._width = value;
    }

    /**
     * The height of the sprite, setting this will actually modify the scale to achieve the value set
     *
     * @member {number}
     */
    get height() {
        this.updateLocalBoundsSelf()
        return Math.abs(this.scale.y) * this._localBoundsSelf.height;
    }

    set height(value) {
        this.updateLocalBoundsSelf()
        const height = this._localBoundsSelf.height;
        if (height !== 0) {
            const s = sign(this.scale.y) || 1;
            this.scale.y = s * value / height;
        } else {
            this.scale.y = 1;
        }
        this._height = value;
    }

    /**
     * Process the holes data.
     *
     * @param {GraphicsData[]} holes - Holes to render
     * @protected
     */
    private proccessHoles(holes: GraphicsData[]) {
        for (let i = 0; i < holes.length; i++) {
            const hole = holes[i];
            const command = fillCommands[hole.type];
            command.build(hole);
            if (hole.matrix) this.transformPoints(hole.points, hole.matrix);

        }
    }
    /**
     * Generates the UVs for a shape.
     * 不支持纹理填充，所以uv都时0
     * @protected
     * @param {number[]} verts - Vertices
     * @param {number[]} uvs - UVs
     * @param {Texture} texture - Reference to Texture
     * @param {number} start - Index buffer start index.
     * @param {number} size - The size/length for index buffer.
     * @param {Matrix} [matrix] - Optional transform for all points.
     */
    private addUvs(verts, uvs, texture, start, size, matrix?) {
        let index = 0;
        const uvsStart = uvs.length;
        const frame = texture.frame;
        while (index < size) {
            let x = verts[(start + index) * 2];
            let y = verts[((start + index) * 2) + 1];
            //矩阵应用还有问题
            if (matrix) {
                const nx = (matrix.a * x) + (matrix.c * y) + matrix.tx;
                y = (matrix.b * x) + (matrix.d * y) + matrix.ty;
                x = nx;
            }
            index++;
            const frame = texture.frame;
            uvs.push(x / frame.width, y / frame.height);
            //uvs.push(0, 0);
        }

        const baseTexture = texture.baseTexture;

        if (frame.width < baseTexture.width
            || frame.height < baseTexture.height) {
            this.adjustUvs(uvs, texture, uvsStart, size);
        }
    }
    /**
     * Modify uvs array according to position of texture region
     * Does not work with rotated or trimmed textures
     * @param {number[]} uvs array
     * @param {Texture} texture region
     * @param {number} start starting index for uvs
     * @param {number} size how many points to adjust
     */
    private adjustUvs(uvs, texture, start, size) {
        const baseTexture = texture.baseTexture;
        const eps = 1e-6;
        const finish = start + (size * 2);
        const frame = texture.frame;
        const scaleX = frame.width / baseTexture.width;
        const scaleY = frame.height / baseTexture.height;
        let offsetX = frame.x / frame.width;
        let offsetY = frame.y / frame.height;
        let minX = Math.floor(uvs[start] + eps);
        let minY = Math.floor(uvs[start + 1] + eps);

        for (let i = start + 2; i < finish; i += 2) {
            minX = Math.min(minX, Math.floor(uvs[i] + eps));
            minY = Math.min(minY, Math.floor(uvs[i + 1] + eps));
        }
        offsetX -= minX;
        offsetY -= minY;
        for (let i = start; i < finish; i += 2) {
            uvs[i] = (uvs[i] + offsetX) * scaleX;
            uvs[i + 1] = (uvs[i + 1] + offsetY) * scaleY;
        }
    }
}





/**
 * A little internal structure to hold interim batch objects.
 *
 * @private
 */
class geoBatchPart {
    style: FillStyle | LineStyle
    size: number;
    start: number;
    attribStart: number;
    attribSize: number;
    constructor() {
        this.style = null
        this.size = 0;
        this.start = 0;
        this.attribStart = 0;
        this.attribSize = 0;
    }
}