"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LineArrowAnnotation = exports.EArrowHeadPosition = void 0;
var classFactory_1 = require("../../../Core/classFactory");
var Deleter_1 = require("../../../Core/Deleter");
var Guard_1 = require("../../../Core/Guard");
var BaseType_1 = require("../../../types/BaseType");
var geometryHelpers_1 = require("../../../utils/geometryHelpers");
var BrushCache_1 = require("../../Drawing/BrushCache");
var Pen2DCache_1 = require("../../Drawing/Pen2DCache");
var DpiHelper_1 = require("../TextureManager/DpiHelper");
var constants_1 = require("./constants");
var EAnnotationType_1 = require("./types/EAnnotationType");
var LineAnnotation_1 = require("./LineAnnotation");
/**
 * Enumeration of possible positions for the arrow head inside a {@link LineArrowAnnotation}
 */
var EArrowHeadPosition;
(function (EArrowHeadPosition) {
    /**
     * The arrowhead will be at `x2y2`
     * Default value
     */
    EArrowHeadPosition["End"] = "End";
    /**
     * The arrowhead will be at `x1y1`
     */
    EArrowHeadPosition["Start"] = "Start";
    /**
     * One arrowhead will be at `x1y1` and another at `x2y2`
     */
    EArrowHeadPosition["StartEnd"] = "StartEnd";
})(EArrowHeadPosition = exports.EArrowHeadPosition || (exports.EArrowHeadPosition = {}));
var TRANSPARENT = "transparent";
/**
 * @summary The {@link LineArrowAnnotation} provides an {@link LineAnnotation} which draws 1 or 2 arrow heads at
 * x1y1, x2y2 coordinates over the {@link SciChartSurface}
 * @description
 * To add a {@link LineArrowAnnotation} to a {@link SciChartSurface}, use the following code:
 * ```ts
 * const sciChartSurface: SciChartSurface;
 * const lineArrowAnnotation = new LineArrowAnnotation({
 *   x1: 1, y1: 3, x2: 2, y2: 4,
 *   stroke: "#FFFFFF",
 *   arrowHeadPosition: EArrowHeadPosition.End,
 *   arrowStyle: {
 *     headLength: 10,
 *     headWidth: 8,
 *     headDepth: 0.8,
 *     fill: "#000000",
 *     stroke: "#FFFFFF",
 *     strokeThickness: 2,
 *   },
 *   arrowHeadPosition: EArrowHeadPosition.End,
 *   isArrowHeadScalable: true
 * });
 * sciChartSurface.annotations.add(lineArrowAnnotation);
 * ```
 * @remarks Uses the fast WebGL/WebAssembly {@link WebGL2RenderingContext} for rendering
 */
