"use strict";
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);
};
var __read = (this && this.__read) || function (o, n) {
    var m = typeof Symbol === "function" && o[Symbol.iterator];
    if (!m) return o;
    var i = m.call(o), r, ar = [], e;
    try {
        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
    }
    catch (error) { e = { error: error }; }
    finally {
        try {
            if (r && !r.done && (m = i["return"])) m.call(i);
        }
        finally { if (e) throw e.error; }
    }
    return ar;
};
var __spread = (this && this.__spread) || function () {
    for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
    return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var chroma_js_1 = __importDefault(require("chroma-js"));
var logger_1 = require("../../../../utils/logger");
var circline_geometry_1 = require("../circline_geometry");
var geometry_1 = require("../geometry");
var calcs_1 = require("../utils/calcs");
var constants_1 = require("../utils/constants");
var math_1 = require("../utils/math");
var constants_2 = require("./constants");
var INFINITY_RADIUS = 1e4;
function ringSectorStartAngle(d) {
    return math_1.trueBearingToStandardPositionAngle(d.x0 + Math.max(0, d.x1 - d.x0 - constants_1.TAU / 2) / 2);
}
function ringSectorEndAngle(d) {
    return math_1.trueBearingToStandardPositionAngle(d.x1 - Math.max(0, d.x1 - d.x0 - constants_1.TAU / 2) / 2);
}
function ringSectorInnerRadius(innerRadius, ringThickness) {
    return function (d) { return innerRadius + d.y0 * ringThickness; };
}
function ringSectorOuterRadius(innerRadius, ringThickness) {
    return function (d) { return innerRadius + (d.y0 + 1) * ringThickness; };
}
function angleToCircline(midRadius, alpha, direction) {
    var sectorRadiusLineX = Math.cos(alpha) * midRadius;
    var sectorRadiusLineY = Math.sin(alpha) * midRadius;
    var normalAngle = alpha + (direction * Math.PI) / 2;
    var x = sectorRadiusLineX + INFINITY_RADIUS * Math.cos(normalAngle);
    var y = sectorRadiusLineY + INFINITY_RADIUS * Math.sin(normalAngle);
    var sectorRadiusCircline = { x: x, y: y, r: INFINITY_RADIUS, inside: false, from: 0, to: constants_1.TAU };
    return sectorRadiusCircline;
}
function nodeId(node) {
    return node.x0 + "|" + node.y0;
}
exports.nodeId = nodeId;
function ringSectorConstruction(config, innerRadius, ringThickness) {
    return function (ringSector) {
        var circlePadding = config.circlePadding, radialPadding = config.radialPadding, fillOutside = config.fillOutside, radiusOutside = config.radiusOutside, fillRectangleWidth = config.fillRectangleWidth, fillRectangleHeight = config.fillRectangleHeight;
        var radiusGetter = fillOutside ? ringSectorOuterRadius : ringSectorInnerRadius;
        var geometricInnerRadius = radiusGetter(innerRadius, ringThickness)(ringSector);
        var innerR = geometricInnerRadius + circlePadding * 2;
        var outerR = Math.max(innerR, ringSectorOuterRadius(innerRadius, ringThickness)(ringSector) - circlePadding + (fillOutside ? radiusOutside : 0));
        var startAngle = ringSectorStartAngle(ringSector);
        var endAngle = ringSectorEndAngle(ringSector);
        var innerCircline = { x: 0, y: 0, r: innerR, inside: true, from: 0, to: constants_1.TAU };
        var outerCircline = { x: 0, y: 0, r: outerR, inside: false, from: 0, to: constants_1.TAU };
        var midRadius = (innerR + outerR) / 2;
        var sectorStartCircle = angleToCircline(midRadius, startAngle - radialPadding, -1);
        var sectorEndCircle = angleToCircline(midRadius, endAngle + radialPadding, 1);
        var outerRadiusFromRectangleWidth = fillRectangleWidth / 2;
        var outerRadiusFromRectanglHeight = fillRectangleHeight / 2;
        var fullCircle = ringSector.x0 === 0 && ringSector.x1 === constants_1.TAU && geometricInnerRadius === 0;
        var sectorCirclines = __spread((fullCircle && innerRadius === 0 ? [] : [innerCircline]), [
            outerCircline
        ], (fullCircle ? [] : [sectorStartCircle, sectorEndCircle]));
        var rectangleCirclines = outerRadiusFromRectangleWidth === Infinity && outerRadiusFromRectanglHeight === Infinity
            ? []
            : [
                { x: INFINITY_RADIUS - outerRadiusFromRectangleWidth, y: 0, r: INFINITY_RADIUS, inside: true },
                { x: -INFINITY_RADIUS + outerRadiusFromRectangleWidth, y: 0, r: INFINITY_RADIUS, inside: true },
                { x: 0, y: INFINITY_RADIUS - outerRadiusFromRectanglHeight, r: INFINITY_RADIUS, inside: true },
                { x: 0, y: -INFINITY_RADIUS + outerRadiusFromRectanglHeight, r: INFINITY_RADIUS, inside: true },
            ];
        return __spread(sectorCirclines, rectangleCirclines);
    };
}
exports.ringSectorConstruction = ringSectorConstruction;
function makeRowCircline(cx, cy, radialOffset, rotation, fontSize, offsetSign) {
    var r = INFINITY_RADIUS;
    var offset = (offsetSign * fontSize) / 2;
    var topRadius = r - offset;
    var x = cx + topRadius * Math.cos(-rotation + constants_1.TAU / 4);
    var y = cy + topRadius * Math.cos(-rotation + constants_1.TAU / 2);
    var circline = { r: r + radialOffset, x: x, y: y };
    return circline;
}
exports.getSectorRowGeometry = function (ringSector, cx, cy, totalRowCount, linePitch, rowIndex, fontSize, rotation) {
    var offset = (totalRowCount / 2) * fontSize + fontSize / 2 - linePitch * rowIndex;
    var topCircline = makeRowCircline(cx, cy, offset, rotation, fontSize, 1);
    var bottomCircline = makeRowCircline(cx, cy, offset, rotation, fontSize, -1);
    var midCircline = makeRowCircline(cx, cy, offset, rotation, 0, 0);
    var valid1 = circline_geometry_1.conjunctiveConstraint(ringSector, __assign(__assign({}, topCircline), { from: 0, to: constants_1.TAU }))[0];
    if (!valid1)
        return { rowAnchorX: cx, rowAnchorY: cy, maximumRowLength: 0 };
    var valid2 = circline_geometry_1.conjunctiveConstraint(ringSector, __assign(__assign({}, bottomCircline), { from: 0, to: constants_1.TAU }))[0];
    if (!valid2)
        return { rowAnchorX: cx, rowAnchorY: cy, maximumRowLength: 0 };
    var from = Math.max(valid1.from, valid2.from);
    var to = Math.min(valid1.to, valid2.to);
    var midAngle = (from + to) / 2;
    var cheapTangent = Math.max(0, to - from);
    var rowAnchorX = midCircline.r * Math.cos(midAngle) + midCircline.x;
    var rowAnchorY = midCircline.r * Math.sin(midAngle) + midCircline.y;
    var maximumRowLength = cheapTangent * INFINITY_RADIUS;
    return { rowAnchorX: rowAnchorX, rowAnchorY: rowAnchorY, maximumRowLength: maximumRowLength };
};
function getVerticalAlignment(container, verticalAlignment, linePitch, totalRowCount, rowIndex, paddingTop, paddingBottom, fontSize, overhang) {
    switch (verticalAlignment) {
        case constants_2.VerticalAlignments.top:
            return -(container.y0 + linePitch * rowIndex + paddingTop + fontSize * overhang);
        case constants_2.VerticalAlignments.bottom:
            return -(container.y1 - linePitch * (totalRowCount - 1 - rowIndex) - paddingBottom - fontSize * overhang);
        default:
            return -((container.y0 + container.y1) / 2 + (linePitch * (rowIndex - totalRowCount)) / 2);
    }
}
exports.getRectangleRowGeometry = function (container, cx, cy, totalRowCount, linePitch, rowIndex, fontSize, _rotation, verticalAlignment, padding) {
    var defaultPad = 2;
    var _a = typeof padding === 'number'
        ? { top: padding, right: padding, bottom: padding, left: padding }
        : __assign({ top: defaultPad, right: defaultPad, bottom: defaultPad, left: defaultPad }, padding), top = _a.top, right = _a.right, bottom = _a.bottom, left = _a.left;
    var overhang = 0.05;
    var topPaddingAdjustment = fontSize < 6 ? 0 : Math.max(1, Math.min(2, fontSize / 16));
    var adjustedTop = top + topPaddingAdjustment;
    if ((container.y1 - container.y0 - adjustedTop - bottom) / totalRowCount < linePitch) {
        return {
            rowAnchorX: NaN,
            rowAnchorY: NaN,
            maximumRowLength: 0,
        };
    }
    var rowAnchorY = getVerticalAlignment(container, verticalAlignment, linePitch, totalRowCount, rowIndex, adjustedTop, bottom, fontSize, overhang);
    return {
        rowAnchorX: cx + left / 2 - right / 2,
        rowAnchorY: rowAnchorY,
        maximumRowLength: container.x1 - container.x0 - left - right,
    };
};
function rowSetComplete(rowSet, measuredBoxes) {
    return (!measuredBoxes.length &&
        !rowSet.rows.some(function (r) { return isNaN(r.length) || r.rowWords.length === 0 || r.rowWords.every(function (rw) { return rw.text.length === 0; }); }));
}
function identityRowSet() {
    return {
        id: '',
        rows: [],
        fontSize: NaN,
        fillTextColor: '',
        rotation: NaN,
        verticalAlignment: constants_2.VerticalAlignments.middle,
        leftAlign: false,
    };
}
function getAllBoxes(rawTextGetter, valueGetter, valueFormatter, sizeInvariantFontShorthand, valueFont, node) {
    return rawTextGetter(node)
        .split(' ')
        .map(function (text) { return (__assign({ text: text }, sizeInvariantFontShorthand)); })
        .concat(valueFormatter(valueGetter(node))
        .split(' ')
        .map(function (text) { return (__assign(__assign({ text: text }, sizeInvariantFontShorthand), valueFont)); }));
}
function getWordSpacing(fontSize) {
    return fontSize / 4;
}
function getFillTextColor(textColor, textInvertible, textContrast, sliceFillColor, containerBackgroundColor) {
    var bgColorAlpha = calcs_1.isColorValid(containerBackgroundColor) ? chroma_js_1.default(containerBackgroundColor).alpha() : 1;
    if (!calcs_1.isColorValid(containerBackgroundColor) || bgColorAlpha < 1) {
        if (bgColorAlpha < 1) {
            logger_1.Logger.expected('Text contrast requires a background color with an alpha value of 1', 1, bgColorAlpha);
        }
        else if (containerBackgroundColor !== 'transparent') {
            logger_1.Logger.warn("Invalid background color \"" + containerBackgroundColor + "\"");
        }
        return calcs_1.getTextColorIfTextInvertible(calcs_1.colorIsDark(sliceFillColor), calcs_1.colorIsDark(textColor), textColor, false, 'white');
    }
    var adjustedTextColor = textColor;
    var containerBackground = calcs_1.combineColors(sliceFillColor, containerBackgroundColor);
    var textShouldBeInvertedAndTextContrastIsFalse = textInvertible && !textContrast;
    var textShouldBeInvertedAndTextContrastIsSetToTrue = textInvertible && typeof textContrast !== 'number';
    var textContrastIsSetToANumberValue = typeof textContrast === 'number';
    var textShouldNotBeInvertedButTextContrastIsDefined = textContrast && !textInvertible;
    if (textShouldBeInvertedAndTextContrastIsFalse || textShouldBeInvertedAndTextContrastIsSetToTrue) {
        var backgroundIsDark = calcs_1.colorIsDark(calcs_1.combineColors(sliceFillColor, containerBackgroundColor));
        var specifiedTextColorIsDark = calcs_1.colorIsDark(textColor);
        adjustedTextColor = calcs_1.getTextColorIfTextInvertible(backgroundIsDark, specifiedTextColorIsDark, textColor, textContrast, containerBackground);
    }
    else if (textContrastIsSetToANumberValue || textShouldNotBeInvertedButTextContrastIsDefined) {
        return calcs_1.makeHighContrastColor(adjustedTextColor, containerBackground);
    }
    return adjustedTextColor;
}
exports.getFillTextColor = getFillTextColor;
function fill(shapeConstructor, getShapeRowGeometry, getRotation, containerBackgroundColor) {
    return function fillClosure(config, layers, measure, rawTextGetter, valueGetter, formatter, leftAlign, middleAlign) {
        var maxRowCount = config.maxRowCount, fillLabel = config.fillLabel, configFontFamily = config.fontFamily;
        return function (allFontSizes, textFillOrigin, node) {
            var _a;
            var container = shapeConstructor(node);
            var rotation = getRotation(node);
            var layer = layers[node.depth - 1] || {};
            var verticalAlignment = middleAlign
                ? constants_2.VerticalAlignments.middle
                : node.depth < layers.length
                    ? constants_2.VerticalAlignments.bottom
                    : constants_2.VerticalAlignments.top;
            var fontSizes = allFontSizes[Math.min(node.depth, allFontSizes.length) - 1];
            var _b = __assign(__assign(__assign(__assign({ fontFamily: configFontFamily, fontWeight: 'normal', padding: 2 }, fillLabel), { valueFormatter: formatter }), layer.fillLabel), layer.shape), textColor = _b.textColor, textInvertible = _b.textInvertible, fontStyle = _b.fontStyle, fontVariant = _b.fontVariant, fontFamily = _b.fontFamily, fontWeight = _b.fontWeight, valueFormatter = _b.valueFormatter, padding = _b.padding, textContrast = _b.textContrast, textOpacity = _b.textOpacity;
            var fillTextColor = getFillTextColor(textColor, textInvertible, textContrast, node.fillColor, containerBackgroundColor);
            var valueFont = __assign(__assign(__assign(__assign({ fontFamily: configFontFamily, fontWeight: 'normal' }, fillLabel), fillLabel.valueFont), layer.fillLabel), (_a = layer.fillLabel) === null || _a === void 0 ? void 0 : _a.valueFont);
            var sizeInvariantFont = {
                fontStyle: fontStyle,
                fontVariant: fontVariant,
                fontWeight: fontWeight,
                fontFamily: fontFamily,
                textColor: textColor,
                textOpacity: textOpacity,
            };
            var allBoxes = getAllBoxes(rawTextGetter, valueGetter, valueFormatter, sizeInvariantFont, valueFont, node);
            var _c = __read(textFillOrigin, 2), cx = _c[0], cy = _c[1];
            return __assign(__assign({}, getRowSet(allBoxes, maxRowCount, fontSizes, measure, rotation, verticalAlignment, leftAlign, container, getShapeRowGeometry, cx, cy, padding, node)), { fillTextColor: fillTextColor });
        };
    };
}
function tryFontSize(measure, rotation, verticalAlignment, leftAlign, container, getShapeRowGeometry, cx, cy, padding, node, boxes, maxRowCount) {
    return function (initialRowSet, fontSize) {
        var rowSet = initialRowSet;
        var wordSpacing = getWordSpacing(fontSize);
        var measurements = measure(fontSize, boxes);
        var allMeasuredBoxes = measurements.map(function (_a, i) {
            var width = _a.width, emHeightDescent = _a.emHeightDescent, emHeightAscent = _a.emHeightAscent;
            return (__assign(__assign({ width: width, verticalOffset: -(emHeightDescent + emHeightAscent) / 2, wordBeginning: NaN }, boxes[i]), { fontSize: fontSize }));
        });
        var linePitch = fontSize;
        var targetRowCount = 0;
        var measuredBoxes = allMeasuredBoxes.slice();
        var innerCompleted = false;
        while (++targetRowCount <= maxRowCount && !innerCompleted) {
            measuredBoxes = allMeasuredBoxes.slice();
            rowSet = {
                id: nodeId(node),
                fontSize: fontSize,
                fillTextColor: '',
                rotation: rotation,
                verticalAlignment: verticalAlignment,
                leftAlign: leftAlign,
                rows: __spread(new Array(targetRowCount)).map(function () { return ({
                    rowWords: [],
                    rowAnchorX: NaN,
                    rowAnchorY: NaN,
                    maximumLength: NaN,
                    length: NaN,
                }); }),
                container: container,
            };
            var currentRowIndex = 0;
            while (currentRowIndex < targetRowCount) {
                var currentRow = rowSet.rows[currentRowIndex];
                var currentRowWords = currentRow.rowWords;
                var _a = getShapeRowGeometry(container, cx, cy, targetRowCount, linePitch, currentRowIndex, fontSize, rotation, verticalAlignment, padding), maximumRowLength = _a.maximumRowLength, rowAnchorX = _a.rowAnchorX, rowAnchorY = _a.rowAnchorY;
                currentRow.rowAnchorX = rowAnchorX;
                currentRow.rowAnchorY = rowAnchorY;
                currentRow.maximumLength = maximumRowLength;
                var currentRowLength = 0;
                var rowHasRoom = true;
                while (measuredBoxes.length && rowHasRoom) {
                    var currentBox = measuredBoxes[0];
                    var wordBeginning = currentRowLength;
                    currentRowLength += currentBox.width + wordSpacing;
                    if (currentRowLength <= currentRow.maximumLength) {
                        currentRowWords.push(__assign(__assign({}, currentBox), { wordBeginning: wordBeginning }));
                        currentRow.length = currentRowLength;
                        measuredBoxes.shift();
                    }
                    else {
                        rowHasRoom = false;
                    }
                }
                currentRowIndex++;
            }
            innerCompleted = rowSetComplete(rowSet, measuredBoxes);
        }
        var completed = !measuredBoxes.length;
        return { rowSet: rowSet, completed: completed };
    };
}
function getRowSet(boxes, maxRowCount, fontSizes, measure, rotation, verticalAlignment, leftAlign, container, getShapeRowGeometry, cx, cy, padding, node) {
    var tryFunction = tryFontSize(measure, rotation, verticalAlignment, leftAlign, container, getShapeRowGeometry, cx, cy, padding, node, boxes, maxRowCount);
    var largestIndex = fontSizes.length - 1;
    var response = function (i) { return i + (tryFunction(identityRowSet(), fontSizes[i]).completed ? 0 : largestIndex + 1); };
    var fontSizeIndex = calcs_1.monotonicHillClimb(response, largestIndex, largestIndex, calcs_1.integerSnap);
    if (!(fontSizeIndex >= 0)) {
        return identityRowSet();
    }
    var _a = tryFunction(identityRowSet(), fontSizes[fontSizeIndex]), rowSet = _a.rowSet, completed = _a.completed;
    return __assign(__assign({}, rowSet), { rows: rowSet.rows.filter(function (r) { return completed && !isNaN(r.length); }) });
}
function inSectorRotation(horizontalTextEnforcer, horizontalTextAngleThreshold) {
    return function (node) {
        var rotation = math_1.trueBearingToStandardPositionAngle((node.x0 + node.x1) / 2);
        if (Math.abs(node.x1 - node.x0) > horizontalTextAngleThreshold && horizontalTextEnforcer > 0)
            rotation *= 1 - horizontalTextEnforcer;
        if (constants_1.TAU / 4 < rotation && rotation < (3 * constants_1.TAU) / 4)
            rotation = geometry_1.wrapToTau(rotation - constants_1.TAU / 2);
        return rotation;
    };
}
exports.inSectorRotation = inSectorRotation;
function fillTextLayout(shapeConstructor, getShapeRowGeometry, getRotation, containerBackgroundColor) {
    var specificFiller = fill(shapeConstructor, getShapeRowGeometry, getRotation, containerBackgroundColor);
    return function fillTextLayoutClosure(measure, rawTextGetter, valueGetter, valueFormatter, childNodes, config, layers, textFillOrigins, leftAlign, middleAlign) {
        var allFontSizes = [];
        for (var l = 0; l <= layers.length; l++) {
            var _a = __assign(__assign({}, config), (l < layers.length && layers[l].fillLabel)), minFontSize = _a.minFontSize, maxFontSize = _a.maxFontSize, idealFontSizeJump = _a.idealFontSizeJump;
            var fontSizeMagnification = maxFontSize / minFontSize;
            var fontSizeJumpCount = Math.round(math_1.logarithm(idealFontSizeJump, fontSizeMagnification));
            var realFontSizeJump = Math.pow(fontSizeMagnification, 1 / fontSizeJumpCount);
            var fontSizes = [];
            for (var i = 0; i <= fontSizeJumpCount; i++) {
                var fontSize = Math.round(minFontSize * Math.pow(realFontSizeJump, i));
                if (!fontSizes.includes(fontSize)) {
                    fontSizes.push(fontSize);
                }
            }
            allFontSizes.push(fontSizes);
        }
        var filler = specificFiller(config, layers, measure, rawTextGetter, valueGetter, valueFormatter, leftAlign, middleAlign);
        return childNodes
            .map(function (node, i) { return ({ node: node, origin: textFillOrigins[i] }); })
            .sort(function (a, b) { return b.node.value - a.node.value; })
            .reduce(function (_a, _b) {
            var rowSets = _a.rowSets, fontSizes = _a.fontSizes;
            var node = _b.node, origin = _b.origin;
            var nextRowSet = filler(fontSizes, origin, node);
            var layerIndex = node.depth - 1;
            return {
                rowSets: __spread(rowSets, [nextRowSet]),
                fontSizes: fontSizes.map(function (layerFontSizes, index) {
                    var _a, _b;
                    return index === layerIndex && !((_b = (_a = layers[layerIndex]) === null || _a === void 0 ? void 0 : _a.fillLabel) === null || _b === void 0 ? void 0 : _b.maximizeFontSize)
                        ? layerFontSizes.filter(function (size) { return size <= nextRowSet.fontSize; })
                        : layerFontSizes;
                }),
            };
        }, { rowSets: [], fontSizes: allFontSizes }).rowSets;
    };
}
exports.fillTextLayout = fillTextLayout;
//# sourceMappingURL=fill_text_layout.js.map