/**
 * @private
 *
 * A general {@link Ext.picker.Picker} slot class.  Slots are used to organize multiple scrollable slots into
 * a single {@link Ext.picker.Picker}.
 *
 *     {
 *         name : 'limit_speed',
 *         title: 'Speed Limit',
 *         data : [
 *             {text: '50 KB/s', value: 50},
 *             {text: '100 KB/s', value: 100},
 *             {text: '200 KB/s', value: 200},
 *             {text: '300 KB/s', value: 300}
 *         ]
 *     }
 *
 * See the {@link Ext.picker.Picker} documentation on how to use slots.
 */
Ext.define('Ext.picker.Slot', {
    extend: 'Ext.dataview.DataView',
    xtype: 'pickerslot',
 
    /**
     * @event slotpick
     * Fires whenever an slot is picked
     * @param {Ext.picker.Slot} this
     * @param {Mixed} value The value of the pick
     * @param {HTMLElement} node The node element of the pick
     */
 
    isSlot: true,
 
    config: {
        /**
         * @cfg {String} title The title to use for this slot, or `null` for no title.
         * @accessor
         */
        title: null,
 
        /**
         * @private
         * @cfg {Boolean} showTitle 
         * @accessor
         */
        showTitle: true,
 
        /**
         * @private
         * @cfg {String} cls The main component class
         * @accessor
         */
        cls: Ext.baseCSSPrefix + 'picker-slot',
 
        /**
         * @cfg {String} name (required) The name of this slot.
         * @accessor
         */
        name: null,
 
        /**
         * @cfg {Number} value The value of this slot
         * @accessor
         */
        value: null,
 
        /**
         * @cfg {Number} flex 
         * @accessor
         * @private
         */
        flex: 1,
 
        /**
         * @cfg {String} align The horizontal alignment of the slot's contents.
         *
         * Valid values are: "left", "center", and "right".
         * @accessor
         */
        align: 'left',
 
        /**
         * @cfg {String} displayField The display field in the store.
         * @accessor
         */
        displayField: 'text',
 
        /**
         * @cfg {String} valueField The value field in the store.
         * @accessor
         */
        valueField: 'value',
 
        /**
         * @cfg {String} itemTpl The template to be used in this slot.
         * If you set this, {@link #displayField} will be ignored.
         */
        itemTpl: null,
 
        /**
         * @cfg {Object} scrollable 
         * @accessor
         * @hide
         */
        scrollable: {
            x: false,
            y: true,
            scrollbars: false
        },
 
        /**
         * @cfg {Boolean} verticallyCenterItems 
         * @private
         */
        verticallyCenterItems: true
    },
 
    itemsFocusable: false,
 
    scrollToTopOnRefresh: false,
 
    snapSelector: '.' + Ext.baseCSSPrefix + 'dataview-item',
 
    /**
     * @property selectedIndex
     * @type Number
     * The current `selectedIndex` of the picker slot.
     * @private
     */
    selectedIndex: 0,
 
    deselectable: false,
 
    /**
     * Sets the title for this dataview by creating element.
     * @param {String} title 
     * @return {String}
     */
    applyTitle: function(title) {
        //check if the title isnt defined 
        if (title) {
            //create a new title element 
            title = Ext.create('Ext.Component', {
                cls: Ext.baseCSSPrefix + 'picker-slot-title',
                docked: 'top',
                html: title
            });
        }
 
        return title;
    },
 
    updateTitle: function(newTitle, oldTitle) {
        if (newTitle) {
            this.add(newTitle);
            this.setupBar();
        }
 
        if (oldTitle) {
            this.remove(oldTitle);
        }
    },
 
    updateShowTitle: function(showTitle) {
        var title = this.getTitle(),
            mode = showTitle ? 'show' : 'hide';
        if (title) {
            title.on(mode, this.setupBar, this, { single: true, delay: 50 });
            title[showTitle ? 'show' : 'hide']();
        }
    },
 
    updateDisplayField: function(newDisplayField) {
        if (!this.config.itemTpl) {
            this.setItemTpl('<div class="' + Ext.baseCSSPrefix + 'picker-item {cls} <tpl if="extra">' + Ext.baseCSSPrefix + 'picker-invalid</tpl>">{' + newDisplayField + '}</div>');
        }
    },
 
    /**
     * Updates the {@link #align} configuration
     */
    updateAlign: function(newAlign, oldAlign) {
        var element = this.element;
        element.addCls(Ext.baseCSSPrefix + 'picker-' + newAlign);
        element.removeCls(Ext.baseCSSPrefix + 'picker-' + oldAlign);
    },
 
    /**
     * Looks at the {@link #data} configuration and turns it into {@link #store}.
     * @param {Object} data 
     * @return {Object}
     */
    applyData: function(data) {
        var parsedData = [],
            ln = data && data.length,
            i, item, obj;
 
        if (data && Ext.isArray(data) && ln) {
            for (= 0; i < ln; i++) {
                item = data[i];
                obj = {};
                if (Ext.isArray(item)) {
                    obj[this.valueField] = item[0];
                    obj[this.displayField] = item[1];
                }
                else if (Ext.isString(item)) {
                    obj[this.valueField] = item;
                    obj[this.displayField] = item;
                }
                else if (Ext.isObject(item)) {
                    obj = item;
                }
                parsedData.push(obj);
            }
        }
 
        return data;
    },
 
    /**
     * @private
     */
    initialize: function() {
        var me = this,
            scroller;
 
        me.callParent();
 
        scroller = me.getScrollable();
 
        me.on({
            scope: me,
            painted: 'onPainted',
            childtap: 'doChildTap'
        });
 
        me.picker.on({
            scope: me,
            beforehiddenchange: 'onBeforeHiddenChange'
        });
 
        me.element.on({
            scope: me,
            touchstart: 'onTouchStart',
            touchend: 'onTouchEnd'
        });
 
        scroller.on({
            scope: me,
            scrollend: 'onSlotScrollEnd'
        });
    },
 
    /**
     * @private
     */
    onPainted: function() {
        this.setupBar();
    },
 
    /**
     * @private
     */
    onResize: function() {
        var value = this.getValue();
        if (value) {
            this.doSetValue(value);
        }
    },
 
    /**
     * @private
     */
    onBeforeHiddenChange: function (picker, hidden) {
        if (!hidden) {
            this.doSetValue(this.getValue());
        }        
    },
 
    /**
     * Returns an instance of the owner picker.
     * @return {Object}
     * @private
     */
    getPicker: function() {
        if (!this.picker) {
            this.picker = this.getParent();
        }
 
        return this.picker;
    },
 
    /**
     * @private
     */
    setupBar: function() {
        if (!this.isPainted()) {
            //if the component isnt rendered yet, there is no point in calculating the padding just eyt 
            return;
        }
 
        var element = this.element,
            bodyElement = this.bodyElement,
            picker = this.getPicker(),
            bar = picker.bar,
            value = this.getValue(),
            showTitle = this.getShowTitle(),
            title = this.getTitle(),
            titleHeight = 0,
            barHeight, offset;
 
        barHeight = bar.dom.getBoundingClientRect().height;
 
        if (showTitle && title) {
            titleHeight = title.element.getHeight();
        }
 
        offset = Math.ceil((element.getHeight() - titleHeight - barHeight) / 2);
 
        if (this.getVerticallyCenterItems()) {
            bodyElement.setStyle({
                'padding-top' : offset + 'px'
            });
            // Due to a change on how browsers set the element now, padding is applied  
            // at the content edge, not after any overflow. So the padding-bottom will 
            // be clipped if the content becomes scrollable. 
            // For more info see: https://bugzilla.mozilla.org/show_bug.cgi?id=74851 
            if (!this.bottomSpacer) {
                this.bottomSpacer = this.add({
                    xtype: 'component',
                    scrollDock: 'end',
                    height: offset,
                    style: 'pointer-events: none'
                });
            } else {
                this.bottomSpacer.setHeight(offset);
            }
        }
 
        this.doSetValue(value);
    },
 
    /**
     * @private
     */
    doChildTap: function(list, e) {
        var me = this;
        me.selectedIndex = e.viewIndex;
        me.selectedNode = e.child;
        me.scrollToItem(me.selectedNode, true);
    },
 
    /**
     * @private
     */
    scrollToItem: function(item, animated) {
        // The difference between the required item an the overlayed bar is how far to scroll. 
        var difference = item.getY() - this.picker.bar.getY();
 
        if (difference) {
            this.getScrollable().scrollBy(0, difference, animated);
        }
    },
 
    /**
     * @private
     */
    onTouchStart: function() {
        this.element.addCls(Ext.baseCSSPrefix + 'scrolling');
    },
 
    /**
     * @private
     */
    onTouchEnd: function() {
        this.element.removeCls(Ext.baseCSSPrefix + 'scrolling');
    },
 
    /**
     * @private
     */
    onSlotScrollEnd: function(scroller, x, y) {
        var me = this,
            index = Math.round(/ me.picker.bar.dom.getBoundingClientRect().height),
            viewItems = me.getViewItems(),
            item = viewItems[index];
 
        if (item) {
            me.selectedIndex = index;
            me.selectedNode = item;
 
            this.setValueAnimated(this.getValue(true));
            me.fireEvent('slotpick', me, me.getValue(), me.selectedNode);
        }
    },
 
    /**
     * Returns the value of this slot
     * @private
     */
    getValue: function(useDom) {
        var store = this.getStore(),
            record, value;
 
        if (!store) {
            return;
        }
 
        if (!useDom) {
            return this._value;
        }
 
        //if the value is ever false, that means we do not want to return anything 
        if (this._value === false) {
            return null;
        }
 
        record = store.getAt(this.selectedIndex);
 
        value = record ? record.get(this.getValueField()) : null;
 
        return value;
    },
 
    /**
     * Sets the value of this slot
     * @private
     */
    setValue: function(value) {
        return this.doSetValue(value);
    },
 
    /**
     * Sets the value of this slot
     * @private
     */
    setValueAnimated: function(value) {
        return this.doSetValue(value, true);
    },
 
    doSetValue: function(value, animated) {
        var me = this,
            hasSelection = true,
            index, item;
 
        index = me.getStore().findExact(me.getValueField(), value);
 
        if (index === -1) {
            hasSelection = false;
            index = 0;
        }
 
        me.selectedIndex = index;
 
        if (me.refreshCounter) {
            item = Ext.get(me.getViewItems()[index]);
            if (item) {
                me.scrollToItem(item, animated);
                if (hasSelection) {
                    // only set selection if an item is actually selected 
                    me.select(me.selectedIndex);
                }
            }
        }
 
        me._value = value;
    }
});