/**
 * @class Ext.plugin.SortableList
 * @extends Ext.Component
 * The SortableList plugin gives your list items the ability to be reordered by tapping and 
 * dragging elements within the item.   
 *
 * The list-sortablehandle is not added to your tpl by default, so it's important that you 
 * manually include it. It's also important to recognize that list-items are not draggable 
 * themselves.  You must add an element to the itemTpl for it to be dragged.
 *
 *     Ext.Viewport.add({
 *          xtype: 'list',
 *          infinite: true,
 *          plugins: 'sortablelist',
 *          itemTpl: '<span class="myStyle ' + Ext.baseCSSPrefix + 'list-sortablehandle"></span>{text}',
 *          data: [{
 *              text: 'Item 1'
 *          }, {
 *              text: 'Item 2'
 *          }, {
 *              text: 'Item 3'
 *          }]
 *     });
 *
 * The CSS for MyStyle can be anything that creates an element to tap and drag.  For this 
 * example we made a simple rectangle like so:
 *
 *      .myStyle{
 *          width:30px;
 *          height:20px;
 *          background:gray;
 *          float:left;
 *      }
 * 
 * Note: You must have infinite set to 'true' when using the SortableList plugin.
 * 
 */
Ext.define('Ext.plugin.SortableList', {
    extend: 'Ext.Component',
 
    alias: 'plugin.sortablelist',
 
    mixins: ['Ext.mixin.Hookable'],
 
    config: {
        list: null,
        handleSelector: '.' + Ext.baseCSSPrefix + 'list-sortablehandle'
    },
 
    init: function(list) {
        this.setList(list);
    },
 
    updateList: function(list) {
        if (list) {
            if (list.initialized) {
                this.attachListeners();
            }
            else {
                list.on({
                    initialize: 'attachListeners',
                    scope: this,
                    single: true
                });
            }
        }
    },
 
    attachListeners: function() {
        var list = this.getList(),
            scrollerElement = list.getScrollable().getElement();
 
        this.scrollerElement = scrollerElement;
 
        scrollerElement.onBefore({
            dragstart: 'onScrollerDragStart',
            scope: this
        });
    },
 
    onScrollerDragStart: function(e, target) {
        if (Ext.DomQuery.is(target, this.getHandleSelector())) {
            if (!this.animating) {
                this.onDragStart(e, target);
            }
            return false;
        }
    },
 
    onDragStart: function(e) {
        var row = Ext.getCmp(e.getTarget('.' + Ext.baseCSSPrefix + 'listitem').id),
            list = this.getList(),
            store = list.getStore();
 
        this.scrollerElement.on({
            drag: 'onDrag',
            dragend: 'onDragEnd',
            scope: this
        });
 
        this.positionMap = list.getItemMap();
        this.listStore = store;
        this.previousIndexDistance = 0;
 
        this.dragRow = row;
        this.dragRecord = row.getRecord();
 
        this.dragRowIndex = this.currentDragRowIndex = row.$dataIndex;
        this.dragRowHeight = this.positionMap.getItemHeight(this.dragRowIndex);
 
        if (list.getInfinite()) {
            this.startTranslate = this.positionMap.map[this.dragRowIndex];
        } else {
            row.translate(0, 0);
            this.startTranslate = 0;
        }
 
        row.addCls(Ext.baseCSSPrefix + 'list-item-dragging');
    },
 
    onDrag: function(e) {
        var list = this.getList(),
            listItems = list.listItems,
            store = list.getStore(),
            collection = list.getStore().data,
            dragRow = this.dragRow,
            dragRecordKey = dragRow.id,
            listItemInfo = list.getListItemInfo(),
            positionMap = this.positionMap,
            distance = 0,
            i, item, ln, targetItem, targetIndex, itemIndex,
            swapIndex, swapPosition, record, swapKey, draggingUp;
 
        this.dragRowPosition = this.startTranslate + e.deltaY;
        dragRow.translate(0, this.dragRowPosition);
 
        targetIndex = positionMap.findIndex(this.dragRowPosition + (this.dragRowHeight / 2));
        targetItem = list.getItemAt(targetIndex);
 
        if (targetItem) {
            distance = targetIndex - this.currentDragRowIndex;
 
            if (distance !== 0) {
                draggingUp = distance < 0;
 
                for (= 0, ln = Math.abs(distance); i < ln; i++) {
                    if (draggingUp) {
                        swapIndex = this.currentDragRowIndex - i;
                        item = list.getItemAt(swapIndex - 1);
                    } else {
                        swapIndex = this.currentDragRowIndex + i;
                        item = list.getItemAt(swapIndex + 1);
                    }
 
                    swapPosition = positionMap.map[swapIndex];
 
                    item.translate(0, swapPosition);
 
                    record = item.getRecord();
                    swapKey = record.id;
 
                    Ext.Array.remove(collection.items, record);
                    collection.items.splice(swapIndex, 0, record);
                    collection.indices[dragRecordKey] = collection.indices[swapKey];
                    collection.indices[swapKey] = swapIndex;
 
                    list.updateListItem(item, swapIndex, listItemInfo);
                    item.$position = swapPosition;
                }
 
                itemIndex = listItems.indexOf(dragRow);
                Ext.Array.remove(listItems, dragRow);
                listItems.splice(itemIndex + distance, 0, dragRow);
 
                dragRow.$dataIndex = targetIndex;
                dragRow.$position = positionMap.map[targetIndex];
 
                this.currentDragRowIndex = targetIndex;
            }
        }
    },
 
    onDragEnd: function() {
        var me = this,
            row = me.dragRow,
            list = me.getList(),
            listItemInfo = list.getListItemInfo(),
            position = row.$position;
 
        me.scrollerElement.un({
            drag: 'onDrag',
            dragend: 'onDragEnd',
            scope: me
        });
 
        me.animating = true;
 
        row.getTranslatable().on('animationend', function() {
            row.removeCls(Ext.baseCSSPrefix + 'list-item-dragging');
 
            var currentIdx = this.currentDragRowIndex,
                dragIdx = this.dragRowIndex;
 
            if (currentIdx !== dragIdx) {
                list.updateListItem(row, row.$dataIndex, listItemInfo);
                row.$position = position;
 
                list.fireEvent('dragsort', list, row, currentIdx, dragIdx);
            }
            me.animating = false;
        }, me, {single: true});
 
        row.translate(0, position, {duration: 100});
    }
});