var LineArrowAnnotation = /** @class */ (function (_super) {
    __extends(LineArrowAnnotation, _super);
    /**
     * Creates an instance of a LineArrowAnnotation
     * @param options Optional parameters of type {@link ILineArrowAnnotationOptions} which configure the annotation upon construction
     */
    function LineArrowAnnotation(options) {
        var _this = this;
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
        _this = _super.call(this, options) || this;
        /** @inheritDoc */
        _this.type = EAnnotationType_1.EAnnotationType.RenderContextLineArrowAnnotation;
        _this.arrowHeadPositionProperty = EArrowHeadPosition.End;
        _this.isArrowHeadScalableProperty = false;
        /**
         * Creates a proxy for the arrow style object to trigger updates when properties change
         */
        _this.getArrowStyleProxy = function (newStyle) {
            return new Proxy(newStyle, {
                set: function (target, key, value) {
                    target[key] = value;
                    _this.notifyPropertyChanged("arrowStyle");
                    return true;
                }
            });
        };
        _this.arrowHeadPositionProperty = (_a = options === null || options === void 0 ? void 0 : options.arrowHeadPosition) !== null && _a !== void 0 ? _a : EArrowHeadPosition.End;
        _this.isArrowHeadScalable = (_b = options === null || options === void 0 ? void 0 : options.isArrowHeadScalable) !== null && _b !== void 0 ? _b : false;
        _this.arrowStyleProperty = _this.getArrowStyleProxy({
            headLength: (_d = (_c = options === null || options === void 0 ? void 0 : options.arrowStyle) === null || _c === void 0 ? void 0 : _c.headLength) !== null && _d !== void 0 ? _d : 10,
            headWidth: (_f = (_e = options === null || options === void 0 ? void 0 : options.arrowStyle) === null || _e === void 0 ? void 0 : _e.headWidth) !== null && _f !== void 0 ? _f : 8,
            headDepth: (_h = (_g = options === null || options === void 0 ? void 0 : options.arrowStyle) === null || _g === void 0 ? void 0 : _g.headDepth) !== null && _h !== void 0 ? _h : 0,
            fill: (_k = (_j = options === null || options === void 0 ? void 0 : options.arrowStyle) === null || _j === void 0 ? void 0 : _j.fill) !== null && _k !== void 0 ? _k : TRANSPARENT,
            stroke: (_o = (_m = (_l = options === null || options === void 0 ? void 0 : options.arrowStyle) === null || _l === void 0 ? void 0 : _l.stroke) !== null && _m !== void 0 ? _m : _this.stroke) !== null && _o !== void 0 ? _o : "#FFFFFF",
            strokeThickness: (_r = (_q = (_p = options === null || options === void 0 ? void 0 : options.arrowStyle) === null || _p === void 0 ? void 0 : _p.strokeThickness) !== null && _q !== void 0 ? _q : _this.strokeThickness) !== null && _r !== void 0 ? _r : 1
        });
        if (options === null || options === void 0 ? void 0 : options.onArrowHeadSizeChanged) {
            if (typeof options.onArrowHeadSizeChanged === "string") {
                _this.typeMap.set("onArrowHeadSizeChanged", options.onArrowHeadSizeChanged);
                _this.onArrowHeadSizeChangedCallback = (0, classFactory_1.getFunction)(BaseType_1.EBaseType.OptionFunction, options.onArrowHeadSizeChanged);
            }
            else {
                _this.onArrowHeadSizeChangedCallback = options.onArrowHeadSizeChanged;
            }
        }
        return _this;
    }
    Object.defineProperty(LineArrowAnnotation.prototype, "arrowStyle", {
        /**
         * Gets the arrow style configuration
         */
        get: function () {
            return this.arrowStyleProperty;
        },
        /**
         * Sets the arrow style configuration
         */
        set: function (value) {
            var newStyle = __assign(__assign({}, this.arrowStyle), value);
            this.arrowStyleProperty = this.getArrowStyleProxy(newStyle);
            this.notifyPropertyChanged("arrowStyle");
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LineArrowAnnotation.prototype, "arrowHeadPosition", {
        /**
         * Gets the position of the arrow head
         */
        get: function () {
            return this.arrowHeadPositionProperty;
        },
        /**
         * Sets the position of the arrow head
         */
        set: function (value) {
            this.arrowHeadPositionProperty = value;
            this.notifyPropertyChanged(constants_1.PROPERTY.ARROW_HEAD_POSITION);
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LineArrowAnnotation.prototype, "isArrowHeadScalable", {
        /**
         * Gets whether the arrow head's sizes should be calculated relatively to the line length or not
         */
        get: function () {
            return this.isArrowHeadScalableProperty;
        },
        /**
         * Sets whether the arrow head's sizes should be calculated relatively to the line length or not
         */
        set: function (value) {
            this.isArrowHeadScalableProperty = value;
            this.notifyPropertyChanged(constants_1.PROPERTY.IS_ARROW_HEAD_RELATIVE);
        },
        enumerable: false,
        configurable: true
    });
    /** @inheritDoc */
    LineArrowAnnotation.prototype.onAttach = function (scs) {
        _super.prototype.onAttach.call(this, scs);
        this.updateArrowheadBrushes(scs);
    };
    /**
     * Updates the fill brush and stroke pen for the arrowhead based on the current arrow style
     */
    LineArrowAnnotation.prototype.updateArrowheadBrushes = function (scs) {
        if (!this.arrowheadStrokePenCache) {
            this.arrowheadStrokePenCache = new Pen2DCache_1.Pen2DCache(scs.webAssemblyContext2D);
        }
        (0, Pen2DCache_1.createPenInCache)(this.arrowheadStrokePenCache, this.arrowStyle.stroke, this.arrowStyle.strokeThickness, this.opacity);
        if (this.arrowStyle.fill && this.arrowStyle.fill !== TRANSPARENT) {
            if (!this.arrowheadFillBrushCache) {
                this.arrowheadFillBrushCache = new BrushCache_1.BrushCache(scs.webAssemblyContext2D);
            }
            (0, BrushCache_1.createBrushInCache)(this.arrowheadFillBrushCache, this.arrowStyle.fill, this.opacity);
        }
    };
    /** @inheritDoc */
    LineArrowAnnotation.prototype.delete = function () {
        _super.prototype.delete.call(this);
        this.arrowheadFillBrushCache = (0, Deleter_1.deleteSafe)(this.arrowheadFillBrushCache);
        this.arrowheadStrokePenCache = (0, Deleter_1.deleteSafe)(this.arrowheadStrokePenCache);
    };
    /** @inheritDoc */
    LineArrowAnnotation.prototype.drawWithContext = function (renderContext, xCalc, yCalc, seriesViewRect, surfaceViewRect, chartViewRect) {
        Guard_1.Guard.notNull(renderContext, "renderContext");
        Guard_1.Guard.notNull(xCalc, "xCalc");
        Guard_1.Guard.notNull(yCalc, "yCalc");
        var _a = this.convertPolarToCartesian(this.getX1Coordinate(xCalc, yCalc), this.getY1Coordinate(xCalc, yCalc)), borderX1 = _a.x, borderY1 = _a.y;
        var _b = this.convertPolarToCartesian(this.getX2Coordinate(xCalc, yCalc), this.getY2Coordinate(xCalc, yCalc)), borderX2 = _b.x, borderY2 = _b.y;
        this.setAnnotationBorders(borderX1, borderX2, borderY1, borderY2);
        var clipRect = this.getClippingRect(this.clipping, seriesViewRect, surfaceViewRect, chartViewRect);
        var _c = this.getAnnotationBorders(), x1 = _c.x1, x2 = _c.x2, y1 = _c.y1, y2 = _c.y2;
        var dx = x2 - x1;
        var dy = y2 - y1;
        if (dx === 0 && dy === 0) {
            return;
        }
        var angle = Math.atan2(dy, dx);
        var scaledHeadLength;
        var scaledHeadWidth;
        if (this.isArrowHeadScalable) {
            var currentXRange = this.xAxis.visibleRange.max - this.xAxis.visibleRange.min;
            var currentYRange = this.yAxis.visibleRange.max - this.yAxis.visibleRange.min;
            if (!this.storedXRange || !this.storedYRange) {
                this.storedXRange = currentXRange;
                this.storedYRange = currentYRange;
            }
            var zoomFactorX = currentXRange / this.storedXRange;
            var zoomFactorY = currentYRange / this.storedYRange;
            var zoomFactor = (zoomFactorX + zoomFactorY) / 2;
            scaledHeadLength = (this.arrowStyle.headLength / zoomFactor) * DpiHelper_1.DpiHelper.PIXEL_RATIO;
            scaledHeadWidth = (this.arrowStyle.headWidth / zoomFactor) * DpiHelper_1.DpiHelper.PIXEL_RATIO;
        }
        else {
            scaledHeadLength = this.arrowStyle.headLength * DpiHelper_1.DpiHelper.PIXEL_RATIO;
            scaledHeadWidth = this.arrowStyle.headWidth * DpiHelper_1.DpiHelper.PIXEL_RATIO;
        }
        var scaledHeadDepth = this.arrowStyle.headDepth * scaledHeadLength;
        if (this.onArrowHeadSizeChangedCallback) {
            var callbackArgs = {
                headLength: this.arrowStyle.headLength,
                headWidth: this.arrowStyle.headWidth,
                headDepth: this.arrowStyle.headDepth,
                angle: angle,
                x1: x1,
                y1: y1,
                x2: x2,
                y2: y2
            };
            var result = this.onArrowHeadSizeChangedCallback(callbackArgs);
            if (result) {
                if (result.headLength !== undefined) {
                    scaledHeadLength = result.headLength * DpiHelper_1.DpiHelper.PIXEL_RATIO;
                }
                if (result.headWidth !== undefined) {
                    scaledHeadWidth = result.headWidth * DpiHelper_1.DpiHelper.PIXEL_RATIO;
                }
                if (result.headDepth !== undefined) {
                    scaledHeadDepth = result.headDepth * scaledHeadLength;
                }
            }
        }
        var _d = geometryHelpers_1.geometryHelpers.calcArrowHeadParameters(x1, y1, angle + Math.PI, scaledHeadLength, scaledHeadWidth, scaledHeadDepth), baseMidX = _d.baseMidX, baseMidY = _d.baseMidY, leftX = _d.leftX, leftY = _d.leftY, rightX = _d.rightX, rightY = _d.rightY;
        var _e = geometryHelpers_1.geometryHelpers.calcArrowHeadParameters(x2, y2, angle, scaledHeadLength, scaledHeadWidth, scaledHeadDepth), baseMidX2 = _e.baseMidX, baseMidY2 = _e.baseMidY, leftX2 = _e.leftX, leftY2 = _e.leftY, rightX2 = _e.rightX, rightY2 = _e.rightY;
        var lineX1 = x1;
        var lineY1 = y1;
        var lineX2 = x2;
        var lineY2 = y2;
        if (this.arrowStyle.headWidth > 0 && this.arrowStyle.headLength > 0) {
            if ([EArrowHeadPosition.Start, EArrowHeadPosition.StartEnd].includes(this.arrowHeadPosition)) {
                lineX1 = baseMidX;
                lineY1 = baseMidY;
                this.drawArrowHead(renderContext, x1, y1, scaledHeadDepth === 0, baseMidX, baseMidY, leftX, leftY, rightX, rightY, seriesViewRect, clipRect);
            }
            if ([EArrowHeadPosition.End, EArrowHeadPosition.StartEnd].includes(this.arrowHeadPosition)) {
                lineX2 = baseMidX2;
                lineY2 = baseMidY2;
                this.drawArrowHead(renderContext, x2, y2, scaledHeadDepth === 0, baseMidX2, baseMidY2, leftX2, leftY2, rightX2, rightY2, seriesViewRect, clipRect);
            }
        }
        // only draw main line until the base(s) of the arrowhead(s)
        this.drawLine(renderContext, lineX1, lineY1, lineX2, lineY2, seriesViewRect, surfaceViewRect, chartViewRect);
    };
    LineArrowAnnotation.prototype.drawLine = function (renderContext, x1Coord, y1Coord, x2Coord, y2Coord, seriesViewRect, surfaceViewRect, chartViewRect) {
        var strokePen = this.stroke && this.strokeThickness && this.strokePenCache
            ? (0, Pen2DCache_1.getWebGlPenFromCache)(this.strokePenCache)
            : undefined;
        if (strokePen) {
            var clipRect = this.getClippingRect(this.clipping, seriesViewRect, surfaceViewRect, chartViewRect);
            renderContext.drawLine(x1Coord, y1Coord, x2Coord, y2Coord, strokePen, seriesViewRect, clipRect);
        }
        this.updateAdornerInner();
    };
    /**
     * Used internally to draw the arrow head at the specified coordinates
     */
    LineArrowAnnotation.prototype.drawArrowHead = function (renderContext, tipX, tipY, isZeroHeadDepth, baseMidX, baseMidY, leftX, leftY, rightX, rightY, seriesViewRect, clipRect) {
        var arrowStrokePen = (0, Pen2DCache_1.getWebGlPenFromCache)(this.arrowheadStrokePenCache);
        if (!arrowStrokePen) {
            return;
        }
        var isFilled = this.arrowStyle.fill && this.arrowStyle.fill !== TRANSPARENT;
        var fillBrush = isFilled ? (0, BrushCache_1.getWebGlBrushFromCache)(this.arrowheadFillBrushCache) : undefined;
        if (isZeroHeadDepth) {
            // only draw left and right arrow hands, no need for fill or base lines
            renderContext.drawLines([leftX, leftY, tipX, tipY, rightX, rightY], arrowStrokePen, seriesViewRect, clipRect);
        }
        else {
            if (isFilled) {
                renderContext.drawTriangleStrip([leftX, baseMidX, tipX, rightX], [leftY, baseMidY, tipY, rightY], seriesViewRect, clipRect, fillBrush);
            }
            renderContext.drawLines([baseMidX, baseMidY, leftX, leftY, tipX, tipY, rightX, rightY, baseMidX, baseMidY], arrowStrokePen, seriesViewRect, clipRect);
        }
    };
    /** @inheritDoc */
    LineArrowAnnotation.prototype.notifyPropertyChanged = function (propertyName) {
        _super.prototype.notifyPropertyChanged.call(this, propertyName);
        if (propertyName === "arrowStyle" || propertyName === constants_1.PROPERTY.OPACITY) {
            this.updateArrowheadBrushes(this.parentSurface);
        }
    };
    /** @inheritDoc */
    LineArrowAnnotation.prototype.toJSON = function () {
        var json = _super.prototype.toJSON.call(this);
        var options = {
            arrowStyle: {
                headLength: this.arrowStyle.headLength,
                headWidth: this.arrowStyle.headWidth,
                headDepth: this.arrowStyle.headDepth,
                fill: this.arrowStyle.fill,
                stroke: this.arrowStyle.stroke,
                strokeThickness: this.arrowStyle.strokeThickness
            },
            arrowHeadPosition: this.arrowHeadPosition,
            isArrowHeadScalable: this.isArrowHeadScalable,
            onArrowHeadSizeChanged: this.typeMap.get("onArrowHeadSizeChanged")
        };
        Object.assign(json.options, options);
        return json;
    };
    return LineArrowAnnotation;
}(LineAnnotation_1.LineAnnotation));
exports.LineArrowAnnotation = LineArrowAnnotation;
