/**
 * A general picker class. {@link Ext.picker.Slot}s are used to organize multiple scrollable
 * slots into a single picker. {@link #slots} is the only necessary configuration.
 *
 * The {@link #slots} configuration with a few key values:
 *
 * - `name`: The name of the slot (will be the key when using {@link #getValues} in this
 *  {@link Ext.picker.Picker}).
 * - `title`: The title of this slot (if {@link #useTitles} is set to `true`).
 * - `data`/`store`: The data or store to use for this slot.
 *
 * Remember, {@link Ext.picker.Slot} class extends from {@link Ext.dataview.DataView}.
 *
 * ## Examples
 *
 *     @example
 *     var picker = Ext.create('Ext.Picker', {
 *         slots: [
 *             {
 *                 name : 'limit_speed',
 *                 title: 'Speed',
 *                 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}
 *                 ]
 *             }
 *         ]
 *     });
 *     Ext.Viewport.add(picker);
 *     picker.show();
 *
 * You can also customize the top toolbar on the {@link Ext.picker.Picker} by changing the
 * {@link #doneButton} and {@link #cancelButton} configurations:
 *
 *     @example
 *     var picker = Ext.create('Ext.Picker', {
 *         doneButton: 'I\'m done!',
 *         cancelButton: false,
 *         slots: [
 *             {
 *                 name : 'limit_speed',
 *                 title: 'Speed',
 *                 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}
 *                 ]
 *             }
 *         ]
 *     });
 *     Ext.Viewport.add(picker);
 *     picker.show();
 *
 * Or by passing a custom {@link #toolbar} configuration:
 *
 *     @example
 *     var picker = Ext.create('Ext.Picker', {
 *         doneButton: false,
 *         cancelButton: false,
 *         toolbar: {
 *             ui: 'light',
 *             title: 'My Picker!'
 *         },
 *         slots: [
 *             {
 *                 name : 'limit_speed',
 *                 title: 'Speed',
 *                 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}
 *                 ]
 *             }
 *         ]
 *     });
 *     Ext.Viewport.add(picker);
 *     picker.show();
 */
