/**
 * This class manages a double-linked list. It provides an absolutely minimal container
 * interface.
 *
 * @class Ext.util.LRU
 * @private
 * @since 6.5.0
 */
/* eslint-disable indent */
(function(LRU, prototype) {
// @define Ext.util.LRU
 
    // NOTE: We have to implement this class old-school because it is used by the
    // platformConfig class processor (so Ext.define is not yet ready for action).
    (Ext.util || (Ext.util = {})).LRU = LRU = function(config) {
        var me = this,
            head;
 
        if (config) {
            Ext.apply(me, config);
        }
 
        // Give all entries the same object shape.
        me.head = head = {
            //<debug>
            id: (me.seed = 0),
            //</debug>
            key: null,
            value: null
        };
 
        /**
         * @property {Object} map
         * The items in the list indexed by their `key`.
         * @readonly
         */
        me.map = {};
 
        head.next = head.prev = head;
    };
 
    LRU.prototype = prototype = {
        /**
         * @property {Number} count
         * The number of items in this list.
         * @readonly
         */
        count: 0,
 
        /**
         * Adds an item to the list with the specified `key`. Items are added at the
         * front (MRU) of the list.
         * @param {String} key 
         * @param {Object} value 
         */
        add: function(key, value) {
            var me = this,
                map = me.map,
                entry = map[key];
 
            if (entry) {
                me.unlink(entry);
                --me.count;
            }
 
            map[key] = entry = {
                //<debug>
                id: ++me.seed,
                //</debug>
                key: key,
                value: value
            };
 
            me.link(entry);
            ++me.count;
 
            return entry;
        },
 
        /**
         * Removes all items from this list optionally calling a function for each
         * remove item.
         * @param {Function} [fn] A function to call for each removed item.
         * @param {Object} fn.key The key of the removed item.
         * @param {Object} fn.value The removed item.
         * @param {Object} [scope] The `this` pointer for `fn`.
         */
        clear: function(fn, scope) {
            var me = this,
                head = me.head,
                entry = head.next;
 
            head.next = head.prev = head;
 
            me.count = 0;
 
            if (fn && !fn.$nullFn) {
                for (; entry !== head; entry = entry.next) {
                    fn.call(scope || me, entry.key, entry.value);
                }
            }
        },
 
        /**
         * Calls the given function `fn` for each item in the list. The items will be
         * passed to `fn` from most-to-least recently added or touched.
         * @param {Function} fn The function to call for each cache item.
         * @param {String} fn.key The key for the item.
         * @param {Object} fn.value The item.
         * @param {Object} [scope] The `this` pointer to use for `fn`.
         */
        each: function(fn, scope) {
            var head, ent;
            
            scope = scope || this;
 
            for (head = this.head, ent = head.next; ent !== head; ent = ent.next) {
                if (fn.call(scope, ent.key, ent.value)) {
                    break;
                }
            }
        },
 
        /**
         * Removes the item at the end (LRU) of the list. Optionally the item can be passed
         * to a callback function. If the list is empty, no callback is made and this
         * method will return `undefined`.
         * @param {Function} fn The function to call for the removed item.
         * @param {Object} fn.key The key of the removed item.
         * @param {Object} fn.value The removed item.
         * @param {Object} [scope] The `this` pointer to use for `fn`.
         * @return {Object} The removed item.
         */
        prune: function(fn, scope) {
            var me = this,
                entry = me.head.prev,
                ret;
 
            if (me.count) {
                ret = entry.value;
                me.unlink(entry);
                --me.count;
 
                if (fn) {
                    fn.call(scope || me, entry.key, ret);
                }
            }
 
            return ret;
        },
 
        /**
         * Removes an item from the list given its key.
         * @param {String} key The key of the item to remove.
         * @return {Object} The removed item or `undefined` if the key was not found.
         */
        remove: function(key) {
            var me = this,
                map = me.map,
                entry = map[key],
                value;
 
            if (entry) {
                me.unlink(entry);
                value = entry.value;
                delete map[key];
                --me.count;
            }
 
            return value;
        },
 
        /**
         * Moves the item with the given key to the front (MRU) of the list.
         * @param {String} key The key of the item to move to the front.
         */
        touch: function(key) {
            var me = this,
                head = me.head,
                entry = me.map[key];
 
            if (entry && entry.prev !== head) {
                // The entry is not at the front, so remove it and insert it at the front
                // (to make it the MRU - Most Recently Used).
                me.unlink(entry);
                me.link(entry);
            }
        },
 
        /**
         * Reduces the length of the list to be no more than the specified `size`, removing
         * items from the end of the list as necessary. Optionally each removed item can
         * be passed to a callback `fn`.
         * @param {Number} size The number of items in the list
         * @param {Function} [fn] A function to call for each removed item.
         * @param {Object} fn.key The key of the removed item.
         * @param {Object} fn.value The removed item.
         * @param {Object} [scope] The `this` pointer for `fn`.
         */
        trim: function(size, fn, scope) {
            while (this.count > size) {
                this.prune(fn, scope);
            }
        },
 
        //-------------------------------------------------------------------------
        // Internals
 
        /**
         * Inserts the given entry at the front (MRU) end of the entry list.
         * @param {Object} entry The cache item entry.
         * @private
         */
        link: function(entry) {
            var head = this.head,
                first = head.next;
 
            entry.next = first;
            entry.prev = head;
            head.next = entry;
            first.prev = entry;
        },
 
        /**
         * Removes the given entry from the entry list.
         * @param {Object} entry The cache item entry.
         * @private
         */
        unlink: function(entry) {
            var next = entry.next,
                prev = entry.prev;
 
            prev.next = next;
            next.prev = prev;
        }
    };
 
    prototype.destroy = function() {
        this.clear.apply(this, arguments);
    };
}());