/**
 * @class Ext.form.field.Picker
 * @extends Ext.form.field.Trigger
 * <p>An abstract class for fields that have a single trigger which opens a "picker" popup below
 * the field, e.g. a combobox menu list or a date picker. It provides a base implementation for
 * toggling the picker's visibility when the trigger is clicked, as well as keyboard navigation
 * and some basic events. Sizing and alignment of the picker can be controlled via the {@link #matchFieldWidth}
 * and {@link #pickerAlign}/{@link #pickerOffset} config properties respectively.</p>
 * <p>You would not normally use this class directly, but instead use it as the parent class for
 * a specific picker field implementation. Subclasses must implement the {@link #createPicker} method
 * to create a picker component appropriate for the field.</p>
 *
 * @xtype pickerfield
 * @constructor
 * Create a new picker field
 * @param {Object} config
 */
Ext.define('Ext.form.field.Picker', {
    extend: 'Ext.form.field.Trigger',
    alias: 'widget.pickerfield',
    alternateClassName: 'Ext.form.Picker',
    requires: ['Ext.util.KeyNav'],

    /**
     * @cfg {Boolean} matchFieldWidth
     * Whether the picker dropdown's width should be explicitly set to match the width of the field.
     * Defaults to <tt>true</tt>.
     */
    matchFieldWidth: true,

    /**
     * @cfg {String} pickerAlign
     * The {@link Ext.core.Element#alignTo alignment position} with which to align the picker. Defaults
     * to <tt>"tl-bl?"</tt>
     */
    pickerAlign: 'tl-bl?',

    /**
     * @cfg {Array} pickerOffset
     * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
     * Defaults to undefined.
     */

    /**
     * @cfg {String} openCls
     * A class to be added to the field's {@link #bodyEl} element when the picker is opened. Defaults
     * to 'x-pickerfield-open'.
     */
    openCls: Ext.baseCSSPrefix + 'pickerfield-open',

    /**
     * @property isExpanded
     * @type Boolean
     * True if the picker is currently expanded, false if not.
     */

    /**
     * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field;
     * the field can only have its value set via selecting a value from the picker. In this state, the picker
     * can also be opened by clicking directly on the input field itself.
     * (defaults to <tt>true</tt>).
     */
    editable: true,


    initComponent: function() {
        this.callParent();

        // Custom events
        this.addEvents(
            /**
             * @event expand
             * Fires when the field's picker is expanded.
             * @param {Ext.form.field.Picker} field This field instance
             */
            'expand',
            /**
             * @event collapse
             * Fires when the field's picker is collapsed.
             * @param {Ext.form.field.Picker} field This field instance
             */
            'collapse',
            /**
             * @event select
             * Fires when a value is selected via the picker.
             * @param {Ext.form.field.Picker} field This field instance
             * @param {Mixed} value The value that was selected. The exact type of this value is dependent on
             * the individual field and picker implementations.
             */
            'select'
        );
    },


    initEvents: function() {
        var me = this;
        me.callParent();

        // Add handlers for keys to expand/collapse the picker
        me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
            down: function() {
                if (!me.isExpanded) {
                    // Don't call expand() directly as there may be additional processing involved before
                    // expanding, e.g. in the case of a ComboBox query.
                    me.onTriggerClick();
                }
            },
            esc: me.collapse,
            scope: me,
            forceKeyDown: true
        });

        // Non-editable allows opening the picker by clicking the field
        if (!me.editable) {
            me.mon(me.inputEl, 'click', me.onTriggerClick, me);
        }

        // Disable native browser autocomplete
        if (Ext.isGecko) {
            me.inputEl.dom.setAttribute('autocomplete', 'off');
        }
    },


    /**
     * Expand this field's picker dropdown.
     */
    expand: function() {
        var me = this,
            bodyEl, picker, collapseIf;

        if (me.rendered && !me.isExpanded && !me.isDestroyed) {
            bodyEl = me.bodyEl;
            picker = me.getPicker();
            collapseIf = me.collapseIf;

            // show the picker and set isExpanded flag
            picker.show();
            me.isExpanded = true;
            me.alignPicker();
            bodyEl.addCls(me.openCls);

            // monitor clicking and mousewheel
            me.mon(Ext.getDoc(), {
                mousewheel: collapseIf,
                mousedown: collapseIf,
                scope: me
            });

            me.fireEvent('expand', me);
            me.onExpand();
        }
    },

    onExpand: Ext.emptyFn,

    /**
     * @protected
     * Aligns the picker to the
     */
    alignPicker: function() {
        var me = this,
            picker, isAbove,
            aboveSfx = '-above';

        if (this.isExpanded) {
            picker = me.getPicker();
            if (me.matchFieldWidth) {
                // Auto the height (it will be constrained by min and max width) unless there are no records to display.
                picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
            }
            if (picker.isFloating()) {
                picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);

                // add the {openCls}-above class if the picker was aligned above
                // the field due to hitting the bottom of the viewport
                isAbove = picker.el.getY() < me.inputEl.getY();
                me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
                picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
            }
        }
    },

    /**
     * Collapse this field's picker dropdown.
     */
    collapse: function() {
        if (this.isExpanded && !this.isDestroyed) {
            var me = this,
                openCls = me.openCls,
                picker = me.picker,
                doc = Ext.getDoc(),
                collapseIf = me.collapseIf,
                aboveSfx = '-above';

            // hide the picker and set isExpanded flag
            picker.hide();
            me.isExpanded = false;

            // remove the openCls
            me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
            picker.el.removeCls(picker.baseCls + aboveSfx);

            // remove event listeners
            doc.un('mousewheel', collapseIf, me);
            doc.un('mousedown', collapseIf, me);

            me.fireEvent('collapse', me);
            me.onCollapse();
        }
    },

    onCollapse: Ext.emptyFn,


    /**
     * @private
     * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
     */
    collapseIf: function(e) {
        var me = this;
        if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
            me.collapse();
        }
    },

    /**
     * Return a reference to the picker component for this field, creating it if necessary by
     * calling {@link #createPicker}.
     * @return {Ext.Component} The picker component
     */
    getPicker: function() {
        var me = this;
        return me.picker || (me.picker = me.createPicker());
    },

    /**
     * Create and return the component to be used as this field's picker. Must be implemented
     * by subclasses of Picker.
     * @return {Ext.Component} The picker component
     */
    createPicker: Ext.emptyFn,

    /**
     * Handles the trigger click; by default toggles between expanding and collapsing the
     * picker component.
     */
    onTriggerClick: function() {
        var me = this;
        if (!me.readOnly && !me.disabled) {
            if (me.isExpanded) {
                me.collapse();
            } else {
                me.expand();
            }
            me.inputEl.focus();
        }
    },

    mimicBlur: function(e) {
        var me = this,
            picker = me.picker;
        // ignore mousedown events within the picker element
        if (!picker || !e.within(picker.el, false, true)) {
            me.callParent(arguments);
        }
    },

    onDestroy : function(){
        var me = this;
        Ext.destroy(me.picker, me.keyNav);
        me.callParent();
    }

});