Ext.define('Ext.picker.Picker', {
    extend: 'Ext.Sheet',
    xtype: 'picker',
    alternateClassName: 'Ext.Picker',
    requires: ['Ext.picker.Slot', 'Ext.TitleBar', 'Ext.data.Model', 'Ext.util.InputBlocker'],
 
    isPicker: true,
 
    /**
     * @event pick
     * Fired when a slot has been picked
     * @param {Ext.Picker} this This Picker.
     * @param {Object} values The values of this picker's slots, in `{name:'value'}` format.
     * @param {Ext.picker.Slot} slot An instance of Ext.Picker.Slot that has been picked.
     */
 
    /**
     * @event change
     * Fired when the value of this picker has changed the Done button has been pressed.
     * @param {Ext.picker.Picker} this This Picker.
     * @param {Object} values The values of this picker's slots, in `{name:'value'}` format.
     */
 
    /**
     * @event cancel
     * Fired when the cancel button is tapped and the values are reverted back to
     * what they were.
     * @param {Ext.Picker} this This Picker.
     */
 
    config: {
        /**
         * @cfg {String/Mixed} doneButton
         * Can be either:
         *
         * - A {String} text to be used on the Done button.
         * - An {Object} as config for {@link Ext.Button}.
         * - `false` or `null` to hide it.
         * @accessor
         */
        doneButton: true,
 
        /**
         * @cfg {String/Mixed} cancelButton
         * Can be either:
         *
         * - A {String} text to be used on the Cancel button.
         * - An {Object} as config for {@link Ext.Button}.
         * - `false` or `null` to hide it.
         * @accessor
         */
        cancelButton: true,
 
        /**
         * @cfg {Boolean} useTitles
         * Generate a title header for each individual slot and use
         * the title configuration of the slot.
         * @accessor
         */
        useTitles: false,
 
        /**
         * @cfg {Array} slots
         * An array of slot configurations.
         *
         * - `name` {String} - Name of the slot
         * - `data` {Array} - An array of text/value pairs in the format `{text: 'myKey',
         * value: 'myValue'}`
         * - `title` {String} - Title of the slot. This is used in conjunction with
         * `useTitles: true`.
         *
         * @accessor
         */
        slots: null,
 
        /**
         * @cfg {Object} value
         * The value to initialize the picker with. The value must be an object with the
         * key being the name of the slot to set the value to.
         *
         *     Ext.create('Ext.picker.Picker', {
         *         displayed: true,
         *         side: 'bottom',
         *         value: {
         *             limit_speed: 100
         *         },
         *         slots: [{
         *             name: 'limit_speed',
         *             title: 'Speed',
         *             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}
         *             ]
         *         }]
         *     });
         *
         * @accessor
         */
        value: null,
 
        /**
         * @cfg {Number} height
         * The height of the picker.
         * @accessor
         */
        height: 220,
 
        /**
         * @cfg layout
         * @inheritdoc
         */
        layout: {
            type: 'hbox',
            align: 'stretch'
        },
 
        /**
         * @cfg centered
         * @hide
         */
        centered: false,
 
        /**
         * @cfg left
         * @inheritdoc
         */
        left: 0,
 
        /**
         * @cfg right
         * @inheritdoc
         */
        right: 0,
 
        /**
         * @cfg bottom
         * @inheritdoc
         */
        bottom: 0,
 
        /**
         * @private
         */
        defaultType: 'pickerslot',
 
        toolbarPosition: 'top',
 
        /**
         * @cfg {Ext.TitleBar/Ext.Toolbar/Object} toolbar
         * The toolbar which contains the {@link #doneButton} and {@link #cancelButton}
         * buttons. You can override this if you wish, and add your own configurations.
         * Just ensure that you take into account the {@link #doneButton} and
         * {@link #cancelButton} configurations.
         *
         * The default xtype is a {@link Ext.TitleBar}:
         *
         *     toolbar: {
         *         items: [
         *             {
         *                 xtype: 'button',
         *                 text: 'Left',
         *                 align: 'left'
         *             },
         *             {
         *                 xtype: 'button',
         *                 text: 'Right',
         *                 align: 'left'
         *             }
         *         ]
         *     }
         *
         * Or to use a {@link Ext.Toolbar instead}:
         *
         *     toolbar: {
         *         xtype: 'toolbar',
         *         items: [
         *             {
         *                 xtype: 'button',
         *                 text: 'Left'
         *             },
         *             {
         *                 xtype: 'button',
         *                 text: 'Left Two'
         *             }
         *         ]
         *     }
         *
         * @accessor
         */
        toolbar: {
            xtype: 'titlebar'
        },
 
        /**
         * @cfg side
         * @inheritdoc
         */
        side: 'bottom'
    },
 
    /**
     * @property baseCls
     * @inheritdoc
     */
    baseCls: Ext.baseCSSPrefix + 'picker',
 
    /**
     * @cfg floated
     * @inheritdoc
     */
    floated: true,
 
    /**
     * @property focusable
     * @inheritdoc
     */
    focusable: true,
    
    /**
     * @cfg tabIndex
     * @inheritdoc
     */
    tabIndex: -1,
 
    constructor: function(config) {
        this.callParent([config]);
 
        // TODO: Should value be a config?
        // It would entail overriding the generated getter & setter
        if ('value' in config) {
            this.setValue(config.value);
        }
    },
 
    initialize: function() {
        this.callParent();
 
        this.on({
            scope: this,
            delegate: 'pickerslot',
            slotpick: 'onSlotPick'
        });
    },
 
    getTemplate: function() {
        var me = this,
            clsPrefix = Ext.baseCSSPrefix,
            template = me.callParent();
 
        template[0].children[0].children = [{
            reference: 'mask',
            cls: clsPrefix + 'picker-mask',
            children: [{
                reference: 'bar',
                cls: clsPrefix + 'picker-bar'
            }]
        }];
 
        return template;
    },
 
    /**
     * @private
     */
    applyToolbar: function(config, oldToolbar) {
        if (config) {
            if (config === true) {
                config = {};
            }
 
            Ext.applyIf(config, {
                docked: this.getToolbarPosition()
            });
        }
 
        return Ext.factory(config, 'Ext.TitleBar', oldToolbar);
    },
 
    /**
     * @private
     */
    updateToolbar: function(newToolbar) {
        if (newToolbar) {
            this.add(newToolbar);
        }
    },
 
    /**
     * Updates the {@link #doneButton} configuration. Will change it into a button when
     * appropriate, or just update the text if needed.
     * @param {Object} config 
     * @param {Object} oldButton 
     * @return {Object} 
     */
    applyDoneButton: function(config, oldButton) {
        if (config) {
            if (config === true) {
                config = {};
            }
 
            if (typeof config === "string") {
                config = {
                    text: config
                };
            }
 
            Ext.applyIf(config, {
                align: 'right',
                text: 'Done'
            });
        }
 
        return Ext.factory(config, 'Ext.Button', oldButton);
    },
 
    updateDoneButton: function(newDoneButton) {
        var toolbar = this.getToolbar();
 
        if (newDoneButton) {
            toolbar.add(newDoneButton);
            newDoneButton.on('tap', this.onDoneButtonTap, this);
        }
    },
 
    /**
     * Updates the {@link #cancelButton} configuration. Will change it into a button when
     * appropriate, or just update the text if needed.
     * @param {Object} config 
     * @param {Object} oldButton 
     * @return {Object} 
     */
    applyCancelButton: function(config, oldButton) {
        if (config) {
            if (Ext.isBoolean(config)) {
                config = {};
            }
 
            if (typeof config === "string") {
                config = {
                    text: config
                };
            }
 
            Ext.applyIf(config, {
                align: 'left',
                text: 'Cancel'
            });
        }
 
        return Ext.factory(config, 'Ext.Button', oldButton);
    },
 
    updateCancelButton: function(newCancelButton) {
        var toolbar = this.getToolbar();
 
        if (newCancelButton) {
            toolbar.add(newCancelButton);
            newCancelButton.on('tap', this.onCancelButtonTap, this);
        }
    },
 
    /**
     * @private
     */
    updateUseTitles: function(useTitles) {
        var innerItems = this.getInnerItems(),
            ln = innerItems.length,
            cls = Ext.baseCSSPrefix + 'use-titles',
            i, innerItem;
 
        this.toggleCls(cls, useTitles);
 
        for (= 0; i < ln; i++) {
            innerItem = innerItems[i];
 
            if (innerItem.isSlot) {
                innerItem.setShowTitle(useTitles);
            }
        }
    },
 
    applySlots: function(slots) {
        if (slots) {
            // eslint-disable-next-line vars-on-top
            var ln = slots.length,
                i;
 
            for (= 0; i < ln; i++) {
                slots[i].picker = this;
            }
        }
 
        return slots;
    },
 
    /**
     * Adds any new {@link #slots} to this picker, and removes existing {@link #slots}
     * @private
     */
    updateSlots: function(newSlots) {
        var me = this,
            bcss = Ext.baseCSSPrefix,
            innerItems;
 
        me.removeAll();
 
        if (newSlots) {
            me.add(newSlots);
        }
 
        innerItems = me.getInnerItems();
 
        if (innerItems.length > 0) {
            innerItems[0].addCls(bcss + 'first');
            innerItems[innerItems.length - 1].addCls(bcss + 'last');
        }
 
        me.updateUseTitles(me.getUseTitles());
        me.setValue(me.getValue());
    },
 
    /**
     * @private
     * Called when the done button has been tapped.
     */
    onDoneButtonTap: function() {
        var me = this,
            oldValue = me._value,
            newValue = me.getValue(true);
 
        if (newValue !== oldValue) {
            me._values = me._value = newValue;
 
            me.fireEvent('change', me, newValue);
        }
 
        me.hide();
        Ext.util.InputBlocker.unblockInputs();
    },
 
    /**
     * @private
     * Called when the cancel button has been tapped.
     */
    onCancelButtonTap: function() {
        this.fireEvent('cancel', this);
        this.hide();
        Ext.util.InputBlocker.unblockInputs();
    },
 
    /**
     * @private
     * Called when a slot has been picked.
     */
    onSlotPick: function(slot) {
        this.fireEvent('pick', this, this.getValue(true), slot);
    },
 
    afterShow: function(me) {
        me.callParent([me]);
 
        me.scrollSlotsIntoView();
 
        Ext.util.InputBlocker.blockInputs();
    },
 
    updateDisplayed: function(displayed, oldDisplayed) {
        this.callParent([displayed, oldDisplayed]);
 
        Ext.util.InputBlocker.blockInputs();
    },
 
    scrollSlotsIntoView: function() {
        var slots = this.getInnerItems(),
            i, slot, location;
 
        for (= 0; i < slots.length; i++) {
            slot = slots[i];
            location = slot.getNavigationModel().location;
 
            // MultiSelection means that you cannot scroll to a selection.
            // Scroll to the last navigation position
            slot.navigateToItem(location ? location.item : slot.itemFromRecord(0));
        }
    },
 
    /**
     * Sets the values of the pickers slots.
     * @param {Object} values The values in a {name:'value'} format.
     * @param {Boolean} animated `true` to animate setting the values.
     * @return {Ext.Picker} this This picker.
     */
    setValue: function(values, animated) {
        var me = this,
            slots = me.getInnerItems(),
            ln = slots.length,
            key, slot, i, value;
 
        if (!values) {
            values = {};
 
            for (= 0; i < ln; i++) {
                // Set the value to false so the slot will return null when getValue is called
                values[slots[i].getName()] = null;
            }
        }
 
        for (key in values) {
            value = values[key];
 
            for (= 0; i < slots.length; i++) {
                slot = slots[i];
 
                if (slot.getName() === key) {
                    if (animated) {
                        slot.setValueAnimated(value);
                    }
                    else {
                        slot.setValue(value);
                    }
 
                    break;
                }
            }
        }
 
        me._values = me._value = values;
 
        return me;
    },
 
    setValueAnimated: function(values) {
        this.setValue(values, true);
    },
 
    /**
     * Returns the values of each of the pickers slots
     * @return {Object} The values of the pickers slots
     */
    getValue: function(useDom) {
        var values = {},
            items = this.getItems().items,
            ln = items.length,
            item, i;
 
        if (useDom) {
            for (= 0; i < ln; i++) {
                item = items[i];
 
                if (item && item.isSlot) {
                    values[item.getName()] = item.getValue(useDom);
                }
            }
 
            this._values = values;
        }
 
        return this._values;
    },
 
    /**
     * Returns the values of each of the pickers slots.
     * @return {Object} The values of the pickers slots.
     */
    getValues: function() {
        return this.getValue();
    }
});