/**
 * A menu item that contains a togglable checkbox by default, but that can also be a part of a radio group.
 *
 *     @example
 *     Ext.create('Ext.menu.Menu', {
 *         width: 100,
 *         height: 110,
 *         items: [{
 *             xtype: 'menucheckitem',
 *             text: 'select all'
 *         },{
 *             xtype: 'menucheckitem',
 *             text: 'select specific'
 *         },{
 *             iconCls: 'add16',
 *             text: 'icon item'
 *         },{
 *             text: 'regular item'
 *         }]
 *     });
 */
Ext.define('Ext.menu.CheckItem', {
    extend: 'Ext.menu.Item',
    xtype: 'menucheckitem',
 
    /**
     * @property {Boolean} isMenuCheckItem 
     * `true` in this class to identify an object as an instantiated Menu CheckItem, or subclass thereof.
     */
    isMenuCheckItem: true,
 
    /**
     * @cfg {String} iconAlign 
     * @hide
     * Not supported at this level. Checkbox is on the left, iconAlign is always 'right'
     */
    iconAlign: 'right',
 
    /**
     * @cfg {Boolean} [hideOnClick=false]
     * Whether to not to hide the owning menu when this item is clicked.
     * Defaults to `false` for checkbox items, and radio group items.
     */
    hideOnClick: false,
 
    config: {
        /**
         * @cfg {Boolean} [checked=false]
         * True to render the menuitem initially checked.
         */
        checked: false,
 
        /**
         * @cfg {Function/String} [checkHandler]
         * @param {Ext.menu.CheckItem} This menu CheckItem
         * @param {Boolean} checked The new checked state
         * Alternative for the {@link #checkchange} event.  Gets called with the same parameters.
         * @controllable
         */
        checkHandler: null,
 
        /**
         * @cfg {Object} [scope]
         * Scope for the {@link #checkHandler} callback.
         */
 
        /**
         * @cfg {Boolean} [checkChangeDisabled=false]
         * True to prevent the checked item from being toggled. Any submenu will still be accessible.
         */
        checkChangeDisabled: false,
 
        value: null,
 
        showCheckbox: null
    },
 
    classCls: Ext.baseCSSPrefix + 'menucheckitem',
 
    checkedCls: Ext.baseCSSPrefix + 'checked',
 
    checkboxIconElCls: Ext.baseCSSPrefix + 'checkbox-icon-el',
 
    ariaRole: 'menuitemcheckbox',
 
    defaultBindProperty: 'checked',
 
    /**
     * @cfg {String} submenuText Text to be announced by screen readers when a check item
     * submenu is focused.
     * @locale
     */
    submenuText: '{0} submenu',
 
    /**
     * @hide
     * Not supported on CheckItems and RadioItems
     */
    href: null,
 
    /**
     * @hide
     * Not supported on CheckItems and RadioItems
     */
    target: null,
 
    /**
     * @event beforecheckchange
     * Fires before a change event. Return false to cancel.
     * @param {Ext.menu.CheckItem} this CheckItem
     * @param {Boolean} checked 
     */
 
    /**
     * @event checkchange
     * Fires after a change event.
     * @param {Ext.menu.CheckItem} this CheckItem
     * @param {Boolean} checked 
     */
 
    element: {
        reference: 'element',
        cls: Ext.baseCSSPrefix + 'unselectable ' +
            // The checkbox always occupies the "left" icon space 
            Ext.baseCSSPrefix + 'has-left-icon',
        onmousedown: 'return Ext.doEv(this, event);'
    },
 
    eventHandlers: {
        change: 'onCheckboxChange',
        mousedown: 'onCheckboxMousedown'
    },
 
    focusEl: 'checkboxElement',
    ariaEl: 'checkboxElement',
 
    getTemplate: function() {
        var template = this.callParent(),
            body = template[0];
 
        body.tag = 'div';
        body.href = null;
 
        body.children.push({
            // An absolutely positioned transparent checkbox that acts as the focus/aria element 
            tag: 'input',
            type: 'checkbox',
            reference: 'checkboxElement',
            cls: Ext.baseCSSPrefix + 'checkbox-el',
            onchange: 'return Ext.doEv(this, event);'
        });
 
        return template;
    },
 
    enableFocusable: function() {
        this.mixins.focusable.enableFocusable();
 
        // Menuitems only go readonly when disabled. 
        this.checkboxElement.dom.readOnly = '';
    },
 
    disableFocusable: function() {
        this.mixins.focusable.disableFocusable();
 
        // Menu items must be focusable, but not active when disabled. 
        this.checkboxElement.dom.readOnly = 'readonly';
    },
 
    /**
     * Sets the checked state of the item
     * @param {Boolean} checked True to check, false to un-check
     * @param {Boolean} [suppressEvents=false] True to prevent firing the checkchange events.
     */
    setChecked: function(checked, suppressEvents) {
        var me = this,
            isConfiguring = me.isConfiguring;
 
        // Events and handlers are suppressed during configuration 
        if (suppressEvents) {
            me.isConfiguring = true;
        }
        me.callParent([checked]);
        if (suppressEvents) {
            me.isConfiguring = isConfiguring;
        }
    },
 
    applyChecked: function (checked, oldChecked) {
        // Cast to boolean 
        checked = !!checked;
 
        // Do not fire events if set in configuration 
        if (checked !== oldChecked && (this.isConfiguring || this.fireEvent('beforecheckchange', this, checked) !== false)) {
            return checked;
        }
    },
 
    updateChecked: function (checked) {
        this.checkboxElement.dom.checked = checked;
 
        // We do not get an event on programmatic check change 
        // so call it proramatically. 
        this.onCheckChange();
    },
 
    updateCheckChangeDisabled: function (checkChangeDisabled) {
        this.checkboxElement.dom.readOnly = checkChangeDisabled;
    },
 
    updateValue: function (value) {
        this.checkboxElement.dom.value = value;
    },
 
    updateText: function (text) {
        var me = this,
            ariaDom = me.ariaEl.dom;
 
        me.callParent([text]);
 
        if (ariaDom && me.getMenu()) {
            ariaDom.setAttribute('aria-label', Ext.String.formatEncode(me.submenuText, text));
        }
    },
 
    applyShowCheckbox: function (showCheckbox) {
        return !!showCheckbox;
    },
 
    updateShowCheckbox: function (showCheckbox) {
        this.checkboxElement.setDisplayed(showCheckbox);
    },
 
    updateIconAlign: function (iconAlign, oldIconAlign) {
        var me = this,
            leftIconElement = me.leftIconElement,
            rightIconElement = me.rightIconElement,
            checkboxIconElCls = me.checkboxIconElCls,
            checkboxIconElement, oldCheckboxIconElement;
 
        if (iconAlign === 'left') {
            checkboxIconElement = rightIconElement;
            oldCheckboxIconElement = leftIconElement;
        } else {
            checkboxIconElement = leftIconElement;
            oldCheckboxIconElement = rightIconElement;
        }
 
        checkboxIconElement.addCls(checkboxIconElCls);
        oldCheckboxIconElement.removeCls(checkboxIconElCls);
 
        me.callParent([iconAlign, oldIconAlign]);
    },
 
    privates: {
        onSpace: function (e) {
            // Disabled menuitems are still focusable, but must not react 
            if (this.getDisabled()) {
                e.preventDefault();
            }
        },
 
        onClick: function (e) {
            var me = this,
                arrowElement = me.arrowElement,
                result, parentResult, region;
 
            // Disabled menuitems are still focusable, but must not react 
            if (me.getDisabled()) {
                e.preventDefault();
            }
 
            // Clicking on the checkboxElement is processed natively, and we react to the 
            // change event. 
            if (e.pointerType !== 'mouse') {
                region = me.bodyElement.getRegion();
                if (me.getMenu()) {
                    region.setWidth(region.getWidth() - arrowElement.getWidth() - arrowElement.getMargin('lr'));
                }
 
                // When interacting with a menucheckitem via a touch screen the submenu 
                // is shown by tapping directly on the arrow.  Tapping anywhere else on 
                // the item will simply toggle the checked state. 
                if (region.contains(e.getPoint())) {
                    // clicked on the icon or text - veto menu show 
                    result = false;
                } else {
                    // clicked on the arrow - allow the menu to be shown, but preventDefault 
                    // to stop the checkbox from being toggled 
                    e.preventDefault();
                }
            }
 
            parentResult = me.callParent([e]);
 
            // Allow either to veto menu showing 
            return (result === false) ? result : parentResult;
        },
 
        onCheckboxMousedown: function(e) {
            // Prevent focus movement away from the checkboxElement on mousedown outside of the checkboxElement. 
            // The mouseover will have focused it. 
            // Also, checkboxes are not focusable by default on Apple Operating Systems. 
            // See http://www.weba11y.com/blog/2014/07/07/keyboard-navigation-in-mac-browsers/ 
            // So to prevent focus flying to body on mousedown, we prevent default. 
            if ((Ext.isApple && !Ext.isChrome) || !this.checkboxElement.contains(e.target)) {
                e.preventDefault();
            }
        },
 
        onCheckboxChange: function() {
            // Sync widget state in response to the checkbox changing state. 
            this.setChecked(this.checkboxElement.dom.checked);
        },
 
        onCheckChange: function () {
            var me = this,
                checked = this.checkboxElement.dom.checked,
                el = me.el,
                ariaDom = me.ariaEl.dom;
 
            el.toggleCls(me.checkedCls, !!checked);
            
            if (ariaDom) {
                ariaDom.setAttribute('aria-checked', me.getMenu() ? 'mixed' : checked);
            }
 
            me.publishState('checked', checked);
 
            // Do not fire events if set in configuration 
            if (!me.isConfiguring) {
                Ext.callback(me.getCheckHandler(), me.scope, [me, checked], 0, me);
                me.fireEvent('checkchange', me, checked);
            }
        },
 
        syncHasIconCls: function () {
            var me = this;
 
            me.toggleCls(me.hasRightIconCls, !!(me.getIconCls() || me.getIcon()));
        }
    }
});