/**
 * @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 (i = 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});
    }
});
