/**
 * @class Ext.util.MixedCollection
 * <p>
 * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
 * must be unique, the same key cannot exist twice. This collection is ordered, items in the
 * collection can be accessed by index  or via the key. Newly added items are added to
 * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
 * is heavier and provides more functionality. Sample usage:
 * <pre><code>
var coll = new Ext.util.MixedCollection();
coll.add('key1', 'val1');
coll.add('key2', 'val2');
coll.add('key3', 'val3');

console.log(coll.get('key1')); // prints 'val1'
console.log(coll.indexOfKey('key3')); // prints 2
 * </code></pre>
 *
 * <p>
 * The MixedCollection also has support for sorting and filtering of the values in the collection.
 * <pre><code>
var coll = new Ext.util.MixedCollection();
coll.add('key1', 100);
coll.add('key2', -100);
coll.add('key3', 17);
coll.add('key4', 0);
var biggerThanZero = coll.filterBy(function(value){
    return value > 0;
});
console.log(biggerThanZero.getCount()); // prints 2
 * </code></pre>
 * </p>
 *
 * @constructor
 * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
 * function should add function references to the collection. Defaults to
 * <tt>false</tt>.
 * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
 * and return the key value for that item.  This is used when available to look up the key on items that
 * were passed without an explicit key parameter to a MixedCollection method.  Passing this parameter is
 * equivalent to providing an implementation for the {@link #getKey} method.
 */

Ext.define('Ext.util.MixedCollection', {
    extend
: 'Ext.util.AbstractMixedCollection',
    mixins
: {
        sortable
: 'Ext.util.Sortable'
   
},

    constructor
: function() {
       
var me = this;
        me
.callParent(arguments);
        me
.addEvents('sort');
        me
.mixins.sortable.initSortable.call(me);
   
},

    doSort
: function(sorterFn) {
       
this.sortBy(sorterFn);
   
},

    /**
     * @private
     * Performs the actual sorting based on a direction and a sorting function. Internally,
     * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
     * the sorted array data back into this.items and this.keys
     * @param {String} property Property to sort by ('key', 'value', or 'index')
     * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
     * @param {Function} fn (optional) Comparison function that defines the sort order.
     * Defaults to sorting by numeric value.
     */

    _sort
: function(property, dir, fn){
       
var me = this,
            i
, len,
            dsc  
= String(dir).toUpperCase() == 'DESC' ? -1 : 1,

           
//this is a temporary array used to apply the sorting function
            c    
= [],
            keys  
= me.keys,
            items
= me.items;

       
//default to a simple sorter function if one is not provided
        fn
= fn || function(a, b) {
           
return a - b;
       
};

       
//copy all the items into a temporary array, which we will sort
       
for(i = 0, len = items.length; i < len; i++){
            c
[c.length] = {
                key  
: keys[i],
                value
: items[i],
                index
: i
           
};
       
}

       
//sort the temporary array
       
Ext.Array.sort(c, function(a, b){
           
var v = fn(a[property], b[property]) * dsc;
           
if(v === 0){
                v
= (a.index < b.index ? -1 : 1);
           
}
           
return v;
       
});

       
//copy the temporary array back into the main this.items and this.keys objects
       
for(i = 0, len = c.length; i < len; i++){
            items
[i] = c[i].value;
            keys
[i]  = c[i].key;
       
}

        me
.fireEvent('sort', me);
   
},

    /**
     * Sorts the collection by a single sorter function
     * @param {Function} sorterFn The function to sort by
     */

    sortBy
: function(sorterFn) {
       
var me     = this,
            items  
= me.items,
            keys  
= me.keys,
            length
= items.length,
            temp  
= [],
            i
;

       
//first we create a copy of the items array so that we can sort it
       
for (i = 0; i < length; i++) {
            temp
[i] = {
                key  
: keys[i],
                value
: items[i],
                index
: i
           
};
       
}

       
Ext.Array.sort(temp, function(a, b) {
           
var v = sorterFn(a.value, b.value);
           
if (v === 0) {
                v
= (a.index < b.index ? -1 : 1);
           
}

           
return v;
       
});

       
//copy the temporary array back into the main this.items and this.keys objects
       
for (i = 0; i < length; i++) {
            items
[i] = temp[i].value;
            keys
[i]  = temp[i].key;
       
}
       
        me
.fireEvent('sort', me, items, keys);
   
},

    /**
     * Reorders each of the items based on a mapping from old index to new index. Internally this
     * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
     * @param {Object} mapping Mapping from old item index to new item index
     */

    reorder
: function(mapping) {
       
var me = this,
            items
= me.items,
            index
= 0,
            length
= items.length,
            order
= [],
            remaining
= [],
            oldIndex
;

        me
.suspendEvents();

       
//object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
       
for (oldIndex in mapping) {
            order
[mapping[oldIndex]] = items[oldIndex];
       
}

       
for (index = 0; index < length; index++) {
           
if (mapping[index] == undefined) {
                remaining
.push(items[index]);
           
}
       
}

       
for (index = 0; index < length; index++) {
           
if (order[index] == undefined) {
                order
[index] = remaining.shift();
           
}
       
}

        me
.clear();
        me
.addAll(order);

        me
.resumeEvents();
        me
.fireEvent('sort', me);
   
},

    /**
     * Sorts this collection by <b>key</b>s.
     * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
     * @param {Function} fn (optional) Comparison function that defines the sort order.
     * Defaults to sorting by case insensitive string.
     */

    sortByKey
: function(dir, fn){
       
this._sort('key', dir, fn || function(a, b){
           
var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
           
return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
       
});
   
}
});