ko.extenders = {
    'debounce': (target, timeout) => target.limit(callback => debounce(callback, timeout)),

    'rateLimit': (target, options) => {
        var timeout, method, limitFunction;

        if (typeof options == 'number') {
            timeout = options;
        } else {
            timeout = options['timeout'];
            method = options['method'];
        }

        limitFunction = typeof method == 'function' ? method : throttle;
        target.limit(callback => limitFunction(callback, timeout, options));
    },

    'notify': (target, notifyWhen) => {
        target["equalityComparer"] = notifyWhen == "always" ?
            null :  // null equalityComparer means to always notify
            valuesArePrimitiveAndEqual;
    }
};

var primitiveTypes = { 'undefined':1, 'boolean':1, 'number':1, 'string':1 };
function valuesArePrimitiveAndEqual(a, b) {
    return (a === null || primitiveTypes[typeof(a)]) ? (a === b) : false;
}

function throttle(callback, timeout) {
    var timeoutInstance;
    return () => {
        if (!timeoutInstance) {
            timeoutInstance = ko.utils.setTimeout(() => {
                timeoutInstance = 0;
                callback();
            }, timeout);
        }
    };
}

function debounce(callback, timeout) {
    var timeoutInstance;
    return () => {
        clearTimeout(timeoutInstance);
        timeoutInstance = ko.utils.setTimeout(callback, timeout);
    };
}

ko.exportSymbol('extenders', ko.extenders);
