import CanvasRenderer from '../CanvasRenderer';
import { SHAPES } from '../../const';
import Graphics from '../../graphics/Graphics';
import GraphicsData from '../../graphics/GraphicsData';

/**
 * Renderer dedicated to drawing and batching graphics objects.
 *
 * @class
 * @private
 */
export class CanvasGraphicsRenderer {
    renderer: CanvasRenderer;
    /**
     * @param {CanvasRenderer} renderer - The current renderer.
     */
    constructor(renderer: CanvasRenderer) {
        this.renderer = renderer;
    }

    /**
     * Renders a Graphics object to a canvas.
     *
     * @param {Graphics} graphics - the actual graphics object to render
     */
    render(graphics: Graphics) {
        const renderer = this.renderer;
        const context = renderer.context;
        const _worldAlpha = graphics._worldAlpha;
        const transform = graphics.transform.worldMatrix;

        context.setTransform(
            transform.a,
            transform.b,
            transform.c,
            transform.d,
            transform.tx,
            transform.ty
        );
        //加混色
        renderer.setBlendMode(graphics.blendMode);
        for (let i = 0; i < graphics.graphicsData.length; i++) {
            const data = graphics.graphicsData[i];
            const shape = data.shape;

            const fillStyle = data.fillStyle;
            const lineStyle = data.lineStyle;

            const fillColor = fillStyle.color;
            const lineColor = lineStyle.color;

            context.lineWidth = lineStyle.width;

            var ccw = true;

            if (data.type === SHAPES.POLY) {
                //必须每次清空子路径列表，因为每个data视为独立
                context.beginPath();
                var points = shape.points
                context.moveTo(points[0], points[1]);
                for (let m = 1; m < points.length / 2; ++m) {
                    context.lineTo(points[m * 2], points[(m * 2) + 1]);
                }
                ccw = !judgeCcw(points)
            }
            else if (data.type === SHAPES.RECT) {
                if (data.holes.length) {
                    context.beginPath();
                    context.moveTo(shape.x, shape.y);
                    context.lineTo(shape.x + shape.width, shape.y);
                    context.lineTo(shape.x + shape.width, shape.y + shape.height);
                    context.lineTo(shape.x, shape.y + shape.height);
                    context.lineTo(shape.x, shape.y);
                } else {
                    //简单处理，直接下一个
                    if (fillStyle.visible) {
                        context.globalAlpha = fillStyle.alpha * _worldAlpha;
                        context.fillStyle = `#${(`00000${(fillColor | 0).toString(16)}`).substr(-6)}`;
                        context.fillRect(shape["x"], shape["y"], shape["width"], shape["height"]);
                    }
                    if (lineStyle.visible) {
                        context.globalAlpha = lineStyle.alpha * _worldAlpha;
                        context.strokeStyle = `#${(`00000${(lineColor | 0).toString(16)}`).substr(-6)}`;
                        context.strokeRect(shape["x"], shape["y"], shape["width"], shape["height"]);
                    }
                    continue
                }
            }
            else if (data.type === SHAPES.CIRC) {
                //https://www.w3school.com.cn/tags/canvas_arc.asp
                //默认顺时针 counterclockwise= false
                context.beginPath();
                context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI, false);
            }
            else if (data.type === SHAPES.ELIP) {
                // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas

                const w = shape.width * 2;
                const h = shape.height * 2;

                const x = shape.x - (w / 2);
                const y = shape.y - (h / 2);

                context.beginPath();

                const kappa = 0.5522848;
                const ox = (w / 2) * kappa; // control point offset horizontal
                const oy = (h / 2) * kappa; // control point offset vertical
                const xe = x + w;           // x-end
                const ye = y + h;           // y-end
                const xm = x + (w / 2);       // x-middle
                const ym = y + (h / 2);       // y-middle

                context.moveTo(x, ym);
                context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
                context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
                context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
                context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
            }
            else if (data.type === SHAPES.RREC) {
                const rx = shape.x;
                const ry = shape.y;
                const width = shape.width;
                const height = shape.height;
                let radius = shape.radius;

                const maxRadius = Math.min(width, height) / 2 | 0;

                radius = radius > maxRadius ? maxRadius : radius;

                context.beginPath();
                context.moveTo(rx, ry + radius);
                context.quadraticCurveTo(rx, ry, rx + radius, ry);
                context.lineTo(rx + width - radius, ry);
                context.quadraticCurveTo(rx + width, ry, rx + width, ry + radius);
                context.lineTo(rx + width, ry + height - radius);
                context.quadraticCurveTo(rx + width, ry + height, rx + width - radius, ry + height);
                context.lineTo(rx + radius, ry + height);
                context.quadraticCurveTo(rx, ry + height, rx, ry + height - radius);
                context.lineTo(rx, ry + radius);

            }

            //有洞的话，洞的路径需反向,必须闭合，上面一般图形都是顺时针
            for (let j = 0; j < data.holes.length; j++) {
                holePath(data.holes[i], context, ccw)
            }

            if (fillStyle.visible) {
                //fill会闭合路径
                context.globalAlpha = fillStyle.alpha * _worldAlpha;
                context.fillStyle = `#${(`00000${(fillColor | 0).toString(16)}`).substr(-6)}`;
                context.fill();
            }

            if (lineStyle.visible) {
                if (shape.closed) context.closePath();
                context.globalAlpha = lineStyle.alpha * _worldAlpha;
                context.strokeStyle = `#${(`00000${(lineColor | 0).toString(16)}`).substr(-6)}`;
                context.stroke();
            }
        }
    }



    /**
     * destroy graphics object
     *
     */
    destroy() {
        this.renderer = null;
    }
}

