import { isUndefined } from "./util";
import { Dictionary } from './Dictionary';

export class FactoryDictionary<K, V> extends Dictionary<K, V> {

    /**
     * Factory to create default values.
     * @type {function(Object):string}
     * @protected
     */
    protected defaultFactoryFunction: () => V;

    /**
     * Creates an empty dictionary.
     * @class <p>Dictionaries map keys to values; each key can map to at most one value.
     * This implementation accepts any kind of objects as keys.</p>
     *
     * <p>The default factory function should return a new object of the provided
     * type. Example:</p>
     * <pre>
     * function petFactory() {
     *  return new Pet();
     * }
     * </pre>
     *
     * <p>If the keys are custom objects a function which converts keys to unique
     * strings must be provided. Example:</p>
     * <pre>
     * function petToString(pet) {
     *  return pet.name;
     * }
     * </pre>
     * @constructor
     * @param {function():V=} defaultFactoryFunction function used to create a
     * default object.
     * @param {function(Object):string=} toStrFunction optional function used
     * to convert keys to strings. If the keys aren't strings or if toString()
     * is not appropriate, a custom function which receives a key and returns a
     * unique string must be provided.
     */
    constructor(defaultFactoryFunction: () => V, toStrFunction?: (key: K) => string) {
        super(toStrFunction);

        this.defaultFactoryFunction = defaultFactoryFunction;
    }


    /**
     * Associates the specified default value with the specified key in this dictionary,
     * if it didn't contain the key yet. If the key existed, the existing value will be used.
     * @param {Object} key key with which the specified value is to be
     * associated.
     * @param {Object} defaultValue default value to be associated with the specified key.
     * @return {*} previous value associated with the specified key, or the default value,
     * if the key didn't exist yet.
     */
    setDefault(key: K, defaultValue: V): V {
        const currentValue: V | undefined = super.getValue(key);

        if (isUndefined(currentValue)) {
            this.setValue(key, defaultValue);

            return defaultValue;
        }

        return currentValue;
    }

    /**
     * Returns the value to which this dictionary maps the specified key.
     * Returns a default value created by the factory passed in the constructor,
     * if this dictionary contains no mapping for this key. The missing key will
     * automatically be added to the dictionary.
     * @param {Object} key key whose associated value is to be returned.
     * @return {*} the value to which this dictionary maps the specified key or
     * a default value if the map contains no mapping for this key.
     */
    getValue(key: K): V {
        return this.setDefault(key, this.defaultFactoryFunction());
    }
}
