/**
 * @ignore
 * render base class for kissy
 * @author yiminghe@gmail.com
 * refer: http://martinfowler.com/eaaDev/uiArchs.html
 */
KISSY.add("component/control/render", function (S, Node, XTemplateRuntime, ComponentProcess, RenderTpl, Manager) {
    var ON_SET = '_onSet',
        trim = S.trim,
        $ = Node.all,
        UA = S.UA,
        startTpl = RenderTpl,
        endTpl = '</div>',
        doc = S.Env.host.document,
        HTML_PARSER = 'HTML_PARSER';

    function pxSetter(v) {
        if (typeof v == 'number') {
            v += 'px';
        }
        return v;
    }

    function applyParser(srcNode, parser, control) {
        var view = this,
            p, v, ret;

        // 从 parser 中,默默设置属性,不触发事件
        // html parser 优先,超过 js 配置值
        for (p in parser) {
            v = parser[p];
            // 函数
            if (typeof v === 'function') {
                // html parser 放弃
                ret = v.call(view, srcNode);
                if (ret !== undefined) {
                    control.setInternal(p, ret);
                }
            }
            // 单选选择器
            else if (typeof v == 'string') {
                control.setInternal(p, srcNode.one(v));
            }
            // 多选选择器
            else if (S.isArray(v) && v[0]) {
                control.setInternal(p, srcNode.all(v[0]))
            }
        }
    }

    function normalExtras(extras) {
        if (!extras) {
            extras = [''];
        }
        if (typeof extras == "string") {
            extras = extras.split(/\s+/);
        }
        return extras;
    }

    function prefixExtra(prefixCls, componentCls, extras) {
        var cls = '',
            i = 0,
            l = extras.length,
            e,
            prefix = prefixCls + componentCls;
        for (; i < l; i++) {
            e = extras[i];
            e = e ? ('-' + e) : e;
            cls += ' ' + prefix + e;
        }
        return cls;
    }

    function onSetAttrChange(e) {
        var self = this,
            method;
        // ignore bubbling
        if (e.target == self.control) {
            method = self[ON_SET + e.type.slice(5).slice(0, -6)];
            method.call(self, e.newVal, e);
        }
    }

    // scope option
    function getBaseCssClassesCmd() {
        return this.config.view.getBaseCssClasses(arguments[1].params[0]);
    }

    function getBaseCssClassCmd() {
        return this.config.view.getBaseCssClass(arguments[1].params[0]);
    }

    /**
     * Base Render class for KISSY Component.
     * @class KISSY.Component.Control.Process
     * @private
     */
    return ComponentProcess.extend({
        isRender: true,

        createInternal: function () {
            var self = this,
                srcNode = self.control.get('srcNode');

            if (srcNode) {
                // decorate from existing dom structure
                self.decorateDom(srcNode);
            } else {
                self.callSuper();
            }
        },

        beforeCreateDom: function (renderData) {
            var self = this,
                control = self.control,
                width,
                height,
                visible,
                elAttrs = control.get('elAttrs'),
                cls = control.get('elCls'),
                disabled,
                attrs = control['getAttrs'](),
                a,
                attr,
                elStyle = control.get('elStyle'),
                zIndex,
                elCls = control.get('elCls');

            for (a in attrs) {
                attr = attrs[a];
                if (attr.view) {
                    renderData[a] = control.get(a);
                }
            }

            width = renderData.width;
            height = renderData.height;
            visible = renderData.visible;
            zIndex = renderData.zIndex;

            if (width) {
                elStyle.width = pxSetter(width);
            }
            if (height) {
                elStyle.height = pxSetter(height);
            }
            if (zIndex) {
                elStyle['z-index'] = zIndex;
            }

            if (!visible) {
                elCls.push(self.getBaseCssClasses('hidden'));
            }

            if (disabled = control.get('disabled')) {
                cls.push(self.getBaseCssClasses('disabled'));
                elAttrs['aria-disabled'] = 'true';
            }
            if (control.get('highlighted')) {
                cls.push(self.getBaseCssClasses('hover'));
            }
            if (control.get('focusable')) {
                if (UA.ie) {
                    elAttrs['hideFocus'] = 'true';
                }
                elAttrs['tabindex'] = disabled ? '-1' : '0';
            }
        },

        createDom: function () {
            var self = this;
            self['beforeCreateDom'](
                self.renderData = {},
                self.childrenElSelectors = {},
                self.renderCommands = {
                    getBaseCssClasses: getBaseCssClassesCmd,
                    getBaseCssClass: getBaseCssClassCmd
                }
            );

            var control = self.control,
                html;
            html = self.renderTpl(startTpl) + self.renderTpl(self.get('contentTpl')) + endTpl;
            control.setInternal("el", self.$el = $(html));
            self.el = self.$el[0];
            self.fillChildrenElsBySelectors();
        },

        decorateDom: function (srcNode) {
            var self = this,
                control = self.control;
            if (!srcNode.attr('id')) {
                srcNode.attr('id', control.get('id'));
            }
            applyParser.call(self, srcNode, self.constructor.HTML_PARSER, control);
            control.setInternal("el", self.$el = srcNode);
            self.el = srcNode[0];
        },

        renderUI: function () {
            var self = this,
                control = self.control,
                el = self.$el;

            // need to insert created dom into body
            if (!control.get('srcNode')) {
                var render = control.get('render'),
                    renderBefore = control.get('elBefore');
                if (renderBefore) {
                    el['insertBefore'](renderBefore, undefined);
                } else if (render) {
                    el.appendTo(render, undefined);
                } else {
                    el.appendTo(doc.body, undefined);
                }
            }
        },

        bindUI: function () {
            var self = this;
            var control = self.control;
            var attrs = control['getAttrs']();
            var attrName, attrCfg;
            for (attrName in attrs) {
                attrCfg = attrs[attrName];
                var ucName = S.ucfirst(attrName);
                var attrChangeFn = self[ON_SET + ucName];
                if (attrCfg.view && attrChangeFn) {
                    // 通知 render 处理
                    control.on("after" + ucName + "Change", onSetAttrChange, self);
                }
            }
        },

        destructor: function () {
            if (this.$el) {
                this.$el.remove();
            }
        },

        $: function (selector) {
            return this.$el.all(selector);
        },

        fillChildrenElsBySelectors: function (childrenElSelectors) {
            var self = this,
                el = self.$el,
                control = self.control,
                childName,
                selector;

            childrenElSelectors = childrenElSelectors || self.childrenElSelectors;

            for (childName in childrenElSelectors) {
                selector = childrenElSelectors[childName];
                if (typeof selector === "function") {
                    control.setInternal(childName, selector(el));
                } else {
                    control.setInternal(childName,
                        self.$(S.substitute(selector, self.renderData)));
                }
                delete childrenElSelectors[childName];
            }
        },

        renderTpl: function (tpl, renderData, renderCommands) {
            var self = this;
            renderData = renderData || self.renderData;
            renderCommands = renderCommands || self.renderCommands;
            var XTemplate = self.get('xtemplate');
            return new XTemplate(tpl, {
                control: self.control,
                view: self,
                commands: renderCommands
            }).render(renderData);
        },

        /**
         * Get component's constructor from KISSY Node.
         * @param prefixCls
         * @param {KISSY.NodeList} childNode Child component's root node.
         */
        getComponentConstructorByNode: function (prefixCls, childNode) {
            var cls = childNode[0].className;
            // 过滤掉特定前缀
            if (cls) {
                cls = cls.replace(new RegExp("\\b" + prefixCls, "ig"), "");
                return Manager.getConstructorByXClass(cls);
            }
            return null;
        },

        getComponentCssClasses: function () {
            var self = this;
            if (self.componentCssClasses) {
                return self.componentCssClasses;
            }
            var control = self.control,
                constructor = control.constructor,
                xclass,
                re = [];
            while (constructor && !constructor.prototype.hasOwnProperty('isControl')) {
                xclass = constructor.xclass;
                if (xclass) {
                    re.push(xclass);
                }
                constructor = constructor.superclass &&
                    constructor.superclass.constructor;
            }
            return self.componentCssClasses = re;
        },

        /**
         * Get all css class name to be applied to the root element of this component for given extra class names.
         * the css class names are prefixed with component name.
         * @param extras {String[]|String} class names without prefixCls and current component class name.
         */
        getBaseCssClasses: function (extras) {
            extras = normalExtras(extras);
            var componentCssClasses = this.getComponentCssClasses(),
                i = 0,
                control = this.get('control'),
                cls = '',
                l = componentCssClasses.length,
                prefixCls = control.get('prefixCls');
            for (; i < l; i++) {
                cls += prefixExtra(prefixCls, componentCssClasses[i], extras);
            }
            return trim(cls);
        },

        /**
         * Get full class name (with prefix) for current component
         * @param extras {String[]|String} class names without prefixCls and current component class name.
         * @method
         * @return {String} class name with prefixCls and current component class name.
         */
        getBaseCssClass: function (extras) {
            return trim(prefixExtra(
                this.control.get('prefixCls'),
                this.getComponentCssClasses()[0],
                normalExtras(extras)
            ));
        },

        /**
         * Returns the dom element which is responsible for listening keyboard events.
         * @return {KISSY.NodeList}
         * @ignore
         */
        getKeyEventTarget: function () {
            return this.$el;
        },

        '_onSetWidth': function (w) {
            this.$el.width(w);
        },

        _onSetHeight: function (h) {
            this.$el.height(h);
        },

        '_onSetContent': function (c) {
            var el = this.$el;
            el.html(c);
            // ie needs to set unselectable attribute recursively
            if (UA.ie < 9 && !this.get('allowTextSelection')) {
                el['unselectable']();
            }
        },

        _onSetVisible: function (visible) {
            var self = this,
                el = self.$el,
                hiddenCls = self.getBaseCssClasses('hidden');
            if (visible) {
                el.removeClass(hiddenCls);
            } else {
                el.addClass(hiddenCls);
            }
        },

        /**
         * @ignore
         */
        _onSetHighlighted: function (v) {
            var self = this,
                componentCls = self.getBaseCssClasses("hover"),
                el = self.$el;
            el[v ? 'addClass' : 'removeClass'](componentCls);
        },

        /**
         * @ignore
         */
        _onSetDisabled: function (v) {
            var self = this,
                control = self.control,
                componentCls = self.getBaseCssClasses("disabled"),
                el = self.$el;
            el[v ? 'addClass' : 'removeClass'](componentCls)
                .attr("aria-disabled", v);
            if (control.get("focusable")) {
                //不能被 tab focus 到
                self.getKeyEventTarget().attr("tabindex", v ? -1 : 0);
            }
        },
        /**
         * @ignore
         */
        '_onSetActive': function (v) {
            var self = this,
                componentCls = self.getBaseCssClasses("active");
            self.$el[v ? 'addClass' : 'removeClass'](componentCls)
                .attr("aria-pressed", !!v);
        },
        /**
         * @ignore
         */
        _onSetFocused: function (v) {
            var self = this,
                el = self.$el,
                componentCls = self.getBaseCssClasses("focused");
            el[v ? 'addClass' : 'removeClass'](componentCls);
        },

        '_onSetZIndex': function (x) {
            this.$el.css("z-index", x);
        }
    }, {
        __hooks__: {
            decorateDom: ComponentProcess.prototype.__getHook('__decorateDom'),
            beforeCreateDom: ComponentProcess.prototype.__getHook('__beforeCreateDom')
        },

        /**
         * Create a new class which extends ComponentProcess .
         * @param {Function[]} extensions Class constructors for extending.
         * @param {Object} px Object to be mixed into new class 's prototype.
         * @param {Object} sx Object to be mixed into new class.
         * @static
         * @return {KISSY.Component.Process} A new class which extends ComponentProcess .
         */
        extend: function extend(extensions, px, sx) {
            var SuperClass = this,
                NewClass,
                parsers = {};
            NewClass = ComponentProcess.extend.apply(SuperClass, arguments);
            NewClass[HTML_PARSER] = NewClass[HTML_PARSER] || {};
            if (S.isArray(extensions)) {
                // [ex1,ex2],扩展类后面的优先,ex2 定义的覆盖 ex1 定义的
                // 主类最优先
                S.each(extensions['concat'](NewClass), function (ext) {
                    if (ext) {
                        // 合并 HTML_PARSER 到主类
                        S.each(ext.HTML_PARSER, function (v, name) {
                            parsers[name] = v;
                        });
                    }
                });
                NewClass[HTML_PARSER] = parsers;
            }
            S.mix(NewClass[HTML_PARSER], SuperClass[HTML_PARSER], false);
            NewClass.extend = extend;
            return NewClass;
        },

        //  screen state
        ATTRS: {
            control: {
                setter: function (v) {
                    this.control = v;
                }
            },
            xtemplate: {
                value: XTemplateRuntime
            },
            contentTpl: {
                value: function (scopes) {
                    return scopes && scopes[scopes.length - 1].content || '';
                }
            }
        },

        HTML_PARSER: {
            id: function (el) {
                var id = el[0].id;
                return id ? id : undefined;
            },
            content: function (el) {
                return el.html();
            },
            disabled: function (el) {
                return el.hasClass(this.getBaseCssClass("disabled"));
            }
        },

        name: 'render'
    });

    /**
     * Parse attribute from existing dom node.
     * @static
     * @protected
     * @property HTML_PARSER
     * @member KISSY.Component
     *
     * for example:
     *     @example
     *     Overlay.HTML_PARSER={
     *          // el: root element of current component.
     *          "isRed":function(el){
     *              return el.hasClass("ks-red");
     *          }
     *      };
     */
}, {
    requires: [
        'node',
        'xtemplate/runtime',
        './process',
        './render-xtpl',
        'component/manager'
    ]
});