/**
 * フィールドの下に「ピッカー」(コンボボックスメニューリストやデートピッカーなど)をポップアップ表示する単一のトリガーを持つ、フィールドの抽象クラスです。キーボードのナビゲーションや一部の基本的なイベントのほか、トリガーがクリックされるとピッカーの表示状態を切り替えるベースの実装を提供します。ピッカーのサイズと配置は、それぞれ{@link #matchFieldWidth}および{@link #pickerAlign}/{@link #pickerOffset}コンフィグのプロパティでコントロールできます。
 *
 * 通常はこのクラスを直接使用することはなく、特定のピッカーフィールドを実装するための親クラスとして使用します。フィールドに適したピッカーコンポーネントを作成するため、サブクラスでは{@link #createPicker}メソッドを実装することが必要です。
 */
Ext.define('Ext.form.field.Picker', {
    extend: 'Ext.form.field.Text',
    alias: 'widget.pickerfield',
    alternateClassName: 'Ext.form.Picker',
    requires: ['Ext.util.KeyNav'],

    config: {
        triggers: {
            picker: {
                handler: 'onTriggerClick',
                scope: 'this'
            }
        }
    },

    /**
     * @cfg {Boolean} matchFieldWidth
     * ピッカードロップダウンの幅がフィールド幅と一致するようにするかどうか。デフォルト値はtrueです。
     */
    matchFieldWidth: true,

    /**
     * @cfg {String} pickerAlign
     * ピッカーを配置する{@link Ext.util.Positionable#alignTo 位置} です。デフォルトは"tl-bl?"です。
     */
    pickerAlign: 'tl-bl?',

    /**
     * @cfg {Number[]} pickerOffset
     * ピッカーを配置する際に{@link #pickerAlign}に加えて使用する[x,y]オフセットです。デフォルトはundefinedです。
     */

    /**
     * @cfg {String} [openCls='x-pickerfield-open']
     * ピッカーを開く際にフィールドの{@link #bodyEl}要素に追加するクラスです。
     */
    openCls: Ext.baseCSSPrefix + 'pickerfield-open',

    /**
     * @property {Boolean} isExpanded
     * ピッカーが開いていたらture、そうでなければfalse。
     */

    /**
     * @cfg {Boolean} editable
     * falseを指定すると、フィールドに直接入力できないようにします。 フィールドはピッカーで値を選択するこによってしか値を変更できなくなります。この状態では、ピッカーは入力フィールドそのものを直接クリックしても開くようになります。
     */
    editable: true,

    /**
     * @cfg {String} triggerCls
     * トリガーボタンをスタイリングする追加のCSSクラス。トリガーは必ずクラス'x-form-trigger'を取得し、指定されている場合にはtriggerClsが付加されます。
     */

    /**
     * @event expand
     * ピッカーが開いたときに発火します。
     * @param {Ext.form.field.Picker} field このフィールドのインスタンスです。
     */

    /**
     * @event collapse
     * フィールドのピッカーを閉じたときに発火します。
     * @param {Ext.form.field.Picker} field このフィールドのインスタンスです。
     */

    /**
     * @event select
     * ピッカー内の値が選択されたときに発火します。
     * @param {Ext.form.field.Picker} field このフィールドのインスタンスです。
     * @param {Object} value 選択された値。この値の正確な型は個々のフィールドとピッカーの実装によります。
     */

    applyTriggers: function(triggers) {
        var me = this,
            picker = triggers.picker;

        if (!picker.cls) {
            picker.cls = me.triggerCls;
        }

        return me.callParent([triggers]);
    },

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

        // Add handlers for keys to expand/collapse the picker
        me.keyNav = new Ext.util.KeyNav(me.inputEl, {
            down: me.onDownArrow,
            esc: {
                handler: me.onEsc,
                scope: me,
                defaultEventAction: false
            },
            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');
        }
    },

    // private
    onEsc: function(e) {
        if (Ext.isIE) {
            // Stop the esc key from "restoring" the previous value in IE
            // For example, type "foo". Highlight all the text, hit backspace.
            // Hit esc, "foo" will be restored. This behaviour doesn't occur
            // in any other browsers
            e.preventDefault();
        }

        if (this.isExpanded) {
            this.collapse();
            e.stopEvent();
        }
    },

    onDownArrow: function() {
        if (!this.isExpanded) {
            // Don't call expand() directly as there may be additional processing involved before
            // expanding, e.g. in the case of a ComboBox query.
            this.onTriggerClick();
        }
    },

    /**
     * このフィールドのピッカードロップダウンを開きます。
     */
    expand: function() {
        var me = this,
            bodyEl, picker, collapseIf;

        if (me.rendered && !me.isExpanded && !me.isDestroyed) {
            me.expanding = true;
            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
            });
            Ext.on('resize', me.alignPicker, me);
            me.fireEvent('expand', me);
            me.onExpand();
            delete me.expanding;
        }
    },

    onExpand: Ext.emptyFn,

    /**
     * input要素にピッカーを揃えます。
     * @protected
     */
    alignPicker: function() {
        var me = this,
            bodyElWidth,
            picker = me.getPicker();

        if (me.isExpanded) {
            if (me.matchFieldWidth) {
                bodyElWidth = me.bodyEl.getWidth();
                // Auto the height (it will be constrained by min and max width) unless there are no records to display.
                picker.setWidth(bodyElWidth);
            }
            if (picker.isFloating()) {
                me.doAlign();
            }
        }
    },

    /**
     * クラスのデフォルトで使用しているピッカーのアライメントを適用します。
     * @private
     */
    doAlign: function(){
        var me = this,
            picker = me.picker,
            aboveSfx = '-above',
            isAbove;

        // Align to the trigger wrap because the border isn't always on the input element, which
        // can cause the offset to be off
        me.picker.alignTo(me.triggerWrap, 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[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
    },

    /**
     * フィールドのピッカードロップダウンを閉じます。
     */
    collapse: function() {
        var me = this;
        
        if (me.isExpanded && !me.isDestroyed && !me.destroying) {
            var 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);
            Ext.un('resize', me.alignPicker, me);
            me.fireEvent('collapse', me);
            me.onCollapse();
        }
    },

    onCollapse: Ext.emptyFn,


    /**
     * @private
     * ピッカーを折りたたむかどうかを確認するためにドキュメントのmousewheelとmousedownを実行します。
     */
    collapseIf: function(e) {
        var me = this;

        if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !me.owns(e.target)) {
            me.collapse();
        }
    },

    /**
     * このフィールドのピッカーコンポーネントへの参照を返します。 その際必要であれば{@link #createPicker}をコールして生成します。
     * @return {Ext.Component} ピッカーコンポーネント。
     */
    getPicker: function() {
        var me = this,
            picker = me.picker;

        if (!picker) {
            me.picker = picker = me.createPicker();
            // For upward component searches.
            picker.ownerCmp = me;
        }

        return me.picker;
    },

    // @private
    // The CQ interface. Allow drilling down into the picker when it exists.
    // Important for determining whether an event took place in the bounds of some
    // higher level containing component. See AbstractComponent#owns
    getRefItems: function() {
        var result = [];
        if (this.picker) {
            result[0] = this.picker;
        }
        return result;
    },

    /**
     * @method
     * このフィールドのピッカーとして使用されるコンポーネントを生成して返します。ピッカーのサブクラスにより実装されることが必要です。
     */
    createPicker: Ext.emptyFn,

    /**
     * トリガークリックを処理します。 デフォルトではピッカーの折りたたみと展開を切り替えます。
     * @protected
     */
    onTriggerClick: function(e) {
        var me = this;
        if (!me.readOnly && !me.disabled) {
            if (me.isExpanded) {
                me.collapse();
            } else {
                me.expand();
            }
        }
    },

    onOtherFocus: function(dom) {
        if (this.hasFocus && !this.owns(dom)) {
            this.callParent([dom]);
        }
    },

    beforeDestroy : function(){
        var me = this,
            picker = me.picker;

        me.collapse();
        me.callParent();
        Ext.un('resize', me.alignPicker, me);
        Ext.destroy(me.keyNav, picker);
        if (picker) {
            delete me.picker;
            delete picker.pickerField;
        }
    }
});