/**
 * @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',
    requires: [
        'Ext.dataview.BoundListNavigationModel'
    ],
 
    /**
     * @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
    },
 
    tabIndex: null,
    focusEl: null,
    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,
 
    navigationModel: {
        type: 'boundlist',
        keyboard: false
    },
 
    onFocusEnter: Ext.emptyFn,
    onFocusLeave: Ext.emptyFn,
 
    /**
     * @cfg {'tap'} triggerEvent
     * @hide
     * BoundLists always use tap. This is ignored.
     */
 
    /**
     * 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;
 
        me.callParent();
 
        me.on({
            scope: me,
            painted: 'onPainted',
            single: true
        });
 
        me.picker.on({
            scope: me,
            beforehiddenchange: 'onBeforeHiddenChange'
        });
    },
 
    /**
     * @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 isn't rendered yet, there is no point in calculating the padding just yet
            return;
        }
 
        var me = this,
            title = me.getTitle(),
            titleHeight = me.getShowTitle() && title ? title.el.measure('h') : 0,
            barHeight = me.getPicker().bar.measure('h'),
            offset;
 
        if (me.getVerticallyCenterItems()) {
            offset = Math.ceil((me.el.measure('h') - titleHeight - barHeight) / 2);
            me.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 (!me.bottomSpacer) {
                me.bottomSpacer = me.add({
                    xtype: 'component',
                    scrollDock: 'end',
                    height: offset,
                    style: 'pointer-events: none'
                });
            } else {
                me.bottomSpacer.setHeight(offset);
            }
        }
 
        me.doSetValue(me.getValue());
    },
 
    /**
     * This method is required by the Scroller to return the scrollable client region
     * @return {Ext.util.Region} The scrolling viewport region.
     *
     * It's overridden here because the region required for scrollIntoView to work
     * is the bar of the picker.
     * @private
     */
    getScrollableClientRegion: function() {
        return this.picker.bar.getClientRegion();
    },
 
    /**
     * @private
     */
    scrollToItem: function(item, animated) {
        // Scrollable will scroll into the bar region because of our getScrollableClientRegion
        // implementation above.
        this.getScrollable().scrollIntoView(item.el, false, animated);
    },
 
    /**
     * @private
     * Called directly by our scroller when scrolling has stopped.
     */
    onScrollEnd: function(x, y) {
        var me = this,
            viewItems = me.getViewItems(),
            index = Ext.Number.constrain(Math.round(/ me.picker.bar.measure('h')), 0, viewItems.length - 1),
            item = viewItems[index];
 
        if (item) {
            me.selectedIndex = index;
            me.selectedNode = item;
 
            me.setValueAnimated(me.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,
            store, index, item;
        
        // Store can be null
        store = me.getStore();
        
        index = store ? store.findExact(me.getValueField(), value) : -1;
 
        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;
    },
 
    privates: {
        forceRefreshOnRender: true
    }
});