"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeView = exports.getMergedRegion = exports.getRefreshRegion = exports.refreshElement = exports.drawPath = exports.clearChanged = exports.checkChildrenRefresh = exports.checkRefresh = exports.drawChildren = exports.applyAttrsToContext = void 0;
var util_1 = require("@antv/util");
var parse_1 = require("./parse");
var arc_params_1 = require("./arc-params");
var util_2 = require("./util");
var ArrowUtil = require("../util/arrow");
var SHAPE_ATTRS_MAP = {
    fill: 'fillStyle',
    stroke: 'strokeStyle',
    opacity: 'globalAlpha',
};
function applyAttrsToContext(context, element) {
    var attrs = element.attr();
    for (var k in attrs) {
        var v = attrs[k];
        // 转换一下不与 canvas 兼容的属性名
        var name_1 = SHAPE_ATTRS_MAP[k] ? SHAPE_ATTRS_MAP[k] : k;
        if (name_1 === 'matrix' && v) {
            // 设置矩阵
            context.transform(v[0], v[1], v[3], v[4], v[6], v[7]);
        }
        else if (name_1 === 'lineDash' && context.setLineDash) {
            // 设置虚线，只支持数组形式，非数组形式不做任何操作
            util_1.isArray(v) && context.setLineDash(v);
        }
        else {
            if (name_1 === 'strokeStyle' || name_1 === 'fillStyle') {
                // 如果存在渐变、pattern 这个开销有些大
                // 可以考虑缓存机制，通过 hasUpdate 来避免一些运算
                v = parse_1.parseStyle(context, element, v);
            }
            else if (name_1 === 'globalAlpha') {
                // opacity 效果可以叠加，子元素的 opacity 需要与父元素 opacity 相乘
                v = v * context.globalAlpha;
            }
            context[name_1] = v;
        }
    }
}
exports.applyAttrsToContext = applyAttrsToContext;
function drawChildren(context, children, region) {
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        if (child.cfg.visible) {
            child.draw(context, region);
        }
        else {
            child.skipDraw();
        }
    }
}
exports.drawChildren = drawChildren;
// 这个地方的逻辑比较复杂，简单画了一张图：https://www.yuque.com/antv/ou292n/pcgt5g#OW1QE
function checkRefresh(canvas, children, region) {
    var refreshElements = canvas.get('refreshElements');
    // 先遍历需要刷新的元素，将这些元素的父元素也设置 refresh
    util_1.each(refreshElements, function (el) {
        if (el !== canvas) {
            var parent_1 = el.cfg.parent;
            while (parent_1 && parent_1 !== canvas && !parent_1.cfg.refresh) {
                parent_1.cfg.refresh = true;
                parent_1 = parent_1.cfg.parent;
            }
        }
    });
    if (refreshElements[0] === canvas) {
        setChildrenRefresh(children, region);
    }
    else {
        // 检查所有子元素是否可以刷新
        checkChildrenRefresh(children, region);
    }
}
exports.checkRefresh = checkRefresh;
// 检查所有的子元素是否应该更新
function checkChildrenRefresh(children, region) {
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        if (child.cfg.visible) {
            // 先判断 hasChanged，因为它的优先级判断应该高于 refresh
            if (child.cfg.hasChanged) {
                // 如果节点发生了 change，则需要级联设置子元素的 refresh
                child.cfg.refresh = true;
                if (child.isGroup()) {
                    setChildrenRefresh(child.cfg.children, region);
                }
            }
            else if (child.cfg.refresh) {
                // 如果当前图形/分组 refresh = true，说明其子节点存在 changed
                if (child.isGroup()) {
                    checkChildrenRefresh(child.cfg.children, region);
                }
            }
            else {
                // 这个分支说明此次局部刷新，所有的节点和父元素没有发生变化，仅需要检查包围盒（缓存）是否相交即可
                var refresh = checkElementRefresh(child, region);
                child.cfg.refresh = refresh;
                if (refresh && child.isGroup()) {
                    // 如果需要刷新，说明子元素也需要刷新，继续进行判定
                    checkChildrenRefresh(child.cfg.children, region);
                }
            }
        }
    }
}
exports.checkChildrenRefresh = checkChildrenRefresh;
// 由于对改变的图形放入 refreshElements 时做了优化，判定父元素 changed 时不加入
// 那么有可能会出现 elements 都为空，所以最终 group
function clearChanged(elements) {
    for (var i = 0; i < elements.length; i++) {
        var el = elements[i];
        el.cfg.hasChanged = false;
        // 级联清理
        if (el.isGroup() && !el.destroyed) {
            clearChanged(el.cfg.children);
        }
    }
}
exports.clearChanged = clearChanged;
// 当某个父元素发生改变时，调用这个方法级联设置 refresh
function setChildrenRefresh(children, region) {
    for (var i = 0; i < children.length; i++) {
        var child = children[i];
        if (!child.cfg.visible) {
            continue;
        }
        // let refresh = true;
        // 获取缓存的 bbox，如果这个 bbox 还存在则说明父元素不是矩阵发生了改变
        // const bbox = child.cfg.canvasBBox;
        // if (bbox) {
        //   // 如果这时候
        //   refresh = intersectRect(bbox, region);
        // }
        child.cfg.refresh = true;
        // 如果需要刷新当前节点，所有的子元素设置 refresh
        if (child.isGroup()) {
            setChildrenRefresh(child.get('children'), region);
        }
    }
}
function checkElementRefresh(shape, region) {
    var bbox = shape.cfg.cacheCanvasBBox;
    var isAllow = shape.cfg.isInView && bbox && util_2.intersectRect(bbox, region);
    return isAllow;
}
// 绘制 path
function drawPath(shape, context, attrs, arcParamsCache) {
    var path = attrs.path, startArrow = attrs.startArrow, endArrow = attrs.endArrow;
    if (!path) {
        return;
    }
    var currentPoint = [0, 0]; // 当前图形
    var startMovePoint = [0, 0]; // 开始 M 的点，可能会有多个
    var distance = {
        dx: 0,
        dy: 0,
    };
    context.beginPath();
    for (var i = 0; i < path.length; i++) {
        var params = path[i];
        var command = params[0];
        if (i === 0 && startArrow && startArrow.d) {
            var tangent = shape.getStartTangent();
            distance = ArrowUtil.getShortenOffset(tangent[0][0], tangent[0][1], tangent[1][0], tangent[1][1], startArrow.d);
        }
        else if (i === path.length - 2 && path[i + 1][0] === 'Z' && endArrow && endArrow.d) {
            // 为了防止结尾为 Z 的 segment 缩短不起效，需要取最后两个 segment 特殊处理
            var lastPath = path[i + 1];
            if (lastPath[0] === 'Z') {
                var tangent = shape.getEndTangent();
                distance = ArrowUtil.getShortenOffset(tangent[0][0], tangent[0][1], tangent[1][0], tangent[1][1], endArrow.d);
            }
        }
        else if (i === path.length - 1 && endArrow && endArrow.d) {
            if (path[0] !== 'Z') {
                var tangent = shape.getEndTangent();
                distance = ArrowUtil.getShortenOffset(tangent[0][0], tangent[0][1], tangent[1][0], tangent[1][1], endArrow.d);
            }
        }
        var dx = distance.dx, dy = distance.dy;
        // V,H,S,T 都在前面被转换成标准形式
        switch (command) {
            case 'M':
                context.moveTo(params[1] - dx, params[2] - dy);
                startMovePoint = [params[1], params[2]];
                break;
            case 'L':
                context.lineTo(params[1] - dx, params[2] - dy);
                break;
            case 'Q':
                context.quadraticCurveTo(params[1], params[2], params[3] - dx, params[4] - dy);
                break;
            case 'C':
                context.bezierCurveTo(params[1], params[2], params[3], params[4], params[5] - dx, params[6] - dy);
                break;
            case 'A': {
                var arcParams = void 0;
                // 为了加速绘制，可以提供参数的缓存，各个图形自己缓存
                if (arcParamsCache) {
                    arcParams = arcParamsCache[i];
                    if (!arcParams) {
                        arcParams = arc_params_1.default(currentPoint, params);
                        arcParamsCache[i] = arcParams;
                    }
                }
                else {
                    arcParams = arc_params_1.default(currentPoint, params);
                }
                var cx = arcParams.cx, cy = arcParams.cy, rx = arcParams.rx, ry = arcParams.ry, startAngle = arcParams.startAngle, endAngle = arcParams.endAngle, xRotation = arcParams.xRotation, sweepFlag = arcParams.sweepFlag;
                // 直接使用椭圆的 api
                if (context.ellipse) {
                    context.ellipse(cx, cy, rx, ry, xRotation, startAngle, endAngle, 1 - sweepFlag);
                }
                else {
                    var r = rx > ry ? rx : ry;
                    var scaleX = rx > ry ? 1 : rx / ry;
                    var scaleY = rx > ry ? ry / rx : 1;
                    context.translate(cx, cy);
                    context.rotate(xRotation);
                    context.scale(scaleX, scaleY);
                    context.arc(0, 0, r, startAngle, endAngle, 1 - sweepFlag);
                    context.scale(1 / scaleX, 1 / scaleY);
                    context.rotate(-xRotation);
                    context.translate(-cx, -cy);
                }
                break;
            }
            case 'Z':
                context.closePath();
                break;
            default:
                break;
        }
        // 有了 Z 后，当前节点从开始 M 的点开始
        if (command === 'Z') {
            currentPoint = startMovePoint;
        }
        else {
            var len = params.length;
            currentPoint = [params[len - 2], params[len - 1]];
        }
    }
}
exports.drawPath = drawPath;
// 刷新图形元素(Shape 或者 Group)
function refreshElement(element, changeType) {
    var canvas = element.get('canvas');
    // 只有存在于 canvas 上时生效
    if (canvas) {
        if (changeType === 'remove') {
            // 一旦 remove，则无法在 element 上拿到包围盒
            // destroy 后所有属性都拿不到，所以需要暂存一下
            // 这是一段 hack 的代码
            element._cacheCanvasBBox = element.get('cacheCanvasBBox');
        }
        // 防止反复刷新
        if (!element.get('hasChanged')) {
            // 但是始终要标记为 hasChanged，便于后面进行局部渲染
            element.set('hasChanged', true);
            // 本来只有局部渲染模式下，才需要记录更新的元素队列
            // if (canvas.get('localRefresh')) {
            //   canvas.refreshElement(element, changeType, canvas);
            // }
            // 但对于 https://github.com/antvis/g/issues/422 的场景，全局渲染的模式下也需要记录更新的元素队列
            // 如果当前元素的父元素发生了改变，可以不放入队列，这句话大概能够提升 15% 的初次渲染性能
            if (!(element.cfg.parent && element.cfg.parent.get('hasChanged'))) {
                canvas.refreshElement(element, changeType, canvas);
                if (canvas.get('autoDraw')) {
                    canvas.draw();
                }
            }
        }
    }
}
exports.refreshElement = refreshElement;
function getRefreshRegion(element) {
    var region;
    if (!element.destroyed) {
        var cacheBox = element.get('cacheCanvasBBox');
        var validCache = cacheBox && !!(cacheBox.width && cacheBox.height);
        var bbox = element.getCanvasBBox();
        var validBBox = bbox && !!(bbox.width && bbox.height);
        // 是否是有效 bbox 判定，一些 NaN 或者 宽高为 0 的情况过滤掉
        if (validCache && validBBox) {
            region = util_2.mergeRegion(cacheBox, bbox);
        }
        else if (validCache) {
            region = cacheBox;
        }
        else if (validBBox) {
            region = bbox;
        }
    }
    else {
        // 因为元素已经销毁所以无法获取到缓存的包围盒
        region = element['_cacheCanvasBBox'];
    }
    return region;
}
exports.getRefreshRegion = getRefreshRegion;
function getMergedRegion(elements) {
    if (!elements.length) {
        return null;
    }
    var minXArr = [];
    var minYArr = [];
    var maxXArr = [];
    var maxYArr = [];
    util_1.each(elements, function (el) {
        var region = getRefreshRegion(el);
        if (region) {
            minXArr.push(region.minX);
            minYArr.push(region.minY);
            maxXArr.push(region.maxX);
            maxYArr.push(region.maxY);
        }
    });
    return {
        minX: util_1.min(minXArr),
        minY: util_1.min(minYArr),
        maxX: util_1.max(maxXArr),
        maxY: util_1.max(maxYArr),
    };
}
exports.getMergedRegion = getMergedRegion;
function mergeView(region, viewRegion) {
    if (!region || !viewRegion) {
        return null;
    }
    // 不相交，则直接返回 null
    if (!util_2.intersectRect(region, viewRegion)) {
        return null;
    }
    return {
        minX: Math.max(region.minX, viewRegion.minX),
        minY: Math.max(region.minY, viewRegion.minY),
        maxX: Math.min(region.maxX, viewRegion.maxX),
        maxY: Math.min(region.maxY, viewRegion.maxY),
    };
}
exports.mergeView = mergeView;
//# sourceMappingURL=draw.js.map