/**
 * 画洞
 * @param graphicsData 
 * @param context 
 * @param ccw 逆时针 
 */
export function holePath(graphicsData: GraphicsData, context: CanvasRenderingContext2D, ccw: boolean = true) {
    var type = graphicsData.type;
    var shape = graphicsData.shape;
    switch (type) {
        case SHAPES.POLY:
            var points = shape.points;
            var hCcw = judgeCcw(points)
            if (ccw == hCcw) {
                context.moveTo(points[0], points[1]);
                for (let m = 1; m < points.length / 2; ++m) {
                    context.lineTo(points[m * 2], points[(m * 2) + 1]);
                }
            } else {
                //反序
                context.moveTo(points[points.length - 2], points[points.length - 1]);
                for (let m = points.length / 2 - 2; m >= 0; --m) {
                    context.lineTo(points[m * 2], points[(m * 2) + 1]);
                }
            }

            if (points[0] === points[points.length - 2] && points[1] === points[points.length - 1]) {
                // context.closePath();
            } else {
                context.closePath();
            }
            break;
        case SHAPES.RECT:
            context.moveTo(shape.x, shape.y);
            if (ccw) {
                context.lineTo(shape.x, shape.y + shape.height);
                context.lineTo(shape.x + shape.width, shape.y + shape.height);
                context.lineTo(shape.x + shape.width, shape.y);
                context.lineTo(shape.x, shape.y);
            } else {
                context.lineTo(shape.x + shape.width, shape.y);
                context.lineTo(shape.x + shape.width, shape.y + shape.height);
                context.lineTo(shape.x, shape.y + shape.height);
                context.lineTo(shape.x, shape.y);
            }
            break;
        case SHAPES.CIRC:
            context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI, ccw);
            break;
        case SHAPES.ELIP:
            const w = shape.width * 2;
            const h = shape.height * 2;

            const x = shape.x - (w / 2);
            const y = shape.y - (h / 2);

            const kappa = 0.5522848;
            const ox = (w / 2) * kappa; // control point offset horizontal
            const oy = (h / 2) * kappa; // control point offset vertical
            const xe = x + w;           // x-end
            const ye = y + h;           // y-end
            const xm = x + (w / 2);       // x-middle
            const ym = y + (h / 2);       // y-middle

            context.moveTo(x, ym);
            if (ccw) {
                context.bezierCurveTo(xm - ox, ye, x, ym + oy, xm, ye);
                context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xe, ym);
                context.bezierCurveTo(xm + ox, y, xe, ym - oy, xm, y);
                context.bezierCurveTo(x, ym - oy, xm - ox, y, x, ym);
            } else {
                context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
                context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
                context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
                context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
            }
            break;
        case SHAPES.RREC:
            const rx = shape.x;
            const ry = shape.y;
            const width = shape.width;
            const height = shape.height;
            let radius = shape.radius;
            const maxRadius = Math.min(width, height) / 2 | 0;
            radius = radius > maxRadius ? maxRadius : radius;

            context.moveTo(rx, ry + radius);
            if (ccw) {
                context.lineTo(rx, ry + height - radius);
                context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height);
                context.lineTo(rx + width - radius, ry + height);
                context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius);
                context.lineTo(rx + width, ry + radius);
                context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry);
                context.lineTo(rx + radius, ry);
                context.quadraticCurveTo(rx, ry, rx, ry + radius);
            } else {
                context.quadraticCurveTo(rx, ry, rx + radius, ry);
                context.lineTo(rx + width - radius, ry);
                context.quadraticCurveTo(rx + width, ry, rx + width, ry + radius);
                context.lineTo(rx + width, ry + height - radius);
                context.quadraticCurveTo(rx + width, ry + height, rx + width - radius, ry + height);
                context.lineTo(rx + radius, ry + height);
                context.quadraticCurveTo(rx, ry + height, rx, ry + height - radius);
                context.lineTo(rx, ry + radius);
            }
            break;
    }
}

/**
 * 判断一组点逆时针还是顺时针
 * @param points 点的一维序列 
 * @returns 返回true说明是逆时针ccw，返回false就是顺时针
 */
export function judgeCcw(points: number[]): boolean {
    var vector = [];
    if (points[0] === points[points.length - 2] && points[1] === points[points.length - 1]) {
        // context.closePath();
    }
    for (var i = 0; i < points.length - 2; i += 2) {
        vector.push(points[i + 2] - points[i], points[i + 3] - points[i + 1])
    }
    if (!(points[0] === points[points.length - 2]) || !(points[1] === points[points.length - 1])) {
        //首尾连接
        vector.push(points[0] - points[points.length - 2], points[1] - points[points.length - 1])
    }

    var sum = 0;
    for (var i = 0; i < vector.length - 2; i += 2) {
        //"x1*y2-y1*x2"
        sum = sum + vector[i] * vector[i + 3] - vector[i + 1] * vector[i + 2]
    }
    sum = sum + vector[vector.length - 2] * vector[1] - vector[vector.length - 1] * vector[0];
    //坑爹啊，2D的以左上角为原点，左x正，下y正，取个反
    return !(sum > 0)
}