"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.labelCache = void 0;
var performanceWarnings_1 = require("../../../../constants/performanceWarnings");
var Deleter_1 = require("../../../../Core/Deleter");
var Thickness_1 = require("../../../../Core/Thickness");
var logger_1 = require("../../../../utils/logger");
var labelCacheByTextAndStyle = new Map();
var styleCache = {};
var lastStyleId = 0;
var getStyleId = function (style) {
    for (var key in styleCache) {
        if (styleCache.hasOwnProperty(key)) {
            var otherStyle = styleCache[key];
            if (otherStyle) {
                if (checkTextStyleEqual(style, otherStyle.style)) {
                    otherStyle.uses++;
                    return key;
                }
            }
        }
    }
    lastStyleId++;
    var newStyleId = lastStyleId.toString();
    styleCache[newStyleId] = { style: style, uses: 1 };
    return newStyleId;
};
var freeStyle = function (styleId) {
    var style = styleCache[styleId];
    if (style) {
        style.uses--;
        var styleToClear = styleCache[styleId];
        if (styleToClear && styleToClear.uses === 0) {
            clearCacheByStyle(styleId);
        }
    }
};
var makeCacheKey = function (text, styleId) {
    return "".concat(text, "|:|").concat(styleId);
};
var parseCacheKey = function (key) {
    var sep = key.indexOf("|:|");
    var text = key.substring(0, sep);
    var styleId = key.substring(sep + 3);
    return { text: text, styleId: styleId };
};
var getLabel = function (text, styleId) {
    var key = makeCacheKey(text, styleId);
    var label = labelCacheByTextAndStyle.get(key);
    if (label) {
        label.lastUsed = Date.now();
    }
    return label;
};
var setLabel = function (text, styleId, label) {
    var key = makeCacheKey(text, styleId);
    // THIS is probably not needed as this should not happen after the change in LabelProviderBase2D.
    // Moreover it it happens having this code results in no showing labels referencing the removed texture
    // // Dispose old label texture if it exists to prevent memory leak
    // const existingLabel = labelCacheByTextAndStyle.get(key);
    // if (existingLabel && existingLabel.bitmapTexture) {
    //     try {
    //         deleteSafe(existingLabel.bitmapTexture);
    //     } catch (error: any) {
    //         // WasmContext is probably disposed
    //         Logger.debug(error);
    //     }
    // }
    labelCacheByTextAndStyle.set(key, label);
};
var checkStyle = function (currentStyleId, newStyle) {
    var currentStyle = styleCache[currentStyleId.toString()];
    if (currentStyle) {
        return checkTextStyleEqual(currentStyle.style, newStyle);
    }
    return false;
};
var clearCacheByStyle = function (styleId) {
    for (var _i = 0, _a = Array.from(labelCacheByTextAndStyle.keys()); _i < _a.length; _i++) {
        var keyStr = _a[_i];
        var _b = parseCacheKey(keyStr), text = _b.text, keyStyleId = _b.styleId;
        if (keyStyleId === styleId) {
            var label = labelCacheByTextAndStyle.get(keyStr);
            labelCacheByTextAndStyle.delete(keyStr);
            try {
                (0, Deleter_1.deleteSafe)(label.bitmapTexture);
            }
            catch (error) {
                // WasmContext is probably disposed
                logger_1.Logger.debug(error);
            }
        }
    }
};
var maxSize = 200;
var minAge = 200;
var lastPrune = 0;
var getMaxSize = function () { return maxSize; };
var setMaxSize = function (size) {
    maxSize = size;
};
var getMinAge = function () { return minAge; };
var setMinAge = function (ageInMs) {
    minAge = ageInMs;
};
var pruneCache = function () {
    if (Date.now() > lastPrune + minAge && labelCacheByTextAndStyle.size > maxSize) {
        try {
            // remove more than we need so we do this less.
            var toRemove = Math.min(Math.floor(labelCacheByTextAndStyle.size / 2), (labelCacheByTextAndStyle.size - maxSize) * 2);
            // Sort the items by LastUsed ascending
            var labels = Array.from(labelCacheByTextAndStyle.entries());
            labels.sort(function (a, b) { return a[1].lastUsed - b[1].lastUsed; });
            // remove earliest
            for (var index = 0; index < toRemove; index++) {
                var _a = labels[index], key = _a[0], label = _a[1];
                label.bitmapTexture = (0, Deleter_1.deleteSafe)(label.bitmapTexture);
                labelCacheByTextAndStyle.delete(key);
            }
            if (toRemove > maxSize) {
                performanceWarnings_1.performanceWarnings.labelCacheTooSmall.warn();
            }
            lastPrune = Date.now();
        }
        catch (err) {
            console.warn(err);
        }
    }
};
var checkTextStyleEqual = function (style1, style2) {
    return (style1.alignment === style2.alignment &&
        style1.multilineAlignment === style2.multilineAlignment &&
        style1.color === style2.color &&
        style1.fontFamily === style2.fontFamily &&
        style1.fontSize === style2.fontSize &&
        style1.fontStyle === style2.fontStyle &&
        style1.fontWeight === style2.fontWeight &&
        ((style1.padding === undefined && style2.padding === undefined) ||
            Thickness_1.Thickness.areEqual(style1.padding, style2.padding)) &&
        style1.extras === style2.extras &&
        style1.rotation === style2.rotation &&
        style1.providerId === style2.providerId);
};
var resetCache = function () {
    // Dispose all textures first to prevent memory leak
    labelCacheByTextAndStyle.forEach(function (label) {
        if (label && label.bitmapTexture) {
            try {
                (0, Deleter_1.deleteSafe)(label.bitmapTexture);
            }
            catch (error) {
                // WasmContext is probably disposed
                logger_1.Logger.debug(error);
            }
        }
    });
    labelCacheByTextAndStyle.clear();
    // tslint:disable-next-line: forin
    for (var key in styleCache) {
        delete styleCache[key];
    }
};
var getSize = function () { return labelCacheByTextAndStyle.size; };
/**
 * A global cache for labels, used by all labelProviders, to reduce the amount of time spent creating label textures.
 */
exports.labelCache = {
    /** Get an identifier for the given text style.  Returns an existing identifier if a matching style exists in the cache */
    getStyleId: getStyleId,
    /**
     * Notify the cache that a style is no longer used.
     * Linked labels are only deleted when there are no remaining uses, and then only after minAge has passed.
     */
    freeStyle: freeStyle,
    /** Get a label from the cache.  Returns undefined if none found. */
    getLabel: getLabel,
    /** Add a label to the cache */
    setLabel: setLabel,
    /** Check if a text style matches the one for the given id */
    checkStyle: checkStyle,
    /** Get the maximum number of labels allowed to be stored in the cache.  Used when calling pruneCache */
    getMaxSize: getMaxSize,
    /** Set the maximum number of labels allowed to be stored in the cache.  Used when calling pruneCache */
    setMaxSize: setMaxSize,
    getSize: getSize,
    /** Get the minimum time (in ms) between cache pruning.  Default 200ms
     * This prevents recently used labels from being pruned, or removed when style is freed
     */
    getMinAge: getMinAge,
    /** Set the minimum time (in ms) between cache pruning.  Default 200ms
     * This prevents recently used labels from being pruned, or removed when style is freed
     */
    setMinAge: setMinAge,
    /** Remove old labels from the cache, if there are more than MaxSize.   */
    pruneCache: pruneCache,
    /** Completely clears and resets the cache.  Testing use only */
    resetCache: resetCache
};
