/**
 * SegmentedButton is a container for a group of {@link Ext.button.Button Button}s.  You 
 * may populate the segmented button's children by adding buttons to the items config.  The segmented 
 * button's children enjoy the same customizations as regular buttons, such as 
 * menu, tooltip, etc.  You can see usages of the various configuration
 * possibilities in the example below.
 *
 *     @example @preview 
 *     Ext.create('Ext.button.Segmented', {            
 *          renderTo: Ext.getBody(),
 *          allowMultiple: true,
 *          items: [{
 *               text: 'Segment Item 1',
 *               menu: [{
 *                   text: 'Menu Item 1'
 *               }]
 *          },{
 *               text: 'Segment Item 2',
 *               tooltip: 'My custom tooltip'
 *          },{
 *               text: 'Segment Item 3'
 *          }],
 *          listeners: {
 *               toggle: function(container, button, pressed) {
 *                    console.log("User toggled the '" + button.text + "' button: " + (pressed ? 'on' : 'off'));
 *               }
 *          }
 *     });
 * 
 */
Ext.define('Ext.button.Segmented', {
    extend: 'Ext.container.Container',
    xtype: 'segmentedbutton',
    requires: [ 
        'Ext.button.Button',
        'Ext.layout.container.SegmentedButton'
    ],
 
    config: {
        /**
         * @cfg {Boolean}
         * Allow toggling the pressed state of each button.
         * Only applicable when {@link #allowMultiple} is `false`.
         */
        allowDepress: false,
 
        /**
         * @cfg {Boolean}
         * Allow multiple pressed buttons.
         */
        allowMultiple: false,
 
        /**
         * @cfg {Boolean}
         * If {@link #allowMultiple} is `true`, this config may be set to `true` to indicate that at least
         * one button in the set must remain pressed at all times.
         *
         * If no {@link #value} is configured, and no child buttons are configured `pressed`, the first child
         * button is set `pressed: true`
         */
        forceSelection: false,
 
        /**
         * @cfg {Boolean}
         * True to enable pressed/not pressed toggling.
         */
        allowToggle: true,
 
        /**
         * @cfg {Boolean}
         * True to align the buttons vertically
         */
        vertical: false,
 
        /**
         * @cfg {String}
         * Default {@link Ext.Component#ui ui} to use for buttons in this segmented button.
         * Buttons can override this default by specifying their own UI
         */
        defaultUI: 'default'
    },
 
    beforeRenderConfig: {
        /**
         * @cfg {String/Number/String[]/Number[]}
         * @accessor
         * The value of this button.  When {@link #allowMultiple} is `false`, value is a
         * String or Number.  When {@link #allowMultiple} is `true`, value is an array
         * of values.  A value corresponds to a child button's {@link Ext.button.Button#value
         * value}, or its index if no child button values match the given value.
         *
         * Using the `value` config of the child buttons with single toggle:
         *
         *     @example
         *     var button = Ext.create('Ext.button.Segmented', {
         *         renderTo: Ext.getBody(),
         *         value: 'optTwo', // begin with "Option Two" selected
         *         items: [{
         *             text: 'Option One',
         *             value: 'optOne'
         *         }, {
         *             text: 'Option Two',
         *             value: 'optTwo'
         *         }, {
         *             text: 'Option Three',
         *             value:  'optThree'
         *         }]
         *     });
         *
         *     console.log(button.getValue()); // optTwo
         *
         *     // Sets the value to optOne, and sets the pressed state of the "Option One" button
         *     button.setValue('optOne');
         *
         *     console.log(button.getValue()); // optOne
         *
         * Using multiple toggle, and index-based values:
         *
         *     @example
         *     var button = Ext.create('Ext.button.Segmented', {
         *         renderTo: Ext.getBody(),
         *         allowMultiple: true
         *         value: [1, 2], // begin with "Option Two" and "Option Three" selected
         *         items: [{
         *             text: 'Option One'
         *         }, {
         *             text: 'Option Two'
         *         }, {
         *             text: 'Option Three'
         *         }]
         *     });
         *
         *     // Sets value to [0, 2], and sets pressed state of "Option One" and "Option Three"
         *     button.setValue([0, 2]);
         *
         *     console.log(button.getValue()); // [0, 2]
         *
         *     // Remove all pressed buttons, and set value to null
         *     button.setValue(null);
         */
        value: undefined
    },
 
    /**
     * @inheritdoc
     */
    defaultBindProperty: 'value',
 
    publishes: ['value'],
    twoWayBindable: ['value'],
 
    layout: 'segmentedbutton',
    defaultType: 'button',
    maskOnDisable: false,
    isSegmentedButton: true,
 
    baseCls: Ext.baseCSSPrefix + 'segmented-button',
    itemCls: Ext.baseCSSPrefix + 'segmented-button-item',
 
    /**
     * @private
     */
    _firstCls: Ext.baseCSSPrefix + 'segmented-button-first',
 
    /**
     * @private
     */
    _lastCls: Ext.baseCSSPrefix + 'segmented-button-last',
 
    /**
     * @private
     */
    _middleCls: Ext.baseCSSPrefix + 'segmented-button-middle',
 
    /**
     * @event toggle
     * Fires when any child button's pressed state has changed.
     * @param {Ext.button.Segmented} this
     * @param {Ext.button.Button} button The toggled button.
     * @param {Boolean} isPressed `true` to indicate if the button was pressed.
     */
 
    /**
     * @event change
     * Fires when any child button's pressed state has changed and caused the value to change.
     * @param {Ext.button.Segmented} this
     * @param {Array} newValue The new value.
     * @param {Array} oldValue The old value.
     */
 
    applyValue: function(value, oldValue) {
        var me = this,
            allowMultiple = me.getAllowMultiple(),
            buttonValue, button, values, oldValues, items, i, ln, hasPressed;
 
        values = (value instanceof Array) ? value : (value == null) ? [] : [value];
        oldValues = (oldValue instanceof Array) ? oldValue :
                (oldValue == null) ? [] : [oldValue];
 
        // Set a flag to tell our toggle listener not to respond to the buttons' toggle 
        // events while we are applying the value. 
        me._isApplyingValue = true;
 
        if (!me.rendered) {
            // first time - add values of buttons with an initial config of pressed:true 
            items = me.items.items;
            for (= items.length - 1; i >= 0; i--) {
                button = items[i];
 
                // If we've got back to zero with no pressed buttons and have forceSelection, 
                // then make button zero pressed. 
                if (me.forceSelection && !&& !hasPressed) {
                    button.pressed = true;
                }
                if (button.pressed) {
                    hasPressed = true;
                    buttonValue = button.value;
                    if (buttonValue == null) {
                        buttonValue = me.items.indexOf(button);
                    }
 
                    // We're looping backwards, unshift the values into the front of the array 
                    if (!Ext.Array.contains(values, buttonValue)) {
                        values.unshift(buttonValue);
                    }
                }
            }
        }
 
        ln = values.length;
 
        //<debug> 
        if (ln > 1 && !allowMultiple) {
            Ext.raise('Cannot set multiple values when allowMultiple is false');
        }
        //</debug> 
 
        // press all buttons corresponding to the values 
        for (= 0; i < ln; i++) {
            value = values[i];
            button = me._lookupButtonByValue(value);
 
            if (button) {
                buttonValue = button.value;
 
                if ((buttonValue != null) && buttonValue !== value) {
                    // button has a value, but it was matched by index. 
                    // transform the index into the button value 
                    values[i] = buttonValue;
                }
 
                if (!button.pressed) {
                    button.setPressed(true);
                }
            }
            //<debug> 
            else {
                // no matched button. fail. 
                Ext.raise("Invalid value '" + value + "' for segmented button: '" + me.id + "'");
            }
            //</debug> 
        }
 
        value = allowMultiple ? values : ln ? values[0] : null;
 
        // unpress buttons for the old values, if they do not exist in the new values array 
        for (= 0, ln = oldValues.length; i < ln; i++) {
            oldValue = oldValues[i];
            if (!Ext.Array.contains(values, oldValue)) {
                me._lookupButtonByValue(oldValue).setPressed(false);
            }
        }
 
        me._isApplyingValue = false;
 
        return value;
    },
 
    updateValue: function (value, oldValue) {
        var me = this,
            same;
 
        if (me.hasListeners.change) {
            if (value && oldValue && me.getAllowMultiple()) {
                same = Ext.Array.equals(value, oldValue);
            }
 
            if (!same) {
                me.fireEvent('change', me, value, oldValue);
            }
        }
    },
 
    beforeRender: function() {
        var me = this;
 
        me.addCls(me.baseCls + me._getClsSuffix());
        me._syncItemClasses(true);
        me.callParent();
    },
 
    onAdd: function(item) {
        var me = this,
            syncItemClasses = '_syncItemClasses';
 
        //<debug> 
        var items = me.items.items,
            ln = items.length,
            i = 0,
            value, defaultUI;
 
        if (item.ui === 'default' && !item.hasOwnProperty('ui')) {
            defaultUI = me.getDefaultUI();
            if (defaultUI !== 'default') {
                item.ui = defaultUI;
            }
        }
 
        for(; i < ln; i++) {
            if (items[i] !== item) {
                value = items[i].value;
                if (value != null && value === item.value) {
                    Ext.raise("Segmented button '" + me.id +
                        "' cannot contain multiple items with value: '" + value + "'");
                }
            }
        }
        //</debug> 
 
        me.mon(item, {
            hide: syncItemClasses,
            show: syncItemClasses,
            beforetoggle: '_onBeforeItemToggle',
            toggle: '_onItemToggle',
            scope: me
        });
 
        if (me.getAllowToggle()) {
            item.enableToggle = true;
            if (!me.getAllowMultiple()) {
                item.toggleGroup = me.getId();
                item.allowDepress = me.getAllowDepress();
            }
        }
 
        item.addCls(me.itemCls + me._getClsSuffix());
 
        me._syncItemClasses();
        me.callParent([item]);
    },
 
    onRemove: function(item) {
        var me = this;
 
        item.removeCls(me.itemCls + me._getClsSuffix());
        me._syncItemClasses();
        me.callParent([item]);
    },
 
    beforeLayout: function() {
        if (Ext.isChrome) {
            // workaround for a chrome bug with table-layout:fixed elements where the element 
            // is layed out with 0 width, for example, in the following test case, without 
            // this workaround the segmented button has 0 width in chrome: 
            // 
            //     Ext.create({ 
            //        renderTo: document.body, 
            //            xtype: 'toolbar', 
            //            items: [{ 
            //            xtype: 'segmentedbutton', 
            //            items: [{ 
            //                text: 'Foo' 
            //            }] 
            //        }] 
            //    }); 
            // 
            // reading offsetWidth corrects the issue. 
            this.el.dom.offsetWidth; // jshint ignore:line 
        }
        this.callParent();
    },
 
    updateDefaultUI: function(defaultUI) {
        var items = this.items,
            item, i, ln;
 
        if (this.rendered) {
            Ext.raise("Changing the ui config of a segmented button after render is not supported.");
        } else if (items) {
            if (items.items) { // Mixed collection already created 
                items = items.items;
            }
            for (= 0, ln = items.length; i < ln; i++) {
                item = items[i];
                if (item.ui === 'default' && defaultUI !== 'default' && !item.hasOwnProperty('ui') ) {
                    items[i].ui = defaultUI;
                }
            }
        }
    },
 
    //<debug> 
    updateAllowDepress: function(newAllowDepress, oldAllowDepress) {
        if (this.rendered && (newAllowDepress !== oldAllowDepress)) {
            Ext.raise("Changing the allowDepress config of a segmented button after render is not supported.");
        }
    },
 
    updateAllowMultiple: function(newAllowMultiple, oldAllowMultiple) {
        if (this.rendered && (newAllowMultiple !== oldAllowMultiple)) {
            Ext.raise("Changing the allowMultiple config of a segmented button after render is not supported.");
        }
    },
 
    updateAllowToggle: function(newAllowToggle, oldAllowToggle) {
        if (this.rendered && (newAllowToggle !== oldAllowToggle)) {
            Ext.raise("Changing the allowToggle config of a segmented button after render is not supported.");
        }
    },
 
    updateVertical: function(newVertical, oldVertical) {
        if (this.rendered && (newVertical !== oldVertical)) {
            Ext.raise("Changing the orientation of a segmented button after render is not supported.");
        }
    },
    //</debug> 
 
    privates: {
        _getClsSuffix: function() {
            return this.getVertical() ? '-vertical' : '-horizontal';
        },
 
        // rtl hook 
        _getFirstCls: function() {
            return this._firstCls;
        },
 
        // rtl hook 
        _getLastCls: function() {
            return this._lastCls;
        },
 
        /**
         * Looks up a child button by its value
         * @private
         * @param {String/Number} value The button's value or index
         * @return {Ext.button.Button}
         */
        _lookupButtonByValue: function(value) {
            var items = this.items.items,
                ln = items.length,
                i = 0,
                button = null,
                buttonValue, btn;
 
            for (; i < ln; i++) {
                btn = items[i];
                buttonValue = btn.value;
                if ((buttonValue != null) && buttonValue === value) {
                    button = btn;
                    break;
                }
            }
 
            if (!button && typeof value === 'number') {
                // no button matched by value, assume value is an index 
                button = items[value];
            }
 
            return button;
        },
        
        _onBeforeItemToggle: function(button, pressed) {
            // If we allow multiple selections, and we are forcing a selection, and we are unpressing 
            // and we only have one value, then veto this. we are not allowing the selection length 
            // to fall to zero. 
            if (this.allowMultiple && this.forceSelection && !pressed && this.getValue().length === 1) {
                return false;
            }
        },
 
        /**
         * Handles the "toggle" event of the child buttons.
         * @private
         * @param {Ext.button.Button} button
         * @param {Boolean} pressed 
         */
        _onItemToggle: function(button, pressed) {
            if (this._isApplyingValue) {
                return;
            }
            var me = this,
                Array = Ext.Array,
                allowMultiple = me.allowMultiple,
                buttonValue = (button.value != null) ? button.value : me.items.indexOf(button),
                value = me.getValue(),
                valueIndex;
 
            if (allowMultiple) {
                valueIndex = Array.indexOf(value, buttonValue);
            }
 
            if (pressed) {
                if (allowMultiple) {
                    if (valueIndex === -1) {
                        // We must not mutate our value property here 
                        value = Array.slice(value);
                        value.push(buttonValue);
                    }
                } else {
                    value = buttonValue;
                }
            } else {
                if (allowMultiple) {
                    if (valueIndex > -1) {
                        // We must not mutate our value property here 
                        value = Array.slice(value);
                        value.splice(valueIndex, 1);
                    }
                } else if (value === buttonValue) {
                    value = null;
                }
            }
 
            me.setValue(value);
 
            me.fireEvent('toggle', me, button, pressed);
        },
 
        /**
         * Synchronizes the "first", "last", and "middle" css classes when buttons are
         * added, removed, shown, or hidden
         * @private
         * @param {Boolean} force force sync even if not rendered.
         */
        _syncItemClasses: function(force) {
            var me = this,
                firstCls, middleCls, lastCls, items, ln, visibleItems, item, i;
 
            if (!force && !me.rendered) {
                return;
            }
 
            firstCls = me._getFirstCls();
            middleCls = me._middleCls;
            lastCls = me._getLastCls();
            items = me.items.items;
            ln = items.length;
            visibleItems = [];
 
            for (= 0; i < ln; i++) {
                item = items[i];
                if (!item.hidden) {
                    visibleItems.push(item);
                }
            }
 
            ln = visibleItems.length;
 
            //remove all existing classes from visible items 
            for (= 0; i < ln; i++) {
                visibleItems[i].removeCls([ firstCls, middleCls, lastCls ]);
            }
 
            // do not add any classes if there is only one item (no border removal needed) 
            if (ln > 1) {
                //add firstCls to the first visible button 
                visibleItems[0].addCls(firstCls);
 
                //add middleCls to all visible buttons in between 
                for (= 1; i < ln - 1; i++) {
                    visibleItems[i].addCls(middleCls);
                }
 
                //add lastCls to the first visible button 
                visibleItems[ln - 1].addCls(lastCls);
            }
        }
    }
});