/**
 * @ignore
 *  Input wrapper for ComboBox component.
 * @author yiminghe@gmail.com
 */
KISSY.add("combobox/base", function (S, Node, Component, ComboBoxRender, Menu, undefined) {
    var ComboBox,
        $ = Node.all,
        KeyCodes = Node.KeyCodes,
        ALIGN = {
            points: ["bl", "tl"],
            overflow: {
                adjustX: 1,
                adjustY: 1
            }
        },
        win = $(S.Env.host);

    /**
     * KISSY ComboBox.
     * xclass: 'combobox'.
     * @extends KISSY.Component.Controller
     * @class KISSY.ComboBox
     */
    ComboBox = Component.Controller.extend({

            // user's input text
            _savedInputValue: null,

            _stopNotify: 0,

            /**
             * normalize returned data
             * @protected
             * @param data
             */
            'normalizeData': function (data) {
                var self = this,
                    contents, v, i, c;
                if (data && data.length) {
                    data = data.slice(0, self.get("maxItemCount"));
                    if (self.get("format")) {
                        contents = self.get("format").call(self, self['getValueInternal'](), data);
                    } else {
                        contents = [];
                    }
                    for (i = 0; i < data.length; i++) {
                        v = data[i];
                        c = contents[i] = S.mix({
                            content: v,
                            textContent: v,
                            value: v
                        }, contents[i]);
                    }
                    return contents;
                }
                return contents;
            },

            bindUI: function () {
                var self = this,
                    input = self.get("input");

                input.on("valuechange", onValueChange, self);

                /**
                 * fired after combobox 's collapsed attribute is changed.
                 * @event afterCollapsedChange
                 * @param e
                 * @param e.newVal current value
                 * @param e.prevVal previous value
                 */

            },

            /**
             * get value
             * @protected
             */
            getValueInternal: function () {
                return this.get('input').val();
            },

            /**
             * set value
             * @protected
             * @method
             * @member KISSY.ComboBox
             */
            setValueInternal: function (value) {
                this.get('input').val(value);
            },

            /**
             * align menu
             * @protected
             */
            'alignInternal': function () {
                var self = this,
                    menu = self.get("menu"),
                    align = S.clone(menu.get("align"));
                align.node = self.get("el");
                S.mix(align, ALIGN, false);
                menu.set("align", align);
            },

            handleFocus: function () {
                var self = this,
                    placeholderEl;
                setInvalid(self, false);
                if (placeholderEl = self.get("placeholderEl")) {
                    placeholderEl.hide();
                }
            },

            handleBlur: function () {
                var self = this,
                    placeholderEl = self.get("placeholderEl"),
                    input;
                ComboBox.superclass.handleBlur.apply(self, arguments);
                delayHide.call(self);
                input = self.get("input");
                self.validate(function (error, val) {
                    if (error) {
                        if (!self.get("focused") && val == input.val()) {
                            setInvalid(self, error);
                        }
                    } else {
                        setInvalid(self, false);
                    }
                });
                if (placeholderEl && !input.val()) {
                    placeholderEl.show();
                }
            },

            handleMouseDown: function (e) {
                var self = this,
                    input,
                    target,
                    trigger ,
                    hasTrigger;
                ComboBox.superclass.handleMouseDown.apply(self, arguments);
                target = e.target;
                trigger = self.get("trigger");
                hasTrigger = self.get('hasTrigger');
                if (hasTrigger && (trigger[0] == target || trigger.contains(target))) {
                    input = self.get("input");
                    if (self.get('collapsed')) {
                        // fetch data
                        input[0].focus();
                        self.sendRequest('');
                    } else {
                        // switch from open to collapse
                        self.set('collapsed', true);
                    }
                    e.preventDefault();
                }
            },

            handleKeyEventInternal: function (e) {
                var self = this,
                    updateInputOnDownUp,
                    input,
                    keyCode = e.keyCode,
                    activeItem,
                    handledByMenu,
                    menu = getMenu(self);

                input = self.get("input");
                updateInputOnDownUp = self.get("updateInputOnDownUp");

                if (updateInputOnDownUp) {
                    // combobox will change input value
                    // but it does not need to reload data
                    if (S.inArray(e.keyCode, [
                        KeyCodes.UP,
                        KeyCodes.DOWN,
                        KeyCodes.ESC
                    ])) {
                        self._stopNotify = 1;
                    } else {
                        self._stopNotify = 0;
                    }
                }

                if (menu && menu.get("visible")) {

                    activeItem = menu.get("activeItem");

                    // https://github.com/kissyteam/kissy/issues/371
                    // combobox: input should be involved in key press sequence
                    if (updateInputOnDownUp && activeItem) {
                        var menuChildren = menu.get('children');
                        if (keyCode == KeyCodes.DOWN &&
                            activeItem == getFirstEnabledItem(menuChildren.concat().reverse())
                            ||
                            keyCode == KeyCodes.UP &&
                                activeItem == getFirstEnabledItem(menuChildren)
                            ) {
                            self.setValueInternal(self._savedInputValue);
                            menu.set('highlightedItem', null);
                            return true;
                        }
                    }

                    handledByMenu = menu['handleKeydown'](e);

                    // esc
                    if (e.keyCode == KeyCodes.ESC) {
                        self.set("collapsed", true);
                        if (updateInputOnDownUp) {
                            // restore original user's input text
                            self['setValueInternal'](self._savedInputValue);
                        }
                        return true;
                    }

                    activeItem = menu.get("activeItem");

                    if (updateInputOnDownUp &&
                        S.inArray(e.keyCode, [KeyCodes.DOWN, KeyCodes.UP])) {
                        // update menu's active value to input just for show
                        this.setValueInternal(activeItem.get("textContent"));
                    }

                    // tab
                    // if menu is open and an menuitem is highlighted, see as click/enter
                    if (e.keyCode == KeyCodes.TAB && activeItem) {
                        // click activeItem
                        activeItem.performActionInternal();
                        // only prevent focus change in multiple mode
                        if (self.get("multiple")) {
                            return true;
                        }
                    }

                    return handledByMenu;
                } else if (e.keyCode == KeyCodes.DOWN || e.keyCode == KeyCodes.UP) {
                    // re-fetch, consider multiple input
                    // S.log("refetch : " + getValue(self));
                    self.sendRequest(this.getValueInternal());
                }
                return  undefined;
            },

            syncUI: function () {
                var self = this,
                    input = self.get("input"),
                    inputValue = self.get("inputValue");
                if (inputValue != undefined) {
                    input.val(inputValue);
                }
                if (self.get("placeholder")) {
                    if (!input.val()) {
                        self.get("placeholderEl").show();
                    }
                }
            },

            validate: function (callback) {
                var self = this,
                    validator = self.get('validator'),
                    val = self.get("input").val();

                if (validator) {
                    validator(val, function (error) {
                        callback(error, val);
                    });
                } else {
                    callback(false, val);
                }
            },

            bindMenu: function () {
                var self = this,
                    el,
                    contentEl,
                    menu = self.get("menu");

                menu.on("click", function (e) {
                    var item = e.target,
                        textContent = item.get('textContent');
                    // stop valuechange event
                    self._stopNotify = 1;
                    self['setValueInternal'](textContent);
                    self._savedInputValue = textContent;
                    self.set("collapsed", true);
                    setTimeout(
                        function () {
                            self._stopNotify = 0;
                        },
                        // valuechange interval, hack
                        50
                    );
                });

                win.on("resize", self.__repositionBuffer = S.buffer(reposition, 50), self);

                el = menu.get("el");
                contentEl = menu.get("contentEl");

                // menu has input!
                el.on("focusout", delayHide, self);
                el.on("focusin", function () {
                    setTimeout(function () {
                        clearDismissTimer.call(self);
                    }, 0);
                }, self);

                contentEl.on("mouseover", onMenuMouseOver, self);

                contentEl.on('mousedown', function () {
                    self._stopNotify = 1;
                });

                contentEl.on('mouseup', function () {
                    self._stopNotify = 0;
                });

                self.bindMenu = S.noop;
            },

            /**
             * fetch comboBox list by value and show comboBox list
             * @param {String} value value for fetching comboBox list
             */
            sendRequest: function (value) {
                var self = this,
                    dataSource = self.get("dataSource");
                dataSource.fetchData(value, renderData, self);
            },

            _onSetCollapsed: function (v) {
                var self = this;
                if (v) {
                    hideMenu(self);
                } else {
                    showMenu(self);
                }
            },

            destructor: function () {
                var self = this,
                    repositionBuffer = self.__repositionBuffer;
                if (repositionBuffer) {
                    win.detach("resize", repositionBuffer, self);
                    repositionBuffer.stop();
                }
            }
        },
        {
            ATTRS: {

                /**
                 * Input element of current combobox.
                 * @type {KISSY.NodeList}
                 * @property input
                 */
                /**
                 * @ignore
                 */
                input: {
                    view: 1
                },

                /**
                 * initial value for input
                 * @cfg {String} inputValue
                 */
                /**
                 * @ignore
                 */
                inputValue: {
                    view: 1
                },

                /**
                 * trigger arrow element
                 * @ignore
                 */
                trigger: {
                    view: 1
                },

                /**
                 * placeholder
                 * @cfg {String} placeholder
                 */
                /**
                 * @ignore
                 */
                placeholder: {
                    view: 1
                },


                /**
                 * label for placeholder in ie
                 * @ignore
                 */
                placeholderEl: {
                    view: 1
                },

                /**
                 * custom validation function
                 * @type Function
                 * @property validator
                 */
                /**
                 * @ignore
                 */
                validator: {

                },

                /**
                 * invalid tag el
                 * @ignore
                 */
                invalidEl: {
                    view: 1
                },

                allowTextSelection: {
                    value: true
                },

                /**
                 * Whether show combobox trigger.
                 * Defaults to: true.
                 * @cfg {Boolean} hasTrigger
                 */
                /**
                 * @ignore
                 */
                hasTrigger: {
                    view: 1
                },

                /**
                 * ComboBox dropDown menuList or config
                 * @cfg {KISSY.Menu.PopupMenu|Object} menu
                 */
                /**
                 * ComboBox dropDown menuList or config
                 * @property menu
                 * @type {KISSY.Menu.PopupMenu}
                 */
                /**
                 * @ignore
                 */
                menu: {
                    value: {},
                    setter: function (m) {
                        if (m && m['isController']) {
                            m.setInternal("parent", this);
                        }
                    }
                },

                defaultChildXClass: {
                    value: 'popupmenu'
                },

                /**
                 * Whether combobox menu is hidden.
                 * @type {Boolean}
                 * @property collapsed
                 */
                /**
                 * @ignore
                 */
                collapsed: {
                    view: 1
                },

                /**
                 * dataSource for comboBox.
                 * @cfg {KISSY.ComboBox.LocalDataSource|KISSY.ComboBox.RemoteDataSource|Object} dataSource
                 */
                /**
                 * @ignore
                 */
                dataSource: {
                    // 和 input 关联起来，input可以有很多，每个数据源可以不一样，但是 menu 共享
                },

                /**
                 * maxItemCount max count of data to be shown
                 * @cfg {Number} maxItemCount
                 */
                /**
                 * @ignore
                 */
                maxItemCount: {
                    value: 99999
                },

                /**
                 * Whether drop down menu is same width with input.
                 * Defaults to: true.
                 * @cfg {Boolean} matchElWidth
                 */
                /**
                 * @ignore
                 */
                matchElWidth: {
                    value: true
                },

                /**
                 * Format function to return array of
                 * html/text/menu item attributes from array of data.
                 * @cfg {Function} format
                 */
                /**
                 * @ignore
                 */
                format: {
                },

                /**
                 * Whether update input's value at keydown or up when combobox menu shows.
                 * Default to: true
                 * @cfg {Boolean} updateInputOnDownUp
                 */
                /**
                 * @ignore
                 */
                updateInputOnDownUp: {
                    value: true
                },

                /**
                 * Whether or not the first row should be highlighted by default.
                 * Defaults to: false
                 * @cfg {Boolean} autoHighlightFirst
                 */
                /**
                 * @ignore
                 */
                autoHighlightFirst: {
                },

                /**
                 * whether highlight item when item content is same with user input.
                 * Defaults to: true
                 * @cfg {Boolean} highlightMatchItem
                 */
                /**
                 * @ignore
                 */
                highlightMatchItem: {
                    value: true
                },

                xrender: {
                    value: ComboBoxRender
                }
            }
        },
        {
            xclass: 'combobox',
            priority: 10
        }
    );


    // #----------------------- private start

    function getFirstEnabledItem(children) {
        for (var i = 0; i < children.length; i++) {
            if (!children[i].get('disabled')) {
                return children[i];
            }
        }
        return null;
    }

    function onMenuMouseOver() {
        var self = this;
        // trigger el focus
        self.get("input")[0].focus();
        // prevent menu from hiding
        clearDismissTimer.call(self);
    }

    function setInvalid(self, error) {
        var el = self.get("el"),
            cls = self.get("prefixCls") + "combobox-invalid",
            invalidEl = self.get("invalidEl");
        if (error) {
            el.addClass(cls);
            invalidEl.attr("title", error);
            invalidEl.show();
        } else {
            el.removeClass(cls);
            invalidEl.hide();
        }
    }

    function getMenu(self, init) {
        var m = self.get("menu");
        if (m && !m['isController']) {
            if (init) {
                m = Component.create(m, self);
                self.setInternal("menu", m);
            } else {
                return null;
            }
        }
        return m;
    }

    function hideMenu(self) {
        var menu = getMenu(self);
        if (menu) {
            menu.hide();
        }
    }

    function reposition() {
        var self = this,
            menu = getMenu(self);
        if (menu && menu.get("visible")) {
            self['alignInternal']();
        }
    }

    function delayHide() {
        var self = this;
        self._focusoutDismissTimer = setTimeout(function () {
            if (self._focusoutDismissTimer) {
                self.set("collapsed", true);
            }
        }, 50);
    }

    function clearDismissTimer() {
        var self = this,
            t;
        if (t = self._focusoutDismissTimer) {
            clearTimeout(t);
            self._focusoutDismissTimer = null;
        }
    }

    function showMenu(self) {
        var el = self.get("el"),
            menu = getMenu(self, 1);

        // 保证显示前已经 bind 好 menu 事件
        clearDismissTimer.call(self);

        if (menu && !menu.get("visible")) {
            // 先 render，监听 width 变化事件
            menu.render();
            self.bindMenu();
            // 根据 el 自动调整大小
            if (self.get("matchElWidth")) {
                menu.render();
                var menuEl = menu.get('el');
                var borderWidth =
                    (parseInt(menuEl.css('borderLeftWidth')) || 0) +
                        (parseInt(menuEl.css('borderRightWidth')) || 0);
                menu.set("width", el[0].offsetWidth - borderWidth);
            }
            menu.show();
            self.get("input").attr("aria-owns", menu.get("el").attr('id'));
        }
    }

    function onValueChange() {
        var self = this,
            value;
        if (self._stopNotify) {
            return;
        }
        value = self['getValueInternal']();
        if (value === undefined) {
            self.set("collapsed", true);
            return;
        }
        self._savedInputValue = value;
        // S.log("value change: " + value);
        self.sendRequest(value);
    }

    function renderData(data) {
        var self = this,
            v,
            children = [],
            val,
            matchVal,
            i,
            menu = getMenu(self, 1);

        data = self['normalizeData'](data);

        menu.removeChildren(true);

        menu.set("highlightedItem", null);

        if (data && data.length) {
            for (i = 0; i < data.length; i++) {
                v = data[i];
                children.push(menu.addChild(v));
            }

            if (self.get('highlightMatchItem')) {
                // make menu item (which textContent is same as input) active
                val = self['getValueInternal']();
                for (i = 0; i < children.length; i++) {
                    if (children[i].get("textContent") == val) {
                        menu.set("highlightedItem", children[i]);
                        matchVal = true;
                        break;
                    }
                }
            }

            // Whether or not the first row should be highlighted by default.
            if (!matchVal && self.get("autoHighlightFirst")) {
                for (i = 0; i < children.length; i++) {
                    if (!children[i].get("disabled")) {
                        menu.set("highlightedItem", children[i]);
                        break;
                    }
                }
            }
            self.set("collapsed", false);
            // 2012-12-28: in case autocomplete list becomes shorted or longer
            reposition.call(self);
        } else {
            self.set("collapsed", true);
        }
    }

    // #------------------------private end

    return ComboBox;
}, {
    requires: [
        'node',
        'component/base',
        './render',
        'menu'
    ]
});

/**
 * @ignore
 *
 * !TODO
 *  - menubutton combobox 抽象提取 picker (extjs)
 *
 *
 * 2012-05
 * auto-complete menu 对齐当前输入位置
 *  - http://kirblog.idetalk.com/2010/03/calculating-cursor-position-in-textarea.html
 *  - https://github.com/kir/js_cursor_position
 *
 * 2012-04-01 可能 issue :
 *  - 用户键盘上下键高亮一些选项，
 *    input 值为高亮项的 textContent,那么点击 body 失去焦点，
 *    到底要不要设置 selectedItem 为当前高亮项？
 *    additional note:
 *    1. tab 时肯定会把当前高亮项设置为 selectedItem
 *    2. 鼠标时不会把高亮项的 textContent 设到 input 上去
 *    1,2 都没问题，关键是键盘结合鼠标时怎么个处理？或者不考虑算了！
 **/