"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.SizedNestedMap = exports.forEachInNestedMap = exports.nestedMapFromFlatList = exports.nestedMapToFlatList = exports.deleteFromNestedMap = exports.getOrDefaultInNestedMap = exports.getOrAddInNestedMap = exports.tryGetFromNestedMap = exports.getOrAddInMap = exports.setInNestedMap = exports.populateNestedMap = exports.tryAddToNestedMap = void 0;
/**
 * If (key1, key2) already has a value in the map, it is returned, otherwise value is added under (key1, key2) and undefined is returned.
 *
 * @internal
 */
function tryAddToNestedMap(map, key1, key2, value) {
    let innerMap = map.get(key1);
    if (innerMap === undefined) {
        innerMap = new Map();
        map.set(key1, innerMap);
    }
    if (innerMap.has(key2)) {
        return innerMap.get(key2);
    }
    innerMap.set(key2, value);
    return undefined;
}
exports.tryAddToNestedMap = tryAddToNestedMap;
/**
 * Copies over all entries from the source map into the destination map.
 *
 * @internal
 */
function populateNestedMap(source, destination, override) {
    for (const [key1, innerMap] of source) {
        const newInner = new Map(destination.get(key1));
        for (const [key2, value] of innerMap) {
            if (override || !newInner.has(key2)) {
                newInner.set(key2, value);
            }
        }
        destination.set(key1, newInner);
    }
}
exports.populateNestedMap = populateNestedMap;
/**
 * Sets the value at (key1, key2) in map to value.
 * If there already is a value for (key1, key2), it is replaced with the provided one.
 *
 * @internal
 */
function setInNestedMap(map, key1, key2, value) {
    const innerMap = getOrAddInMap(map, key1, new Map());
    innerMap.set(key2, value);
}
exports.setInNestedMap = setInNestedMap;
/**
 * Sets the value at `key` in map to value if not already present.
 * Returns the value at `key` after setting it.
 * This is equivalent to a get or default that adds the default to the map.
 */
function getOrAddInMap(map, key, value) {
    const currentValue = map.get(key);
    if (currentValue !== undefined) {
        return currentValue;
    }
    map.set(key, value);
    return value;
}
exports.getOrAddInMap = getOrAddInMap;
/**
 * Returns the value at (key1, key2) in map, or undefined if not present.
 *
 * @internal
 */
function tryGetFromNestedMap(map, key1, key2) {
    const innerMap = map.get(key1);
    if (innerMap === undefined) {
        return undefined;
    }
    return innerMap.get(key2);
}
exports.tryGetFromNestedMap = tryGetFromNestedMap;
/**
 * If (key1, key2) is not in the map, add value to the map.
 * Returns whatever is at (key1, key2) in map (which will be value if it was empty before).
 *
 * @internal
 */
function getOrAddInNestedMap(map, key1, key2, value) {
    const existing = tryAddToNestedMap(map, key1, key2, value);
    if (existing !== undefined) {
        return existing;
    }
    return value;
}
exports.getOrAddInNestedMap = getOrAddInNestedMap;
/**
 * Does not change map.
 * If (key1, key2) is not in map, returns value.
 * If (key1, key2) is in map, return its entry.
 *
 * @internal
 */
function getOrDefaultInNestedMap(map, key1, key2, value) {
    const existing = tryGetFromNestedMap(map, key1, key2);
    if (existing !== undefined) {
        return existing;
    }
    return value;
}
exports.getOrDefaultInNestedMap = getOrDefaultInNestedMap;
/**
 * Removes the value at (key1, key2) from the map.
 *
 * @returns true iff found.
 *
 * @internal
 */
function deleteFromNestedMap(map, key1, key2) {
    const innerMap = map.get(key1);
    if (innerMap === undefined) {
        return false;
    }
    const deleted = innerMap.delete(key2);
    if (innerMap.size === 0) {
        map.delete(key1);
    }
    return deleted;
}
exports.deleteFromNestedMap = deleteFromNestedMap;
/**
 * Converts a nested map to a flat list of triplets.
 */
function nestedMapToFlatList(map) {
    const list = [];
    map.forEach((innerMap, key1) => {
        innerMap.forEach((val, key2) => {
            list.push([key1, key2, val]);
        });
    });
    return list;
}
exports.nestedMapToFlatList = nestedMapToFlatList;
/**
 * Builds a nested map from a flat list of triplets.
 */
function nestedMapFromFlatList(list) {
    const map = new Map();
    for (const [key1, key2, val] of list) {
        getOrAddInMap(map, key1, new Map()).set(key2, val);
    }
    return map;
}
exports.nestedMapFromFlatList = nestedMapFromFlatList;
function forEachInNestedMap(map, delegate) {
    map.forEach((innerMap, keyFirst) => {
        innerMap.forEach((val, keySecond) => {
            delegate(val, keyFirst, keySecond);
        });
    });
}
exports.forEachInNestedMap = forEachInNestedMap;
/**
 * Map with two keys; same semantics as NestedMap, but maintains a size count for the entire collection.
 * Note: undefined is not supported as a value, and will cause incorrect behavior.
 *
 * @internal
 */
class SizedNestedMap {
    constructor() {
        this.nestedMap = new Map();
        this.count = 0;
    }
    /**
     * Returns the total number of elements in this nested map.
     */
    get size() {
        return this.count;
    }
    /**
     * If (key1, key2) already has a value in the map, it is returned, otherwise value is added under (key1, key2) and undefined is
     * returned.
     */
    tryGet(key1, key2) {
        return tryGetFromNestedMap(this.nestedMap, key1, key2);
    }
    /**
     * Does not change map.
     * If (key1, key2) is not in map, returns value.
     * If (key1, key2) is in map, return its entry.
     */
    getOrDefault(key1, key2, value) {
        return getOrDefaultInNestedMap(this.nestedMap, key1, key2, value);
    }
    /**
     * If (key1, key2) already has a value in the map, it is returned, otherwise value is added under (key1, key2) and undefined is
     * returned.
     */
    tryAdd(key1, key2, value) {
        const currentVal = tryAddToNestedMap(this.nestedMap, key1, key2, value);
        if (currentVal === undefined) {
            this.count++;
        }
        return currentVal;
    }
    /**
     * Sets the value at (key1, key2) in map to value.
     * If there already is a value for (key1, key2), it is replaced with the provided one.
     */
    set(key1, key2, value) {
        if (this.tryAdd(key1, key2, value) !== undefined) {
            setInNestedMap(this.nestedMap, key1, key2, value);
        }
    }
    /**
     * Removes the value at (key1, key2) from the map.
     * Returns true iff found.
     */
    delete(key1, key2) {
        const deleted = deleteFromNestedMap(this.nestedMap, key1, key2);
        if (deleted) {
            this.count--;
        }
        return deleted;
    }
    /**
     * Runs the supplied delegate for every (value, key1, key2).
     */
    forEach(delegate) {
        forEachInNestedMap(this.nestedMap, delegate);
    }
    /**
     * Clears the map.
     */
    clear() {
        this.count = 0;
        this.nestedMap.clear();
    }
    values() {
        return Array.from(this.nestedMap.values()).flatMap((innerMap) => innerMap.values())[0];
    }
    [Symbol.iterator]() {
        return this.nestedMap[Symbol.iterator]();
    }
}
exports.SizedNestedMap = SizedNestedMap;
//# sourceMappingURL=nestedMap.js.map